diff --git a/DroidFish/.classpath b/DroidFish/.classpath new file mode 100644 index 0000000..b068c94 --- /dev/null +++ b/DroidFish/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/DroidFish/.externalToolBuilders/Native_Builder.launch b/DroidFish/.externalToolBuilders/Native_Builder.launch new file mode 100644 index 0000000..a474d0c --- /dev/null +++ b/DroidFish/.externalToolBuilders/Native_Builder.launch @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/DroidFish/.project b/DroidFish/.project new file mode 100644 index 0000000..62325d0 --- /dev/null +++ b/DroidFish/.project @@ -0,0 +1,43 @@ + + + DroidFish + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.ui.externaltools.ExternalToolBuilder + auto,full,incremental, + + + LaunchConfigHandle + <project>/.externalToolBuilders/Native_Builder.launch + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/DroidFish/.settings/org.eclipse.jdt.core.prefs b/DroidFish/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1add5a0 --- /dev/null +++ b/DroidFish/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +#Fri Jul 09 17:18:36 CEST 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/DroidFish/AndroidManifest.xml b/DroidFish/AndroidManifest.xml new file mode 100644 index 0000000..018aaac --- /dev/null +++ b/DroidFish/AndroidManifest.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DroidFish/assets/ChessCases.ttf b/DroidFish/assets/ChessCases.ttf new file mode 100644 index 0000000..b5a7a93 Binary files /dev/null and b/DroidFish/assets/ChessCases.ttf differ diff --git a/DroidFish/jni/Android.mk b/DroidFish/jni/Android.mk new file mode 100644 index 0000000..9a19626 --- /dev/null +++ b/DroidFish/jni/Android.mk @@ -0,0 +1,17 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := jni +LOCAL_SRC_FILES := jni.cpp + +LOCAL_CFLAGS := \ + -mandroid \ + -DTARGET_OS=android -D__ANDROID__ \ + -isystem $(SYSROOT)/usr/include + +LOCAL_STATIC_LIBRARIES := stockfish + +include $(BUILD_SHARED_LIBRARY) + +include jni/stockfish/Android.mk diff --git a/DroidFish/jni/Application.mk b/DroidFish/jni/Application.mk new file mode 100644 index 0000000..e5d3191 --- /dev/null +++ b/DroidFish/jni/Application.mk @@ -0,0 +1,2 @@ +APP_ABI := all +APP_STL := stlport_static diff --git a/DroidFish/jni/jni.cpp b/DroidFish/jni/jni.cpp new file mode 100644 index 0000000..0ad5e7c --- /dev/null +++ b/DroidFish/jni/jni.cpp @@ -0,0 +1,169 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]); + +static int fdFromChild = -1; +static int fdToChild = -1; + +/* + * Class: org_petero_droidfish_engine_NativePipedProcess + * Method: startProcess + * Signature: ()V + */ +extern "C" JNIEXPORT void JNICALL Java_org_petero_droidfish_engine_NativePipedProcess_startProcess + (JNIEnv* env, jobject obj) +{ + int fd1[2]; /* parent -> child */ + int fd2[2]; /* child -> parent */ + if (pipe(fd1) < 0) + exit(1); + if (pipe(fd2) < 0) + exit(1); + int childpid = fork(); + if (childpid == -1) { + exit(1); + } + if (childpid == 0) { + close(fd1[1]); + close(fd2[0]); + close(0); dup(fd1[0]); close(fd1[0]); + close(1); dup(fd2[1]); close(fd2[1]); + close(2); dup(1); + static char* argv[] = {(char*)"stockfish", NULL}; + nice(10); + main(1, argv); + _exit(0); + } else { + close(fd1[0]); + close(fd2[1]); + fdFromChild = fd2[0]; + fdToChild = fd1[1]; + fcntl(fdFromChild, F_SETFL, O_NONBLOCK); + } +} + + +static std::deque inBuf; + +static bool getNextChar(int& c, int timeoutMillis) { + if (inBuf.empty()) { + fd_set readfds, writefds; + FD_ZERO(&readfds); + FD_SET(fdFromChild, &readfds); + struct timeval tv; + tv.tv_sec = timeoutMillis / 1000; + tv.tv_usec = (timeoutMillis % 1000) * 1000; + int ret = select(fdFromChild + 1, &readfds, NULL, NULL, &tv); + if (ret < 0) + return false; + + static char buf[4096]; + int len = read(fdFromChild, &buf[0], sizeof(buf)); + for (int i = 0; i < len; i++) + inBuf.push_back(buf[i]); + } + if (inBuf.empty()) { + c = -1; + return true; + } + c = inBuf.front(); + inBuf.pop_front(); + return true; +} + +static std::vector lineBuf; +/* + * Class: org_petero_droidfish_engine_NativePipedProcess + * Method: readFromProcess + * Signature: (I)Ljava/lang/String; + */ +extern "C" JNIEXPORT jstring JNICALL Java_org_petero_droidfish_engine_NativePipedProcess_readFromProcess + (JNIEnv* env, jobject obj, jint timeoutMillis) +{ + struct timeval tv0, tv1; + while (true) { + int c; + gettimeofday(&tv0, NULL); + if (!getNextChar(c, timeoutMillis)) + return 0; // Error + gettimeofday(&tv1, NULL); + int elapsedMillis = (tv1.tv_sec - tv0.tv_sec) * 1000 + (tv1.tv_usec - tv0.tv_usec) / 1000; + if (elapsedMillis > 0) { + timeoutMillis -= elapsedMillis; + if (timeoutMillis < 0) timeoutMillis = 0; + } + if (c == -1) { // Timeout + static char emptyString = 0; + return (*env).NewStringUTF(&emptyString); + } + if (c == '\n' || (c == '\r')) { + if (lineBuf.size() > 0) { + lineBuf.push_back(0); + jstring ret = (*env).NewStringUTF(&lineBuf[0]); + lineBuf.clear(); + return ret; + } + } else { + lineBuf.push_back((char)c); + } + } +} + +/* + * Class: org_petero_droidfish_engine_NativePipedProcess + * Method: writeToProcess + * Signature: (Ljava/lang/String;)V + */ +extern "C" JNIEXPORT void JNICALL Java_org_petero_droidfish_engine_NativePipedProcess_writeToProcess + (JNIEnv* env, jobject obj, jstring msg) +{ + const char* str = (*env).GetStringUTFChars(msg, NULL); + if (str) { + int len = strlen(str); + int written = 0; + while (written < len) { + int n = write(fdToChild, &str[written], len - written); + if (n <= 0) + break; + written += n; + } + (*env).ReleaseStringUTFChars(msg, str); + } +} + +/* + * Class: org_petero_droidfish_engine_NativePipedProcess + * Method: getNPhysicalProcessors + * Signature: ()I + */ +extern "C" JNIEXPORT jint JNICALL Java_org_petero_droidfish_engine_NativePipedProcess_getNPhysicalProcessors + (JNIEnv *, jclass) +{ + return sysconf(_SC_NPROCESSORS_ONLN); +} diff --git a/DroidFish/jni/stockfish/Android.mk b/DroidFish/jni/stockfish/Android.mk new file mode 100644 index 0000000..474f5e0 --- /dev/null +++ b/DroidFish/jni/stockfish/Android.mk @@ -0,0 +1,22 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := stockfish +LOCAL_SRC_FILES := \ + evaluate.cpp move.cpp search.cpp \ + benchmark.cpp movegen.cpp tt.cpp \ + bitbase.cpp main.cpp movepick.cpp uci.cpp \ + bitboard.cpp pawns.cpp ucioption.cpp \ + book.cpp material.cpp position.cpp \ + endgame.cpp misc.cpp timeman.cpp thread.cpp + +LOCAL_CFLAGS := -I$(LOCAL_PATH)/../stlport/stlport \ + -mandroid \ + -DTARGET_OS=android -D__ANDROID__ \ + -isystem $(SYSROOT)/usr/include \ + -DNO_PREFETCH=1 + +LOCAL_STATIC_LIBRARIES := stlport + +include $(BUILD_STATIC_LIBRARY) diff --git a/DroidFish/jni/stockfish/Copying.txt b/DroidFish/jni/stockfish/Copying.txt new file mode 100644 index 0000000..818433e --- /dev/null +++ b/DroidFish/jni/stockfish/Copying.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/DroidFish/jni/stockfish/benchmark.cpp b/DroidFish/jni/stockfish/benchmark.cpp new file mode 100644 index 0000000..df49787 --- /dev/null +++ b/DroidFish/jni/stockfish/benchmark.cpp @@ -0,0 +1,153 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +#include "position.h" +#include "search.h" +#include "ucioption.h" + +using namespace std; + +static const string Defaults[] = { + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", + "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -", + "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -", + "4rrk1/pp1n3p/3q2pQ/2p1pb2/2PP4/2P3N1/P2B2PP/4RRK1 b - - 7 19", + "rq3rk1/ppp2ppp/1bnpb3/3N2B1/3NP3/7P/PPPQ1PP1/2KR3R w - - 7 14", + "r1bq1r1k/1pp1n1pp/1p1p4/4p2Q/4Pp2/1BNP4/PPP2PPP/3R1RK1 w - - 2 14", + "r3r1k1/2p2ppp/p1p1bn2/8/1q2P3/2NPQN2/PPP3PP/R4RK1 b - - 2 15", + "r1bbk1nr/pp3p1p/2n5/1N4p1/2Np1B2/8/PPP2PPP/2KR1B1R w kq - 0 13", + "r1bq1rk1/ppp1nppp/4n3/3p3Q/3P4/1BP1B3/PP1N2PP/R4RK1 w - - 1 16", + "4r1k1/r1q2ppp/ppp2n2/4P3/5Rb1/1N1BQ3/PPP3PP/R5K1 w - - 1 17", + "2rqkb1r/ppp2p2/2npb1p1/1N1Nn2p/2P1PP2/8/PP2B1PP/R1BQK2R b KQ - 0 11", + "r1bq1r1k/b1p1npp1/p2p3p/1p6/3PP3/1B2NN2/PP3PPP/R2Q1RK1 w - - 1 16", + "3r1rk1/p5pp/bpp1pp2/8/q1PP1P2/b3P3/P2NQRPP/1R2B1K1 b - - 6 22", + "r1q2rk1/2p1bppp/2Pp4/p6b/Q1PNp3/4B3/PP1R1PPP/2K4R w - - 2 18", + "4k2r/1pb2ppp/1p2p3/1R1p4/3P4/2r1PN2/P4PPP/1R4K1 b - - 3 22", + "3q2k1/pb3p1p/4pbp1/2r5/PpN2N2/1P2P2P/5PP1/Q2R2K1 b - - 4 26", + "" +}; + + +/// benchmark() runs a simple benchmark by letting Stockfish analyze a set +/// of positions for a given limit each. There are five parameters; the +/// transposition table size, the number of search threads that should +/// be used, the limit value spent for each position (optional, default +/// is ply 12), an optional file name where to look for positions in fen +/// format (default are the BenchmarkPositions defined above) and the type +/// of the limit value: depth (default), time in secs or number of nodes. +/// The analysis is written to a file named bench.txt. + +void benchmark(int argc, char* argv[]) { + + vector fenList; + SearchLimits limits; + int64_t totalNodes; + int time; + + // Load default positions + for (int i = 0; !Defaults[i].empty(); i++) + fenList.push_back(Defaults[i]); + + // Assign default values to missing arguments + string ttSize = argc > 2 ? argv[2] : "128"; + string threads = argc > 3 ? argv[3] : "1"; + string valStr = argc > 4 ? argv[4] : "12"; + string fenFile = argc > 5 ? argv[5] : "default"; + string valType = argc > 6 ? argv[6] : "depth"; + + Options["Hash"].set_value(ttSize); + Options["Threads"].set_value(threads); + Options["OwnBook"].set_value("false"); + + // Search should be limited by nodes, time or depth ? + if (valType == "nodes") + limits.maxNodes = atoi(valStr.c_str()); + else if (valType == "time") + limits.maxTime = 1000 * atoi(valStr.c_str()); // maxTime is in ms + else + limits.maxDepth = atoi(valStr.c_str()); + + // Do we need to load positions from a given FEN file ? + if (fenFile != "default") + { + string fen; + ifstream f(fenFile.c_str()); + + if (f.is_open()) + { + fenList.clear(); + + while (getline(f, fen)) + if (!fen.empty()) + fenList.push_back(fen); + + f.close(); + } + else + { + cerr << "Unable to open FEN file " << fenFile << endl; + exit(EXIT_FAILURE); + } + } + + // Ok, let's start the benchmark ! + totalNodes = 0; + time = get_system_time(); + + for (size_t i = 0; i < fenList.size(); i++) + { + Move moves[] = { MOVE_NONE }; + Position pos(fenList[i], false, 0); + + cerr << "\nBench position: " << i + 1 << '/' << fenList.size() << endl; + + if (valType == "perft") + { + int64_t cnt = perft(pos, limits.maxDepth * ONE_PLY); + totalNodes += cnt; + + cerr << "\nPerft " << limits.maxDepth << " nodes counted: " << cnt << endl; + } + else + { + if (!think(pos, limits, moves)) + break; + + totalNodes += pos.nodes_searched(); + } + } + + time = get_system_time() - time; + + cerr << "\n===============================" + << "\nTotal time (ms) : " << time + << "\nNodes searched : " << totalNodes + << "\nNodes/second : " << (int)(totalNodes / (time / 1000.0)) << endl << endl; + + // MS Visual C++ debug window always unconditionally closes when program + // exits, this is bad because we want to read results before. + #if (defined(WINDOWS) || defined(WIN32) || defined(WIN64)) + cerr << "Press any key to exit" << endl; + cin >> time; + #endif +} diff --git a/DroidFish/jni/stockfish/bitbase.cpp b/DroidFish/jni/stockfish/bitbase.cpp new file mode 100644 index 0000000..d623839 --- /dev/null +++ b/DroidFish/jni/stockfish/bitbase.cpp @@ -0,0 +1,284 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bitboard.h" +#include "types.h" + +namespace { + + enum Result { + RESULT_UNKNOWN, + RESULT_INVALID, + RESULT_WIN, + RESULT_LOSS, + RESULT_DRAW + }; + + struct KPKPosition { + void from_index(int index); + bool is_legal() const; + bool is_immediate_draw() const; + bool is_immediate_win() const; + Bitboard wk_attacks() const { return StepAttacksBB[WK][whiteKingSquare]; } + Bitboard bk_attacks() const { return StepAttacksBB[BK][blackKingSquare]; } + Bitboard pawn_attacks() const { return StepAttacksBB[WP][pawnSquare]; } + + Square whiteKingSquare, blackKingSquare, pawnSquare; + Color sideToMove; + }; + + // The possible pawns squares are 24, the first 4 files and ranks from 2 to 7 + const int IndexMax = 2 * 24 * 64 * 64; // color * wp_sq * wk_sq * bk_sq + + // Each uint32_t stores results of 32 positions, one per bit + uint32_t KPKBitbase[IndexMax / 32]; + + Result classify_wtm(const KPKPosition& pos, const Result bb[]); + Result classify_btm(const KPKPosition& pos, const Result bb[]); + int compute_index(Square wksq, Square bksq, Square wpsq, Color stm); +} + + +uint32_t probe_kpk_bitbase(Square wksq, Square wpsq, Square bksq, Color stm) { + + int index = compute_index(wksq, bksq, wpsq, stm); + + return KPKBitbase[index / 32] & (1 << (index & 31)); +} + + +void init_kpk_bitbase() { + + Result bb[IndexMax]; + KPKPosition pos; + bool repeat; + + // Initialize table + for (int i = 0; i < IndexMax; i++) + { + pos.from_index(i); + bb[i] = !pos.is_legal() ? RESULT_INVALID + : pos.is_immediate_draw() ? RESULT_DRAW + : pos.is_immediate_win() ? RESULT_WIN : RESULT_UNKNOWN; + } + + // Iterate until all positions are classified (30 cycles needed) + do { + repeat = false; + + for (int i = 0; i < IndexMax; i++) + if (bb[i] == RESULT_UNKNOWN) + { + pos.from_index(i); + + bb[i] = (pos.sideToMove == WHITE) ? classify_wtm(pos, bb) + : classify_btm(pos, bb); + if (bb[i] != RESULT_UNKNOWN) + repeat = true; + } + + } while (repeat); + + // Map 32 position results into one KPKBitbase[] entry + for (int i = 0; i < IndexMax / 32; i++) + for (int j = 0; j < 32; j++) + if (bb[32 * i + j] == RESULT_WIN || bb[32 * i + j] == RESULT_LOSS) + KPKBitbase[i] |= (1 << j); +} + + +namespace { + + // A KPK bitbase index is an integer in [0, IndexMax] range + // + // Information is mapped in this way + // + // bit 0: side to move (WHITE or BLACK) + // bit 1- 6: black king square (from SQ_A1 to SQ_H8) + // bit 7-12: white king square (from SQ_A1 to SQ_H8) + // bit 13-14: white pawn file (from FILE_A to FILE_D) + // bit 15-17: white pawn rank - 1 (from RANK_2 - 1 to RANK_7 - 1) + + int compute_index(Square wksq, Square bksq, Square wpsq, Color stm) { + + assert(square_file(wpsq) <= FILE_D); + + int p = int(square_file(wpsq)) + 4 * int(square_rank(wpsq) - 1); + int r = int(stm) + 2 * int(bksq) + 128 * int(wksq) + 8192 * p; + + assert(r >= 0 && r < IndexMax); + + return r; + } + + void KPKPosition::from_index(int index) { + + int s = (index / 8192) % 24; + + sideToMove = Color(index % 2); + blackKingSquare = Square((index / 2) % 64); + whiteKingSquare = Square((index / 128) % 64); + pawnSquare = make_square(File(s % 4), Rank(s / 4 + 1)); + } + + bool KPKPosition::is_legal() const { + + if ( whiteKingSquare == pawnSquare + || whiteKingSquare == blackKingSquare + || blackKingSquare == pawnSquare) + return false; + + if (sideToMove == WHITE) + { + if ( bit_is_set(wk_attacks(), blackKingSquare) + || bit_is_set(pawn_attacks(), blackKingSquare)) + return false; + } + else if (bit_is_set(bk_attacks(), whiteKingSquare)) + return false; + + return true; + } + + bool KPKPosition::is_immediate_draw() const { + + if (sideToMove == BLACK) + { + Bitboard wka = wk_attacks(); + Bitboard bka = bk_attacks(); + + // Case 1: Stalemate + if ((bka & ~(wka | pawn_attacks())) == EmptyBoardBB) + return true; + + // Case 2: King can capture pawn + if (bit_is_set(bka, pawnSquare) && !bit_is_set(wka, pawnSquare)) + return true; + } + else + { + // Case 1: Stalemate (possible pawn files are only from A to D) + if ( whiteKingSquare == SQ_A8 + && pawnSquare == SQ_A7 + && (blackKingSquare == SQ_C7 || blackKingSquare == SQ_C8)) + return true; + } + return false; + } + + bool KPKPosition::is_immediate_win() const { + + // The position is an immediate win if it is white to move and the + // white pawn can be promoted without getting captured. + return sideToMove == WHITE + && square_rank(pawnSquare) == RANK_7 + && whiteKingSquare != pawnSquare + DELTA_N + && ( square_distance(blackKingSquare, pawnSquare + DELTA_N) > 1 + || bit_is_set(wk_attacks(), pawnSquare + DELTA_N)); + } + + Result classify_wtm(const KPKPosition& pos, const Result bb[]) { + + // If one move leads to a position classified as RESULT_LOSS, the result + // of the current position is RESULT_WIN. If all moves lead to positions + // classified as RESULT_DRAW, the current position is classified RESULT_DRAW + // otherwise the current position is classified as RESULT_UNKNOWN. + + bool unknownFound = false; + Bitboard b; + Square s; + Result r; + + // King moves + b = pos.wk_attacks(); + while (b) + { + s = pop_1st_bit(&b); + r = bb[compute_index(s, pos.blackKingSquare, pos.pawnSquare, BLACK)]; + + if (r == RESULT_LOSS) + return RESULT_WIN; + + if (r == RESULT_UNKNOWN) + unknownFound = true; + } + + // Pawn moves + if (square_rank(pos.pawnSquare) < RANK_7) + { + s = pos.pawnSquare + DELTA_N; + r = bb[compute_index(pos.whiteKingSquare, pos.blackKingSquare, s, BLACK)]; + + if (r == RESULT_LOSS) + return RESULT_WIN; + + if (r == RESULT_UNKNOWN) + unknownFound = true; + + // Double pawn push + if ( square_rank(s) == RANK_3 + && s != pos.whiteKingSquare + && s != pos.blackKingSquare) + { + s += DELTA_N; + r = bb[compute_index(pos.whiteKingSquare, pos.blackKingSquare, s, BLACK)]; + + if (r == RESULT_LOSS) + return RESULT_WIN; + + if (r == RESULT_UNKNOWN) + unknownFound = true; + } + } + return unknownFound ? RESULT_UNKNOWN : RESULT_DRAW; + } + + + Result classify_btm(const KPKPosition& pos, const Result bb[]) { + + // If one move leads to a position classified as RESULT_DRAW, the result + // of the current position is RESULT_DRAW. If all moves lead to positions + // classified as RESULT_WIN, the current position is classified as + // RESULT_LOSS. Otherwise, the current position is classified as + // RESULT_UNKNOWN. + + bool unknownFound = false; + Bitboard b; + Square s; + Result r; + + // King moves + b = pos.bk_attacks(); + while (b) + { + s = pop_1st_bit(&b); + r = bb[compute_index(pos.whiteKingSquare, s, pos.pawnSquare, WHITE)]; + + if (r == RESULT_DRAW) + return RESULT_DRAW; + + if (r == RESULT_UNKNOWN) + unknownFound = true; + } + return unknownFound ? RESULT_UNKNOWN : RESULT_LOSS; + } + +} diff --git a/DroidFish/jni/stockfish/bitboard.cpp b/DroidFish/jni/stockfish/bitboard.cpp new file mode 100644 index 0000000..110609d --- /dev/null +++ b/DroidFish/jni/stockfish/bitboard.cpp @@ -0,0 +1,486 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bitboard.h" +#include "bitcount.h" + +#if defined(IS_64BIT) + +const uint64_t BMult[64] = { + 0x0440049104032280ULL, 0x1021023C82008040ULL, 0x0404040082000048ULL, + 0x48C4440084048090ULL, 0x2801104026490000ULL, 0x4100880442040800ULL, + 0x0181011002E06040ULL, 0x9101004104200E00ULL, 0x1240848848310401ULL, + 0x2000142828050024ULL, 0x00001004024D5000ULL, 0x0102044400800200ULL, + 0x8108108820112000ULL, 0xA880818210C00046ULL, 0x4008008801082000ULL, + 0x0060882404049400ULL, 0x0104402004240810ULL, 0x000A002084250200ULL, + 0x00100B0880801100ULL, 0x0004080201220101ULL, 0x0044008080A00000ULL, + 0x0000202200842000ULL, 0x5006004882D00808ULL, 0x0000200045080802ULL, + 0x0086100020200601ULL, 0xA802080A20112C02ULL, 0x0080411218080900ULL, + 0x000200A0880080A0ULL, 0x9A01010000104000ULL, 0x0028008003100080ULL, + 0x0211021004480417ULL, 0x0401004188220806ULL, 0x00825051400C2006ULL, + 0x00140C0210943000ULL, 0x0000242800300080ULL, 0x00C2208120080200ULL, + 0x2430008200002200ULL, 0x1010100112008040ULL, 0x8141050100020842ULL, + 0x0000822081014405ULL, 0x800C049E40400804ULL, 0x4A0404028A000820ULL, + 0x0022060201041200ULL, 0x0360904200840801ULL, 0x0881A08208800400ULL, + 0x0060202C00400420ULL, 0x1204440086061400ULL, 0x0008184042804040ULL, + 0x0064040315300400ULL, 0x0C01008801090A00ULL, 0x0808010401140C00ULL, + 0x04004830C2020040ULL, 0x0080005002020054ULL, 0x40000C14481A0490ULL, + 0x0010500101042048ULL, 0x1010100200424000ULL, 0x0000640901901040ULL, + 0x00000A0201014840ULL, 0x00840082AA011002ULL, 0x010010840084240AULL, + 0x0420400810420608ULL, 0x8D40230408102100ULL, 0x4A00200612222409ULL, + 0x0A08520292120600ULL +}; + +const uint64_t RMult[64] = { + 0x0A8002C000108020ULL, 0x4440200140003000ULL, 0x8080200010011880ULL, + 0x0380180080141000ULL, 0x1A00060008211044ULL, 0x410001000A0C0008ULL, + 0x9500060004008100ULL, 0x0100024284A20700ULL, 0x0000802140008000ULL, + 0x0080C01002A00840ULL, 0x0402004282011020ULL, 0x9862000820420050ULL, + 0x0001001448011100ULL, 0x6432800200800400ULL, 0x040100010002000CULL, + 0x0002800D0010C080ULL, 0x90C0008000803042ULL, 0x4010004000200041ULL, + 0x0003010010200040ULL, 0x0A40828028001000ULL, 0x0123010008000430ULL, + 0x0024008004020080ULL, 0x0060040001104802ULL, 0x00582200028400D1ULL, + 0x4000802080044000ULL, 0x0408208200420308ULL, 0x0610038080102000ULL, + 0x3601000900100020ULL, 0x0000080080040180ULL, 0x00C2020080040080ULL, + 0x0080084400100102ULL, 0x4022408200014401ULL, 0x0040052040800082ULL, + 0x0B08200280804000ULL, 0x008A80A008801000ULL, 0x4000480080801000ULL, + 0x0911808800801401ULL, 0x822A003002001894ULL, 0x401068091400108AULL, + 0x000004A10A00004CULL, 0x2000800640008024ULL, 0x1486408102020020ULL, + 0x000100A000D50041ULL, 0x00810050020B0020ULL, 0x0204000800808004ULL, + 0x00020048100A000CULL, 0x0112000831020004ULL, 0x0009000040810002ULL, + 0x0440490200208200ULL, 0x8910401000200040ULL, 0x6404200050008480ULL, + 0x4B824A2010010100ULL, 0x04080801810C0080ULL, 0x00000400802A0080ULL, + 0x8224080110026400ULL, 0x40002C4104088200ULL, 0x01002100104A0282ULL, + 0x1208400811048021ULL, 0x3201014A40D02001ULL, 0x0005100019200501ULL, + 0x0101000208001005ULL, 0x0002008450080702ULL, 0x001002080301D00CULL, + 0x410201CE5C030092ULL +}; + +const int BShift[64] = { + 58, 59, 59, 59, 59, 59, 59, 58, 59, 59, 59, 59, 59, 59, 59, 59, + 59, 59, 57, 57, 57, 57, 59, 59, 59, 59, 57, 55, 55, 57, 59, 59, + 59, 59, 57, 55, 55, 57, 59, 59, 59, 59, 57, 57, 57, 57, 59, 59, + 59, 59, 59, 59, 59, 59, 59, 59, 58, 59, 59, 59, 59, 59, 59, 58 +}; + +const int RShift[64] = { + 52, 53, 53, 53, 53, 53, 53, 52, 53, 54, 54, 54, 54, 54, 54, 53, + 53, 54, 54, 54, 54, 54, 54, 53, 53, 54, 54, 54, 54, 54, 54, 53, + 53, 54, 54, 54, 54, 54, 54, 53, 53, 54, 54, 54, 54, 54, 54, 53, + 53, 54, 54, 54, 54, 54, 54, 53, 52, 53, 53, 53, 53, 53, 53, 52 +}; + +#else // if !defined(IS_64BIT) + +const uint64_t BMult[64] = { + 0x54142844C6A22981ULL, 0x710358A6EA25C19EULL, 0x704F746D63A4A8DCULL, + 0xBFED1A0B80F838C5ULL, 0x90561D5631E62110ULL, 0x2804260376E60944ULL, + 0x84A656409AA76871ULL, 0xF0267F64C28B6197ULL, 0x70764EBB762F0585ULL, + 0x92AA09E0CFE161DEULL, 0x41EE1F6BB266F60EULL, 0xDDCBF04F6039C444ULL, + 0x5A3FAB7BAC0D988AULL, 0xD3727877FA4EAA03ULL, 0xD988402D868DDAAEULL, + 0x812B291AFA075C7CULL, 0x94FAF987B685A932ULL, 0x3ED867D8470D08DBULL, + 0x92517660B8901DE8ULL, 0x2D97E43E058814B4ULL, 0x880A10C220B25582ULL, + 0xC7C6520D1F1A0477ULL, 0xDBFC7FBCD7656AA6ULL, 0x78B1B9BFB1A2B84FULL, + 0x2F20037F112A0BC1ULL, 0x657171EA2269A916ULL, 0xC08302B07142210EULL, + 0x0880A4403064080BULL, 0x3602420842208C00ULL, 0x852800DC7E0B6602ULL, + 0x595A3FBBAA0F03B2ULL, 0x9F01411558159D5EULL, 0x2B4A4A5F88B394F2ULL, + 0x4AFCBFFC292DD03AULL, 0x4A4094A3B3F10522ULL, 0xB06F00B491F30048ULL, + 0xD5B3820280D77004ULL, 0x8B2E01E7C8E57A75ULL, 0x2D342794E886C2E6ULL, + 0xC302C410CDE21461ULL, 0x111F426F1379C274ULL, 0xE0569220ABB31588ULL, + 0x5026D3064D453324ULL, 0xE2076040C343CD8AULL, 0x93EFD1E1738021EEULL, + 0xB680804BED143132ULL, 0x44E361B21986944CULL, 0x44C60170EF5C598CULL, + 0xF4DA475C195C9C94ULL, 0xA3AFBB5F72060B1DULL, 0xBC75F410E41C4FFCULL, + 0xB51C099390520922ULL, 0x902C011F8F8EC368ULL, 0x950B56B3D6F5490AULL, + 0x3909E0635BF202D0ULL, 0x5744F90206EC10CCULL, 0xDC59FD76317ABBC1ULL, + 0x881C7C67FCBFC4F6ULL, 0x47CA41E7E440D423ULL, 0xEB0C88112048D004ULL, + 0x51C60E04359AEF1AULL, 0x1AA1FE0E957A5554ULL, 0xDD9448DB4F5E3104ULL, + 0xDC01F6DCA4BEBBDCULL, +}; + +const uint64_t RMult[64] = { + 0xD7445CDEC88002C0ULL, 0xD0A505C1F2001722ULL, 0xE065D1C896002182ULL, + 0x9A8C41E75A000892ULL, 0x8900B10C89002AA8ULL, 0x9B28D1C1D60005A2ULL, + 0x015D6C88DE002D9AULL, 0xB1DBFC802E8016A9ULL, 0x149A1042D9D60029ULL, + 0xB9C08050599E002FULL, 0x132208C3AF300403ULL, 0xC1000CE2E9C50070ULL, + 0x9D9AA13C99020012ULL, 0xB6B078DAF71E0046ULL, 0x9D880182FB6E002EULL, + 0x52889F467E850037ULL, 0xDA6DC008D19A8480ULL, 0x468286034F902420ULL, + 0x7140AC09DC54C020ULL, 0xD76FFFFA39548808ULL, 0xEA901C4141500808ULL, + 0xC91004093F953A02ULL, 0x02882AFA8F6BB402ULL, 0xAEBE335692442C01ULL, + 0x0E904A22079FB91EULL, 0x13A514851055F606ULL, 0x76C782018C8FE632ULL, + 0x1DC012A9D116DA06ULL, 0x3C9E0037264FFFA6ULL, 0x2036002853C6E4A2ULL, + 0xE3FE08500AFB47D4ULL, 0xF38AF25C86B025C2ULL, 0xC0800E2182CF9A40ULL, + 0x72002480D1F60673ULL, 0x2500200BAE6E9B53ULL, 0xC60018C1EEFCA252ULL, + 0x0600590473E3608AULL, 0x46002C4AB3FE51B2ULL, 0xA200011486BCC8D2ULL, + 0xB680078095784C63ULL, 0x2742002639BF11AEULL, 0xC7D60021A5BDB142ULL, + 0xC8C04016BB83D820ULL, 0xBD520028123B4842ULL, 0x9D1600344AC2A832ULL, + 0x6A808005631C8A05ULL, 0x604600A148D5389AULL, 0xE2E40103D40DEA65ULL, + 0x945B5A0087C62A81ULL, 0x012DC200CD82D28EULL, 0x2431C600B5F9EF76ULL, + 0xFB142A006A9B314AULL, 0x06870E00A1C97D62ULL, 0x2A9DB2004A2689A2ULL, + 0xD3594600CAF5D1A2ULL, 0xEE0E4900439344A7ULL, 0x89C4D266CA25007AULL, + 0x3E0013A2743F97E3ULL, 0x0180E31A0431378AULL, 0x3A9E465A4D42A512ULL, + 0x98D0A11A0C0D9CC2ULL, 0x8E711C1ABA19B01EULL, 0x8DCDC836DD201142ULL, + 0x5AC08A4735370479ULL, +}; + +const int BShift[64] = { + 26, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 25, 25, 25, 25, 27, 27, 27, 27, 25, 23, 23, 25, 27, 27, + 27, 27, 25, 23, 23, 25, 27, 27, 27, 27, 25, 25, 25, 25, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 26 +}; + +const int RShift[64] = { + 20, 21, 21, 21, 21, 21, 21, 20, 21, 22, 22, 22, 22, 22, 22, 21, + 21, 22, 22, 22, 22, 22, 22, 21, 21, 22, 22, 22, 22, 22, 22, 21, + 21, 22, 22, 22, 22, 22, 22, 21, 21, 22, 22, 22, 22, 22, 22, 21, + 21, 22, 22, 22, 22, 22, 22, 21, 20, 21, 21, 21, 21, 21, 21, 20 +}; + +#endif // defined(IS_64BIT) + +// Global bitboards definitions with static storage duration are +// automatically set to zero before enter main(). +Bitboard RMask[64]; +int RAttackIndex[64]; +Bitboard RAttacks[0x19000]; + +Bitboard BMask[64]; +int BAttackIndex[64]; +Bitboard BAttacks[0x1480]; + +Bitboard SetMaskBB[65]; +Bitboard ClearMaskBB[65]; + +Bitboard SquaresByColorBB[2]; +Bitboard FileBB[8]; +Bitboard RankBB[8]; +Bitboard NeighboringFilesBB[8]; +Bitboard ThisAndNeighboringFilesBB[8]; +Bitboard InFrontBB[2][8]; +Bitboard StepAttacksBB[16][64]; +Bitboard BetweenBB[64][64]; +Bitboard SquaresInFrontMask[2][64]; +Bitboard PassedPawnMask[2][64]; +Bitboard AttackSpanMask[2][64]; + +Bitboard BishopPseudoAttacks[64]; +Bitboard RookPseudoAttacks[64]; +Bitboard QueenPseudoAttacks[64]; + +uint8_t BitCount8Bit[256]; + + +namespace { + + void init_masks(); + void init_step_attacks(); + void init_pseudo_attacks(); + void init_between_bitboards(); + Bitboard index_to_bitboard(int index, Bitboard mask); + Bitboard sliding_attacks(int sq, Bitboard occupied, int deltas[][2], + int fmin, int fmax, int rmin, int rmax); + void init_sliding_attacks(Bitboard attacks[], int attackIndex[], Bitboard mask[], + const int shift[], const Bitboard mult[], int deltas[][2]); +} + + +/// print_bitboard() prints a bitboard in an easily readable format to the +/// standard output. This is sometimes useful for debugging. + +void print_bitboard(Bitboard b) { + + for (Rank r = RANK_8; r >= RANK_1; r--) + { + std::cout << "+---+---+---+---+---+---+---+---+" << '\n'; + for (File f = FILE_A; f <= FILE_H; f++) + std::cout << "| " << (bit_is_set(b, make_square(f, r)) ? 'X' : ' ') << ' '; + + std::cout << "|\n"; + } + std::cout << "+---+---+---+---+---+---+---+---+" << std::endl; +} + + +/// first_1() finds the least significant nonzero bit in a nonzero bitboard. +/// pop_1st_bit() finds and clears the least significant nonzero bit in a +/// nonzero bitboard. + +#if defined(IS_64BIT) && !defined(USE_BSFQ) + +static CACHE_LINE_ALIGNMENT +const int BitTable[64] = { + 0, 1, 2, 7, 3, 13, 8, 19, 4, 25, 14, 28, 9, 34, 20, 40, 5, 17, 26, + 38, 15, 46, 29, 48, 10, 31, 35, 54, 21, 50, 41, 57, 63, 6, 12, 18, 24, 27, + 33, 39, 16, 37, 45, 47, 30, 53, 49, 56, 62, 11, 23, 32, 36, 44, 52, 55, 61, + 22, 43, 51, 60, 42, 59, 58 +}; + +Square first_1(Bitboard b) { + return Square(BitTable[((b & -b) * 0x218a392cd3d5dbfULL) >> 58]); +} + +Square pop_1st_bit(Bitboard* b) { + Bitboard bb = *b; + *b &= (*b - 1); + return Square(BitTable[((bb & -bb) * 0x218a392cd3d5dbfULL) >> 58]); +} + +#elif !defined(USE_BSFQ) + +static CACHE_LINE_ALIGNMENT +const int BitTable[64] = { + 63, 30, 3, 32, 25, 41, 22, 33, 15, 50, 42, 13, 11, 53, 19, 34, 61, 29, 2, + 51, 21, 43, 45, 10, 18, 47, 1, 54, 9, 57, 0, 35, 62, 31, 40, 4, 49, 5, + 52, 26, 60, 6, 23, 44, 46, 27, 56, 16, 7, 39, 48, 24, 59, 14, 12, 55, 38, + 28, 58, 20, 37, 17, 36, 8 +}; + +Square first_1(Bitboard b) { + + b ^= (b - 1); + uint32_t fold = int(b) ^ int(b >> 32); + return Square(BitTable[(fold * 0x783a9b23) >> 26]); +} + +// Use type-punning +union b_union { + + Bitboard b; + struct { +#if defined (BIGENDIAN) + uint32_t h; + uint32_t l; +#else + uint32_t l; + uint32_t h; +#endif + } dw; +}; + +Square pop_1st_bit(Bitboard* bb) { + + b_union u; + Square ret; + + u.b = *bb; + + if (u.dw.l) + { + ret = Square(BitTable[((u.dw.l ^ (u.dw.l - 1)) * 0x783a9b23) >> 26]); + u.dw.l &= (u.dw.l - 1); + *bb = u.b; + return ret; + } + ret = Square(BitTable[((~(u.dw.h ^ (u.dw.h - 1))) * 0x783a9b23) >> 26]); + u.dw.h &= (u.dw.h - 1); + *bb = u.b; + return ret; +} + +#endif // !defined(USE_BSFQ) + + +/// init_bitboards() initializes various bitboard arrays. It is called during +/// program initialization. + +void init_bitboards() { + + int rookDeltas[4][2] = {{0,1},{0,-1},{1,0},{-1,0}}; + int bishopDeltas[4][2] = {{1,1},{-1,1},{1,-1},{-1,-1}}; + + init_masks(); + init_step_attacks(); + init_sliding_attacks(RAttacks, RAttackIndex, RMask, RShift, RMult, rookDeltas); + init_sliding_attacks(BAttacks, BAttackIndex, BMask, BShift, BMult, bishopDeltas); + init_pseudo_attacks(); + init_between_bitboards(); +} + +namespace { + + // All functions below are used to precompute various bitboards during + // program initialization. Some of the functions may be difficult to + // understand, but they all seem to work correctly, and it should never + // be necessary to touch any of them. + + void init_masks() { + + SquaresByColorBB[DARK] = 0xAA55AA55AA55AA55ULL; + SquaresByColorBB[LIGHT] = ~SquaresByColorBB[DARK]; + + FileBB[FILE_A] = FileABB; + RankBB[RANK_1] = Rank1BB; + + for (int f = FILE_B; f <= FILE_H; f++) + { + FileBB[f] = FileBB[f - 1] << 1; + RankBB[f] = RankBB[f - 1] << 8; + } + + for (int f = FILE_A; f <= FILE_H; f++) + { + NeighboringFilesBB[f] = (f > FILE_A ? FileBB[f - 1] : 0) | (f < FILE_H ? FileBB[f + 1] : 0); + ThisAndNeighboringFilesBB[f] = FileBB[f] | NeighboringFilesBB[f]; + } + + for (int rw = RANK_7, rb = RANK_2; rw >= RANK_1; rw--, rb++) + { + InFrontBB[WHITE][rw] = InFrontBB[WHITE][rw + 1] | RankBB[rw + 1]; + InFrontBB[BLACK][rb] = InFrontBB[BLACK][rb - 1] | RankBB[rb - 1]; + } + + SetMaskBB[SQ_NONE] = EmptyBoardBB; + ClearMaskBB[SQ_NONE] = ~SetMaskBB[SQ_NONE]; + + for (Square s = SQ_A1; s <= SQ_H8; s++) + { + SetMaskBB[s] = (1ULL << s); + ClearMaskBB[s] = ~SetMaskBB[s]; + } + + for (Color c = WHITE; c <= BLACK; c++) + for (Square s = SQ_A1; s <= SQ_H8; s++) + { + SquaresInFrontMask[c][s] = in_front_bb(c, s) & file_bb(s); + PassedPawnMask[c][s] = in_front_bb(c, s) & this_and_neighboring_files_bb(s); + AttackSpanMask[c][s] = in_front_bb(c, s) & neighboring_files_bb(s); + } + + for (Bitboard b = 0; b < 256; b++) + BitCount8Bit[b] = (uint8_t)count_1s(b); + } + + void init_step_attacks() { + + const int step[][9] = { + {0}, + {7,9,0}, {17,15,10,6,-6,-10,-15,-17,0}, {0}, {0}, {0}, + {9,7,-7,-9,8,1,-1,-8,0}, {0}, {0}, + {-7,-9,0}, {17,15,10,6,-6,-10,-15,-17,0}, {0}, {0}, {0}, + {9,7,-7,-9,8,1,-1,-8,0} + }; + + for (Square s = SQ_A1; s <= SQ_H8; s++) + for (Piece pc = WP; pc <= BK; pc++) + for (int k = 0; step[pc][k] != 0; k++) + { + Square to = s + Square(step[pc][k]); + + if (square_is_ok(to) && square_distance(s, to) < 3) + set_bit(&StepAttacksBB[pc][s], to); + } + } + + Bitboard sliding_attacks(int sq, Bitboard occupied, int deltas[][2], + int fmin, int fmax, int rmin, int rmax) { + int dx, dy, f, r; + int rk = sq / 8; + int fl = sq % 8; + Bitboard attacks = EmptyBoardBB; + + for (int i = 0; i < 4; i++) + { + dx = deltas[i][0]; + dy = deltas[i][1]; + f = fl + dx; + r = rk + dy; + + while ( (dx == 0 || (f >= fmin && f <= fmax)) + && (dy == 0 || (r >= rmin && r <= rmax))) + { + attacks |= (1ULL << (f + r * 8)); + + if (occupied & (1ULL << (f + r * 8))) + break; + + f += dx; + r += dy; + } + } + return attacks; + } + + Bitboard index_to_bitboard(int index, Bitboard mask) { + + Bitboard result = EmptyBoardBB; + int sq, cnt = 0; + + while (mask) + { + sq = pop_1st_bit(&mask); + + if (index & (1 << cnt++)) + result |= (1ULL << sq); + } + return result; + } + + void init_sliding_attacks(Bitboard attacks[], int attackIndex[], Bitboard mask[], + const int shift[], const Bitboard mult[], int deltas[][2]) { + Bitboard b, v; + int i, j, index; + + for (i = index = 0; i < 64; i++) + { + attackIndex[i] = index; + mask[i] = sliding_attacks(i, 0, deltas, 1, 6, 1, 6); + j = 1 << ((CpuIs64Bit ? 64 : 32) - shift[i]); + + for (int k = 0; k < j; k++) + { + b = index_to_bitboard(k, mask[i]); + v = CpuIs64Bit ? b * mult[i] : unsigned(b * mult[i] ^ (b >> 32) * (mult[i] >> 32)); + attacks[index + (v >> shift[i])] = sliding_attacks(i, b, deltas, 0, 7, 0, 7); + } + index += j; + } + } + + void init_pseudo_attacks() { + + for (Square s = SQ_A1; s <= SQ_H8; s++) + { + BishopPseudoAttacks[s] = bishop_attacks_bb(s, EmptyBoardBB); + RookPseudoAttacks[s] = rook_attacks_bb(s, EmptyBoardBB); + QueenPseudoAttacks[s] = queen_attacks_bb(s, EmptyBoardBB); + } + } + + void init_between_bitboards() { + + Square s1, s2, s3, d; + int f, r; + + for (s1 = SQ_A1; s1 <= SQ_H8; s1++) + for (s2 = SQ_A1; s2 <= SQ_H8; s2++) + if (bit_is_set(QueenPseudoAttacks[s1], s2)) + { + f = file_distance(s1, s2); + r = rank_distance(s1, s2); + + d = (s2 - s1) / Max(f, r); + + for (s3 = s1 + d; s3 != s2; s3 += d) + set_bit(&(BetweenBB[s1][s2]), s3); + } + } + +} diff --git a/DroidFish/jni/stockfish/bitboard.h b/DroidFish/jni/stockfish/bitboard.h new file mode 100644 index 0000000..9704839 --- /dev/null +++ b/DroidFish/jni/stockfish/bitboard.h @@ -0,0 +1,295 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(BITBOARD_H_INCLUDED) +#define BITBOARD_H_INCLUDED + +#include "types.h" + +const Bitboard EmptyBoardBB = 0; + +const Bitboard FileABB = 0x0101010101010101ULL; +const Bitboard FileBBB = FileABB << 1; +const Bitboard FileCBB = FileABB << 2; +const Bitboard FileDBB = FileABB << 3; +const Bitboard FileEBB = FileABB << 4; +const Bitboard FileFBB = FileABB << 5; +const Bitboard FileGBB = FileABB << 6; +const Bitboard FileHBB = FileABB << 7; + +const Bitboard Rank1BB = 0xFF; +const Bitboard Rank2BB = Rank1BB << (8 * 1); +const Bitboard Rank3BB = Rank1BB << (8 * 2); +const Bitboard Rank4BB = Rank1BB << (8 * 3); +const Bitboard Rank5BB = Rank1BB << (8 * 4); +const Bitboard Rank6BB = Rank1BB << (8 * 5); +const Bitboard Rank7BB = Rank1BB << (8 * 6); +const Bitboard Rank8BB = Rank1BB << (8 * 7); + +extern Bitboard SquaresByColorBB[2]; +extern Bitboard FileBB[8]; +extern Bitboard NeighboringFilesBB[8]; +extern Bitboard ThisAndNeighboringFilesBB[8]; +extern Bitboard RankBB[8]; +extern Bitboard InFrontBB[2][8]; + +extern Bitboard SetMaskBB[65]; +extern Bitboard ClearMaskBB[65]; + +extern Bitboard StepAttacksBB[16][64]; +extern Bitboard BetweenBB[64][64]; + +extern Bitboard SquaresInFrontMask[2][64]; +extern Bitboard PassedPawnMask[2][64]; +extern Bitboard AttackSpanMask[2][64]; + +extern const uint64_t RMult[64]; +extern const int RShift[64]; +extern Bitboard RMask[64]; +extern int RAttackIndex[64]; +extern Bitboard RAttacks[0x19000]; + +extern const uint64_t BMult[64]; +extern const int BShift[64]; +extern Bitboard BMask[64]; +extern int BAttackIndex[64]; +extern Bitboard BAttacks[0x1480]; + +extern Bitboard BishopPseudoAttacks[64]; +extern Bitboard RookPseudoAttacks[64]; +extern Bitboard QueenPseudoAttacks[64]; + +extern uint8_t BitCount8Bit[256]; + + +/// Functions for testing whether a given bit is set in a bitboard, and for +/// setting and clearing bits. + +inline Bitboard bit_is_set(Bitboard b, Square s) { + return b & SetMaskBB[s]; +} + +inline void set_bit(Bitboard *b, Square s) { + *b |= SetMaskBB[s]; +} + +inline void clear_bit(Bitboard *b, Square s) { + *b &= ClearMaskBB[s]; +} + + +/// Functions used to update a bitboard after a move. This is faster +/// then calling a sequence of clear_bit() + set_bit() + +inline Bitboard make_move_bb(Square from, Square to) { + return SetMaskBB[from] | SetMaskBB[to]; +} + +inline void do_move_bb(Bitboard *b, Bitboard move_bb) { + *b ^= move_bb; +} + + +/// rank_bb() and file_bb() take a file or a square as input and return +/// a bitboard representing all squares on the given file or rank. + +inline Bitboard rank_bb(Rank r) { + return RankBB[r]; +} + +inline Bitboard rank_bb(Square s) { + return RankBB[square_rank(s)]; +} + +inline Bitboard file_bb(File f) { + return FileBB[f]; +} + +inline Bitboard file_bb(Square s) { + return FileBB[square_file(s)]; +} + + +/// neighboring_files_bb takes a file or a square as input and returns a +/// bitboard representing all squares on the neighboring files. + +inline Bitboard neighboring_files_bb(File f) { + return NeighboringFilesBB[f]; +} + +inline Bitboard neighboring_files_bb(Square s) { + return NeighboringFilesBB[square_file(s)]; +} + + +/// this_and_neighboring_files_bb takes a file or a square as input and returns +/// a bitboard representing all squares on the given and neighboring files. + +inline Bitboard this_and_neighboring_files_bb(File f) { + return ThisAndNeighboringFilesBB[f]; +} + +inline Bitboard this_and_neighboring_files_bb(Square s) { + return ThisAndNeighboringFilesBB[square_file(s)]; +} + + +/// in_front_bb() takes a color and a rank or square as input, and returns a +/// bitboard representing all the squares on all ranks in front of the rank +/// (or square), from the given color's point of view. For instance, +/// in_front_bb(WHITE, RANK_5) will give all squares on ranks 6, 7 and 8, while +/// in_front_bb(BLACK, SQ_D3) will give all squares on ranks 1 and 2. + +inline Bitboard in_front_bb(Color c, Rank r) { + return InFrontBB[c][r]; +} + +inline Bitboard in_front_bb(Color c, Square s) { + return InFrontBB[c][square_rank(s)]; +} + + +/// Functions for computing sliding attack bitboards. rook_attacks_bb(), +/// bishop_attacks_bb() and queen_attacks_bb() all take a square and a +/// bitboard of occupied squares as input, and return a bitboard representing +/// all squares attacked by a rook, bishop or queen on the given square. + +#if defined(IS_64BIT) + +inline Bitboard rook_attacks_bb(Square s, Bitboard blockers) { + Bitboard b = blockers & RMask[s]; + return RAttacks[RAttackIndex[s] + ((b * RMult[s]) >> RShift[s])]; +} + +inline Bitboard bishop_attacks_bb(Square s, Bitboard blockers) { + Bitboard b = blockers & BMask[s]; + return BAttacks[BAttackIndex[s] + ((b * BMult[s]) >> BShift[s])]; +} + +#else // if !defined(IS_64BIT) + +inline Bitboard rook_attacks_bb(Square s, Bitboard blockers) { + Bitboard b = blockers & RMask[s]; + return RAttacks[RAttackIndex[s] + + (unsigned(int(b) * int(RMult[s]) ^ int(b >> 32) * int(RMult[s] >> 32)) >> RShift[s])]; +} + +inline Bitboard bishop_attacks_bb(Square s, Bitboard blockers) { + Bitboard b = blockers & BMask[s]; + return BAttacks[BAttackIndex[s] + + (unsigned(int(b) * int(BMult[s]) ^ int(b >> 32) * int(BMult[s] >> 32)) >> BShift[s])]; +} + +#endif + +inline Bitboard queen_attacks_bb(Square s, Bitboard blockers) { + return rook_attacks_bb(s, blockers) | bishop_attacks_bb(s, blockers); +} + + +/// squares_between returns a bitboard representing all squares between +/// two squares. For instance, squares_between(SQ_C4, SQ_F7) returns a +/// bitboard with the bits for square d5 and e6 set. If s1 and s2 are not +/// on the same line, file or diagonal, EmptyBoardBB is returned. + +inline Bitboard squares_between(Square s1, Square s2) { + return BetweenBB[s1][s2]; +} + + +/// squares_in_front_of takes a color and a square as input, and returns a +/// bitboard representing all squares along the line in front of the square, +/// from the point of view of the given color. Definition of the table is: +/// SquaresInFrontOf[c][s] = in_front_bb(c, s) & file_bb(s) + +inline Bitboard squares_in_front_of(Color c, Square s) { + return SquaresInFrontMask[c][s]; +} + + +/// passed_pawn_mask takes a color and a square as input, and returns a +/// bitboard mask which can be used to test if a pawn of the given color on +/// the given square is a passed pawn. Definition of the table is: +/// PassedPawnMask[c][s] = in_front_bb(c, s) & this_and_neighboring_files_bb(s) + +inline Bitboard passed_pawn_mask(Color c, Square s) { + return PassedPawnMask[c][s]; +} + + +/// attack_span_mask takes a color and a square as input, and returns a bitboard +/// representing all squares that can be attacked by a pawn of the given color +/// when it moves along its file starting from the given square. Definition is: +/// AttackSpanMask[c][s] = in_front_bb(c, s) & neighboring_files_bb(s); + +inline Bitboard attack_span_mask(Color c, Square s) { + return AttackSpanMask[c][s]; +} + + +/// squares_aligned returns true if the squares s1, s2 and s3 are aligned +/// either on a straight or on a diagonal line. + +inline bool squares_aligned(Square s1, Square s2, Square s3) { + return (BetweenBB[s1][s2] | BetweenBB[s1][s3] | BetweenBB[s2][s3]) + & ((1ULL << s1) | (1ULL << s2) | (1ULL << s3)); +} + + +/// first_1() finds the least significant nonzero bit in a nonzero bitboard. +/// pop_1st_bit() finds and clears the least significant nonzero bit in a +/// nonzero bitboard. + +#if defined(USE_BSFQ) + +#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) + +FORCE_INLINE Square first_1(Bitboard b) { + unsigned long index; + _BitScanForward64(&index, b); + return (Square) index; +} +#else + +FORCE_INLINE Square first_1(Bitboard b) { // Assembly code by Heinz van Saanen + Bitboard dummy; + __asm__("bsfq %1, %0": "=r"(dummy): "rm"(b) ); + return (Square) dummy; +} +#endif + +FORCE_INLINE Square pop_1st_bit(Bitboard* b) { + const Square s = first_1(*b); + *b &= ~(1ULL<. +*/ + +#if !defined(BITCOUNT_H_INCLUDED) +#define BITCOUNT_H_INCLUDED + +#include "types.h" + +enum BitCountType { + CNT64, + CNT64_MAX15, + CNT32, + CNT32_MAX15, + CNT_POPCNT +}; + +/// count_1s() counts the number of nonzero bits in a bitboard. +/// We have different optimized versions according if platform +/// is 32 or 64 bits, and to the maximum number of nonzero bits. +/// We also support hardware popcnt instruction. See Readme.txt +/// on how to pgo compile with popcnt support. +template inline int count_1s(Bitboard); + +template<> +inline int count_1s(Bitboard b) { + b -= ((b>>1) & 0x5555555555555555ULL); + b = ((b>>2) & 0x3333333333333333ULL) + (b & 0x3333333333333333ULL); + b = ((b>>4) + b) & 0x0F0F0F0F0F0F0F0FULL; + b *= 0x0101010101010101ULL; + return int(b >> 56); +} + +template<> +inline int count_1s(Bitboard b) { + b -= (b>>1) & 0x5555555555555555ULL; + b = ((b>>2) & 0x3333333333333333ULL) + (b & 0x3333333333333333ULL); + b *= 0x1111111111111111ULL; + return int(b >> 60); +} + +template<> +inline int count_1s(Bitboard b) { + unsigned w = unsigned(b >> 32), v = unsigned(b); + v -= (v >> 1) & 0x55555555; // 0-2 in 2 bits + w -= (w >> 1) & 0x55555555; + v = ((v >> 2) & 0x33333333) + (v & 0x33333333); // 0-4 in 4 bits + w = ((w >> 2) & 0x33333333) + (w & 0x33333333); + v = ((v >> 4) + v) & 0x0F0F0F0F; // 0-8 in 8 bits + v += (((w >> 4) + w) & 0x0F0F0F0F); // 0-16 in 8 bits + v *= 0x01010101; // mul is fast on amd procs + return int(v >> 24); +} + +template<> +inline int count_1s(Bitboard b) { + unsigned w = unsigned(b >> 32), v = unsigned(b); + v -= (v >> 1) & 0x55555555; // 0-2 in 2 bits + w -= (w >> 1) & 0x55555555; + v = ((v >> 2) & 0x33333333) + (v & 0x33333333); // 0-4 in 4 bits + w = ((w >> 2) & 0x33333333) + (w & 0x33333333); + v += w; // 0-8 in 4 bits + v *= 0x11111111; + return int(v >> 28); +} + +template<> +inline int count_1s(Bitboard b) { +#if !defined(USE_POPCNT) + return int(b != 0); // Avoid 'b not used' warning +#elif defined(_MSC_VER) && defined(__INTEL_COMPILER) + return _mm_popcnt_u64(b); +#elif defined(_MSC_VER) + return (int)__popcnt64(b); +#elif defined(__GNUC__) + unsigned long ret; + __asm__("popcnt %1, %0" : "=r" (ret) : "r" (b)); + return ret; +#endif +} + +#endif // !defined(BITCOUNT_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/book.cpp b/DroidFish/jni/stockfish/book.cpp new file mode 100644 index 0000000..8c7cd52 --- /dev/null +++ b/DroidFish/jni/stockfish/book.cpp @@ -0,0 +1,525 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +/* + The code in this file is based on the opening book code in PolyGlot + by Fabien Letouzey. PolyGlot is available under the GNU General + Public License, and can be downloaded from http://wbec-ridderkerk.nl +*/ + +#include +#include + +#include "book.h" +#include "movegen.h" + +using namespace std; + +namespace { + + // Random numbers from PolyGlot, used to compute book hash keys + const uint64_t Random64[781] = { + 0x9D39247E33776D41ULL, 0x2AF7398005AAA5C7ULL, 0x44DB015024623547ULL, + 0x9C15F73E62A76AE2ULL, 0x75834465489C0C89ULL, 0x3290AC3A203001BFULL, + 0x0FBBAD1F61042279ULL, 0xE83A908FF2FB60CAULL, 0x0D7E765D58755C10ULL, + 0x1A083822CEAFE02DULL, 0x9605D5F0E25EC3B0ULL, 0xD021FF5CD13A2ED5ULL, + 0x40BDF15D4A672E32ULL, 0x011355146FD56395ULL, 0x5DB4832046F3D9E5ULL, + 0x239F8B2D7FF719CCULL, 0x05D1A1AE85B49AA1ULL, 0x679F848F6E8FC971ULL, + 0x7449BBFF801FED0BULL, 0x7D11CDB1C3B7ADF0ULL, 0x82C7709E781EB7CCULL, + 0xF3218F1C9510786CULL, 0x331478F3AF51BBE6ULL, 0x4BB38DE5E7219443ULL, + 0xAA649C6EBCFD50FCULL, 0x8DBD98A352AFD40BULL, 0x87D2074B81D79217ULL, + 0x19F3C751D3E92AE1ULL, 0xB4AB30F062B19ABFULL, 0x7B0500AC42047AC4ULL, + 0xC9452CA81A09D85DULL, 0x24AA6C514DA27500ULL, 0x4C9F34427501B447ULL, + 0x14A68FD73C910841ULL, 0xA71B9B83461CBD93ULL, 0x03488B95B0F1850FULL, + 0x637B2B34FF93C040ULL, 0x09D1BC9A3DD90A94ULL, 0x3575668334A1DD3BULL, + 0x735E2B97A4C45A23ULL, 0x18727070F1BD400BULL, 0x1FCBACD259BF02E7ULL, + 0xD310A7C2CE9B6555ULL, 0xBF983FE0FE5D8244ULL, 0x9F74D14F7454A824ULL, + 0x51EBDC4AB9BA3035ULL, 0x5C82C505DB9AB0FAULL, 0xFCF7FE8A3430B241ULL, + 0x3253A729B9BA3DDEULL, 0x8C74C368081B3075ULL, 0xB9BC6C87167C33E7ULL, + 0x7EF48F2B83024E20ULL, 0x11D505D4C351BD7FULL, 0x6568FCA92C76A243ULL, + 0x4DE0B0F40F32A7B8ULL, 0x96D693460CC37E5DULL, 0x42E240CB63689F2FULL, + 0x6D2BDCDAE2919661ULL, 0x42880B0236E4D951ULL, 0x5F0F4A5898171BB6ULL, + 0x39F890F579F92F88ULL, 0x93C5B5F47356388BULL, 0x63DC359D8D231B78ULL, + 0xEC16CA8AEA98AD76ULL, 0x5355F900C2A82DC7ULL, 0x07FB9F855A997142ULL, + 0x5093417AA8A7ED5EULL, 0x7BCBC38DA25A7F3CULL, 0x19FC8A768CF4B6D4ULL, + 0x637A7780DECFC0D9ULL, 0x8249A47AEE0E41F7ULL, 0x79AD695501E7D1E8ULL, + 0x14ACBAF4777D5776ULL, 0xF145B6BECCDEA195ULL, 0xDABF2AC8201752FCULL, + 0x24C3C94DF9C8D3F6ULL, 0xBB6E2924F03912EAULL, 0x0CE26C0B95C980D9ULL, + 0xA49CD132BFBF7CC4ULL, 0xE99D662AF4243939ULL, 0x27E6AD7891165C3FULL, + 0x8535F040B9744FF1ULL, 0x54B3F4FA5F40D873ULL, 0x72B12C32127FED2BULL, + 0xEE954D3C7B411F47ULL, 0x9A85AC909A24EAA1ULL, 0x70AC4CD9F04F21F5ULL, + 0xF9B89D3E99A075C2ULL, 0x87B3E2B2B5C907B1ULL, 0xA366E5B8C54F48B8ULL, + 0xAE4A9346CC3F7CF2ULL, 0x1920C04D47267BBDULL, 0x87BF02C6B49E2AE9ULL, + 0x092237AC237F3859ULL, 0xFF07F64EF8ED14D0ULL, 0x8DE8DCA9F03CC54EULL, + 0x9C1633264DB49C89ULL, 0xB3F22C3D0B0B38EDULL, 0x390E5FB44D01144BULL, + 0x5BFEA5B4712768E9ULL, 0x1E1032911FA78984ULL, 0x9A74ACB964E78CB3ULL, + 0x4F80F7A035DAFB04ULL, 0x6304D09A0B3738C4ULL, 0x2171E64683023A08ULL, + 0x5B9B63EB9CEFF80CULL, 0x506AACF489889342ULL, 0x1881AFC9A3A701D6ULL, + 0x6503080440750644ULL, 0xDFD395339CDBF4A7ULL, 0xEF927DBCF00C20F2ULL, + 0x7B32F7D1E03680ECULL, 0xB9FD7620E7316243ULL, 0x05A7E8A57DB91B77ULL, + 0xB5889C6E15630A75ULL, 0x4A750A09CE9573F7ULL, 0xCF464CEC899A2F8AULL, + 0xF538639CE705B824ULL, 0x3C79A0FF5580EF7FULL, 0xEDE6C87F8477609DULL, + 0x799E81F05BC93F31ULL, 0x86536B8CF3428A8CULL, 0x97D7374C60087B73ULL, + 0xA246637CFF328532ULL, 0x043FCAE60CC0EBA0ULL, 0x920E449535DD359EULL, + 0x70EB093B15B290CCULL, 0x73A1921916591CBDULL, 0x56436C9FE1A1AA8DULL, + 0xEFAC4B70633B8F81ULL, 0xBB215798D45DF7AFULL, 0x45F20042F24F1768ULL, + 0x930F80F4E8EB7462ULL, 0xFF6712FFCFD75EA1ULL, 0xAE623FD67468AA70ULL, + 0xDD2C5BC84BC8D8FCULL, 0x7EED120D54CF2DD9ULL, 0x22FE545401165F1CULL, + 0xC91800E98FB99929ULL, 0x808BD68E6AC10365ULL, 0xDEC468145B7605F6ULL, + 0x1BEDE3A3AEF53302ULL, 0x43539603D6C55602ULL, 0xAA969B5C691CCB7AULL, + 0xA87832D392EFEE56ULL, 0x65942C7B3C7E11AEULL, 0xDED2D633CAD004F6ULL, + 0x21F08570F420E565ULL, 0xB415938D7DA94E3CULL, 0x91B859E59ECB6350ULL, + 0x10CFF333E0ED804AULL, 0x28AED140BE0BB7DDULL, 0xC5CC1D89724FA456ULL, + 0x5648F680F11A2741ULL, 0x2D255069F0B7DAB3ULL, 0x9BC5A38EF729ABD4ULL, + 0xEF2F054308F6A2BCULL, 0xAF2042F5CC5C2858ULL, 0x480412BAB7F5BE2AULL, + 0xAEF3AF4A563DFE43ULL, 0x19AFE59AE451497FULL, 0x52593803DFF1E840ULL, + 0xF4F076E65F2CE6F0ULL, 0x11379625747D5AF3ULL, 0xBCE5D2248682C115ULL, + 0x9DA4243DE836994FULL, 0x066F70B33FE09017ULL, 0x4DC4DE189B671A1CULL, + 0x51039AB7712457C3ULL, 0xC07A3F80C31FB4B4ULL, 0xB46EE9C5E64A6E7CULL, + 0xB3819A42ABE61C87ULL, 0x21A007933A522A20ULL, 0x2DF16F761598AA4FULL, + 0x763C4A1371B368FDULL, 0xF793C46702E086A0ULL, 0xD7288E012AEB8D31ULL, + 0xDE336A2A4BC1C44BULL, 0x0BF692B38D079F23ULL, 0x2C604A7A177326B3ULL, + 0x4850E73E03EB6064ULL, 0xCFC447F1E53C8E1BULL, 0xB05CA3F564268D99ULL, + 0x9AE182C8BC9474E8ULL, 0xA4FC4BD4FC5558CAULL, 0xE755178D58FC4E76ULL, + 0x69B97DB1A4C03DFEULL, 0xF9B5B7C4ACC67C96ULL, 0xFC6A82D64B8655FBULL, + 0x9C684CB6C4D24417ULL, 0x8EC97D2917456ED0ULL, 0x6703DF9D2924E97EULL, + 0xC547F57E42A7444EULL, 0x78E37644E7CAD29EULL, 0xFE9A44E9362F05FAULL, + 0x08BD35CC38336615ULL, 0x9315E5EB3A129ACEULL, 0x94061B871E04DF75ULL, + 0xDF1D9F9D784BA010ULL, 0x3BBA57B68871B59DULL, 0xD2B7ADEEDED1F73FULL, + 0xF7A255D83BC373F8ULL, 0xD7F4F2448C0CEB81ULL, 0xD95BE88CD210FFA7ULL, + 0x336F52F8FF4728E7ULL, 0xA74049DAC312AC71ULL, 0xA2F61BB6E437FDB5ULL, + 0x4F2A5CB07F6A35B3ULL, 0x87D380BDA5BF7859ULL, 0x16B9F7E06C453A21ULL, + 0x7BA2484C8A0FD54EULL, 0xF3A678CAD9A2E38CULL, 0x39B0BF7DDE437BA2ULL, + 0xFCAF55C1BF8A4424ULL, 0x18FCF680573FA594ULL, 0x4C0563B89F495AC3ULL, + 0x40E087931A00930DULL, 0x8CFFA9412EB642C1ULL, 0x68CA39053261169FULL, + 0x7A1EE967D27579E2ULL, 0x9D1D60E5076F5B6FULL, 0x3810E399B6F65BA2ULL, + 0x32095B6D4AB5F9B1ULL, 0x35CAB62109DD038AULL, 0xA90B24499FCFAFB1ULL, + 0x77A225A07CC2C6BDULL, 0x513E5E634C70E331ULL, 0x4361C0CA3F692F12ULL, + 0xD941ACA44B20A45BULL, 0x528F7C8602C5807BULL, 0x52AB92BEB9613989ULL, + 0x9D1DFA2EFC557F73ULL, 0x722FF175F572C348ULL, 0x1D1260A51107FE97ULL, + 0x7A249A57EC0C9BA2ULL, 0x04208FE9E8F7F2D6ULL, 0x5A110C6058B920A0ULL, + 0x0CD9A497658A5698ULL, 0x56FD23C8F9715A4CULL, 0x284C847B9D887AAEULL, + 0x04FEABFBBDB619CBULL, 0x742E1E651C60BA83ULL, 0x9A9632E65904AD3CULL, + 0x881B82A13B51B9E2ULL, 0x506E6744CD974924ULL, 0xB0183DB56FFC6A79ULL, + 0x0ED9B915C66ED37EULL, 0x5E11E86D5873D484ULL, 0xF678647E3519AC6EULL, + 0x1B85D488D0F20CC5ULL, 0xDAB9FE6525D89021ULL, 0x0D151D86ADB73615ULL, + 0xA865A54EDCC0F019ULL, 0x93C42566AEF98FFBULL, 0x99E7AFEABE000731ULL, + 0x48CBFF086DDF285AULL, 0x7F9B6AF1EBF78BAFULL, 0x58627E1A149BBA21ULL, + 0x2CD16E2ABD791E33ULL, 0xD363EFF5F0977996ULL, 0x0CE2A38C344A6EEDULL, + 0x1A804AADB9CFA741ULL, 0x907F30421D78C5DEULL, 0x501F65EDB3034D07ULL, + 0x37624AE5A48FA6E9ULL, 0x957BAF61700CFF4EULL, 0x3A6C27934E31188AULL, + 0xD49503536ABCA345ULL, 0x088E049589C432E0ULL, 0xF943AEE7FEBF21B8ULL, + 0x6C3B8E3E336139D3ULL, 0x364F6FFA464EE52EULL, 0xD60F6DCEDC314222ULL, + 0x56963B0DCA418FC0ULL, 0x16F50EDF91E513AFULL, 0xEF1955914B609F93ULL, + 0x565601C0364E3228ULL, 0xECB53939887E8175ULL, 0xBAC7A9A18531294BULL, + 0xB344C470397BBA52ULL, 0x65D34954DAF3CEBDULL, 0xB4B81B3FA97511E2ULL, + 0xB422061193D6F6A7ULL, 0x071582401C38434DULL, 0x7A13F18BBEDC4FF5ULL, + 0xBC4097B116C524D2ULL, 0x59B97885E2F2EA28ULL, 0x99170A5DC3115544ULL, + 0x6F423357E7C6A9F9ULL, 0x325928EE6E6F8794ULL, 0xD0E4366228B03343ULL, + 0x565C31F7DE89EA27ULL, 0x30F5611484119414ULL, 0xD873DB391292ED4FULL, + 0x7BD94E1D8E17DEBCULL, 0xC7D9F16864A76E94ULL, 0x947AE053EE56E63CULL, + 0xC8C93882F9475F5FULL, 0x3A9BF55BA91F81CAULL, 0xD9A11FBB3D9808E4ULL, + 0x0FD22063EDC29FCAULL, 0xB3F256D8ACA0B0B9ULL, 0xB03031A8B4516E84ULL, + 0x35DD37D5871448AFULL, 0xE9F6082B05542E4EULL, 0xEBFAFA33D7254B59ULL, + 0x9255ABB50D532280ULL, 0xB9AB4CE57F2D34F3ULL, 0x693501D628297551ULL, + 0xC62C58F97DD949BFULL, 0xCD454F8F19C5126AULL, 0xBBE83F4ECC2BDECBULL, + 0xDC842B7E2819E230ULL, 0xBA89142E007503B8ULL, 0xA3BC941D0A5061CBULL, + 0xE9F6760E32CD8021ULL, 0x09C7E552BC76492FULL, 0x852F54934DA55CC9ULL, + 0x8107FCCF064FCF56ULL, 0x098954D51FFF6580ULL, 0x23B70EDB1955C4BFULL, + 0xC330DE426430F69DULL, 0x4715ED43E8A45C0AULL, 0xA8D7E4DAB780A08DULL, + 0x0572B974F03CE0BBULL, 0xB57D2E985E1419C7ULL, 0xE8D9ECBE2CF3D73FULL, + 0x2FE4B17170E59750ULL, 0x11317BA87905E790ULL, 0x7FBF21EC8A1F45ECULL, + 0x1725CABFCB045B00ULL, 0x964E915CD5E2B207ULL, 0x3E2B8BCBF016D66DULL, + 0xBE7444E39328A0ACULL, 0xF85B2B4FBCDE44B7ULL, 0x49353FEA39BA63B1ULL, + 0x1DD01AAFCD53486AULL, 0x1FCA8A92FD719F85ULL, 0xFC7C95D827357AFAULL, + 0x18A6A990C8B35EBDULL, 0xCCCB7005C6B9C28DULL, 0x3BDBB92C43B17F26ULL, + 0xAA70B5B4F89695A2ULL, 0xE94C39A54A98307FULL, 0xB7A0B174CFF6F36EULL, + 0xD4DBA84729AF48ADULL, 0x2E18BC1AD9704A68ULL, 0x2DE0966DAF2F8B1CULL, + 0xB9C11D5B1E43A07EULL, 0x64972D68DEE33360ULL, 0x94628D38D0C20584ULL, + 0xDBC0D2B6AB90A559ULL, 0xD2733C4335C6A72FULL, 0x7E75D99D94A70F4DULL, + 0x6CED1983376FA72BULL, 0x97FCAACBF030BC24ULL, 0x7B77497B32503B12ULL, + 0x8547EDDFB81CCB94ULL, 0x79999CDFF70902CBULL, 0xCFFE1939438E9B24ULL, + 0x829626E3892D95D7ULL, 0x92FAE24291F2B3F1ULL, 0x63E22C147B9C3403ULL, + 0xC678B6D860284A1CULL, 0x5873888850659AE7ULL, 0x0981DCD296A8736DULL, + 0x9F65789A6509A440ULL, 0x9FF38FED72E9052FULL, 0xE479EE5B9930578CULL, + 0xE7F28ECD2D49EECDULL, 0x56C074A581EA17FEULL, 0x5544F7D774B14AEFULL, + 0x7B3F0195FC6F290FULL, 0x12153635B2C0CF57ULL, 0x7F5126DBBA5E0CA7ULL, + 0x7A76956C3EAFB413ULL, 0x3D5774A11D31AB39ULL, 0x8A1B083821F40CB4ULL, + 0x7B4A38E32537DF62ULL, 0x950113646D1D6E03ULL, 0x4DA8979A0041E8A9ULL, + 0x3BC36E078F7515D7ULL, 0x5D0A12F27AD310D1ULL, 0x7F9D1A2E1EBE1327ULL, + 0xDA3A361B1C5157B1ULL, 0xDCDD7D20903D0C25ULL, 0x36833336D068F707ULL, + 0xCE68341F79893389ULL, 0xAB9090168DD05F34ULL, 0x43954B3252DC25E5ULL, + 0xB438C2B67F98E5E9ULL, 0x10DCD78E3851A492ULL, 0xDBC27AB5447822BFULL, + 0x9B3CDB65F82CA382ULL, 0xB67B7896167B4C84ULL, 0xBFCED1B0048EAC50ULL, + 0xA9119B60369FFEBDULL, 0x1FFF7AC80904BF45ULL, 0xAC12FB171817EEE7ULL, + 0xAF08DA9177DDA93DULL, 0x1B0CAB936E65C744ULL, 0xB559EB1D04E5E932ULL, + 0xC37B45B3F8D6F2BAULL, 0xC3A9DC228CAAC9E9ULL, 0xF3B8B6675A6507FFULL, + 0x9FC477DE4ED681DAULL, 0x67378D8ECCEF96CBULL, 0x6DD856D94D259236ULL, + 0xA319CE15B0B4DB31ULL, 0x073973751F12DD5EULL, 0x8A8E849EB32781A5ULL, + 0xE1925C71285279F5ULL, 0x74C04BF1790C0EFEULL, 0x4DDA48153C94938AULL, + 0x9D266D6A1CC0542CULL, 0x7440FB816508C4FEULL, 0x13328503DF48229FULL, + 0xD6BF7BAEE43CAC40ULL, 0x4838D65F6EF6748FULL, 0x1E152328F3318DEAULL, + 0x8F8419A348F296BFULL, 0x72C8834A5957B511ULL, 0xD7A023A73260B45CULL, + 0x94EBC8ABCFB56DAEULL, 0x9FC10D0F989993E0ULL, 0xDE68A2355B93CAE6ULL, + 0xA44CFE79AE538BBEULL, 0x9D1D84FCCE371425ULL, 0x51D2B1AB2DDFB636ULL, + 0x2FD7E4B9E72CD38CULL, 0x65CA5B96B7552210ULL, 0xDD69A0D8AB3B546DULL, + 0x604D51B25FBF70E2ULL, 0x73AA8A564FB7AC9EULL, 0x1A8C1E992B941148ULL, + 0xAAC40A2703D9BEA0ULL, 0x764DBEAE7FA4F3A6ULL, 0x1E99B96E70A9BE8BULL, + 0x2C5E9DEB57EF4743ULL, 0x3A938FEE32D29981ULL, 0x26E6DB8FFDF5ADFEULL, + 0x469356C504EC9F9DULL, 0xC8763C5B08D1908CULL, 0x3F6C6AF859D80055ULL, + 0x7F7CC39420A3A545ULL, 0x9BFB227EBDF4C5CEULL, 0x89039D79D6FC5C5CULL, + 0x8FE88B57305E2AB6ULL, 0xA09E8C8C35AB96DEULL, 0xFA7E393983325753ULL, + 0xD6B6D0ECC617C699ULL, 0xDFEA21EA9E7557E3ULL, 0xB67C1FA481680AF8ULL, + 0xCA1E3785A9E724E5ULL, 0x1CFC8BED0D681639ULL, 0xD18D8549D140CAEAULL, + 0x4ED0FE7E9DC91335ULL, 0xE4DBF0634473F5D2ULL, 0x1761F93A44D5AEFEULL, + 0x53898E4C3910DA55ULL, 0x734DE8181F6EC39AULL, 0x2680B122BAA28D97ULL, + 0x298AF231C85BAFABULL, 0x7983EED3740847D5ULL, 0x66C1A2A1A60CD889ULL, + 0x9E17E49642A3E4C1ULL, 0xEDB454E7BADC0805ULL, 0x50B704CAB602C329ULL, + 0x4CC317FB9CDDD023ULL, 0x66B4835D9EAFEA22ULL, 0x219B97E26FFC81BDULL, + 0x261E4E4C0A333A9DULL, 0x1FE2CCA76517DB90ULL, 0xD7504DFA8816EDBBULL, + 0xB9571FA04DC089C8ULL, 0x1DDC0325259B27DEULL, 0xCF3F4688801EB9AAULL, + 0xF4F5D05C10CAB243ULL, 0x38B6525C21A42B0EULL, 0x36F60E2BA4FA6800ULL, + 0xEB3593803173E0CEULL, 0x9C4CD6257C5A3603ULL, 0xAF0C317D32ADAA8AULL, + 0x258E5A80C7204C4BULL, 0x8B889D624D44885DULL, 0xF4D14597E660F855ULL, + 0xD4347F66EC8941C3ULL, 0xE699ED85B0DFB40DULL, 0x2472F6207C2D0484ULL, + 0xC2A1E7B5B459AEB5ULL, 0xAB4F6451CC1D45ECULL, 0x63767572AE3D6174ULL, + 0xA59E0BD101731A28ULL, 0x116D0016CB948F09ULL, 0x2CF9C8CA052F6E9FULL, + 0x0B090A7560A968E3ULL, 0xABEEDDB2DDE06FF1ULL, 0x58EFC10B06A2068DULL, + 0xC6E57A78FBD986E0ULL, 0x2EAB8CA63CE802D7ULL, 0x14A195640116F336ULL, + 0x7C0828DD624EC390ULL, 0xD74BBE77E6116AC7ULL, 0x804456AF10F5FB53ULL, + 0xEBE9EA2ADF4321C7ULL, 0x03219A39EE587A30ULL, 0x49787FEF17AF9924ULL, + 0xA1E9300CD8520548ULL, 0x5B45E522E4B1B4EFULL, 0xB49C3B3995091A36ULL, + 0xD4490AD526F14431ULL, 0x12A8F216AF9418C2ULL, 0x001F837CC7350524ULL, + 0x1877B51E57A764D5ULL, 0xA2853B80F17F58EEULL, 0x993E1DE72D36D310ULL, + 0xB3598080CE64A656ULL, 0x252F59CF0D9F04BBULL, 0xD23C8E176D113600ULL, + 0x1BDA0492E7E4586EULL, 0x21E0BD5026C619BFULL, 0x3B097ADAF088F94EULL, + 0x8D14DEDB30BE846EULL, 0xF95CFFA23AF5F6F4ULL, 0x3871700761B3F743ULL, + 0xCA672B91E9E4FA16ULL, 0x64C8E531BFF53B55ULL, 0x241260ED4AD1E87DULL, + 0x106C09B972D2E822ULL, 0x7FBA195410E5CA30ULL, 0x7884D9BC6CB569D8ULL, + 0x0647DFEDCD894A29ULL, 0x63573FF03E224774ULL, 0x4FC8E9560F91B123ULL, + 0x1DB956E450275779ULL, 0xB8D91274B9E9D4FBULL, 0xA2EBEE47E2FBFCE1ULL, + 0xD9F1F30CCD97FB09ULL, 0xEFED53D75FD64E6BULL, 0x2E6D02C36017F67FULL, + 0xA9AA4D20DB084E9BULL, 0xB64BE8D8B25396C1ULL, 0x70CB6AF7C2D5BCF0ULL, + 0x98F076A4F7A2322EULL, 0xBF84470805E69B5FULL, 0x94C3251F06F90CF3ULL, + 0x3E003E616A6591E9ULL, 0xB925A6CD0421AFF3ULL, 0x61BDD1307C66E300ULL, + 0xBF8D5108E27E0D48ULL, 0x240AB57A8B888B20ULL, 0xFC87614BAF287E07ULL, + 0xEF02CDD06FFDB432ULL, 0xA1082C0466DF6C0AULL, 0x8215E577001332C8ULL, + 0xD39BB9C3A48DB6CFULL, 0x2738259634305C14ULL, 0x61CF4F94C97DF93DULL, + 0x1B6BACA2AE4E125BULL, 0x758F450C88572E0BULL, 0x959F587D507A8359ULL, + 0xB063E962E045F54DULL, 0x60E8ED72C0DFF5D1ULL, 0x7B64978555326F9FULL, + 0xFD080D236DA814BAULL, 0x8C90FD9B083F4558ULL, 0x106F72FE81E2C590ULL, + 0x7976033A39F7D952ULL, 0xA4EC0132764CA04BULL, 0x733EA705FAE4FA77ULL, + 0xB4D8F77BC3E56167ULL, 0x9E21F4F903B33FD9ULL, 0x9D765E419FB69F6DULL, + 0xD30C088BA61EA5EFULL, 0x5D94337FBFAF7F5BULL, 0x1A4E4822EB4D7A59ULL, + 0x6FFE73E81B637FB3ULL, 0xDDF957BC36D8B9CAULL, 0x64D0E29EEA8838B3ULL, + 0x08DD9BDFD96B9F63ULL, 0x087E79E5A57D1D13ULL, 0xE328E230E3E2B3FBULL, + 0x1C2559E30F0946BEULL, 0x720BF5F26F4D2EAAULL, 0xB0774D261CC609DBULL, + 0x443F64EC5A371195ULL, 0x4112CF68649A260EULL, 0xD813F2FAB7F5C5CAULL, + 0x660D3257380841EEULL, 0x59AC2C7873F910A3ULL, 0xE846963877671A17ULL, + 0x93B633ABFA3469F8ULL, 0xC0C0F5A60EF4CDCFULL, 0xCAF21ECD4377B28CULL, + 0x57277707199B8175ULL, 0x506C11B9D90E8B1DULL, 0xD83CC2687A19255FULL, + 0x4A29C6465A314CD1ULL, 0xED2DF21216235097ULL, 0xB5635C95FF7296E2ULL, + 0x22AF003AB672E811ULL, 0x52E762596BF68235ULL, 0x9AEBA33AC6ECC6B0ULL, + 0x944F6DE09134DFB6ULL, 0x6C47BEC883A7DE39ULL, 0x6AD047C430A12104ULL, + 0xA5B1CFDBA0AB4067ULL, 0x7C45D833AFF07862ULL, 0x5092EF950A16DA0BULL, + 0x9338E69C052B8E7BULL, 0x455A4B4CFE30E3F5ULL, 0x6B02E63195AD0CF8ULL, + 0x6B17B224BAD6BF27ULL, 0xD1E0CCD25BB9C169ULL, 0xDE0C89A556B9AE70ULL, + 0x50065E535A213CF6ULL, 0x9C1169FA2777B874ULL, 0x78EDEFD694AF1EEDULL, + 0x6DC93D9526A50E68ULL, 0xEE97F453F06791EDULL, 0x32AB0EDB696703D3ULL, + 0x3A6853C7E70757A7ULL, 0x31865CED6120F37DULL, 0x67FEF95D92607890ULL, + 0x1F2B1D1F15F6DC9CULL, 0xB69E38A8965C6B65ULL, 0xAA9119FF184CCCF4ULL, + 0xF43C732873F24C13ULL, 0xFB4A3D794A9A80D2ULL, 0x3550C2321FD6109CULL, + 0x371F77E76BB8417EULL, 0x6BFA9AAE5EC05779ULL, 0xCD04F3FF001A4778ULL, + 0xE3273522064480CAULL, 0x9F91508BFFCFC14AULL, 0x049A7F41061A9E60ULL, + 0xFCB6BE43A9F2FE9BULL, 0x08DE8A1C7797DA9BULL, 0x8F9887E6078735A1ULL, + 0xB5B4071DBFC73A66ULL, 0x230E343DFBA08D33ULL, 0x43ED7F5A0FAE657DULL, + 0x3A88A0FBBCB05C63ULL, 0x21874B8B4D2DBC4FULL, 0x1BDEA12E35F6A8C9ULL, + 0x53C065C6C8E63528ULL, 0xE34A1D250E7A8D6BULL, 0xD6B04D3B7651DD7EULL, + 0x5E90277E7CB39E2DULL, 0x2C046F22062DC67DULL, 0xB10BB459132D0A26ULL, + 0x3FA9DDFB67E2F199ULL, 0x0E09B88E1914F7AFULL, 0x10E8B35AF3EEAB37ULL, + 0x9EEDECA8E272B933ULL, 0xD4C718BC4AE8AE5FULL, 0x81536D601170FC20ULL, + 0x91B534F885818A06ULL, 0xEC8177F83F900978ULL, 0x190E714FADA5156EULL, + 0xB592BF39B0364963ULL, 0x89C350C893AE7DC1ULL, 0xAC042E70F8B383F2ULL, + 0xB49B52E587A1EE60ULL, 0xFB152FE3FF26DA89ULL, 0x3E666E6F69AE2C15ULL, + 0x3B544EBE544C19F9ULL, 0xE805A1E290CF2456ULL, 0x24B33C9D7ED25117ULL, + 0xE74733427B72F0C1ULL, 0x0A804D18B7097475ULL, 0x57E3306D881EDB4FULL, + 0x4AE7D6A36EB5DBCBULL, 0x2D8D5432157064C8ULL, 0xD1E649DE1E7F268BULL, + 0x8A328A1CEDFE552CULL, 0x07A3AEC79624C7DAULL, 0x84547DDC3E203C94ULL, + 0x990A98FD5071D263ULL, 0x1A4FF12616EEFC89ULL, 0xF6F7FD1431714200ULL, + 0x30C05B1BA332F41CULL, 0x8D2636B81555A786ULL, 0x46C9FEB55D120902ULL, + 0xCCEC0A73B49C9921ULL, 0x4E9D2827355FC492ULL, 0x19EBB029435DCB0FULL, + 0x4659D2B743848A2CULL, 0x963EF2C96B33BE31ULL, 0x74F85198B05A2E7DULL, + 0x5A0F544DD2B1FB18ULL, 0x03727073C2E134B1ULL, 0xC7F6AA2DE59AEA61ULL, + 0x352787BAA0D7C22FULL, 0x9853EAB63B5E0B35ULL, 0xABBDCDD7ED5C0860ULL, + 0xCF05DAF5AC8D77B0ULL, 0x49CAD48CEBF4A71EULL, 0x7A4C10EC2158C4A6ULL, + 0xD9E92AA246BF719EULL, 0x13AE978D09FE5557ULL, 0x730499AF921549FFULL, + 0x4E4B705B92903BA4ULL, 0xFF577222C14F0A3AULL, 0x55B6344CF97AAFAEULL, + 0xB862225B055B6960ULL, 0xCAC09AFBDDD2CDB4ULL, 0xDAF8E9829FE96B5FULL, + 0xB5FDFC5D3132C498ULL, 0x310CB380DB6F7503ULL, 0xE87FBB46217A360EULL, + 0x2102AE466EBB1148ULL, 0xF8549E1A3AA5E00DULL, 0x07A69AFDCC42261AULL, + 0xC4C118BFE78FEAAEULL, 0xF9F4892ED96BD438ULL, 0x1AF3DBE25D8F45DAULL, + 0xF5B4B0B0D2DEEEB4ULL, 0x962ACEEFA82E1C84ULL, 0x046E3ECAAF453CE9ULL, + 0xF05D129681949A4CULL, 0x964781CE734B3C84ULL, 0x9C2ED44081CE5FBDULL, + 0x522E23F3925E319EULL, 0x177E00F9FC32F791ULL, 0x2BC60A63A6F3B3F2ULL, + 0x222BBFAE61725606ULL, 0x486289DDCC3D6780ULL, 0x7DC7785B8EFDFC80ULL, + 0x8AF38731C02BA980ULL, 0x1FAB64EA29A2DDF7ULL, 0xE4D9429322CD065AULL, + 0x9DA058C67844F20CULL, 0x24C0E332B70019B0ULL, 0x233003B5A6CFE6ADULL, + 0xD586BD01C5C217F6ULL, 0x5E5637885F29BC2BULL, 0x7EBA726D8C94094BULL, + 0x0A56A5F0BFE39272ULL, 0xD79476A84EE20D06ULL, 0x9E4C1269BAA4BF37ULL, + 0x17EFEE45B0DEE640ULL, 0x1D95B0A5FCF90BC6ULL, 0x93CBE0B699C2585DULL, + 0x65FA4F227A2B6D79ULL, 0xD5F9E858292504D5ULL, 0xC2B5A03F71471A6FULL, + 0x59300222B4561E00ULL, 0xCE2F8642CA0712DCULL, 0x7CA9723FBB2E8988ULL, + 0x2785338347F2BA08ULL, 0xC61BB3A141E50E8CULL, 0x150F361DAB9DEC26ULL, + 0x9F6A419D382595F4ULL, 0x64A53DC924FE7AC9ULL, 0x142DE49FFF7A7C3DULL, + 0x0C335248857FA9E7ULL, 0x0A9C32D5EAE45305ULL, 0xE6C42178C4BBB92EULL, + 0x71F1CE2490D20B07ULL, 0xF1BCC3D275AFE51AULL, 0xE728E8C83C334074ULL, + 0x96FBF83A12884624ULL, 0x81A1549FD6573DA5ULL, 0x5FA7867CAF35E149ULL, + 0x56986E2EF3ED091BULL, 0x917F1DD5F8886C61ULL, 0xD20D8C88C8FFE65FULL, + 0x31D71DCE64B2C310ULL, 0xF165B587DF898190ULL, 0xA57E6339DD2CF3A0ULL, + 0x1EF6E6DBB1961EC9ULL, 0x70CC73D90BC26E24ULL, 0xE21A6B35DF0C3AD7ULL, + 0x003A93D8B2806962ULL, 0x1C99DED33CB890A1ULL, 0xCF3145DE0ADD4289ULL, + 0xD0E4427A5514FB72ULL, 0x77C621CC9FB3A483ULL, 0x67A34DAC4356550BULL, + 0xF8D626AAAF278509ULL + }; + + // Indices to the Random64[] array + const int PieceIdx = 0; + const int CastleIdx = 768; + const int EnPassantIdx = 772; + const int TurnIdx = 780; + + // book_key() builds up a PolyGlot hash key out of a position + uint64_t book_key(const Position& pos) { + + // Piece offset is calculated as (64 * PolyPieceType + square), where + // PolyPieceType is: BP = 0, WP = 1, BN = 2, WN = 3 .... BK = 10, WK = 11 + static const int PieceToPoly[] = { 0, 1, 3, 5, 7, 9, 11, 0, 0, 0, 2, 4, 6, 8, 10 }; + + uint64_t result = 0; + Bitboard b = pos.occupied_squares(); + + while (b) + { + Square s = pop_1st_bit(&b); + int p = PieceToPoly[int(pos.piece_on(s))]; + result ^= Random64[PieceIdx + 64 * p + int(s)]; + } + + if (pos.can_castle_kingside(WHITE)) + result ^= Random64[CastleIdx + 0]; + + if (pos.can_castle_queenside(WHITE)) + result ^= Random64[CastleIdx + 1]; + + if (pos.can_castle_kingside(BLACK)) + result ^= Random64[CastleIdx + 2]; + + if (pos.can_castle_queenside(BLACK)) + result ^= Random64[CastleIdx + 3]; + + if (pos.ep_square() != SQ_NONE) + result ^= Random64[EnPassantIdx + square_file(pos.ep_square())]; + + if (pos.side_to_move() == WHITE) + result ^= Random64[TurnIdx]; + + return result; + } + +} + + +/// Book c'tor. Make random number generation less deterministic, for book moves +Book::Book() { + + for (int i = abs(get_system_time() % 10000); i > 0; i--) + RKiss.rand(); +} + + +/// Book destructor. Be sure file is closed before we leave. + +Book::~Book() { + + close(); +} + + +/// Book::close() closes the file only if it is open, otherwise +/// we can end up in a little mess due to how std::ifstream works. + +void Book::close() { + + if (bookFile.is_open()) + bookFile.close(); + + bookName = ""; +} + + +/// Book::open() opens a book file with a given file name + +void Book::open(const string& fileName) { + + // Close old file before opening the new + close(); + + bookFile.open(fileName.c_str(), ifstream::in | ifstream::binary); + + // Silently return when asked to open a non-exsistent file + if (!bookFile.is_open()) + return; + + // Get the book size in number of entries + bookFile.seekg(0, ios::end); + bookSize = long(bookFile.tellg()) / sizeof(BookEntry); + + if (!bookFile.good()) + { + cerr << "Failed to open book file " << fileName << endl; + exit(EXIT_FAILURE); + } + + // Set only if successful + bookName = fileName; +} + + +/// Book::get_move() gets a book move for a given position. Returns +/// MOVE_NONE if no book move is found. If findBestMove is true then +/// return always the highest rated book move. + +Move Book::get_move(const Position& pos, bool findBestMove) { + + if (!bookFile.is_open() || bookSize == 0) + return MOVE_NONE; + + BookEntry entry; + int bookMove = MOVE_NONE; + unsigned score, scoresSum = 0, bestScore = 0; + uint64_t key = book_key(pos); + + // Choose a book move among the possible moves for the given position + for (int idx = find_entry(key); idx < bookSize; idx++) + { + entry = read_entry(idx); + + if (entry.key != key) + break; + + score = entry.count; + + if (!findBestMove) + { + // Choose book move according to its score. If a move has a very + // high score it has more probability to be choosen then a one with + // lower score. Note that first entry is always chosen. + scoresSum += score; + if (RKiss.rand() % scoresSum < score) + bookMove = entry.move; + } + else if (score > bestScore) + { + bestScore = score; + bookMove = entry.move; + } + } + + // A PolyGlot book move is encoded as follows: + // + // bit 0- 5: destination square (from 0 to 63) + // bit 6-11: origin square (from 0 to 63) + // bit 12-13-14: promotion piece (from KNIGHT == 1 to QUEEN == 4) + // + // Castling moves follow "king captures rook" representation. So in case + // book move is a promotion we have to convert to our representation, in + // all other cases we can directly compare with a Move after having + // masked out special Move's flags that are not supported by PolyGlot. + int p = (bookMove >> 12) & 7; + + if (p) + bookMove = int(make_promotion_move(move_from(Move(bookMove)), + move_to(Move(bookMove)), PieceType(p + 1))); + + // Verify the book move (if any) is legal + MoveStack mlist[MAX_MOVES]; + MoveStack* last = generate(pos, mlist); + for (MoveStack* cur = mlist; cur != last; cur++) + if ((int(cur->move) & ~(3 << 14)) == bookMove) // Mask out special flags + return cur->move; + + return MOVE_NONE; +} + + +/// Book::find_entry() takes a book key as input, and does a binary search +/// through the book file for the given key. The index to the first book +/// entry with the same key as the input is returned. When the key is not +/// found in the book file, bookSize is returned. + +int Book::find_entry(uint64_t key) { + + int left, right, mid; + + // Binary search (finds the leftmost entry) + left = 0; + right = bookSize - 1; + + assert(left <= right); + + while (left < right) + { + mid = (left + right) / 2; + + assert(mid >= left && mid < right); + + if (key <= read_entry(mid).key) + right = mid; + else + left = mid + 1; + } + + assert(left == right); + + return read_entry(left).key == key ? left : bookSize; +} + + +/// Book::read_entry() takes an integer index, and returns the BookEntry +/// at the given index in the book file. + +BookEntry Book::read_entry(int idx) { + + assert(idx >= 0 && idx < bookSize); + assert(bookFile.is_open()); + + BookEntry e; + + bookFile.seekg(idx * sizeof(BookEntry), ios_base::beg); + + *this >> e.key >> e.move >> e.count >> e.learn; + + if (!bookFile.good()) + { + cerr << "Failed to read book entry at index " << idx << endl; + exit(EXIT_FAILURE); + } + return e; +} diff --git a/DroidFish/jni/stockfish/book.h b/DroidFish/jni/stockfish/book.h new file mode 100644 index 0000000..ed68210 --- /dev/null +++ b/DroidFish/jni/stockfish/book.h @@ -0,0 +1,71 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(BOOK_H_INCLUDED) +#define BOOK_H_INCLUDED + +#include +#include + +#include "move.h" +#include "position.h" +#include "rkiss.h" + + +// A Polyglot book is a series of "entries" of 16 bytes. All integers are +// stored highest byte first (regardless of size). The entries are ordered +// according to key. Lowest key first. +struct BookEntry { + uint64_t key; + uint16_t move; + uint16_t count; + uint32_t learn; +}; + +class Book { +public: + Book(); + ~Book(); + void open(const std::string& fileName); + void close(); + Move get_move(const Position& pos, bool findBestMove); + const std::string name() const { return bookName; } + +private: + // read n chars from the file stream and converts them in an + // integer number. Integers are stored with highest byte first. + template uint64_t get_int(); + + template + Book& operator>>(T& n) { n = (T)get_int(); return *this; } + + BookEntry read_entry(int idx); + int find_entry(uint64_t key); + + std::ifstream bookFile; + std::string bookName; + int bookSize; + RKISS RKiss; +}; + +// Yes, we indulge a bit here ;-) +template inline uint64_t Book::get_int() { return 256 * get_int() + bookFile.get(); } +template<> inline uint64_t Book::get_int<1>() { return bookFile.get(); } + +#endif // !defined(BOOK_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/endgame.cpp b/DroidFish/jni/stockfish/endgame.cpp new file mode 100644 index 0000000..c8c491f --- /dev/null +++ b/DroidFish/jni/stockfish/endgame.cpp @@ -0,0 +1,939 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bitcount.h" +#include "endgame.h" +#include "pawns.h" + +using std::string; + +extern uint32_t probe_kpk_bitbase(Square wksq, Square wpsq, Square bksq, Color stm); + +namespace { + + // Table used to drive the defending king towards the edge of the board + // in KX vs K and KQ vs KR endgames. + const int MateTable[64] = { + 100, 90, 80, 70, 70, 80, 90, 100, + 90, 70, 60, 50, 50, 60, 70, 90, + 80, 60, 40, 30, 30, 40, 60, 80, + 70, 50, 30, 20, 20, 30, 50, 70, + 70, 50, 30, 20, 20, 30, 50, 70, + 80, 60, 40, 30, 30, 40, 60, 80, + 90, 70, 60, 50, 50, 60, 70, 90, + 100, 90, 80, 70, 70, 80, 90, 100, + }; + + // Table used to drive the defending king towards a corner square of the + // right color in KBN vs K endgames. + const int KBNKMateTable[64] = { + 200, 190, 180, 170, 160, 150, 140, 130, + 190, 180, 170, 160, 150, 140, 130, 140, + 180, 170, 155, 140, 140, 125, 140, 150, + 170, 160, 140, 120, 110, 140, 150, 160, + 160, 150, 140, 110, 120, 140, 160, 170, + 150, 140, 125, 140, 140, 155, 170, 180, + 140, 130, 140, 150, 160, 170, 180, 190, + 130, 140, 150, 160, 170, 180, 190, 200 + }; + + // The attacking side is given a descending bonus based on distance between + // the two kings in basic endgames. + const int DistanceBonus[8] = { 0, 0, 100, 80, 60, 40, 20, 10 }; + + // Penalty for big distance between king and knight for the defending king + // and knight in KR vs KN endgames. + const int KRKNKingKnightDistancePenalty[8] = { 0, 0, 4, 10, 20, 32, 48, 70 }; + + // Build corresponding key code for the opposite color: "KBPKN" -> "KNKBP" + const string swap_colors(const string& keyCode) { + + size_t idx = keyCode.find('K', 1); + return keyCode.substr(idx) + keyCode.substr(0, idx); + } + + // Get the material key of a position out of the given endgame key code + // like "KBPKN". The trick here is to first build up a FEN string and then + // let a Position object to do the work for us. Note that the FEN string + // could correspond to an illegal position. + Key mat_key(const string& keyCode) { + + assert(keyCode.length() > 0 && keyCode.length() < 8); + assert(keyCode[0] == 'K'); + + string fen; + size_t i = 0; + + // First add white and then black pieces + do fen += keyCode[i]; while (keyCode[++i] != 'K'); + do fen += char(tolower(keyCode[i])); while (++i < keyCode.length()); + + // Add file padding and remaining empty ranks + fen += string(1, '0' + int(8 - keyCode.length())) + "/8/8/8/8/8/8/8 w - -"; + + // Build a Position out of the fen string and get its material key + return Position(fen, false, 0).get_material_key(); + } + + typedef EndgameBase EF; + typedef EndgameBase SF; + +} // namespace + + +/// Endgames member definitions + +template<> const Endgames::EFMap& Endgames::get() const { return maps.first; } +template<> const Endgames::SFMap& Endgames::get() const { return maps.second; } + +Endgames::Endgames() { + + add >("KNNK"); + add >("KPK"); + add >("KBNK"); + add >("KRKP"); + add >("KRKB"); + add >("KRKN"); + add >("KQKR"); + add >("KBBKN"); + + add >("KNPK"); + add >("KRPKR"); + add >("KBPKB"); + add >("KBPPKB"); + add >("KBPKN"); + add >("KRPPKRP"); +} + +Endgames::~Endgames() { + + for (EFMap::const_iterator it = get().begin(); it != get().end(); ++it) + delete it->second; + + for (SFMap::const_iterator it = get().begin(); it != get().end(); ++it) + delete it->second; +} + +template +void Endgames::add(const string& keyCode) { + + typedef typename T::Base F; + typedef std::map M; + + const_cast(get()).insert(std::pair(mat_key(keyCode), new T(WHITE))); + const_cast(get()).insert(std::pair(mat_key(swap_colors(keyCode)), new T(BLACK))); +} + +template +T* Endgames::get(Key key) const { + + typename std::map::const_iterator it = get().find(key); + return it != get().end() ? it->second : NULL; +} + +// Explicit template instantiations +template EF* Endgames::get(Key key) const; +template SF* Endgames::get(Key key) const; + + +/// Mate with KX vs K. This function is used to evaluate positions with +/// King and plenty of material vs a lone king. It simply gives the +/// attacking side a bonus for driving the defending king towards the edge +/// of the board, and for keeping the distance between the two kings small. +template<> +Value Endgame::apply(const Position& pos) const { + + assert(pos.non_pawn_material(weakerSide) == VALUE_ZERO); + assert(pos.piece_count(weakerSide, PAWN) == VALUE_ZERO); + + Square winnerKSq = pos.king_square(strongerSide); + Square loserKSq = pos.king_square(weakerSide); + + Value result = pos.non_pawn_material(strongerSide) + + pos.piece_count(strongerSide, PAWN) * PawnValueEndgame + + MateTable[loserKSq] + + DistanceBonus[square_distance(winnerKSq, loserKSq)]; + + if ( pos.piece_count(strongerSide, QUEEN) + || pos.piece_count(strongerSide, ROOK) + || pos.piece_count(strongerSide, BISHOP) > 1) + // TODO: check for two equal-colored bishops! + result += VALUE_KNOWN_WIN; + + return strongerSide == pos.side_to_move() ? result : -result; +} + + +/// Mate with KBN vs K. This is similar to KX vs K, but we have to drive the +/// defending king towards a corner square of the right color. +template<> +Value Endgame::apply(const Position& pos) const { + + assert(pos.non_pawn_material(weakerSide) == VALUE_ZERO); + assert(pos.piece_count(weakerSide, PAWN) == VALUE_ZERO); + assert(pos.non_pawn_material(strongerSide) == KnightValueMidgame + BishopValueMidgame); + assert(pos.piece_count(strongerSide, BISHOP) == 1); + assert(pos.piece_count(strongerSide, KNIGHT) == 1); + assert(pos.piece_count(strongerSide, PAWN) == 0); + + Square winnerKSq = pos.king_square(strongerSide); + Square loserKSq = pos.king_square(weakerSide); + Square bishopSquare = pos.piece_list(strongerSide, BISHOP, 0); + + // kbnk_mate_table() tries to drive toward corners A1 or H8, + // if we have a bishop that cannot reach the above squares we + // mirror the kings so to drive enemy toward corners A8 or H1. + if (opposite_color_squares(bishopSquare, SQ_A1)) + { + winnerKSq = flop_square(winnerKSq); + loserKSq = flop_square(loserKSq); + } + + Value result = VALUE_KNOWN_WIN + + DistanceBonus[square_distance(winnerKSq, loserKSq)] + + KBNKMateTable[loserKSq]; + + return strongerSide == pos.side_to_move() ? result : -result; +} + + +/// KP vs K. This endgame is evaluated with the help of a bitbase. +template<> +Value Endgame::apply(const Position& pos) const { + + assert(pos.non_pawn_material(strongerSide) == VALUE_ZERO); + assert(pos.non_pawn_material(weakerSide) == VALUE_ZERO); + assert(pos.piece_count(strongerSide, PAWN) == 1); + assert(pos.piece_count(weakerSide, PAWN) == 0); + + Square wksq, bksq, wpsq; + Color stm; + + if (strongerSide == WHITE) + { + wksq = pos.king_square(WHITE); + bksq = pos.king_square(BLACK); + wpsq = pos.piece_list(WHITE, PAWN, 0); + stm = pos.side_to_move(); + } + else + { + wksq = flip_square(pos.king_square(BLACK)); + bksq = flip_square(pos.king_square(WHITE)); + wpsq = flip_square(pos.piece_list(BLACK, PAWN, 0)); + stm = opposite_color(pos.side_to_move()); + } + + if (square_file(wpsq) >= FILE_E) + { + wksq = flop_square(wksq); + bksq = flop_square(bksq); + wpsq = flop_square(wpsq); + } + + if (!probe_kpk_bitbase(wksq, wpsq, bksq, stm)) + return VALUE_DRAW; + + Value result = VALUE_KNOWN_WIN + + PawnValueEndgame + + Value(square_rank(wpsq)); + + return strongerSide == pos.side_to_move() ? result : -result; +} + + +/// KR vs KP. This is a somewhat tricky endgame to evaluate precisely without +/// a bitbase. The function below returns drawish scores when the pawn is +/// far advanced with support of the king, while the attacking king is far +/// away. +template<> +Value Endgame::apply(const Position& pos) const { + + assert(pos.non_pawn_material(strongerSide) == RookValueMidgame); + assert(pos.piece_count(strongerSide, PAWN) == 0); + assert(pos.non_pawn_material(weakerSide) == 0); + assert(pos.piece_count(weakerSide, PAWN) == 1); + + Square wksq, wrsq, bksq, bpsq; + int tempo = (pos.side_to_move() == strongerSide); + + wksq = pos.king_square(strongerSide); + wrsq = pos.piece_list(strongerSide, ROOK, 0); + bksq = pos.king_square(weakerSide); + bpsq = pos.piece_list(weakerSide, PAWN, 0); + + if (strongerSide == BLACK) + { + wksq = flip_square(wksq); + wrsq = flip_square(wrsq); + bksq = flip_square(bksq); + bpsq = flip_square(bpsq); + } + + Square queeningSq = make_square(square_file(bpsq), RANK_1); + Value result; + + // If the stronger side's king is in front of the pawn, it's a win + if (wksq < bpsq && square_file(wksq) == square_file(bpsq)) + result = RookValueEndgame - Value(square_distance(wksq, bpsq)); + + // If the weaker side's king is too far from the pawn and the rook, + // it's a win + else if ( square_distance(bksq, bpsq) - (tempo ^ 1) >= 3 + && square_distance(bksq, wrsq) >= 3) + result = RookValueEndgame - Value(square_distance(wksq, bpsq)); + + // If the pawn is far advanced and supported by the defending king, + // the position is drawish + else if ( square_rank(bksq) <= RANK_3 + && square_distance(bksq, bpsq) == 1 + && square_rank(wksq) >= RANK_4 + && square_distance(wksq, bpsq) - tempo > 2) + result = Value(80 - square_distance(wksq, bpsq) * 8); + + else + result = Value(200) + - Value(square_distance(wksq, bpsq + DELTA_S) * 8) + + Value(square_distance(bksq, bpsq + DELTA_S) * 8) + + Value(square_distance(bpsq, queeningSq) * 8); + + return strongerSide == pos.side_to_move() ? result : -result; +} + + +/// KR vs KB. This is very simple, and always returns drawish scores. The +/// score is slightly bigger when the defending king is close to the edge. +template<> +Value Endgame::apply(const Position& pos) const { + + assert(pos.non_pawn_material(strongerSide) == RookValueMidgame); + assert(pos.piece_count(strongerSide, PAWN) == 0); + assert(pos.non_pawn_material(weakerSide) == BishopValueMidgame); + assert(pos.piece_count(weakerSide, PAWN) == 0); + assert(pos.piece_count(weakerSide, BISHOP) == 1); + + Value result = Value(MateTable[pos.king_square(weakerSide)]); + return strongerSide == pos.side_to_move() ? result : -result; +} + + +/// KR vs KN. The attacking side has slightly better winning chances than +/// in KR vs KB, particularly if the king and the knight are far apart. +template<> +Value Endgame::apply(const Position& pos) const { + + assert(pos.non_pawn_material(strongerSide) == RookValueMidgame); + assert(pos.piece_count(strongerSide, PAWN) == 0); + assert(pos.non_pawn_material(weakerSide) == KnightValueMidgame); + assert(pos.piece_count(weakerSide, PAWN) == 0); + assert(pos.piece_count(weakerSide, KNIGHT) == 1); + + Square defendingKSq = pos.king_square(weakerSide); + Square nSq = pos.piece_list(weakerSide, KNIGHT, 0); + + int d = square_distance(defendingKSq, nSq); + Value result = Value(10) + + MateTable[defendingKSq] + + KRKNKingKnightDistancePenalty[d]; + + return strongerSide == pos.side_to_move() ? result : -result; +} + + +/// KQ vs KR. This is almost identical to KX vs K: We give the attacking +/// king a bonus for having the kings close together, and for forcing the +/// defending king towards the edge. If we also take care to avoid null move +/// for the defending side in the search, this is usually sufficient to be +/// able to win KQ vs KR. +template<> +Value Endgame::apply(const Position& pos) const { + + assert(pos.non_pawn_material(strongerSide) == QueenValueMidgame); + assert(pos.piece_count(strongerSide, PAWN) == 0); + assert(pos.non_pawn_material(weakerSide) == RookValueMidgame); + assert(pos.piece_count(weakerSide, PAWN) == 0); + + Square winnerKSq = pos.king_square(strongerSide); + Square loserKSq = pos.king_square(weakerSide); + + Value result = QueenValueEndgame + - RookValueEndgame + + MateTable[loserKSq] + + DistanceBonus[square_distance(winnerKSq, loserKSq)]; + + return strongerSide == pos.side_to_move() ? result : -result; +} + +template<> +Value Endgame::apply(const Position& pos) const { + + assert(pos.piece_count(strongerSide, BISHOP) == 2); + assert(pos.non_pawn_material(strongerSide) == 2*BishopValueMidgame); + assert(pos.piece_count(weakerSide, KNIGHT) == 1); + assert(pos.non_pawn_material(weakerSide) == KnightValueMidgame); + assert(pos.pieces(PAWN) == EmptyBoardBB); + + Value result = BishopValueEndgame; + Square wksq = pos.king_square(strongerSide); + Square bksq = pos.king_square(weakerSide); + Square nsq = pos.piece_list(weakerSide, KNIGHT, 0); + + // Bonus for attacking king close to defending king + result += Value(DistanceBonus[square_distance(wksq, bksq)]); + + // Bonus for driving the defending king and knight apart + result += Value(square_distance(bksq, nsq) * 32); + + // Bonus for restricting the knight's mobility + result += Value((8 - count_1s(pos.attacks_from(nsq))) * 8); + + return strongerSide == pos.side_to_move() ? result : -result; +} + + +/// K and two minors vs K and one or two minors or K and two knights against +/// king alone are always draw. +template<> +Value Endgame::apply(const Position&) const { + return VALUE_DRAW; +} + +template<> +Value Endgame::apply(const Position&) const { + return VALUE_DRAW; +} + +/// KBPKScalingFunction scales endgames where the stronger side has king, +/// bishop and one or more pawns. It checks for draws with rook pawns and a +/// bishop of the wrong color. If such a draw is detected, SCALE_FACTOR_ZERO is +/// returned. If not, the return value is SCALE_FACTOR_NONE, i.e. no scaling +/// will be used. +template<> +ScaleFactor Endgame::apply(const Position& pos) const { + + assert(pos.non_pawn_material(strongerSide) == BishopValueMidgame); + assert(pos.piece_count(strongerSide, BISHOP) == 1); + assert(pos.piece_count(strongerSide, PAWN) >= 1); + + // No assertions about the material of weakerSide, because we want draws to + // be detected even when the weaker side has some pawns. + + Bitboard pawns = pos.pieces(PAWN, strongerSide); + File pawnFile = square_file(pos.piece_list(strongerSide, PAWN, 0)); + + // All pawns are on a single rook file ? + if ( (pawnFile == FILE_A || pawnFile == FILE_H) + && (pawns & ~file_bb(pawnFile)) == EmptyBoardBB) + { + Square bishopSq = pos.piece_list(strongerSide, BISHOP, 0); + Square queeningSq = relative_square(strongerSide, make_square(pawnFile, RANK_8)); + Square kingSq = pos.king_square(weakerSide); + + if ( opposite_color_squares(queeningSq, bishopSq) + && abs(square_file(kingSq) - pawnFile) <= 1) + { + // The bishop has the wrong color, and the defending king is on the + // file of the pawn(s) or the neighboring file. Find the rank of the + // frontmost pawn. + Rank rank; + if (strongerSide == WHITE) + { + for (rank = RANK_7; (rank_bb(rank) & pawns) == EmptyBoardBB; rank--) {} + assert(rank >= RANK_2 && rank <= RANK_7); + } + else + { + for (rank = RANK_2; (rank_bb(rank) & pawns) == EmptyBoardBB; rank++) {} + rank = Rank(rank ^ 7); // HACK to get the relative rank + assert(rank >= RANK_2 && rank <= RANK_7); + } + // If the defending king has distance 1 to the promotion square or + // is placed somewhere in front of the pawn, it's a draw. + if ( square_distance(kingSq, queeningSq) <= 1 + || relative_rank(strongerSide, kingSq) >= rank) + return SCALE_FACTOR_ZERO; + } + } + return SCALE_FACTOR_NONE; +} + + +/// KQKRPScalingFunction scales endgames where the stronger side has only +/// king and queen, while the weaker side has at least a rook and a pawn. +/// It tests for fortress draws with a rook on the third rank defended by +/// a pawn. +template<> +ScaleFactor Endgame::apply(const Position& pos) const { + + assert(pos.non_pawn_material(strongerSide) == QueenValueMidgame); + assert(pos.piece_count(strongerSide, QUEEN) == 1); + assert(pos.piece_count(strongerSide, PAWN) == 0); + assert(pos.piece_count(weakerSide, ROOK) == 1); + assert(pos.piece_count(weakerSide, PAWN) >= 1); + + Square kingSq = pos.king_square(weakerSide); + if ( relative_rank(weakerSide, kingSq) <= RANK_2 + && relative_rank(weakerSide, pos.king_square(strongerSide)) >= RANK_4 + && (pos.pieces(ROOK, weakerSide) & rank_bb(relative_rank(weakerSide, RANK_3))) + && (pos.pieces(PAWN, weakerSide) & rank_bb(relative_rank(weakerSide, RANK_2))) + && (pos.attacks_from(kingSq) & pos.pieces(PAWN, weakerSide))) + { + Square rsq = pos.piece_list(weakerSide, ROOK, 0); + if (pos.attacks_from(rsq, strongerSide) & pos.pieces(PAWN, weakerSide)) + return SCALE_FACTOR_ZERO; + } + return SCALE_FACTOR_NONE; +} + + +/// KRPKRScalingFunction scales KRP vs KR endgames. This function knows a +/// handful of the most important classes of drawn positions, but is far +/// from perfect. It would probably be a good idea to add more knowledge +/// in the future. +/// +/// It would also be nice to rewrite the actual code for this function, +/// which is mostly copied from Glaurung 1.x, and not very pretty. +template<> +ScaleFactor Endgame::apply(const Position& pos) const { + + assert(pos.non_pawn_material(strongerSide) == RookValueMidgame); + assert(pos.piece_count(strongerSide, PAWN) == 1); + assert(pos.non_pawn_material(weakerSide) == RookValueMidgame); + assert(pos.piece_count(weakerSide, PAWN) == 0); + + Square wksq = pos.king_square(strongerSide); + Square wrsq = pos.piece_list(strongerSide, ROOK, 0); + Square wpsq = pos.piece_list(strongerSide, PAWN, 0); + Square bksq = pos.king_square(weakerSide); + Square brsq = pos.piece_list(weakerSide, ROOK, 0); + + // Orient the board in such a way that the stronger side is white, and the + // pawn is on the left half of the board. + if (strongerSide == BLACK) + { + wksq = flip_square(wksq); + wrsq = flip_square(wrsq); + wpsq = flip_square(wpsq); + bksq = flip_square(bksq); + brsq = flip_square(brsq); + } + if (square_file(wpsq) > FILE_D) + { + wksq = flop_square(wksq); + wrsq = flop_square(wrsq); + wpsq = flop_square(wpsq); + bksq = flop_square(bksq); + brsq = flop_square(brsq); + } + + File f = square_file(wpsq); + Rank r = square_rank(wpsq); + Square queeningSq = make_square(f, RANK_8); + int tempo = (pos.side_to_move() == strongerSide); + + // If the pawn is not too far advanced and the defending king defends the + // queening square, use the third-rank defence. + if ( r <= RANK_5 + && square_distance(bksq, queeningSq) <= 1 + && wksq <= SQ_H5 + && (square_rank(brsq) == RANK_6 || (r <= RANK_3 && square_rank(wrsq) != RANK_6))) + return SCALE_FACTOR_ZERO; + + // The defending side saves a draw by checking from behind in case the pawn + // has advanced to the 6th rank with the king behind. + if ( r == RANK_6 + && square_distance(bksq, queeningSq) <= 1 + && square_rank(wksq) + tempo <= RANK_6 + && (square_rank(brsq) == RANK_1 || (!tempo && abs(square_file(brsq) - f) >= 3))) + return SCALE_FACTOR_ZERO; + + if ( r >= RANK_6 + && bksq == queeningSq + && square_rank(brsq) == RANK_1 + && (!tempo || square_distance(wksq, wpsq) >= 2)) + return SCALE_FACTOR_ZERO; + + // White pawn on a7 and rook on a8 is a draw if black's king is on g7 or h7 + // and the black rook is behind the pawn. + if ( wpsq == SQ_A7 + && wrsq == SQ_A8 + && (bksq == SQ_H7 || bksq == SQ_G7) + && square_file(brsq) == FILE_A + && (square_rank(brsq) <= RANK_3 || square_file(wksq) >= FILE_D || square_rank(wksq) <= RANK_5)) + return SCALE_FACTOR_ZERO; + + // If the defending king blocks the pawn and the attacking king is too far + // away, it's a draw. + if ( r <= RANK_5 + && bksq == wpsq + DELTA_N + && square_distance(wksq, wpsq) - tempo >= 2 + && square_distance(wksq, brsq) - tempo >= 2) + return SCALE_FACTOR_ZERO; + + // Pawn on the 7th rank supported by the rook from behind usually wins if the + // attacking king is closer to the queening square than the defending king, + // and the defending king cannot gain tempi by threatening the attacking rook. + if ( r == RANK_7 + && f != FILE_A + && square_file(wrsq) == f + && wrsq != queeningSq + && (square_distance(wksq, queeningSq) < square_distance(bksq, queeningSq) - 2 + tempo) + && (square_distance(wksq, queeningSq) < square_distance(bksq, wrsq) + tempo)) + return ScaleFactor(SCALE_FACTOR_MAX - 2 * square_distance(wksq, queeningSq)); + + // Similar to the above, but with the pawn further back + if ( f != FILE_A + && square_file(wrsq) == f + && wrsq < wpsq + && (square_distance(wksq, queeningSq) < square_distance(bksq, queeningSq) - 2 + tempo) + && (square_distance(wksq, wpsq + DELTA_N) < square_distance(bksq, wpsq + DELTA_N) - 2 + tempo) + && ( square_distance(bksq, wrsq) + tempo >= 3 + || ( square_distance(wksq, queeningSq) < square_distance(bksq, wrsq) + tempo + && (square_distance(wksq, wpsq + DELTA_N) < square_distance(bksq, wrsq) + tempo)))) + return ScaleFactor( SCALE_FACTOR_MAX + - 8 * square_distance(wpsq, queeningSq) + - 2 * square_distance(wksq, queeningSq)); + + // If the pawn is not far advanced, and the defending king is somewhere in + // the pawn's path, it's probably a draw. + if (r <= RANK_4 && bksq > wpsq) + { + if (square_file(bksq) == square_file(wpsq)) + return ScaleFactor(10); + if ( abs(square_file(bksq) - square_file(wpsq)) == 1 + && square_distance(wksq, bksq) > 2) + return ScaleFactor(24 - 2 * square_distance(wksq, bksq)); + } + return SCALE_FACTOR_NONE; +} + + +/// KRPPKRPScalingFunction scales KRPP vs KRP endgames. There is only a +/// single pattern: If the stronger side has no pawns and the defending king +/// is actively placed, the position is drawish. +template<> +ScaleFactor Endgame::apply(const Position& pos) const { + + assert(pos.non_pawn_material(strongerSide) == RookValueMidgame); + assert(pos.piece_count(strongerSide, PAWN) == 2); + assert(pos.non_pawn_material(weakerSide) == RookValueMidgame); + assert(pos.piece_count(weakerSide, PAWN) == 1); + + Square wpsq1 = pos.piece_list(strongerSide, PAWN, 0); + Square wpsq2 = pos.piece_list(strongerSide, PAWN, 1); + Square bksq = pos.king_square(weakerSide); + + // Does the stronger side have a passed pawn? + if ( pos.pawn_is_passed(strongerSide, wpsq1) + || pos.pawn_is_passed(strongerSide, wpsq2)) + return SCALE_FACTOR_NONE; + + Rank r = Max(relative_rank(strongerSide, wpsq1), relative_rank(strongerSide, wpsq2)); + + if ( file_distance(bksq, wpsq1) <= 1 + && file_distance(bksq, wpsq2) <= 1 + && relative_rank(strongerSide, bksq) > r) + { + switch (r) { + case RANK_2: return ScaleFactor(10); + case RANK_3: return ScaleFactor(10); + case RANK_4: return ScaleFactor(15); + case RANK_5: return ScaleFactor(20); + case RANK_6: return ScaleFactor(40); + default: assert(false); + } + } + return SCALE_FACTOR_NONE; +} + + +/// KPsKScalingFunction scales endgames with king and two or more pawns +/// against king. There is just a single rule here: If all pawns are on +/// the same rook file and are blocked by the defending king, it's a draw. +template<> +ScaleFactor Endgame::apply(const Position& pos) const { + + assert(pos.non_pawn_material(strongerSide) == VALUE_ZERO); + assert(pos.piece_count(strongerSide, PAWN) >= 2); + assert(pos.non_pawn_material(weakerSide) == VALUE_ZERO); + assert(pos.piece_count(weakerSide, PAWN) == 0); + + Square ksq = pos.king_square(weakerSide); + Bitboard pawns = pos.pieces(PAWN, strongerSide); + + // Are all pawns on the 'a' file? + if ((pawns & ~FileABB) == EmptyBoardBB) + { + // Does the defending king block the pawns? + if ( square_distance(ksq, relative_square(strongerSide, SQ_A8)) <= 1 + || ( square_file(ksq) == FILE_A + && (in_front_bb(strongerSide, ksq) & pawns) == EmptyBoardBB)) + return SCALE_FACTOR_ZERO; + } + // Are all pawns on the 'h' file? + else if ((pawns & ~FileHBB) == EmptyBoardBB) + { + // Does the defending king block the pawns? + if ( square_distance(ksq, relative_square(strongerSide, SQ_H8)) <= 1 + || ( square_file(ksq) == FILE_H + && (in_front_bb(strongerSide, ksq) & pawns) == EmptyBoardBB)) + return SCALE_FACTOR_ZERO; + } + return SCALE_FACTOR_NONE; +} + + +/// KBPKBScalingFunction scales KBP vs KB endgames. There are two rules: +/// If the defending king is somewhere along the path of the pawn, and the +/// square of the king is not of the same color as the stronger side's bishop, +/// it's a draw. If the two bishops have opposite color, it's almost always +/// a draw. +template<> +ScaleFactor Endgame::apply(const Position& pos) const { + + assert(pos.non_pawn_material(strongerSide) == BishopValueMidgame); + assert(pos.piece_count(strongerSide, BISHOP) == 1); + assert(pos.piece_count(strongerSide, PAWN) == 1); + assert(pos.non_pawn_material(weakerSide) == BishopValueMidgame); + assert(pos.piece_count(weakerSide, BISHOP) == 1); + assert(pos.piece_count(weakerSide, PAWN) == 0); + + Square pawnSq = pos.piece_list(strongerSide, PAWN, 0); + Square strongerBishopSq = pos.piece_list(strongerSide, BISHOP, 0); + Square weakerBishopSq = pos.piece_list(weakerSide, BISHOP, 0); + Square weakerKingSq = pos.king_square(weakerSide); + + // Case 1: Defending king blocks the pawn, and cannot be driven away + if ( square_file(weakerKingSq) == square_file(pawnSq) + && relative_rank(strongerSide, pawnSq) < relative_rank(strongerSide, weakerKingSq) + && ( opposite_color_squares(weakerKingSq, strongerBishopSq) + || relative_rank(strongerSide, weakerKingSq) <= RANK_6)) + return SCALE_FACTOR_ZERO; + + // Case 2: Opposite colored bishops + if (opposite_color_squares(strongerBishopSq, weakerBishopSq)) + { + // We assume that the position is drawn in the following three situations: + // + // a. The pawn is on rank 5 or further back. + // b. The defending king is somewhere in the pawn's path. + // c. The defending bishop attacks some square along the pawn's path, + // and is at least three squares away from the pawn. + // + // These rules are probably not perfect, but in practice they work + // reasonably well. + + if (relative_rank(strongerSide, pawnSq) <= RANK_5) + return SCALE_FACTOR_ZERO; + else + { + Bitboard path = squares_in_front_of(strongerSide, pawnSq); + + if (path & pos.pieces(KING, weakerSide)) + return SCALE_FACTOR_ZERO; + + if ( (pos.attacks_from(weakerBishopSq) & path) + && square_distance(weakerBishopSq, pawnSq) >= 3) + return SCALE_FACTOR_ZERO; + } + } + return SCALE_FACTOR_NONE; +} + + +/// KBPPKBScalingFunction scales KBPP vs KB endgames. It detects a few basic +/// draws with opposite-colored bishops. +template<> +ScaleFactor Endgame::apply(const Position& pos) const { + + assert(pos.non_pawn_material(strongerSide) == BishopValueMidgame); + assert(pos.piece_count(strongerSide, BISHOP) == 1); + assert(pos.piece_count(strongerSide, PAWN) == 2); + assert(pos.non_pawn_material(weakerSide) == BishopValueMidgame); + assert(pos.piece_count(weakerSide, BISHOP) == 1); + assert(pos.piece_count(weakerSide, PAWN) == 0); + + Square wbsq = pos.piece_list(strongerSide, BISHOP, 0); + Square bbsq = pos.piece_list(weakerSide, BISHOP, 0); + + if (!opposite_color_squares(wbsq, bbsq)) + return SCALE_FACTOR_NONE; + + Square ksq = pos.king_square(weakerSide); + Square psq1 = pos.piece_list(strongerSide, PAWN, 0); + Square psq2 = pos.piece_list(strongerSide, PAWN, 1); + Rank r1 = square_rank(psq1); + Rank r2 = square_rank(psq2); + Square blockSq1, blockSq2; + + if (relative_rank(strongerSide, psq1) > relative_rank(strongerSide, psq2)) + { + blockSq1 = psq1 + pawn_push(strongerSide); + blockSq2 = make_square(square_file(psq2), square_rank(psq1)); + } + else + { + blockSq1 = psq2 + pawn_push(strongerSide); + blockSq2 = make_square(square_file(psq1), square_rank(psq2)); + } + + switch (file_distance(psq1, psq2)) + { + case 0: + // Both pawns are on the same file. Easy draw if defender firmly controls + // some square in the frontmost pawn's path. + if ( square_file(ksq) == square_file(blockSq1) + && relative_rank(strongerSide, ksq) >= relative_rank(strongerSide, blockSq1) + && opposite_color_squares(ksq, wbsq)) + return SCALE_FACTOR_ZERO; + else + return SCALE_FACTOR_NONE; + + case 1: + // Pawns on neighboring files. Draw if defender firmly controls the square + // in front of the frontmost pawn's path, and the square diagonally behind + // this square on the file of the other pawn. + if ( ksq == blockSq1 + && opposite_color_squares(ksq, wbsq) + && ( bbsq == blockSq2 + || (pos.attacks_from(blockSq2) & pos.pieces(BISHOP, weakerSide)) + || abs(r1 - r2) >= 2)) + return SCALE_FACTOR_ZERO; + + else if ( ksq == blockSq2 + && opposite_color_squares(ksq, wbsq) + && ( bbsq == blockSq1 + || (pos.attacks_from(blockSq1) & pos.pieces(BISHOP, weakerSide)))) + return SCALE_FACTOR_ZERO; + else + return SCALE_FACTOR_NONE; + + default: + // The pawns are not on the same file or adjacent files. No scaling. + return SCALE_FACTOR_NONE; + } +} + + +/// KBPKNScalingFunction scales KBP vs KN endgames. There is a single rule: +/// If the defending king is somewhere along the path of the pawn, and the +/// square of the king is not of the same color as the stronger side's bishop, +/// it's a draw. +template<> +ScaleFactor Endgame::apply(const Position& pos) const { + + assert(pos.non_pawn_material(strongerSide) == BishopValueMidgame); + assert(pos.piece_count(strongerSide, BISHOP) == 1); + assert(pos.piece_count(strongerSide, PAWN) == 1); + assert(pos.non_pawn_material(weakerSide) == KnightValueMidgame); + assert(pos.piece_count(weakerSide, KNIGHT) == 1); + assert(pos.piece_count(weakerSide, PAWN) == 0); + + Square pawnSq = pos.piece_list(strongerSide, PAWN, 0); + Square strongerBishopSq = pos.piece_list(strongerSide, BISHOP, 0); + Square weakerKingSq = pos.king_square(weakerSide); + + if ( square_file(weakerKingSq) == square_file(pawnSq) + && relative_rank(strongerSide, pawnSq) < relative_rank(strongerSide, weakerKingSq) + && ( opposite_color_squares(weakerKingSq, strongerBishopSq) + || relative_rank(strongerSide, weakerKingSq) <= RANK_6)) + return SCALE_FACTOR_ZERO; + + return SCALE_FACTOR_NONE; +} + + +/// KNPKScalingFunction scales KNP vs K endgames. There is a single rule: +/// If the pawn is a rook pawn on the 7th rank and the defending king prevents +/// the pawn from advancing, the position is drawn. +template<> +ScaleFactor Endgame::apply(const Position& pos) const { + + assert(pos.non_pawn_material(strongerSide) == KnightValueMidgame); + assert(pos.piece_count(strongerSide, KNIGHT) == 1); + assert(pos.piece_count(strongerSide, PAWN) == 1); + assert(pos.non_pawn_material(weakerSide) == VALUE_ZERO); + assert(pos.piece_count(weakerSide, PAWN) == 0); + + Square pawnSq = pos.piece_list(strongerSide, PAWN, 0); + Square weakerKingSq = pos.king_square(weakerSide); + + if ( pawnSq == relative_square(strongerSide, SQ_A7) + && square_distance(weakerKingSq, relative_square(strongerSide, SQ_A8)) <= 1) + return SCALE_FACTOR_ZERO; + + if ( pawnSq == relative_square(strongerSide, SQ_H7) + && square_distance(weakerKingSq, relative_square(strongerSide, SQ_H8)) <= 1) + return SCALE_FACTOR_ZERO; + + return SCALE_FACTOR_NONE; +} + + +/// KPKPScalingFunction scales KP vs KP endgames. This is done by removing +/// the weakest side's pawn and probing the KP vs K bitbase: If the weakest +/// side has a draw without the pawn, she probably has at least a draw with +/// the pawn as well. The exception is when the stronger side's pawn is far +/// advanced and not on a rook file; in this case it is often possible to win +/// (e.g. 8/4k3/3p4/3P4/6K1/8/8/8 w - - 0 1). +template<> +ScaleFactor Endgame::apply(const Position& pos) const { + + assert(pos.non_pawn_material(strongerSide) == VALUE_ZERO); + assert(pos.non_pawn_material(weakerSide) == VALUE_ZERO); + assert(pos.piece_count(WHITE, PAWN) == 1); + assert(pos.piece_count(BLACK, PAWN) == 1); + + Square wksq, bksq, wpsq; + Color stm; + + if (strongerSide == WHITE) + { + wksq = pos.king_square(WHITE); + bksq = pos.king_square(BLACK); + wpsq = pos.piece_list(WHITE, PAWN, 0); + stm = pos.side_to_move(); + } + else + { + wksq = flip_square(pos.king_square(BLACK)); + bksq = flip_square(pos.king_square(WHITE)); + wpsq = flip_square(pos.piece_list(BLACK, PAWN, 0)); + stm = opposite_color(pos.side_to_move()); + } + + if (square_file(wpsq) >= FILE_E) + { + wksq = flop_square(wksq); + bksq = flop_square(bksq); + wpsq = flop_square(wpsq); + } + + // If the pawn has advanced to the fifth rank or further, and is not a + // rook pawn, it's too dangerous to assume that it's at least a draw. + if ( square_rank(wpsq) >= RANK_5 + && square_file(wpsq) != FILE_A) + return SCALE_FACTOR_NONE; + + // Probe the KPK bitbase with the weakest side's pawn removed. If it's a + // draw, it's probably at least a draw even with the pawn. + return probe_kpk_bitbase(wksq, wpsq, bksq, stm) ? SCALE_FACTOR_NONE : SCALE_FACTOR_ZERO; +} diff --git a/DroidFish/jni/stockfish/endgame.h b/DroidFish/jni/stockfish/endgame.h new file mode 100644 index 0000000..e35c7b0 --- /dev/null +++ b/DroidFish/jni/stockfish/endgame.h @@ -0,0 +1,112 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(ENDGAME_H_INCLUDED) +#define ENDGAME_H_INCLUDED + +#include +#include + +#include "position.h" +#include "types.h" + + +/// EndgameType lists all supported endgames + +enum EndgameType { + + // Evaluation functions + + KXK, // Generic "mate lone king" eval + KBNK, // KBN vs K + KPK, // KP vs K + KRKP, // KR vs KP + KRKB, // KR vs KB + KRKN, // KR vs KN + KQKR, // KQ vs KR + KBBKN, // KBB vs KN + KNNK, // KNN vs K + KmmKm, // K and two minors vs K and one or two minors + + + // Scaling functions + + KBPsK, // KB+pawns vs K + KQKRPs, // KQ vs KR+pawns + KRPKR, // KRP vs KR + KRPPKRP, // KRPP vs KRP + KPsK, // King and pawns vs king + KBPKB, // KBP vs KB + KBPPKB, // KBPP vs KB + KBPKN, // KBP vs KN + KNPK, // KNP vs K + KPKP // KP vs KP +}; + + +/// Base and derived templates for endgame evaluation and scaling functions + +template +struct EndgameBase { + + typedef EndgameBase Base; + + virtual ~EndgameBase() {} + virtual Color color() const = 0; + virtual T apply(const Position&) const = 0; +}; + + +template +struct Endgame : public EndgameBase { + + explicit Endgame(Color c) : strongerSide(c), weakerSide(opposite_color(c)) {} + Color color() const { return strongerSide; } + T apply(const Position&) const; + +private: + Color strongerSide, weakerSide; +}; + + +/// Endgames class stores in two std::map the pointers to endgame evaluation +/// and scaling base objects. Then we use polymorphism to invoke the actual +/// endgame function calling its apply() method that is virtual. + +class Endgames { + + typedef std::map* > EFMap; + typedef std::map* > SFMap; + +public: + Endgames(); + ~Endgames(); + template T* get(Key key) const; + +private: + template void add(const std::string& keyCode); + + // Here we store two maps, for evaluate and scaling functions... + std::pair maps; + + // ...and here is the accessing template function + template const std::map& get() const; +}; + +#endif // !defined(ENDGAME_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/evaluate.cpp b/DroidFish/jni/stockfish/evaluate.cpp new file mode 100644 index 0000000..8b77a82 --- /dev/null +++ b/DroidFish/jni/stockfish/evaluate.cpp @@ -0,0 +1,1221 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include + +#include "bitcount.h" +#include "evaluate.h" +#include "material.h" +#include "pawns.h" +#include "thread.h" +#include "ucioption.h" + +namespace { + + // Struct EvalInfo contains various information computed and collected + // by the evaluation functions. + struct EvalInfo { + + // Pointers to material and pawn hash table entries + MaterialInfo* mi; + PawnInfo* pi; + + // attackedBy[color][piece type] is a bitboard representing all squares + // attacked by a given color and piece type, attackedBy[color][0] contains + // all squares attacked by the given color. + Bitboard attackedBy[2][8]; + + // kingZone[color] is the zone around the enemy king which is considered + // by the king safety evaluation. This consists of the squares directly + // adjacent to the king, and the three (or two, for a king on an edge file) + // squares two ranks in front of the king. For instance, if black's king + // is on g8, kingZone[WHITE] is a bitboard containing the squares f8, h8, + // f7, g7, h7, f6, g6 and h6. + Bitboard kingZone[2]; + + // kingAttackersCount[color] is the number of pieces of the given color + // which attack a square in the kingZone of the enemy king. + int kingAttackersCount[2]; + + // kingAttackersWeight[color] is the sum of the "weight" of the pieces of the + // given color which attack a square in the kingZone of the enemy king. The + // weights of the individual piece types are given by the variables + // QueenAttackWeight, RookAttackWeight, BishopAttackWeight and + // KnightAttackWeight in evaluate.cpp + int kingAttackersWeight[2]; + + // kingAdjacentZoneAttacksCount[color] is the number of attacks to squares + // directly adjacent to the king of the given color. Pieces which attack + // more than one square are counted multiple times. For instance, if black's + // king is on g8 and there's a white knight on g5, this knight adds + // 2 to kingAdjacentZoneAttacksCount[BLACK]. + int kingAdjacentZoneAttacksCount[2]; + }; + + // Evaluation grain size, must be a power of 2 + const int GrainSize = 8; + + // Evaluation weights, initialized from UCI options + enum { Mobility, PassedPawns, Space, KingDangerUs, KingDangerThem }; + Score Weights[6]; + + typedef Value V; + #define S(mg, eg) make_score(mg, eg) + + // Internal evaluation weights. These are applied on top of the evaluation + // weights read from UCI parameters. The purpose is to be able to change + // the evaluation weights while keeping the default values of the UCI + // parameters at 100, which looks prettier. + // + // Values modified by Joona Kiiski + const Score WeightsInternal[] = { + S(248, 271), S(252, 259), S(46, 0), S(247, 0), S(259, 0) + }; + + // MobilityBonus[PieceType][attacked] contains mobility bonuses for middle and + // end game, indexed by piece type and number of attacked squares not occupied + // by friendly pieces. + const Score MobilityBonus[][32] = { + {}, {}, + { S(-38,-33), S(-25,-23), S(-12,-13), S( 0, -3), S(12, 7), S(25, 17), // Knights + S( 31, 22), S( 38, 27), S( 38, 27) }, + { S(-25,-30), S(-11,-16), S( 3, -2), S(17, 12), S(31, 26), S(45, 40), // Bishops + S( 57, 52), S( 65, 60), S( 71, 65), S(74, 69), S(76, 71), S(78, 73), + S( 79, 74), S( 80, 75), S( 81, 76), S(81, 76) }, + { S(-20,-36), S(-14,-19), S( -8, -3), S(-2, 13), S( 4, 29), S(10, 46), // Rooks + S( 14, 62), S( 19, 79), S( 23, 95), S(26,106), S(27,111), S(28,114), + S( 29,116), S( 30,117), S( 31,118), S(32,118) }, + { S(-10,-18), S( -8,-13), S( -6, -7), S(-3, -2), S(-1, 3), S( 1, 8), // Queens + S( 3, 13), S( 5, 19), S( 8, 23), S(10, 27), S(12, 32), S(15, 34), + S( 16, 35), S( 17, 35), S( 18, 35), S(20, 35), S(20, 35), S(20, 35), + S( 20, 35), S( 20, 35), S( 20, 35), S(20, 35), S(20, 35), S(20, 35), + S( 20, 35), S( 20, 35), S( 20, 35), S(20, 35), S(20, 35), S(20, 35), + S( 20, 35), S( 20, 35) } + }; + + // OutpostBonus[PieceType][Square] contains outpost bonuses of knights and + // bishops, indexed by piece type and square (from white's point of view). + const Value OutpostBonus[][64] = { + { + // A B C D E F G H + V(0), V(0), V(0), V(0), V(0), V(0), V(0), V(0), // Knights + V(0), V(0), V(0), V(0), V(0), V(0), V(0), V(0), + V(0), V(0), V(4), V(8), V(8), V(4), V(0), V(0), + V(0), V(4),V(17),V(26),V(26),V(17), V(4), V(0), + V(0), V(8),V(26),V(35),V(35),V(26), V(8), V(0), + V(0), V(4),V(17),V(17),V(17),V(17), V(4), V(0) }, + { + V(0), V(0), V(0), V(0), V(0), V(0), V(0), V(0), // Bishops + V(0), V(0), V(0), V(0), V(0), V(0), V(0), V(0), + V(0), V(0), V(5), V(5), V(5), V(5), V(0), V(0), + V(0), V(5),V(10),V(10),V(10),V(10), V(5), V(0), + V(0),V(10),V(21),V(21),V(21),V(21),V(10), V(0), + V(0), V(5), V(8), V(8), V(8), V(8), V(5), V(0) } + }; + + // ThreatBonus[attacking][attacked] contains threat bonuses according to + // which piece type attacks which one. + const Score ThreatBonus[][8] = { + {}, {}, + { S(0, 0), S( 7, 39), S( 0, 0), S(24, 49), S(41,100), S(41,100) }, // KNIGHT + { S(0, 0), S( 7, 39), S(24, 49), S( 0, 0), S(41,100), S(41,100) }, // BISHOP + { S(0, 0), S(-1, 29), S(15, 49), S(15, 49), S( 0, 0), S(24, 49) }, // ROOK + { S(0, 0), S(15, 39), S(15, 39), S(15, 39), S(15, 39), S( 0, 0) } // QUEEN + }; + + // ThreatedByPawnPenalty[PieceType] contains a penalty according to which + // piece type is attacked by an enemy pawn. + const Score ThreatedByPawnPenalty[] = { + S(0, 0), S(0, 0), S(56, 70), S(56, 70), S(76, 99), S(86, 118) + }; + + #undef S + + // Rooks and queens on the 7th rank (modified by Joona Kiiski) + const Score RookOn7thBonus = make_score(47, 98); + const Score QueenOn7thBonus = make_score(27, 54); + + // Rooks on open files (modified by Joona Kiiski) + const Score RookOpenFileBonus = make_score(43, 43); + const Score RookHalfOpenFileBonus = make_score(19, 19); + + // Penalty for rooks trapped inside a friendly king which has lost the + // right to castle. + const Value TrappedRookPenalty = Value(180); + + // Penalty for a bishop on a1/h1 (a8/h8 for black) which is trapped by + // a friendly pawn on b2/g2 (b7/g7 for black). This can obviously only + // happen in Chess960 games. + const Score TrappedBishopA1H1Penalty = make_score(100, 100); + + // The SpaceMask[Color] contains the area of the board which is considered + // by the space evaluation. In the middle game, each side is given a bonus + // based on how many squares inside this area are safe and available for + // friendly minor pieces. + const Bitboard SpaceMask[] = { + (1ULL << SQ_C2) | (1ULL << SQ_D2) | (1ULL << SQ_E2) | (1ULL << SQ_F2) | + (1ULL << SQ_C3) | (1ULL << SQ_D3) | (1ULL << SQ_E3) | (1ULL << SQ_F3) | + (1ULL << SQ_C4) | (1ULL << SQ_D4) | (1ULL << SQ_E4) | (1ULL << SQ_F4), + (1ULL << SQ_C7) | (1ULL << SQ_D7) | (1ULL << SQ_E7) | (1ULL << SQ_F7) | + (1ULL << SQ_C6) | (1ULL << SQ_D6) | (1ULL << SQ_E6) | (1ULL << SQ_F6) | + (1ULL << SQ_C5) | (1ULL << SQ_D5) | (1ULL << SQ_E5) | (1ULL << SQ_F5) + }; + + // King danger constants and variables. The king danger scores are taken + // from the KingDangerTable[]. Various little "meta-bonuses" measuring + // the strength of the enemy attack are added up into an integer, which + // is used as an index to KingDangerTable[]. + // + // KingAttackWeights[PieceType] contains king attack weights by piece type + const int KingAttackWeights[] = { 0, 0, 2, 2, 3, 5 }; + + // Bonuses for enemy's safe checks + const int QueenContactCheckBonus = 6; + const int RookContactCheckBonus = 4; + const int QueenCheckBonus = 3; + const int RookCheckBonus = 2; + const int BishopCheckBonus = 1; + const int KnightCheckBonus = 1; + + // InitKingDanger[Square] contains penalties based on the position of the + // defending king, indexed by king's square (from white's point of view). + const int InitKingDanger[] = { + 2, 0, 2, 5, 5, 2, 0, 2, + 2, 2, 4, 8, 8, 4, 2, 2, + 7, 10, 12, 12, 12, 12, 10, 7, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15 + }; + + // KingDangerTable[Color][attackUnits] contains the actual king danger + // weighted scores, indexed by color and by a calculated integer number. + Score KingDangerTable[2][128]; + + // TracedTerms[Color][PieceType || TracedType] contains a breakdown of the + // evaluation terms, used when tracing. + Score TracedScores[2][16]; + std::stringstream TraceStream; + + enum TracedType { + PST = 8, IMBALANCE = 9, MOBILITY = 10, THREAT = 11, + PASSED = 12, UNSTOPPABLE = 13, SPACE = 14, TOTAL = 15 + }; + + // Function prototypes + template + Value do_evaluate(const Position& pos, Value& margin); + + template + void init_eval_info(const Position& pos, EvalInfo& ei); + + template + Score evaluate_pieces_of_color(const Position& pos, EvalInfo& ei, Score& mobility); + + template + Score evaluate_king(const Position& pos, EvalInfo& ei, Value margins[]); + + template + Score evaluate_threats(const Position& pos, EvalInfo& ei); + + template + int evaluate_space(const Position& pos, EvalInfo& ei); + + template + Score evaluate_passed_pawns(const Position& pos, EvalInfo& ei); + + template + Score evaluate_unstoppable_pawns(const Position& pos, EvalInfo& ei); + + inline Score apply_weight(Score v, Score weight); + Value scale_by_game_phase(const Score& v, Phase ph, ScaleFactor sf); + Score weight_option(const std::string& mgOpt, const std::string& egOpt, Score internalWeight); + void init_safety(); + double to_cp(Value v); + void trace_add(int idx, Score term_w, Score term_b = SCORE_ZERO); +} + + +/// evaluate() is the main evaluation function. It always computes two +/// values, an endgame score and a middle game score, and interpolates +/// between them based on the remaining material. +Value evaluate(const Position& pos, Value& margin) { + + return CpuHasPOPCNT ? do_evaluate(pos, margin) + : do_evaluate(pos, margin); +} + +namespace { + +template +Value do_evaluate(const Position& pos, Value& margin) { + + EvalInfo ei; + Value margins[2]; + Score score, mobilityWhite, mobilityBlack; + + assert(pos.is_ok()); + assert(pos.thread() >= 0 && pos.thread() < MAX_THREADS); + assert(!pos.in_check()); + + // Initialize score by reading the incrementally updated scores included + // in the position object (material + piece square tables). + score = pos.value(); + + // margins[] store the uncertainty estimation of position's evaluation + // that typically is used by the search for pruning decisions. + margins[WHITE] = margins[BLACK] = VALUE_ZERO; + + // Probe the material hash table + ei.mi = Threads[pos.thread()].materialTable.get_material_info(pos); + score += ei.mi->material_value(); + + // If we have a specialized evaluation function for the current material + // configuration, call it and return. + if (ei.mi->specialized_eval_exists()) + { + margin = VALUE_ZERO; + return ei.mi->evaluate(pos); + } + + // Probe the pawn hash table + ei.pi = Threads[pos.thread()].pawnTable.get_pawn_info(pos); + score += ei.pi->pawns_value(); + + // Initialize attack and king safety bitboards + init_eval_info(pos, ei); + init_eval_info(pos, ei); + + // Evaluate pieces and mobility + score += evaluate_pieces_of_color(pos, ei, mobilityWhite) + - evaluate_pieces_of_color(pos, ei, mobilityBlack); + + score += apply_weight(mobilityWhite - mobilityBlack, Weights[Mobility]); + + // Evaluate kings after all other pieces because we need complete attack + // information when computing the king safety evaluation. + score += evaluate_king(pos, ei, margins) + - evaluate_king(pos, ei, margins); + + // Evaluate tactical threats, we need full attack information including king + score += evaluate_threats(pos, ei) + - evaluate_threats(pos, ei); + + // Evaluate passed pawns, we need full attack information including king + score += evaluate_passed_pawns(pos, ei) + - evaluate_passed_pawns(pos, ei); + + // If one side has only a king, check whether exists any unstoppable passed pawn + if (!pos.non_pawn_material(WHITE) || !pos.non_pawn_material(BLACK)) + score += evaluate_unstoppable_pawns(pos, ei); + + // Evaluate space for both sides, only in middle-game. + if (ei.mi->space_weight()) + { + int s = evaluate_space(pos, ei) - evaluate_space(pos, ei); + score += apply_weight(make_score(s * ei.mi->space_weight(), 0), Weights[Space]); + } + + // Scale winning side if position is more drawish that what it appears + ScaleFactor sf = eg_value(score) > VALUE_DRAW ? ei.mi->scale_factor(pos, WHITE) + : ei.mi->scale_factor(pos, BLACK); + + // If we don't already have an unusual scale factor, check for opposite + // colored bishop endgames, and use a lower scale for those. + if ( ei.mi->game_phase() < PHASE_MIDGAME + && pos.opposite_colored_bishops() + && sf == SCALE_FACTOR_NORMAL) + { + // Only the two bishops ? + if ( pos.non_pawn_material(WHITE) == BishopValueMidgame + && pos.non_pawn_material(BLACK) == BishopValueMidgame) + { + // Check for KBP vs KB with only a single pawn that is almost + // certainly a draw or at least two pawns. + bool one_pawn = (pos.piece_count(WHITE, PAWN) + pos.piece_count(BLACK, PAWN) == 1); + sf = one_pawn ? ScaleFactor(8) : ScaleFactor(32); + } + else + // Endgame with opposite-colored bishops, but also other pieces. Still + // a bit drawish, but not as drawish as with only the two bishops. + sf = ScaleFactor(50); + } + + // Interpolate between the middle game and the endgame score + margin = margins[pos.side_to_move()]; + Value v = scale_by_game_phase(score, ei.mi->game_phase(), sf); + + // In case of tracing add all single evaluation contributions for both white and black + if (Trace) + { + trace_add(PST, pos.value()); + trace_add(IMBALANCE, ei.mi->material_value()); + trace_add(PAWN, ei.pi->pawns_value()); + trace_add(MOBILITY, apply_weight(mobilityWhite, Weights[Mobility]), apply_weight(mobilityBlack, Weights[Mobility])); + trace_add(THREAT, evaluate_threats(pos, ei), evaluate_threats(pos, ei)); + trace_add(PASSED, evaluate_passed_pawns(pos, ei), evaluate_passed_pawns(pos, ei)); + trace_add(UNSTOPPABLE, evaluate_unstoppable_pawns(pos, ei)); + Score w = make_score(ei.mi->space_weight() * evaluate_space(pos, ei), 0); + Score b = make_score(ei.mi->space_weight() * evaluate_space(pos, ei), 0); + trace_add(SPACE, apply_weight(w, Weights[Space]), apply_weight(b, Weights[Space])); + trace_add(TOTAL, score); + TraceStream << "\nUncertainty margin: White: " << to_cp(margins[WHITE]) + << ", Black: " << to_cp(margins[BLACK]) + << "\nScaling: " << std::noshowpos + << std::setw(6) << 100.0 * ei.mi->game_phase() / 128.0 << "% MG, " + << std::setw(6) << 100.0 * (1.0 - ei.mi->game_phase() / 128.0) << "% * " + << std::setw(6) << (100.0 * sf) / SCALE_FACTOR_NORMAL << "% EG.\n" + << "Total evaluation: " << to_cp(v); + } + + return pos.side_to_move() == WHITE ? v : -v; +} + +} // namespace + + +/// read_weights() reads evaluation weights from the corresponding UCI parameters + +void read_evaluation_uci_options(Color us) { + + // King safety is asymmetrical. Our king danger level is weighted by + // "Cowardice" UCI parameter, instead the opponent one by "Aggressiveness". + const int kingDangerUs = (us == WHITE ? KingDangerUs : KingDangerThem); + const int kingDangerThem = (us == WHITE ? KingDangerThem : KingDangerUs); + + Weights[Mobility] = weight_option("Mobility (Middle Game)", "Mobility (Endgame)", WeightsInternal[Mobility]); + Weights[PassedPawns] = weight_option("Passed Pawns (Middle Game)", "Passed Pawns (Endgame)", WeightsInternal[PassedPawns]); + Weights[Space] = weight_option("Space", "Space", WeightsInternal[Space]); + Weights[kingDangerUs] = weight_option("Cowardice", "Cowardice", WeightsInternal[KingDangerUs]); + Weights[kingDangerThem] = weight_option("Aggressiveness", "Aggressiveness", WeightsInternal[KingDangerThem]); + + // If running in analysis mode, make sure we use symmetrical king safety. We do this + // by replacing both Weights[kingDangerUs] and Weights[kingDangerThem] by their average. + if (Options["UCI_AnalyseMode"].value()) + Weights[kingDangerUs] = Weights[kingDangerThem] = (Weights[kingDangerUs] + Weights[kingDangerThem]) / 2; + + init_safety(); +} + + +namespace { + + // init_eval_info() initializes king bitboards for given color adding + // pawn attacks. To be done at the beginning of the evaluation. + + template + void init_eval_info(const Position& pos, EvalInfo& ei) { + + const BitCountType Max15 = HasPopCnt ? CNT_POPCNT : CpuIs64Bit ? CNT64_MAX15 : CNT32_MAX15; + const Color Them = (Us == WHITE ? BLACK : WHITE); + + Bitboard b = ei.attackedBy[Them][KING] = pos.attacks_from(pos.king_square(Them)); + ei.attackedBy[Us][PAWN] = ei.pi->pawn_attacks(Us); + + // Init king safety tables only if we are going to use them + if ( pos.piece_count(Us, QUEEN) + && pos.non_pawn_material(Us) >= QueenValueMidgame + RookValueMidgame) + { + ei.kingZone[Us] = (b | (Us == WHITE ? b >> 8 : b << 8)); + b &= ei.attackedBy[Us][PAWN]; + ei.kingAttackersCount[Us] = b ? count_1s(b) / 2 : 0; + ei.kingAdjacentZoneAttacksCount[Us] = ei.kingAttackersWeight[Us] = 0; + } else + ei.kingZone[Us] = ei.kingAttackersCount[Us] = 0; + } + + + // evaluate_outposts() evaluates bishop and knight outposts squares + + template + Score evaluate_outposts(const Position& pos, EvalInfo& ei, Square s) { + + const Color Them = (Us == WHITE ? BLACK : WHITE); + + assert (Piece == BISHOP || Piece == KNIGHT); + + // Initial bonus based on square + Value bonus = OutpostBonus[Piece == BISHOP][relative_square(Us, s)]; + + // Increase bonus if supported by pawn, especially if the opponent has + // no minor piece which can exchange the outpost piece. + if (bonus && bit_is_set(ei.attackedBy[Us][PAWN], s)) + { + if ( pos.pieces(KNIGHT, Them) == EmptyBoardBB + && (SquaresByColorBB[square_color(s)] & pos.pieces(BISHOP, Them)) == EmptyBoardBB) + bonus += bonus + bonus / 2; + else + bonus += bonus / 2; + } + return make_score(bonus, bonus); + } + + + // evaluate_pieces<>() assigns bonuses and penalties to the pieces of a given color + + template + Score evaluate_pieces(const Position& pos, EvalInfo& ei, Score& mobility, Bitboard mobilityArea) { + + Bitboard b; + Square s, ksq; + int mob; + File f; + Score score = SCORE_ZERO; + + const BitCountType Full = HasPopCnt ? CNT_POPCNT : CpuIs64Bit ? CNT64 : CNT32; + const BitCountType Max15 = HasPopCnt ? CNT_POPCNT : CpuIs64Bit ? CNT64_MAX15 : CNT32_MAX15; + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Square* ptr = pos.piece_list_begin(Us, Piece); + + ei.attackedBy[Us][Piece] = EmptyBoardBB; + + while ((s = *ptr++) != SQ_NONE) + { + // Find attacked squares, including x-ray attacks for bishops and rooks + if (Piece == KNIGHT || Piece == QUEEN) + b = pos.attacks_from(s); + else if (Piece == BISHOP) + b = bishop_attacks_bb(s, pos.occupied_squares() & ~pos.pieces(QUEEN, Us)); + else if (Piece == ROOK) + b = rook_attacks_bb(s, pos.occupied_squares() & ~pos.pieces(ROOK, QUEEN, Us)); + else + assert(false); + + // Update attack info + ei.attackedBy[Us][Piece] |= b; + + // King attacks + if (b & ei.kingZone[Us]) + { + ei.kingAttackersCount[Us]++; + ei.kingAttackersWeight[Us] += KingAttackWeights[Piece]; + Bitboard bb = (b & ei.attackedBy[Them][KING]); + if (bb) + ei.kingAdjacentZoneAttacksCount[Us] += count_1s(bb); + } + + // Mobility + mob = (Piece != QUEEN ? count_1s(b & mobilityArea) + : count_1s(b & mobilityArea)); + + mobility += MobilityBonus[Piece][mob]; + + // Decrease score if we are attacked by an enemy pawn. Remaining part + // of threat evaluation must be done later when we have full attack info. + if (bit_is_set(ei.attackedBy[Them][PAWN], s)) + score -= ThreatedByPawnPenalty[Piece]; + + // Bishop and knight outposts squares + if ((Piece == BISHOP || Piece == KNIGHT) && pos.square_is_weak(s, Us)) + score += evaluate_outposts(pos, ei, s); + + // Queen or rook on 7th rank + if ( (Piece == ROOK || Piece == QUEEN) + && relative_rank(Us, s) == RANK_7 + && relative_rank(Us, pos.king_square(Them)) == RANK_8) + { + score += (Piece == ROOK ? RookOn7thBonus : QueenOn7thBonus); + } + + // Special extra evaluation for bishops + if (Piece == BISHOP && pos.is_chess960()) + { + // An important Chess960 pattern: A cornered bishop blocked by + // a friendly pawn diagonally in front of it is a very serious + // problem, especially when that pawn is also blocked. + if (s == relative_square(Us, SQ_A1) || s == relative_square(Us, SQ_H1)) + { + Square d = pawn_push(Us) + (square_file(s) == FILE_A ? DELTA_E : DELTA_W); + if (pos.piece_on(s + d) == make_piece(Us, PAWN)) + { + if (!pos.square_is_empty(s + d + pawn_push(Us))) + score -= 2*TrappedBishopA1H1Penalty; + else if (pos.piece_on(s + 2*d) == make_piece(Us, PAWN)) + score -= TrappedBishopA1H1Penalty; + else + score -= TrappedBishopA1H1Penalty / 2; + } + } + } + + // Special extra evaluation for rooks + if (Piece == ROOK) + { + // Open and half-open files + f = square_file(s); + if (ei.pi->file_is_half_open(Us, f)) + { + if (ei.pi->file_is_half_open(Them, f)) + score += RookOpenFileBonus; + else + score += RookHalfOpenFileBonus; + } + + // Penalize rooks which are trapped inside a king. Penalize more if + // king has lost right to castle. + if (mob > 6 || ei.pi->file_is_half_open(Us, f)) + continue; + + ksq = pos.king_square(Us); + + if ( square_file(ksq) >= FILE_E + && square_file(s) > square_file(ksq) + && (relative_rank(Us, ksq) == RANK_1 || square_rank(ksq) == square_rank(s))) + { + // Is there a half-open file between the king and the edge of the board? + if (!ei.pi->has_open_file_to_right(Us, square_file(ksq))) + score -= make_score(pos.can_castle(Us) ? (TrappedRookPenalty - mob * 16) / 2 + : (TrappedRookPenalty - mob * 16), 0); + } + else if ( square_file(ksq) <= FILE_D + && square_file(s) < square_file(ksq) + && (relative_rank(Us, ksq) == RANK_1 || square_rank(ksq) == square_rank(s))) + { + // Is there a half-open file between the king and the edge of the board? + if (!ei.pi->has_open_file_to_left(Us, square_file(ksq))) + score -= make_score(pos.can_castle(Us) ? (TrappedRookPenalty - mob * 16) / 2 + : (TrappedRookPenalty - mob * 16), 0); + } + } + } + + if (Trace) + TracedScores[Us][Piece] = score; + + return score; + } + + + // evaluate_threats<>() assigns bonuses according to the type of attacking piece + // and the type of attacked one. + + template + Score evaluate_threats(const Position& pos, EvalInfo& ei) { + + const Color Them = (Us == WHITE ? BLACK : WHITE); + + Bitboard b; + Score score = SCORE_ZERO; + + // Enemy pieces not defended by a pawn and under our attack + Bitboard weakEnemies = pos.pieces_of_color(Them) + & ~ei.attackedBy[Them][PAWN] + & ei.attackedBy[Us][0]; + if (!weakEnemies) + return SCORE_ZERO; + + // Add bonus according to type of attacked enemy piece and to the + // type of attacking piece, from knights to queens. Kings are not + // considered because are already handled in king evaluation. + for (PieceType pt1 = KNIGHT; pt1 < KING; pt1++) + { + b = ei.attackedBy[Us][pt1] & weakEnemies; + if (b) + for (PieceType pt2 = PAWN; pt2 < KING; pt2++) + if (b & pos.pieces(pt2)) + score += ThreatBonus[pt1][pt2]; + } + return score; + } + + + // evaluate_pieces_of_color<>() assigns bonuses and penalties to all the + // pieces of a given color. + + template + Score evaluate_pieces_of_color(const Position& pos, EvalInfo& ei, Score& mobility) { + + const Color Them = (Us == WHITE ? BLACK : WHITE); + + Score score = mobility = SCORE_ZERO; + + // Do not include in mobility squares protected by enemy pawns or occupied by our pieces + const Bitboard mobilityArea = ~(ei.attackedBy[Them][PAWN] | pos.pieces_of_color(Us)); + + score += evaluate_pieces(pos, ei, mobility, mobilityArea); + score += evaluate_pieces(pos, ei, mobility, mobilityArea); + score += evaluate_pieces(pos, ei, mobility, mobilityArea); + score += evaluate_pieces(pos, ei, mobility, mobilityArea); + + // Sum up all attacked squares + ei.attackedBy[Us][0] = ei.attackedBy[Us][PAWN] | ei.attackedBy[Us][KNIGHT] + | ei.attackedBy[Us][BISHOP] | ei.attackedBy[Us][ROOK] + | ei.attackedBy[Us][QUEEN] | ei.attackedBy[Us][KING]; + return score; + } + + + // evaluate_king<>() assigns bonuses and penalties to a king of a given color + + template + Score evaluate_king(const Position& pos, EvalInfo& ei, Value margins[]) { + + const BitCountType Max15 = HasPopCnt ? CNT_POPCNT : CpuIs64Bit ? CNT64_MAX15 : CNT32_MAX15; + const Color Them = (Us == WHITE ? BLACK : WHITE); + + Bitboard undefended, b, b1, b2, safe; + int attackUnits; + const Square ksq = pos.king_square(Us); + + // King shelter + Score score = ei.pi->king_shelter(pos, ksq); + + // King safety. This is quite complicated, and is almost certainly far + // from optimally tuned. + if ( ei.kingAttackersCount[Them] >= 2 + && ei.kingAdjacentZoneAttacksCount[Them]) + { + // Find the attacked squares around the king which has no defenders + // apart from the king itself + undefended = ei.attackedBy[Them][0] & ei.attackedBy[Us][KING]; + undefended &= ~( ei.attackedBy[Us][PAWN] | ei.attackedBy[Us][KNIGHT] + | ei.attackedBy[Us][BISHOP] | ei.attackedBy[Us][ROOK] + | ei.attackedBy[Us][QUEEN]); + + // Initialize the 'attackUnits' variable, which is used later on as an + // index to the KingDangerTable[] array. The initial value is based on + // the number and types of the enemy's attacking pieces, the number of + // attacked and undefended squares around our king, the square of the + // king, and the quality of the pawn shelter. + attackUnits = Min(25, (ei.kingAttackersCount[Them] * ei.kingAttackersWeight[Them]) / 2) + + 3 * (ei.kingAdjacentZoneAttacksCount[Them] + count_1s(undefended)) + + InitKingDanger[relative_square(Us, ksq)] + - mg_value(ei.pi->king_shelter(pos, ksq)) / 32; + + // Analyse enemy's safe queen contact checks. First find undefended + // squares around the king attacked by enemy queen... + b = undefended & ei.attackedBy[Them][QUEEN] & ~pos.pieces_of_color(Them); + if (b) + { + // ...then remove squares not supported by another enemy piece + b &= ( ei.attackedBy[Them][PAWN] | ei.attackedBy[Them][KNIGHT] + | ei.attackedBy[Them][BISHOP] | ei.attackedBy[Them][ROOK]); + if (b) + attackUnits += QueenContactCheckBonus + * count_1s(b) + * (Them == pos.side_to_move() ? 2 : 1); + } + + // Analyse enemy's safe rook contact checks. First find undefended + // squares around the king attacked by enemy rooks... + b = undefended & ei.attackedBy[Them][ROOK] & ~pos.pieces_of_color(Them); + + // Consider only squares where the enemy rook gives check + b &= RookPseudoAttacks[ksq]; + + if (b) + { + // ...then remove squares not supported by another enemy piece + b &= ( ei.attackedBy[Them][PAWN] | ei.attackedBy[Them][KNIGHT] + | ei.attackedBy[Them][BISHOP] | ei.attackedBy[Them][QUEEN]); + if (b) + attackUnits += RookContactCheckBonus + * count_1s(b) + * (Them == pos.side_to_move() ? 2 : 1); + } + + // Analyse enemy's safe distance checks for sliders and knights + safe = ~(pos.pieces_of_color(Them) | ei.attackedBy[Us][0]); + + b1 = pos.attacks_from(ksq) & safe; + b2 = pos.attacks_from(ksq) & safe; + + // Enemy queen safe checks + b = (b1 | b2) & ei.attackedBy[Them][QUEEN]; + if (b) + attackUnits += QueenCheckBonus * count_1s(b); + + // Enemy rooks safe checks + b = b1 & ei.attackedBy[Them][ROOK]; + if (b) + attackUnits += RookCheckBonus * count_1s(b); + + // Enemy bishops safe checks + b = b2 & ei.attackedBy[Them][BISHOP]; + if (b) + attackUnits += BishopCheckBonus * count_1s(b); + + // Enemy knights safe checks + b = pos.attacks_from(ksq) & ei.attackedBy[Them][KNIGHT] & safe; + if (b) + attackUnits += KnightCheckBonus * count_1s(b); + + // To index KingDangerTable[] attackUnits must be in [0, 99] range + attackUnits = Min(99, Max(0, attackUnits)); + + // Finally, extract the king danger score from the KingDangerTable[] + // array and subtract the score from evaluation. Set also margins[] + // value that will be used for pruning because this value can sometimes + // be very big, and so capturing a single attacking piece can therefore + // result in a score change far bigger than the value of the captured piece. + score -= KingDangerTable[Us][attackUnits]; + margins[Us] += mg_value(KingDangerTable[Us][attackUnits]); + } + + if (Trace) + TracedScores[Us][KING] = score; + + return score; + } + + + // evaluate_passed_pawns<>() evaluates the passed pawns of the given color + + template + Score evaluate_passed_pawns(const Position& pos, EvalInfo& ei) { + + const Color Them = (Us == WHITE ? BLACK : WHITE); + + Bitboard b, squaresToQueen, defendedSquares, unsafeSquares, supportingPawns; + Score score = SCORE_ZERO; + + b = ei.pi->passed_pawns(Us); + + if (!b) + return SCORE_ZERO; + + do { + Square s = pop_1st_bit(&b); + + assert(pos.pawn_is_passed(Us, s)); + + int r = int(relative_rank(Us, s) - RANK_2); + int rr = r * (r - 1); + + // Base bonus based on rank + Value mbonus = Value(20 * rr); + Value ebonus = Value(10 * (rr + r + 1)); + + if (rr) + { + Square blockSq = s + pawn_push(Us); + + // Adjust bonus based on kings proximity + ebonus += Value(square_distance(pos.king_square(Them), blockSq) * 6 * rr); + ebonus -= Value(square_distance(pos.king_square(Us), blockSq) * 3 * rr); + + // If blockSq is not the queening square then consider also a second push + if (square_rank(blockSq) != (Us == WHITE ? RANK_8 : RANK_1)) + ebonus -= Value(square_distance(pos.king_square(Us), blockSq + pawn_push(Us)) * rr); + + // If the pawn is free to advance, increase bonus + if (pos.square_is_empty(blockSq)) + { + squaresToQueen = squares_in_front_of(Us, s); + defendedSquares = squaresToQueen & ei.attackedBy[Us][0]; + + // If there is an enemy rook or queen attacking the pawn from behind, + // add all X-ray attacks by the rook or queen. Otherwise consider only + // the squares in the pawn's path attacked or occupied by the enemy. + if ( (squares_in_front_of(Them, s) & pos.pieces(ROOK, QUEEN, Them)) + && (squares_in_front_of(Them, s) & pos.pieces(ROOK, QUEEN, Them) & pos.attacks_from(s))) + unsafeSquares = squaresToQueen; + else + unsafeSquares = squaresToQueen & (ei.attackedBy[Them][0] | pos.pieces_of_color(Them)); + + // If there aren't enemy attacks or pieces along the path to queen give + // huge bonus. Even bigger if we protect the pawn's path. + if (!unsafeSquares) + ebonus += Value(rr * (squaresToQueen == defendedSquares ? 17 : 15)); + else + // OK, there are enemy attacks or pieces (but not pawns). Are those + // squares which are attacked by the enemy also attacked by us ? + // If yes, big bonus (but smaller than when there are no enemy attacks), + // if no, somewhat smaller bonus. + ebonus += Value(rr * ((unsafeSquares & defendedSquares) == unsafeSquares ? 13 : 8)); + + // At last, add a small bonus when there are no *friendly* pieces + // in the pawn's path. + if (!(squaresToQueen & pos.pieces_of_color(Us))) + ebonus += Value(rr); + } + } // rr != 0 + + // Increase the bonus if the passed pawn is supported by a friendly pawn + // on the same rank and a bit smaller if it's on the previous rank. + supportingPawns = pos.pieces(PAWN, Us) & neighboring_files_bb(s); + if (supportingPawns & rank_bb(s)) + ebonus += Value(r * 20); + else if (supportingPawns & rank_bb(s - pawn_push(Us))) + ebonus += Value(r * 12); + + // Rook pawns are a special case: They are sometimes worse, and + // sometimes better than other passed pawns. It is difficult to find + // good rules for determining whether they are good or bad. For now, + // we try the following: Increase the value for rook pawns if the + // other side has no pieces apart from a knight, and decrease the + // value if the other side has a rook or queen. + if (square_file(s) == FILE_A || square_file(s) == FILE_H) + { + if (pos.non_pawn_material(Them) <= KnightValueMidgame) + ebonus += ebonus / 4; + else if (pos.pieces(ROOK, QUEEN, Them)) + ebonus -= ebonus / 4; + } + score += make_score(mbonus, ebonus); + + } while (b); + + // Add the scores to the middle game and endgame eval + return apply_weight(score, Weights[PassedPawns]); + } + + + // evaluate_unstoppable_pawns() evaluates the unstoppable passed pawns for both sides, this is quite + // conservative and returns a winning score only when we are very sure that the pawn is winning. + + template + Score evaluate_unstoppable_pawns(const Position& pos, EvalInfo& ei) { + + const BitCountType Max15 = HasPopCnt ? CNT_POPCNT : CpuIs64Bit ? CNT64_MAX15 : CNT32_MAX15; + + Bitboard b, b2, blockers, supporters, queeningPath, candidates; + Square s, blockSq, queeningSquare; + Color c, winnerSide, loserSide; + bool pathDefended, opposed; + int pliesToGo, movesToGo, oppMovesToGo, sacptg, blockersCount, minKingDist, kingptg, d; + int pliesToQueen[] = { 256, 256 }; + + // Step 1. Hunt for unstoppable passed pawns. If we find at least one, + // record how many plies are required for promotion. + for (c = WHITE; c <= BLACK; c++) + { + // Skip if other side has non-pawn pieces + if (pos.non_pawn_material(opposite_color(c))) + continue; + + b = ei.pi->passed_pawns(c); + + while (b) + { + s = pop_1st_bit(&b); + queeningSquare = relative_square(c, make_square(square_file(s), RANK_8)); + queeningPath = squares_in_front_of(c, s); + + // Compute plies to queening and check direct advancement + movesToGo = rank_distance(s, queeningSquare) - int(relative_rank(c, s) == RANK_2); + oppMovesToGo = square_distance(pos.king_square(opposite_color(c)), queeningSquare) - int(c != pos.side_to_move()); + pathDefended = ((ei.attackedBy[c][0] & queeningPath) == queeningPath); + + if (movesToGo >= oppMovesToGo && !pathDefended) + continue; + + // Opponent king cannot block because path is defended and position + // is not in check. So only friendly pieces can be blockers. + assert(!pos.in_check()); + assert((queeningPath & pos.occupied_squares()) == (queeningPath & pos.pieces_of_color(c))); + + // Add moves needed to free the path from friendly pieces and retest condition + movesToGo += count_1s(queeningPath & pos.pieces_of_color(c)); + + if (movesToGo >= oppMovesToGo && !pathDefended) + continue; + + pliesToGo = 2 * movesToGo - int(c == pos.side_to_move()); + pliesToQueen[c] = Min(pliesToQueen[c], pliesToGo); + } + } + + // Step 2. If either side cannot promote at least three plies before the other side then situation + // becomes too complex and we give up. Otherwise we determine the possibly "winning side" + if (abs(pliesToQueen[WHITE] - pliesToQueen[BLACK]) < 3) + return SCORE_ZERO; + + winnerSide = (pliesToQueen[WHITE] < pliesToQueen[BLACK] ? WHITE : BLACK); + loserSide = opposite_color(winnerSide); + + // Step 3. Can the losing side possibly create a new passed pawn and thus prevent the loss? + b = candidates = pos.pieces(PAWN, loserSide); + + while (b) + { + s = pop_1st_bit(&b); + + // Compute plies from queening + queeningSquare = relative_square(loserSide, make_square(square_file(s), RANK_8)); + movesToGo = rank_distance(s, queeningSquare) - int(relative_rank(loserSide, s) == RANK_2); + pliesToGo = 2 * movesToGo - int(loserSide == pos.side_to_move()); + + // Check if (without even considering any obstacles) we're too far away or doubled + if ( pliesToQueen[winnerSide] + 3 <= pliesToGo + || (squares_in_front_of(loserSide, s) & pos.pieces(PAWN, loserSide))) + clear_bit(&candidates, s); + } + + // If any candidate is already a passed pawn it _may_ promote in time. We give up. + if (candidates & ei.pi->passed_pawns(loserSide)) + return SCORE_ZERO; + + // Step 4. Check new passed pawn creation through king capturing and pawn sacrifices + b = candidates; + + while (b) + { + s = pop_1st_bit(&b); + sacptg = blockersCount = 0; + minKingDist = kingptg = 256; + + // Compute plies from queening + queeningSquare = relative_square(loserSide, make_square(square_file(s), RANK_8)); + movesToGo = rank_distance(s, queeningSquare) - int(relative_rank(loserSide, s) == RANK_2); + pliesToGo = 2 * movesToGo - int(loserSide == pos.side_to_move()); + + // Generate list of blocking pawns and supporters + supporters = neighboring_files_bb(s) & candidates; + opposed = squares_in_front_of(loserSide, s) & pos.pieces(PAWN, winnerSide); + blockers = passed_pawn_mask(loserSide, s) & pos.pieces(PAWN, winnerSide); + + assert(blockers); + + // How many plies does it take to remove all the blocking pawns? + while (blockers) + { + blockSq = pop_1st_bit(&blockers); + movesToGo = 256; + + // Check pawns that can give support to overcome obstacle, for instance + // black pawns: a4, b4 white: b2 then pawn in b4 is giving support. + if (!opposed) + { + b2 = supporters & in_front_bb(winnerSide, blockSq + pawn_push(winnerSide)); + + while (b2) // This while-loop could be replaced with LSB/MSB (depending on color) + { + d = square_distance(blockSq, pop_1st_bit(&b2)) - 2; + movesToGo = Min(movesToGo, d); + } + } + + // Check pawns that can be sacrificed against the blocking pawn + b2 = attack_span_mask(winnerSide, blockSq) & candidates & ~(1ULL << s); + + while (b2) // This while-loop could be replaced with LSB/MSB (depending on color) + { + d = square_distance(blockSq, pop_1st_bit(&b2)) - 2; + movesToGo = Min(movesToGo, d); + } + + // If obstacle can be destroyed with an immediate pawn exchange / sacrifice, + // it's not a real obstacle and we have nothing to add to pliesToGo. + if (movesToGo <= 0) + continue; + + // Plies needed to sacrifice against all the blocking pawns + sacptg += movesToGo * 2; + blockersCount++; + + // Plies needed for the king to capture all the blocking pawns + d = square_distance(pos.king_square(loserSide), blockSq); + minKingDist = Min(minKingDist, d); + kingptg = (minKingDist + blockersCount) * 2; + } + + // Check if pawn sacrifice plan _may_ save the day + if (pliesToQueen[winnerSide] + 3 > pliesToGo + sacptg) + return SCORE_ZERO; + + // Check if king capture plan _may_ save the day (contains some false positives) + if (pliesToQueen[winnerSide] + 3 > pliesToGo + kingptg) + return SCORE_ZERO; + } + + // Winning pawn is unstoppable and will promote as first, return big score + Score score = make_score(0, (Value) 0x500 - 0x20 * pliesToQueen[winnerSide]); + return winnerSide == WHITE ? score : -score; + } + + + // evaluate_space() computes the space evaluation for a given side. The + // space evaluation is a simple bonus based on the number of safe squares + // available for minor pieces on the central four files on ranks 2--4. Safe + // squares one, two or three squares behind a friendly pawn are counted + // twice. Finally, the space bonus is scaled by a weight taken from the + // material hash table. The aim is to improve play on game opening. + template + int evaluate_space(const Position& pos, EvalInfo& ei) { + + const BitCountType Max15 = HasPopCnt ? CNT_POPCNT : CpuIs64Bit ? CNT64_MAX15 : CNT32_MAX15; + const Color Them = (Us == WHITE ? BLACK : WHITE); + + // Find the safe squares for our pieces inside the area defined by + // SpaceMask[]. A square is unsafe if it is attacked by an enemy + // pawn, or if it is undefended and attacked by an enemy piece. + Bitboard safe = SpaceMask[Us] + & ~pos.pieces(PAWN, Us) + & ~ei.attackedBy[Them][PAWN] + & (ei.attackedBy[Us][0] | ~ei.attackedBy[Them][0]); + + // Find all squares which are at most three squares behind some friendly pawn + Bitboard behind = pos.pieces(PAWN, Us); + behind |= (Us == WHITE ? behind >> 8 : behind << 8); + behind |= (Us == WHITE ? behind >> 16 : behind << 16); + + return count_1s(safe) + count_1s(behind & safe); + } + + + // apply_weight() applies an evaluation weight to a value trying to prevent overflow + + inline Score apply_weight(Score v, Score w) { + return make_score((int(mg_value(v)) * mg_value(w)) / 0x100, + (int(eg_value(v)) * eg_value(w)) / 0x100); + } + + + // scale_by_game_phase() interpolates between a middle game and an endgame score, + // based on game phase. It also scales the return value by a ScaleFactor array. + + Value scale_by_game_phase(const Score& v, Phase ph, ScaleFactor sf) { + + assert(mg_value(v) > -VALUE_INFINITE && mg_value(v) < VALUE_INFINITE); + assert(eg_value(v) > -VALUE_INFINITE && eg_value(v) < VALUE_INFINITE); + assert(ph >= PHASE_ENDGAME && ph <= PHASE_MIDGAME); + + int ev = (eg_value(v) * int(sf)) / SCALE_FACTOR_NORMAL; + int result = (mg_value(v) * int(ph) + ev * int(128 - ph)) / 128; + return Value((result + GrainSize / 2) & ~(GrainSize - 1)); + } + + + // weight_option() computes the value of an evaluation weight, by combining + // two UCI-configurable weights (midgame and endgame) with an internal weight. + + Score weight_option(const std::string& mgOpt, const std::string& egOpt, Score internalWeight) { + + // Scale option value from 100 to 256 + int mg = Options[mgOpt].value() * 256 / 100; + int eg = Options[egOpt].value() * 256 / 100; + + return apply_weight(make_score(mg, eg), internalWeight); + } + + + // init_safety() initizes the king safety evaluation, based on UCI + // parameters. It is called from read_weights(). + + void init_safety() { + + const Value MaxSlope = Value(30); + const Value Peak = Value(1280); + Value t[100]; + + // First setup the base table + for (int i = 0; i < 100; i++) + { + t[i] = Value(int(0.4 * i * i)); + + if (i > 0) + t[i] = Min(t[i], t[i - 1] + MaxSlope); + + t[i] = Min(t[i], Peak); + } + + // Then apply the weights and get the final KingDangerTable[] array + for (Color c = WHITE; c <= BLACK; c++) + for (int i = 0; i < 100; i++) + KingDangerTable[c][i] = apply_weight(make_score(t[i], 0), Weights[KingDangerUs + c]); + } + + + // A couple of little helpers used by tracing code, to_cp() converts a value to + // a double in centipawns scale, trace_add() stores white and black scores. + + double to_cp(Value v) { return double(v) / double(PawnValueMidgame); } + + void trace_add(int idx, Score wScore, Score bScore) { + + TracedScores[WHITE][idx] = wScore; + TracedScores[BLACK][idx] = bScore; + } + + // trace_row() is an helper function used by tracing code to register the + // values of a single evaluation term. + + void trace_row(const char *name, int idx) { + + Score wScore = TracedScores[WHITE][idx]; + Score bScore = TracedScores[BLACK][idx]; + + switch (idx) { + case PST: case IMBALANCE: case PAWN: case UNSTOPPABLE: case TOTAL: + TraceStream << std::setw(20) << name << " | --- --- | --- --- | " + << std::setw(6) << to_cp(mg_value(wScore)) << " " + << std::setw(6) << to_cp(eg_value(wScore)) << " \n"; + break; + default: + TraceStream << std::setw(20) << name << " | " << std::noshowpos + << std::setw(5) << to_cp(mg_value(wScore)) << " " + << std::setw(5) << to_cp(eg_value(wScore)) << " | " + << std::setw(5) << to_cp(mg_value(bScore)) << " " + << std::setw(5) << to_cp(eg_value(bScore)) << " | " + << std::showpos + << std::setw(6) << to_cp(mg_value(wScore - bScore)) << " " + << std::setw(6) << to_cp(eg_value(wScore - bScore)) << " \n"; + } + } +} + + +/// trace_evaluate() is like evaluate() but instead of a value returns a string +/// suitable to be print on stdout with the detailed descriptions and values of +/// each evaluation term. Used mainly for debugging. + +std::string trace_evaluate(const Position& pos) { + + Value margin; + std::string totals; + + TraceStream.str(""); + TraceStream << std::showpoint << std::showpos << std::fixed << std::setprecision(2); + memset(TracedScores, 0, 2 * 16 * sizeof(Score)); + + do_evaluate(pos, margin); + + totals = TraceStream.str(); + TraceStream.str(""); + + TraceStream << std::setw(21) << "Eval term " << "| White | Black | Total \n" + << " | MG EG | MG EG | MG EG \n" + << "---------------------+-------------+-------------+---------------\n"; + + trace_row("Material, PST, Tempo", PST); + trace_row("Material imbalance", IMBALANCE); + trace_row("Pawns", PAWN); + trace_row("Knights", KNIGHT); + trace_row("Bishops", BISHOP); + trace_row("Rooks", ROOK); + trace_row("Queens", QUEEN); + trace_row("Mobility", MOBILITY); + trace_row("King safety", KING); + trace_row("Threats", THREAT); + trace_row("Passed pawns", PASSED); + trace_row("Unstoppable pawns", UNSTOPPABLE); + trace_row("Space", SPACE); + + TraceStream << "---------------------+-------------+-------------+---------------\n"; + trace_row("Total", TOTAL); + TraceStream << totals; + + return TraceStream.str(); +} diff --git a/DroidFish/jni/stockfish/evaluate.h b/DroidFish/jni/stockfish/evaluate.h new file mode 100644 index 0000000..4b90bad --- /dev/null +++ b/DroidFish/jni/stockfish/evaluate.h @@ -0,0 +1,31 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(EVALUATE_H_INCLUDED) +#define EVALUATE_H_INCLUDED + +#include "types.h" + +class Position; + +extern Value evaluate(const Position& pos, Value& margin); +extern std::string trace_evaluate(const Position& pos); +extern void read_evaluation_uci_options(Color sideToMove); + +#endif // !defined(EVALUATE_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/history.h b/DroidFish/jni/stockfish/history.h new file mode 100644 index 0000000..4d84be7 --- /dev/null +++ b/DroidFish/jni/stockfish/history.h @@ -0,0 +1,70 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(HISTORY_H_INCLUDED) +#define HISTORY_H_INCLUDED + +#include +#include "types.h" + +/// The History class stores statistics about how often different moves +/// have been successful or unsuccessful during the current search. These +/// statistics are used for reduction and move ordering decisions. History +/// entries are stored according only to moving piece and destination square, +/// in particular two moves with different origin but same destination and +/// same piece will be considered identical. + +class History { + +public: + void clear(); + Value value(Piece p, Square to) const; + void update(Piece p, Square to, Value bonus); + Value gain(Piece p, Square to) const; + void update_gain(Piece p, Square to, Value g); + + static const Value MaxValue = Value(2000); + +private: + Value history[16][64]; // [piece][to_square] + Value maxGains[16][64]; // [piece][to_square] +}; + +inline void History::clear() { + memset(history, 0, 16 * 64 * sizeof(Value)); + memset(maxGains, 0, 16 * 64 * sizeof(Value)); +} + +inline Value History::value(Piece p, Square to) const { + return history[p][to]; +} + +inline void History::update(Piece p, Square to, Value bonus) { + if (abs(history[p][to] + bonus) < MaxValue) history[p][to] += bonus; +} + +inline Value History::gain(Piece p, Square to) const { + return maxGains[p][to]; +} + +inline void History::update_gain(Piece p, Square to, Value g) { + maxGains[p][to] = Max(g, maxGains[p][to] - 1); +} + +#endif // !defined(HISTORY_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/lock.h b/DroidFish/jni/stockfish/lock.h new file mode 100644 index 0000000..939b7da --- /dev/null +++ b/DroidFish/jni/stockfish/lock.h @@ -0,0 +1,79 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(LOCK_H_INCLUDED) +#define LOCK_H_INCLUDED + +#if !defined(_MSC_VER) + +# include + +typedef pthread_mutex_t Lock; +typedef pthread_cond_t WaitCondition; + +# define lock_init(x) pthread_mutex_init(x, NULL) +# define lock_grab(x) pthread_mutex_lock(x) +# define lock_release(x) pthread_mutex_unlock(x) +# define lock_destroy(x) pthread_mutex_destroy(x) +# define cond_destroy(x) pthread_cond_destroy(x) +# define cond_init(x) pthread_cond_init(x, NULL) +# define cond_signal(x) pthread_cond_signal(x) +# define cond_wait(x,y) pthread_cond_wait(x,y) + +#else + +#define WIN32_LEAN_AND_MEAN +#include +#undef WIN32_LEAN_AND_MEAN + +// Default fast and race free locks and condition variables +#if !defined(OLD_LOCKS) + +typedef SRWLOCK Lock; +typedef CONDITION_VARIABLE WaitCondition; + +# define lock_init(x) InitializeSRWLock(x) +# define lock_grab(x) AcquireSRWLockExclusive(x) +# define lock_release(x) ReleaseSRWLockExclusive(x) +# define lock_destroy(x) (x) +# define cond_destroy(x) (x) +# define cond_init(x) InitializeConditionVariable(x) +# define cond_signal(x) WakeConditionVariable(x) +# define cond_wait(x,y) SleepConditionVariableSRW(x, y, INFINITE,0) + +// Fallback solution to build for Windows XP and older versions, note that +// cond_wait() is racy between lock_release() and WaitForSingleObject(). +#else + +typedef CRITICAL_SECTION Lock; +typedef HANDLE WaitCondition; + +# define lock_init(x) InitializeCriticalSection(x) +# define lock_grab(x) EnterCriticalSection(x) +# define lock_release(x) LeaveCriticalSection(x) +# define lock_destroy(x) DeleteCriticalSection(x) +# define cond_init(x) { *x = CreateEvent(0, FALSE, FALSE, 0); } +# define cond_destroy(x) CloseHandle(*x) +# define cond_signal(x) SetEvent(*x) +# define cond_wait(x,y) { lock_release(y); WaitForSingleObject(*x, INFINITE); lock_grab(y); } +#endif + +#endif + +#endif // !defined(LOCK_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/main.cpp b/DroidFish/jni/stockfish/main.cpp new file mode 100644 index 0000000..e6dfe93 --- /dev/null +++ b/DroidFish/jni/stockfish/main.cpp @@ -0,0 +1,87 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// To profile with callgrind uncomment following line +//#define USE_CALLGRIND + +#include +#include +#include + +#include "bitboard.h" +#include "evaluate.h" +#include "position.h" +#include "thread.h" +#include "search.h" +#include "ucioption.h" + +#ifdef USE_CALLGRIND +#include +#endif + +using namespace std; + +extern bool execute_uci_command(const string& cmd); +extern void benchmark(int argc, char* argv[]); +extern void init_kpk_bitbase(); + +int main(int argc, char* argv[]) { + + // Disable IO buffering for C and C++ standard libraries + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + cout.rdbuf()->pubsetbuf(NULL, 0); + cin.rdbuf()->pubsetbuf(NULL, 0); + + // Startup initializations + init_bitboards(); + Position::init_zobrist(); + Position::init_piece_square_tables(); + init_kpk_bitbase(); + init_search(); + Threads.init(); + +#ifdef USE_CALLGRIND + CALLGRIND_START_INSTRUMENTATION; +#endif + + if (argc < 2) + { + // Print copyright notice + cout << engine_name() << " by " << engine_authors() << endl; + + if (CpuHasPOPCNT) + cout << "Good! CPU has hardware POPCNT." << endl; + + // Wait for a command from the user, and passes this command to + // execute_uci_command() and also intercepts EOF from stdin to + // ensure that we exit gracefully if the GUI dies unexpectedly. + string cmd; + while (getline(cin, cmd) && execute_uci_command(cmd)) {} + } + else if (string(argv[1]) == "bench" && argc < 8) + benchmark(argc, argv); + else + cout << "Usage: stockfish bench [hash size = 128] [threads = 1] " + << "[limit = 12] [fen positions file = default] " + << "[limited by depth, time, nodes or perft = depth]" << endl; + + Threads.exit(); + return 0; +} diff --git a/DroidFish/jni/stockfish/material.cpp b/DroidFish/jni/stockfish/material.cpp new file mode 100644 index 0000000..19a334b --- /dev/null +++ b/DroidFish/jni/stockfish/material.cpp @@ -0,0 +1,285 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "material.h" + +using namespace std; + +namespace { + + // Values modified by Joona Kiiski + const Value MidgameLimit = Value(15581); + const Value EndgameLimit = Value(3998); + + // Scale factors used when one side has no more pawns + const int NoPawnsSF[4] = { 6, 12, 32 }; + + // Polynomial material balance parameters + const Value RedundantQueenPenalty = Value(320); + const Value RedundantRookPenalty = Value(554); + + const int LinearCoefficients[6] = { 1617, -162, -1172, -190, 105, 26 }; + + const int QuadraticCoefficientsSameColor[][8] = { + { 7, 7, 7, 7, 7, 7 }, { 39, 2, 7, 7, 7, 7 }, { 35, 271, -4, 7, 7, 7 }, + { 7, 25, 4, 7, 7, 7 }, { -27, -2, 46, 100, 56, 7 }, { 58, 29, 83, 148, -3, -25 } }; + + const int QuadraticCoefficientsOppositeColor[][8] = { + { 41, 41, 41, 41, 41, 41 }, { 37, 41, 41, 41, 41, 41 }, { 10, 62, 41, 41, 41, 41 }, + { 57, 64, 39, 41, 41, 41 }, { 50, 40, 23, -22, 41, 41 }, { 106, 101, 3, 151, 171, 41 } }; + + // Endgame evaluation and scaling functions accessed direcly and not through + // the function maps because correspond to more then one material hash key. + Endgame EvaluateKmmKm[] = { Endgame(WHITE), Endgame(BLACK) }; + Endgame EvaluateKXK[] = { Endgame(WHITE), Endgame(BLACK) }; + + Endgame ScaleKBPsK[] = { Endgame(WHITE), Endgame(BLACK) }; + Endgame ScaleKQKRPs[] = { Endgame(WHITE), Endgame(BLACK) }; + Endgame ScaleKPsK[] = { Endgame(WHITE), Endgame(BLACK) }; + Endgame ScaleKPKP[] = { Endgame(WHITE), Endgame(BLACK) }; + + // Helper templates used to detect a given material distribution + template bool is_KXK(const Position& pos) { + const Color Them = (Us == WHITE ? BLACK : WHITE); + return pos.non_pawn_material(Them) == VALUE_ZERO + && pos.piece_count(Them, PAWN) == 0 + && pos.non_pawn_material(Us) >= RookValueMidgame; + } + + template bool is_KBPsKs(const Position& pos) { + return pos.non_pawn_material(Us) == BishopValueMidgame + && pos.piece_count(Us, BISHOP) == 1 + && pos.piece_count(Us, PAWN) >= 1; + } + + template bool is_KQKRPs(const Position& pos) { + const Color Them = (Us == WHITE ? BLACK : WHITE); + return pos.piece_count(Us, PAWN) == 0 + && pos.non_pawn_material(Us) == QueenValueMidgame + && pos.piece_count(Us, QUEEN) == 1 + && pos.piece_count(Them, ROOK) == 1 + && pos.piece_count(Them, PAWN) >= 1; + } + +} // namespace + + +/// MaterialInfoTable c'tor and d'tor allocate and free the space for Endgames + +void MaterialInfoTable::init() { Base::init(); if (!funcs) funcs = new Endgames(); } +MaterialInfoTable::~MaterialInfoTable() { delete funcs; } + + +/// MaterialInfoTable::get_material_info() takes a position object as input, +/// computes or looks up a MaterialInfo object, and returns a pointer to it. +/// If the material configuration is not already present in the table, it +/// is stored there, so we don't have to recompute everything when the +/// same material configuration occurs again. + +MaterialInfo* MaterialInfoTable::get_material_info(const Position& pos) const { + + Key key = pos.get_material_key(); + MaterialInfo* mi = probe(key); + + // If mi->key matches the position's material hash key, it means that we + // have analysed this material configuration before, and we can simply + // return the information we found the last time instead of recomputing it. + if (mi->key == key) + return mi; + + // Initialize MaterialInfo entry + memset(mi, 0, sizeof(MaterialInfo)); + mi->key = key; + mi->factor[WHITE] = mi->factor[BLACK] = (uint8_t)SCALE_FACTOR_NORMAL; + + // Store game phase + mi->gamePhase = MaterialInfoTable::game_phase(pos); + + // Let's look if we have a specialized evaluation function for this + // particular material configuration. First we look for a fixed + // configuration one, then a generic one if previous search failed. + if ((mi->evaluationFunction = funcs->get >(key)) != NULL) + return mi; + + if (is_KXK(pos)) + { + mi->evaluationFunction = &EvaluateKXK[WHITE]; + return mi; + } + + if (is_KXK(pos)) + { + mi->evaluationFunction = &EvaluateKXK[BLACK]; + return mi; + } + + if (!pos.pieces(PAWN) && !pos.pieces(ROOK) && !pos.pieces(QUEEN)) + { + // Minor piece endgame with at least one minor piece per side and + // no pawns. Note that the case KmmK is already handled by KXK. + assert((pos.pieces(KNIGHT, WHITE) | pos.pieces(BISHOP, WHITE))); + assert((pos.pieces(KNIGHT, BLACK) | pos.pieces(BISHOP, BLACK))); + + if ( pos.piece_count(WHITE, BISHOP) + pos.piece_count(WHITE, KNIGHT) <= 2 + && pos.piece_count(BLACK, BISHOP) + pos.piece_count(BLACK, KNIGHT) <= 2) + { + mi->evaluationFunction = &EvaluateKmmKm[WHITE]; + return mi; + } + } + + // OK, we didn't find any special evaluation function for the current + // material configuration. Is there a suitable scaling function? + // + // We face problems when there are several conflicting applicable + // scaling functions and we need to decide which one to use. + EndgameBase* sf; + + if ((sf = funcs->get >(key)) != NULL) + { + mi->scalingFunction[sf->color()] = sf; + return mi; + } + + // Generic scaling functions that refer to more then one material + // distribution. Should be probed after the specialized ones. + // Note that these ones don't return after setting the function. + if (is_KBPsKs(pos)) + mi->scalingFunction[WHITE] = &ScaleKBPsK[WHITE]; + + if (is_KBPsKs(pos)) + mi->scalingFunction[BLACK] = &ScaleKBPsK[BLACK]; + + if (is_KQKRPs(pos)) + mi->scalingFunction[WHITE] = &ScaleKQKRPs[WHITE]; + + else if (is_KQKRPs(pos)) + mi->scalingFunction[BLACK] = &ScaleKQKRPs[BLACK]; + + Value npm_w = pos.non_pawn_material(WHITE); + Value npm_b = pos.non_pawn_material(BLACK); + + if (npm_w + npm_b == VALUE_ZERO) + { + if (pos.piece_count(BLACK, PAWN) == 0) + { + assert(pos.piece_count(WHITE, PAWN) >= 2); + mi->scalingFunction[WHITE] = &ScaleKPsK[WHITE]; + } + else if (pos.piece_count(WHITE, PAWN) == 0) + { + assert(pos.piece_count(BLACK, PAWN) >= 2); + mi->scalingFunction[BLACK] = &ScaleKPsK[BLACK]; + } + else if (pos.piece_count(WHITE, PAWN) == 1 && pos.piece_count(BLACK, PAWN) == 1) + { + // This is a special case because we set scaling functions + // for both colors instead of only one. + mi->scalingFunction[WHITE] = &ScaleKPKP[WHITE]; + mi->scalingFunction[BLACK] = &ScaleKPKP[BLACK]; + } + } + + // No pawns makes it difficult to win, even with a material advantage + if (pos.piece_count(WHITE, PAWN) == 0 && npm_w - npm_b <= BishopValueMidgame) + { + mi->factor[WHITE] = uint8_t + (npm_w == npm_b || npm_w < RookValueMidgame ? 0 : NoPawnsSF[Min(pos.piece_count(WHITE, BISHOP), 2)]); + } + + if (pos.piece_count(BLACK, PAWN) == 0 && npm_b - npm_w <= BishopValueMidgame) + { + mi->factor[BLACK] = uint8_t + (npm_w == npm_b || npm_b < RookValueMidgame ? 0 : NoPawnsSF[Min(pos.piece_count(BLACK, BISHOP), 2)]); + } + + // Compute the space weight + if (npm_w + npm_b >= 2 * QueenValueMidgame + 4 * RookValueMidgame + 2 * KnightValueMidgame) + { + int minorPieceCount = pos.piece_count(WHITE, KNIGHT) + pos.piece_count(WHITE, BISHOP) + + pos.piece_count(BLACK, KNIGHT) + pos.piece_count(BLACK, BISHOP); + + mi->spaceWeight = minorPieceCount * minorPieceCount; + } + + // Evaluate the material imbalance. We use PIECE_TYPE_NONE as a place holder + // for the bishop pair "extended piece", this allow us to be more flexible + // in defining bishop pair bonuses. + const int pieceCount[2][8] = { + { pos.piece_count(WHITE, BISHOP) > 1, pos.piece_count(WHITE, PAWN), pos.piece_count(WHITE, KNIGHT), + pos.piece_count(WHITE, BISHOP) , pos.piece_count(WHITE, ROOK), pos.piece_count(WHITE, QUEEN) }, + { pos.piece_count(BLACK, BISHOP) > 1, pos.piece_count(BLACK, PAWN), pos.piece_count(BLACK, KNIGHT), + pos.piece_count(BLACK, BISHOP) , pos.piece_count(BLACK, ROOK), pos.piece_count(BLACK, QUEEN) } }; + + mi->value = int16_t((imbalance(pieceCount) - imbalance(pieceCount)) / 16); + return mi; +} + + +/// MaterialInfoTable::imbalance() calculates imbalance comparing piece count of each +/// piece type for both colors. + +template +int MaterialInfoTable::imbalance(const int pieceCount[][8]) { + + const Color Them = (Us == WHITE ? BLACK : WHITE); + + int pt1, pt2, pc, v; + int value = 0; + + // Redundancy of major pieces, formula based on Kaufman's paper + // "The Evaluation of Material Imbalances in Chess" + if (pieceCount[Us][ROOK] > 0) + value -= RedundantRookPenalty * (pieceCount[Us][ROOK] - 1) + + RedundantQueenPenalty * pieceCount[Us][QUEEN]; + + // Second-degree polynomial material imbalance by Tord Romstad + for (pt1 = PIECE_TYPE_NONE; pt1 <= QUEEN; pt1++) + { + pc = pieceCount[Us][pt1]; + if (!pc) + continue; + + v = LinearCoefficients[pt1]; + + for (pt2 = PIECE_TYPE_NONE; pt2 <= pt1; pt2++) + v += QuadraticCoefficientsSameColor[pt1][pt2] * pieceCount[Us][pt2] + + QuadraticCoefficientsOppositeColor[pt1][pt2] * pieceCount[Them][pt2]; + + value += pc * v; + } + return value; +} + + +/// MaterialInfoTable::game_phase() calculates the phase given the current +/// position. Because the phase is strictly a function of the material, it +/// is stored in MaterialInfo. + +Phase MaterialInfoTable::game_phase(const Position& pos) { + + Value npm = pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK); + + return npm >= MidgameLimit ? PHASE_MIDGAME + : npm <= EndgameLimit ? PHASE_ENDGAME + : Phase(((npm - EndgameLimit) * 128) / (MidgameLimit - EndgameLimit)); +} diff --git a/DroidFish/jni/stockfish/material.h b/DroidFish/jni/stockfish/material.h new file mode 100644 index 0000000..c99320c --- /dev/null +++ b/DroidFish/jni/stockfish/material.h @@ -0,0 +1,117 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(MATERIAL_H_INCLUDED) +#define MATERIAL_H_INCLUDED + +#include "endgame.h" +#include "position.h" +#include "tt.h" +#include "types.h" + +const int MaterialTableSize = 8192; + +/// MaterialInfo is a class which contains various information about a +/// material configuration. It contains a material balance evaluation, +/// a function pointer to a special endgame evaluation function (which in +/// most cases is NULL, meaning that the standard evaluation function will +/// be used), and "scale factors" for black and white. +/// +/// The scale factors are used to scale the evaluation score up or down. +/// For instance, in KRB vs KR endgames, the score is scaled down by a factor +/// of 4, which will result in scores of absolute value less than one pawn. + +class MaterialInfo { + + friend class MaterialInfoTable; + +public: + Score material_value() const; + ScaleFactor scale_factor(const Position& pos, Color c) const; + int space_weight() const; + Phase game_phase() const; + bool specialized_eval_exists() const; + Value evaluate(const Position& pos) const; + +private: + Key key; + int16_t value; + uint8_t factor[2]; + EndgameBase* evaluationFunction; + EndgameBase* scalingFunction[2]; + int spaceWeight; + Phase gamePhase; +}; + + +/// The MaterialInfoTable class represents a pawn hash table. The most important +/// method is get_material_info, which returns a pointer to a MaterialInfo object. + +class MaterialInfoTable : public SimpleHash { +public: + ~MaterialInfoTable(); + void init(); + MaterialInfo* get_material_info(const Position& pos) const; + static Phase game_phase(const Position& pos); + +private: + template + static int imbalance(const int pieceCount[][8]); + + Endgames* funcs; +}; + + +/// MaterialInfo::scale_factor takes a position and a color as input, and +/// returns a scale factor for the given color. We have to provide the +/// position in addition to the color, because the scale factor need not +/// to be a constant: It can also be a function which should be applied to +/// the position. For instance, in KBP vs K endgames, a scaling function +/// which checks for draws with rook pawns and wrong-colored bishops. + +inline ScaleFactor MaterialInfo::scale_factor(const Position& pos, Color c) const { + + if (!scalingFunction[c]) + return ScaleFactor(factor[c]); + + ScaleFactor sf = scalingFunction[c]->apply(pos); + return sf == SCALE_FACTOR_NONE ? ScaleFactor(factor[c]) : sf; +} + +inline Value MaterialInfo::evaluate(const Position& pos) const { + return evaluationFunction->apply(pos); +} + +inline Score MaterialInfo::material_value() const { + return make_score(value, value); +} + +inline int MaterialInfo::space_weight() const { + return spaceWeight; +} + +inline Phase MaterialInfo::game_phase() const { + return gamePhase; +} + +inline bool MaterialInfo::specialized_eval_exists() const { + return evaluationFunction != NULL; +} + +#endif // !defined(MATERIAL_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/misc.cpp b/DroidFish/jni/stockfish/misc.cpp new file mode 100644 index 0000000..f168782 --- /dev/null +++ b/DroidFish/jni/stockfish/misc.cpp @@ -0,0 +1,270 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(_MSC_VER) + +# include +# include +# include +# if defined(__hpux) +# include +# endif + +#else + +#define _CRT_SECURE_NO_DEPRECATE +#include +#include + +#endif + +#if !defined(NO_PREFETCH) +# include +#endif + +#include +#include +#include +#include +#include + +#include "bitcount.h" +#include "misc.h" +#include "thread.h" + +using namespace std; + +/// Version number. If EngineVersion is left empty, then AppTag plus +/// current date (in the format YYMMDD) is used as a version number. + +static const string AppName = "Stockfish"; +static const string EngineVersion = "2.1"; +static const string AppTag = ""; + + +/// engine_name() returns the full name of the current Stockfish version. +/// This will be either "Stockfish YYMMDD" (where YYMMDD is the date when +/// the program was compiled) or "Stockfish ", depending +/// on whether the constant EngineVersion is empty. + +const string engine_name() { + + const string months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); + const string cpu64(CpuIs64Bit ? " 64bit" : ""); + + if (!EngineVersion.empty()) + return AppName + " " + EngineVersion + cpu64; + + stringstream s, date(__DATE__); // From compiler, format is "Sep 21 2008" + string month, day, year; + + date >> month >> day >> year; + + s << setfill('0') << AppName + " " + AppTag + " " + << year.substr(2, 2) << setw(2) + << (1 + months.find(month) / 4) << setw(2) + << day << cpu64; + + return s.str(); +} + + +/// Our brave developers! Required by UCI + +const string engine_authors() { + + return "Tord Romstad, Marco Costalba and Joona Kiiski"; +} + + +/// Debug stuff. Helper functions used mainly for debugging purposes + +static uint64_t dbg_hit_cnt0; +static uint64_t dbg_hit_cnt1; +static uint64_t dbg_mean_cnt0; +static uint64_t dbg_mean_cnt1; + +void dbg_print_hit_rate() { + + if (dbg_hit_cnt0) + cout << "Total " << dbg_hit_cnt0 << " Hit " << dbg_hit_cnt1 + << " hit rate (%) " << 100 * dbg_hit_cnt1 / dbg_hit_cnt0 << endl; +} + +void dbg_print_mean() { + + if (dbg_mean_cnt0) + cout << "Total " << dbg_mean_cnt0 << " Mean " + << (float)dbg_mean_cnt1 / dbg_mean_cnt0 << endl; +} + +void dbg_mean_of(int v) { + + dbg_mean_cnt0++; + dbg_mean_cnt1 += v; +} + +void dbg_hit_on(bool b) { + + dbg_hit_cnt0++; + if (b) + dbg_hit_cnt1++; +} + +void dbg_hit_on_c(bool c, bool b) { if (c) dbg_hit_on(b); } +void dbg_before() { dbg_hit_on(false); } +void dbg_after() { dbg_hit_on(true); dbg_hit_cnt0--; } + + +/// get_system_time() returns the current system time, measured in milliseconds + +int get_system_time() { + +#if defined(_MSC_VER) + struct _timeb t; + _ftime(&t); + return int(t.time * 1000 + t.millitm); +#else + struct timeval t; + gettimeofday(&t, NULL); + return t.tv_sec * 1000 + t.tv_usec / 1000; +#endif +} + + +/// cpu_count() tries to detect the number of CPU cores + +int cpu_count() { + +#if defined(_MSC_VER) + SYSTEM_INFO s; + GetSystemInfo(&s); + return Min(s.dwNumberOfProcessors, MAX_THREADS); +#else + +# if defined(_SC_NPROCESSORS_ONLN) + return Min(sysconf(_SC_NPROCESSORS_ONLN), MAX_THREADS); +# elif defined(__hpux) + struct pst_dynamic psd; + if (pstat_getdynamic(&psd, sizeof(psd), (size_t)1, 0) == -1) + return 1; + return Min(psd.psd_proc_cnt, MAX_THREADS); +# else + return 1; +# endif + +#endif +} + + +/// Check for console input. Original code from Beowulf, Olithink and Greko + +#ifndef _WIN32 + +int input_available() { + + fd_set readfds; + struct timeval timeout; + + FD_ZERO(&readfds); + FD_SET(fileno(stdin), &readfds); + timeout.tv_sec = 0; // Set to timeout immediately + timeout.tv_usec = 0; + select(16, &readfds, 0, 0, &timeout); + + return (FD_ISSET(fileno(stdin), &readfds)); +} + +#else + +int input_available() { + + static HANDLE inh = NULL; + static bool usePipe = false; + INPUT_RECORD rec[256]; + DWORD nchars, recCnt; + + if (!inh) + { + inh = GetStdHandle(STD_INPUT_HANDLE); + if (GetConsoleMode(inh, &nchars)) + { + SetConsoleMode(inh, nchars & ~(ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT)); + FlushConsoleInputBuffer(inh); + } else + usePipe = true; + } + + // When using Standard C input functions, also check if there + // is anything in the buffer. After a call to such functions, + // the input waiting in the pipe will be copied to the buffer, + // and the call to PeekNamedPipe can indicate no input available. + // Setting stdin to unbuffered was not enough. [from Greko] + if (stdin->_cnt > 0) + return 1; + + // When running under a GUI the input commands are sent to us + // directly over the internal pipe. If PeekNamedPipe() returns 0 + // then something went wrong. Probably the parent program exited. + // Returning 1 will make the next call to the input function + // return EOF, where this should be catched then. + if (usePipe) + return PeekNamedPipe(inh, NULL, 0, NULL, &nchars, NULL) ? nchars : 1; + + // Count the number of unread input records, including keyboard, + // mouse, and window-resizing input records. + GetNumberOfConsoleInputEvents(inh, &nchars); + + // Read data from console without removing it from the buffer + if (nchars <= 0 || !PeekConsoleInput(inh, rec, Min(nchars, 256), &recCnt)) + return 0; + + // Search for at least one keyboard event + for (DWORD i = 0; i < recCnt; i++) + if (rec[i].EventType == KEY_EVENT) + return 1; + + return 0; +} + +#endif + + +/// prefetch() preloads the given address in L1/L2 cache. This is a non +/// blocking function and do not stalls the CPU waiting for data to be +/// loaded from memory, that can be quite slow. +#if defined(NO_PREFETCH) + +void prefetch(char*) {} + +#else + +void prefetch(char* addr) { + +#if defined(__INTEL_COMPILER) || defined(__ICL) + // This hack prevents prefetches to be optimized away by + // Intel compiler. Both MSVC and gcc seems not affected. + __asm__ (""); +#endif + + _mm_prefetch(addr, _MM_HINT_T2); + _mm_prefetch(addr+64, _MM_HINT_T2); // 64 bytes ahead +} + +#endif diff --git a/DroidFish/jni/stockfish/misc.h b/DroidFish/jni/stockfish/misc.h new file mode 100644 index 0000000..f3f9576 --- /dev/null +++ b/DroidFish/jni/stockfish/misc.h @@ -0,0 +1,41 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(MISC_H_INCLUDED) +#define MISC_H_INCLUDED + +#include +#include "types.h" + +extern const std::string engine_name(); +extern const std::string engine_authors(); +extern int get_system_time(); +extern int cpu_count(); +extern int input_available(); +extern void prefetch(char* addr); + +extern void dbg_hit_on(bool b); +extern void dbg_hit_on_c(bool c, bool b); +extern void dbg_before(); +extern void dbg_after(); +extern void dbg_mean_of(int v); +extern void dbg_print_hit_rate(); +extern void dbg_print_mean(); + +#endif // !defined(MISC_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/move.cpp b/DroidFish/jni/stockfish/move.cpp new file mode 100644 index 0000000..da9e977 --- /dev/null +++ b/DroidFish/jni/stockfish/move.cpp @@ -0,0 +1,258 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include + +#include "move.h" +#include "movegen.h" +#include "search.h" + +using std::string; + +namespace { + const string time_string(int milliseconds); + const string score_string(Value v); +} + + +/// move_to_uci() converts a move to a string in coordinate notation +/// (g1f3, a7a8q, etc.). The only special case is castling moves, where we +/// print in the e1g1 notation in normal chess mode, and in e1h1 notation in +/// Chess960 mode. + +const string move_to_uci(Move m, bool chess960) { + + Square from = move_from(m); + Square to = move_to(m); + string promotion; + + if (m == MOVE_NONE) + return "(none)"; + + if (m == MOVE_NULL) + return "0000"; + + if (move_is_short_castle(m) && !chess960) + return from == SQ_E1 ? "e1g1" : "e8g8"; + + if (move_is_long_castle(m) && !chess960) + return from == SQ_E1 ? "e1c1" : "e8c8"; + + if (move_is_promotion(m)) + promotion = char(tolower(piece_type_to_char(move_promotion_piece(m)))); + + return square_to_string(from) + square_to_string(to) + promotion; +} + + +/// move_from_uci() takes a position and a string representing a move in +/// simple coordinate notation and returns an equivalent Move if any. +/// Moves are guaranteed to be legal. + +Move move_from_uci(const Position& pos, const string& str) { + + MoveStack mlist[MAX_MOVES]; + MoveStack* last = generate(pos, mlist); + + for (MoveStack* cur = mlist; cur != last; cur++) + if (str == move_to_uci(cur->move, pos.is_chess960())) + return cur->move; + + return MOVE_NONE; +} + + +/// move_to_san() takes a position and a move as input, where it is assumed +/// that the move is a legal move from the position. The return value is +/// a string containing the move in short algebraic notation. + +const string move_to_san(Position& pos, Move m) { + + assert(pos.is_ok()); + assert(move_is_ok(m)); + + MoveStack mlist[MAX_MOVES]; + Square from = move_from(m); + Square to = move_to(m); + PieceType pt = pos.type_of_piece_on(from); + string san; + + if (m == MOVE_NONE) + return "(none)"; + + if (m == MOVE_NULL) + return "(null)"; + + if (move_is_long_castle(m)) + san = "O-O-O"; + else if (move_is_short_castle(m)) + san = "O-O"; + else + { + if (pt != PAWN) + { + san = piece_type_to_char(pt); + + // Collect all legal moves of piece type 'pt' with destination 'to' + MoveStack* last = generate(pos, mlist); + int f = 0, r = 0; + + for (MoveStack* cur = mlist; cur != last; cur++) + if ( move_to(cur->move) == to + && pos.type_of_piece_on(move_from(cur->move)) == pt) + { + if (square_file(move_from(cur->move)) == square_file(from)) + f++; + + if (square_rank(move_from(cur->move)) == square_rank(from)) + r++; + } + + assert(f > 0 && r > 0); + + // Disambiguation if we have more then one piece with destination 'to' + if (f == 1 && r > 1) + san += file_to_char(square_file(from)); + else if (f > 1 && r == 1) + san += rank_to_char(square_rank(from)); + else if (f > 1 && r > 1) + san += square_to_string(from); + } + + if (pos.move_is_capture(m)) + { + if (pt == PAWN) + san += file_to_char(square_file(from)); + + san += 'x'; + } + + san += square_to_string(to); + + if (move_is_promotion(m)) + { + san += '='; + san += piece_type_to_char(move_promotion_piece(m)); + } + } + + // The move gives check? We don't use pos.move_gives_check() here + // because we need to test for a mate after the move is done. + StateInfo st; + pos.do_move(m, st); + if (pos.in_check()) + san += pos.is_mate() ? "#" : "+"; + pos.undo_move(m); + + return san; +} + + +/// pretty_pv() creates a human-readable string from a position and a PV. +/// It is used to write search information to the log file (which is created +/// when the UCI parameter "Use Search Log" is "true"). + +const string pretty_pv(Position& pos, int depth, Value score, int time, Move pv[]) { + + const int64_t K = 1000; + const int64_t M = 1000000; + const int startColumn = 28; + const size_t maxLength = 80 - startColumn; + const string lf = string("\n") + string(startColumn, ' '); + + StateInfo state[PLY_MAX_PLUS_2], *st = state; + Move* m = pv; + string san; + std::stringstream s; + size_t length = 0; + + // First print depth, score, time and searched nodes... + s << std::setw(2) << depth + << std::setw(8) << score_string(score) + << std::setw(8) << time_string(time); + + if (pos.nodes_searched() < M) + s << std::setw(8) << pos.nodes_searched() / 1 << " "; + else if (pos.nodes_searched() < K * M) + s << std::setw(7) << pos.nodes_searched() / K << "K "; + else + s << std::setw(7) << pos.nodes_searched() / M << "M "; + + // ...then print the full PV line in short algebraic notation + while (*m != MOVE_NONE) + { + san = move_to_san(pos, *m); + length += san.length() + 1; + + if (length > maxLength) + { + length = san.length() + 1; + s << lf; + } + s << san << ' '; + + pos.do_move(*m++, *st++); + } + + // Restore original position before to leave + while (m != pv) pos.undo_move(*--m); + + return s.str(); +} + + +namespace { + + const string time_string(int millisecs) { + + const int MSecMinute = 1000 * 60; + const int MSecHour = 1000 * 60 * 60; + + int hours = millisecs / MSecHour; + int minutes = (millisecs % MSecHour) / MSecMinute; + int seconds = ((millisecs % MSecHour) % MSecMinute) / 1000; + + std::stringstream s; + + if (hours) + s << hours << ':'; + + s << std::setfill('0') << std::setw(2) << minutes << ':' << std::setw(2) << seconds; + return s.str(); + } + + + const string score_string(Value v) { + + std::stringstream s; + + if (v >= VALUE_MATE - 200) + s << "#" << (VALUE_MATE - v + 1) / 2; + else if (v <= -VALUE_MATE + 200) + s << "-#" << (VALUE_MATE + v) / 2; + else + s << std::setprecision(2) << std::fixed << std::showpos << float(v) / PawnValueMidgame; + + return s.str(); + } +} diff --git a/DroidFish/jni/stockfish/move.h b/DroidFish/jni/stockfish/move.h new file mode 100644 index 0000000..299379e --- /dev/null +++ b/DroidFish/jni/stockfish/move.h @@ -0,0 +1,195 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(MOVE_H_INCLUDED) +#define MOVE_H_INCLUDED + +#include + +#include "misc.h" +#include "types.h" + +// Maximum number of allowed moves per position +const int MAX_MOVES = 256; + +/// 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), castle (3) +/// +/// Special cases are MOVE_NONE and MOVE_NULL. We can sneak these in +/// because in any normal move destination square is always different +/// from origin square while MOVE_NONE and MOVE_NULL have the same +/// origin and destination square, 0 and 1 respectively. + +enum Move { + MOVE_NONE = 0, + MOVE_NULL = 65 +}; + + +struct MoveStack { + Move move; + int score; +}; + +inline bool operator<(const MoveStack& f, const MoveStack& s) { return f.score < s.score; } + +// An helper insertion sort implementation, works with pointers and iterators +template +inline void insertion_sort(K firstMove, K lastMove) +{ + T value; + K cur, p, d; + + if (firstMove != lastMove) + for (cur = firstMove + 1; cur != lastMove; cur++) + { + p = d = cur; + value = *p--; + if (*p < value) + { + do *d = *p; + while (--d != firstMove && *--p < value); + *d = value; + } + } +} + +// Our dedicated sort in range [firstMove, lastMove), first splits +// positive scores from ramining then order seaprately the two sets. +template +inline void sort_moves(T* firstMove, T* lastMove, T** lastPositive) +{ + T tmp; + T *p, *d; + + d = lastMove; + p = firstMove - 1; + + d->score = -1; // right guard + + // Split positives vs non-positives + do { + while ((++p)->score > 0) {} + + if (p != d) + { + while (--d != p && d->score <= 0) {} + + tmp = *p; + *p = *d; + *d = tmp; + } + + } while (p != d); + + // Sort just positive scored moves, remaining only when we get there + insertion_sort(firstMove, p); + *lastPositive = p; +} + +// Picks up the best move in range [curMove, lastMove), one per cycle. +// It is faster then sorting all the moves in advance when moves are few, +// as normally are the possible captures. Note that is not a stable alghoritm. +template +inline T pick_best(T* curMove, T* lastMove) +{ + T bestMove, tmp; + + bestMove = *curMove; + while (++curMove != lastMove) + { + if (bestMove < *curMove) + { + tmp = *curMove; + *curMove = bestMove; + bestMove = tmp; + } + } + return bestMove; +} + + +inline Square move_from(Move m) { + return Square((int(m) >> 6) & 0x3F); +} + +inline Square move_to(Move m) { + return Square(m & 0x3F); +} + +inline bool move_is_special(Move m) { + return m & (3 << 14); +} + +inline bool move_is_promotion(Move m) { + return (m & (3 << 14)) == (1 << 14); +} + +inline int move_is_ep(Move m) { + return (m & (3 << 14)) == (2 << 14); +} + +inline int move_is_castle(Move m) { + return (m & (3 << 14)) == (3 << 14); +} + +inline bool move_is_short_castle(Move m) { + return move_is_castle(m) && (move_to(m) > move_from(m)); +} + +inline bool move_is_long_castle(Move m) { + return move_is_castle(m) && (move_to(m) < move_from(m)); +} + +inline PieceType move_promotion_piece(Move m) { + return move_is_promotion(m) ? PieceType(((int(m) >> 12) & 3) + 2) : PIECE_TYPE_NONE; +} + +inline Move make_move(Square from, Square to) { + return Move(int(to) | (int(from) << 6)); +} + +inline Move make_promotion_move(Square from, Square to, PieceType promotion) { + return Move(int(to) | (int(from) << 6) | ((int(promotion) - 2) << 12) | (1 << 14)); +} + +inline Move make_ep_move(Square from, Square to) { + return Move(int(to) | (int(from) << 6) | (2 << 14)); +} + +inline Move make_castle_move(Square from, Square to) { + return Move(int(to) | (int(from) << 6) | (3 << 14)); +} + +inline bool move_is_ok(Move m) { + return move_from(m) != move_to(m); // Catches also MOVE_NONE +} + +class Position; + +extern const std::string move_to_uci(Move m, bool chess960); +extern Move move_from_uci(const Position& pos, const std::string& str); +extern const std::string move_to_san(Position& pos, Move m); +extern const std::string pretty_pv(Position& pos, int depth, Value score, int time, Move pv[]); + +#endif // !defined(MOVE_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/movegen.cpp b/DroidFish/jni/stockfish/movegen.cpp new file mode 100644 index 0000000..f38d422 --- /dev/null +++ b/DroidFish/jni/stockfish/movegen.cpp @@ -0,0 +1,542 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bitcount.h" +#include "movegen.h" + +// Simple macro to wrap a very common while loop, no facny, no flexibility, +// hardcoded list name 'mlist' and from square 'from'. +#define SERIALIZE_MOVES(b) while (b) (*mlist++).move = make_move(from, pop_1st_bit(&b)) + +// Version used for pawns, where the 'from' square is given as a delta from the 'to' square +#define SERIALIZE_MOVES_D(b, d) while (b) { to = pop_1st_bit(&b); (*mlist++).move = make_move(to + (d), to); } + +namespace { + + enum CastlingSide { + KING_SIDE, + QUEEN_SIDE + }; + + template + MoveStack* generate_castle_moves(const Position&, MoveStack*, Color us); + + template + MoveStack* generate_pawn_moves(const Position&, MoveStack*, Bitboard, Square); + + template + inline MoveStack* generate_discovered_checks(const Position& pos, MoveStack* mlist, Square from) { + + assert(Pt != QUEEN); + + Bitboard b = pos.attacks_from(from) & pos.empty_squares(); + if (Pt == KING) + { + Square ksq = pos.king_square(opposite_color(pos.side_to_move())); + b &= ~QueenPseudoAttacks[ksq]; + } + SERIALIZE_MOVES(b); + return mlist; + } + + template + inline MoveStack* generate_direct_checks(const Position& pos, MoveStack* mlist, Color us, + Bitboard dc, Square ksq) { + assert(Pt != KING); + + Bitboard checkSqs, b; + Square from; + const Square* ptr = pos.piece_list_begin(us, Pt); + + if ((from = *ptr++) == SQ_NONE) + return mlist; + + checkSqs = pos.attacks_from(ksq) & pos.empty_squares(); + + do + { + if ( (Pt == QUEEN && !(QueenPseudoAttacks[from] & checkSqs)) + || (Pt == ROOK && !(RookPseudoAttacks[from] & checkSqs)) + || (Pt == BISHOP && !(BishopPseudoAttacks[from] & checkSqs))) + continue; + + if (dc && bit_is_set(dc, from)) + continue; + + b = pos.attacks_from(from) & checkSqs; + SERIALIZE_MOVES(b); + + } while ((from = *ptr++) != SQ_NONE); + + return mlist; + } + + template<> + FORCE_INLINE MoveStack* generate_direct_checks(const Position& p, MoveStack* m, Color us, Bitboard dc, Square ksq) { + + return (us == WHITE ? generate_pawn_moves(p, m, dc, ksq) + : generate_pawn_moves(p, m, dc, ksq)); + } + + template + FORCE_INLINE MoveStack* generate_piece_moves(const Position& p, MoveStack* m, Color us, Bitboard t) { + + assert(Pt == PAWN); + assert(Type == MV_CAPTURE || Type == MV_NON_CAPTURE || Type == MV_EVASION); + + return (us == WHITE ? generate_pawn_moves(p, m, t, SQ_NONE) + : generate_pawn_moves(p, m, t, SQ_NONE)); + } + + template + FORCE_INLINE MoveStack* generate_piece_moves(const Position& pos, MoveStack* mlist, Color us, Bitboard target) { + + Bitboard b; + Square from; + const Square* ptr = pos.piece_list_begin(us, Pt); + + if (*ptr != SQ_NONE) + { + do { + from = *ptr; + b = pos.attacks_from(from) & target; + SERIALIZE_MOVES(b); + } while (*++ptr != SQ_NONE); + } + return mlist; + } + + template<> + FORCE_INLINE MoveStack* generate_piece_moves(const Position& pos, MoveStack* mlist, Color us, Bitboard target) { + + Bitboard b; + Square from = pos.king_square(us); + + b = pos.attacks_from(from) & target; + SERIALIZE_MOVES(b); + return mlist; + } + +} + + +/// generate generates all pseudo-legal captures and queen +/// promotions. Returns a pointer to the end of the move list. +/// +/// generate generates all pseudo-legal non-captures and +/// underpromotions. Returns a pointer to the end of the move list. +/// +/// generate generates all pseudo-legal captures and +/// non-captures. Returns a pointer to the end of the move list. + +template +MoveStack* generate(const Position& pos, MoveStack* mlist) { + + assert(pos.is_ok()); + assert(!pos.in_check()); + + Color us = pos.side_to_move(); + Bitboard target; + + if (Type == MV_CAPTURE || Type == MV_NON_EVASION) + target = pos.pieces_of_color(opposite_color(us)); + else if (Type == MV_NON_CAPTURE) + target = pos.empty_squares(); + else + assert(false); + + if (Type == MV_NON_EVASION) + { + mlist = generate_piece_moves(pos, mlist, us, target); + mlist = generate_piece_moves(pos, mlist, us, pos.empty_squares()); + target |= pos.empty_squares(); + } + else + mlist = generate_piece_moves(pos, mlist, us, target); + + mlist = generate_piece_moves(pos, mlist, us, target); + mlist = generate_piece_moves(pos, mlist, us, target); + mlist = generate_piece_moves(pos, mlist, us, target); + mlist = generate_piece_moves(pos, mlist, us, target); + mlist = generate_piece_moves(pos, mlist, us, target); + + if (Type != MV_CAPTURE) + { + if (pos.can_castle_kingside(us)) + mlist = generate_castle_moves(pos, mlist, us); + + if (pos.can_castle_queenside(us)) + mlist = generate_castle_moves(pos, mlist, us); + } + + return mlist; +} + +// Explicit template instantiations +template MoveStack* generate(const Position& pos, MoveStack* mlist); +template MoveStack* generate(const Position& pos, MoveStack* mlist); +template MoveStack* generate(const Position& pos, MoveStack* mlist); + + +/// generate_non_capture_checks() generates all pseudo-legal non-captures and knight +/// underpromotions that give check. Returns a pointer to the end of the move list. +template<> +MoveStack* generate(const Position& pos, MoveStack* mlist) { + + assert(pos.is_ok()); + assert(!pos.in_check()); + + Bitboard b, dc; + Square from; + Color us = pos.side_to_move(); + Square ksq = pos.king_square(opposite_color(us)); + + assert(pos.piece_on(ksq) == make_piece(opposite_color(us), KING)); + + // Discovered non-capture checks + b = dc = pos.discovered_check_candidates(us); + + while (b) + { + from = pop_1st_bit(&b); + switch (pos.type_of_piece_on(from)) + { + case PAWN: /* Will be generated togheter with pawns direct checks */ break; + case KNIGHT: mlist = generate_discovered_checks(pos, mlist, from); break; + case BISHOP: mlist = generate_discovered_checks(pos, mlist, from); break; + case ROOK: mlist = generate_discovered_checks(pos, mlist, from); break; + case KING: mlist = generate_discovered_checks(pos, mlist, from); break; + default: assert(false); break; + } + } + + // Direct non-capture checks + mlist = generate_direct_checks(pos, mlist, us, dc, ksq); + mlist = generate_direct_checks(pos, mlist, us, dc, ksq); + mlist = generate_direct_checks(pos, mlist, us, dc, ksq); + mlist = generate_direct_checks(pos, mlist, us, dc, ksq); + return generate_direct_checks(pos, mlist, us, dc, ksq); +} + + +/// generate_evasions() generates all pseudo-legal check evasions when +/// the side to move is in check. Returns a pointer to the end of the move list. +template<> +MoveStack* generate(const Position& pos, MoveStack* mlist) { + + assert(pos.is_ok()); + assert(pos.in_check()); + + Bitboard b, target; + Square from, checksq; + int checkersCnt = 0; + Color us = pos.side_to_move(); + Square ksq = pos.king_square(us); + Bitboard checkers = pos.checkers(); + Bitboard sliderAttacks = EmptyBoardBB; + + assert(pos.piece_on(ksq) == make_piece(us, KING)); + assert(checkers); + + // Find squares attacked by slider checkers, we will remove + // them from the king evasions set so to early skip known + // illegal moves and avoid an useless legality check later. + b = checkers; + do + { + checkersCnt++; + checksq = pop_1st_bit(&b); + + assert(pos.color_of_piece_on(checksq) == opposite_color(us)); + + switch (pos.type_of_piece_on(checksq)) + { + case BISHOP: sliderAttacks |= BishopPseudoAttacks[checksq]; break; + case ROOK: sliderAttacks |= RookPseudoAttacks[checksq]; break; + case QUEEN: + // In case of a queen remove also squares attacked in the other direction to + // avoid possible illegal moves when queen and king are on adjacent squares. + if (RookPseudoAttacks[checksq] & (1ULL << ksq)) + sliderAttacks |= RookPseudoAttacks[checksq] | pos.attacks_from(checksq); + else + sliderAttacks |= BishopPseudoAttacks[checksq] | pos.attacks_from(checksq); + default: + break; + } + } while (b); + + // Generate evasions for king, capture and non capture moves + b = pos.attacks_from(ksq) & ~pos.pieces_of_color(us) & ~sliderAttacks; + from = ksq; + SERIALIZE_MOVES(b); + + // Generate evasions for other pieces only if not double check + if (checkersCnt > 1) + return mlist; + + // Find squares where a blocking evasion or a capture of the + // checker piece is possible. + target = squares_between(checksq, ksq) | checkers; + + mlist = generate_piece_moves(pos, mlist, us, target); + mlist = generate_piece_moves(pos, mlist, us, target); + mlist = generate_piece_moves(pos, mlist, us, target); + mlist = generate_piece_moves(pos, mlist, us, target); + return generate_piece_moves(pos, mlist, us, target); +} + + +/// generate computes a complete list of legal +/// or pseudo-legal moves in the current position. +template<> +MoveStack* generate(const Position& pos, MoveStack* mlist) { + + assert(pos.is_ok()); + + return pos.in_check() ? generate(pos, mlist) + : generate(pos, mlist); +} + +template<> +MoveStack* generate(const Position& pos, MoveStack* mlist) { + + assert(pos.is_ok()); + + MoveStack *last, *cur = mlist; + Bitboard pinned = pos.pinned_pieces(pos.side_to_move()); + + last = generate(pos, mlist); + + // Remove illegal moves from the list + while (cur != last) + if (!pos.pl_move_is_legal(cur->move, pinned)) + cur->move = (--last)->move; + else + cur++; + + return last; +} + + +namespace { + + template + inline Bitboard move_pawns(Bitboard p) { + + return Delta == DELTA_N ? p << 8 : Delta == DELTA_S ? p >> 8 : + Delta == DELTA_NE ? p << 9 : Delta == DELTA_SE ? p >> 7 : + Delta == DELTA_NW ? p << 7 : Delta == DELTA_SW ? p >> 9 : p; + } + + template + inline MoveStack* generate_pawn_captures(MoveStack* mlist, Bitboard pawns, Bitboard target) { + + const Bitboard TFileABB = (Delta == DELTA_NE || Delta == DELTA_SE ? FileABB : FileHBB); + + Bitboard b; + Square to; + + // Captures in the a1-h8 (a8-h1 for black) diagonal or in the h1-a8 (h8-a1 for black) + b = move_pawns(pawns) & target & ~TFileABB; + SERIALIZE_MOVES_D(b, -Delta); + return mlist; + } + + template + inline MoveStack* generate_promotions(const Position& pos, MoveStack* mlist, Bitboard pawnsOn7, Bitboard target) { + + const Bitboard TFileABB = (Delta == DELTA_NE || Delta == DELTA_SE ? FileABB : FileHBB); + + Bitboard b; + Square to; + + // Promotions and under-promotions, both captures and non-captures + b = move_pawns(pawnsOn7) & target; + + if (Delta != DELTA_N && Delta != DELTA_S) + b &= ~TFileABB; + + while (b) + { + to = pop_1st_bit(&b); + + if (Type == MV_CAPTURE || Type == MV_EVASION) + (*mlist++).move = make_promotion_move(to - Delta, to, QUEEN); + + if (Type == MV_NON_CAPTURE || Type == MV_EVASION) + { + (*mlist++).move = make_promotion_move(to - Delta, to, ROOK); + (*mlist++).move = make_promotion_move(to - Delta, to, BISHOP); + (*mlist++).move = make_promotion_move(to - Delta, to, KNIGHT); + } + + // This is the only possible under promotion that can give a check + // not already included in the queen-promotion. + if ( Type == MV_CHECK + && bit_is_set(pos.attacks_from(to), pos.king_square(opposite_color(Us)))) + (*mlist++).move = make_promotion_move(to - Delta, to, KNIGHT); + else (void)pos; // Silence a warning under MSVC + } + return mlist; + } + + template + MoveStack* generate_pawn_moves(const Position& pos, MoveStack* mlist, Bitboard target, Square ksq) { + + // Calculate our parametrized parameters at compile time, named + // according to the point of view of white side. + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); + const Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); + const Square TDELTA_N = (Us == WHITE ? DELTA_N : DELTA_S); + const Square TDELTA_NE = (Us == WHITE ? DELTA_NE : DELTA_SE); + const Square TDELTA_NW = (Us == WHITE ? DELTA_NW : DELTA_SW); + + Square to; + Bitboard b1, b2, dc1, dc2, pawnPushes, emptySquares; + Bitboard pawns = pos.pieces(PAWN, Us); + Bitboard pawnsOn7 = pawns & TRank7BB; + Bitboard enemyPieces = (Type == MV_CAPTURE ? target : pos.pieces_of_color(Them)); + + // Pre-calculate pawn pushes before changing emptySquares definition + if (Type != MV_CAPTURE) + { + emptySquares = (Type == MV_NON_CAPTURE ? target : pos.empty_squares()); + pawnPushes = move_pawns(pawns & ~TRank7BB) & emptySquares; + } + + if (Type == MV_EVASION) + { + emptySquares &= target; // Only blocking squares + enemyPieces &= target; // Capture only the checker piece + } + + // Promotions and underpromotions + if (pawnsOn7) + { + if (Type == MV_CAPTURE) + emptySquares = pos.empty_squares(); + + pawns &= ~TRank7BB; + mlist = generate_promotions(pos, mlist, pawnsOn7, enemyPieces); + mlist = generate_promotions(pos, mlist, pawnsOn7, enemyPieces); + mlist = generate_promotions(pos, mlist, pawnsOn7, emptySquares); + } + + // Standard captures + if (Type == MV_CAPTURE || Type == MV_EVASION) + { + mlist = generate_pawn_captures(mlist, pawns, enemyPieces); + mlist = generate_pawn_captures(mlist, pawns, enemyPieces); + } + + // Single and double pawn pushes + if (Type != MV_CAPTURE) + { + b1 = pawnPushes & emptySquares; + b2 = move_pawns(pawnPushes & TRank3BB) & emptySquares; + + if (Type == MV_CHECK) + { + // Consider only pawn moves which give direct checks + b1 &= pos.attacks_from(ksq, Them); + b2 &= pos.attacks_from(ksq, Them); + + // Add pawn moves which gives discovered check. This is possible only + // if the pawn is not on the same file as the enemy king, because we + // don't generate captures. + if (pawns & target) // For CHECK type target is dc bitboard + { + dc1 = move_pawns(pawns & target & ~file_bb(ksq)) & emptySquares; + dc2 = move_pawns(dc1 & TRank3BB) & emptySquares; + + b1 |= dc1; + b2 |= dc2; + } + } + SERIALIZE_MOVES_D(b1, -TDELTA_N); + SERIALIZE_MOVES_D(b2, -TDELTA_N -TDELTA_N); + } + + // En passant captures + if ((Type == MV_CAPTURE || Type == MV_EVASION) && pos.ep_square() != SQ_NONE) + { + assert(Us != WHITE || square_rank(pos.ep_square()) == RANK_6); + assert(Us != BLACK || square_rank(pos.ep_square()) == RANK_3); + + // An en passant capture can be an evasion only if the checking piece + // is the double pushed pawn and so is in the target. Otherwise this + // is a discovery check and we are forced to do otherwise. + if (Type == MV_EVASION && !bit_is_set(target, pos.ep_square() - TDELTA_N)) + return mlist; + + b1 = pawns & pos.attacks_from(pos.ep_square(), Them); + + assert(b1 != EmptyBoardBB); + + while (b1) + { + to = pop_1st_bit(&b1); + (*mlist++).move = make_ep_move(to, pos.ep_square()); + } + } + return mlist; + } + + template + MoveStack* generate_castle_moves(const Position& pos, MoveStack* mlist, Color us) { + + Color them = opposite_color(us); + Square ksq = pos.king_square(us); + + assert(pos.piece_on(ksq) == make_piece(us, KING)); + + Square rsq = (Side == KING_SIDE ? pos.initial_kr_square(us) : pos.initial_qr_square(us)); + Square s1 = relative_square(us, Side == KING_SIDE ? SQ_G1 : SQ_C1); + Square s2 = relative_square(us, Side == KING_SIDE ? SQ_F1 : SQ_D1); + Square s; + bool illegal = false; + + assert(pos.piece_on(rsq) == make_piece(us, ROOK)); + + // It is a bit complicated to correctly handle Chess960 + for (s = Min(ksq, s1); s <= Max(ksq, s1); s++) + if ( (s != ksq && s != rsq && pos.square_is_occupied(s)) + ||(pos.attackers_to(s) & pos.pieces_of_color(them))) + illegal = true; + + for (s = Min(rsq, s2); s <= Max(rsq, s2); s++) + if (s != ksq && s != rsq && pos.square_is_occupied(s)) + illegal = true; + + if ( Side == QUEEN_SIDE + && square_file(rsq) == FILE_B + && ( pos.piece_on(relative_square(us, SQ_A1)) == make_piece(them, ROOK) + || pos.piece_on(relative_square(us, SQ_A1)) == make_piece(them, QUEEN))) + illegal = true; + + if (!illegal) + (*mlist++).move = make_castle_move(ksq, rsq); + + return mlist; + } + +} // namespace diff --git a/DroidFish/jni/stockfish/movegen.h b/DroidFish/jni/stockfish/movegen.h new file mode 100644 index 0000000..6a04248 --- /dev/null +++ b/DroidFish/jni/stockfish/movegen.h @@ -0,0 +1,40 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(MOVEGEN_H_INCLUDED) +#define MOVEGEN_H_INCLUDED + +#include "move.h" +#include "position.h" + +enum MoveType { + MV_CAPTURE, + MV_NON_CAPTURE, + MV_CHECK, + MV_NON_CAPTURE_CHECK, + MV_EVASION, + MV_NON_EVASION, + MV_LEGAL, + MV_PSEUDO_LEGAL +}; + +template +MoveStack* generate(const Position& pos, MoveStack* mlist); + +#endif // !defined(MOVEGEN_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/movepick.cpp b/DroidFish/jni/stockfish/movepick.cpp new file mode 100644 index 0000000..d8c9fbb --- /dev/null +++ b/DroidFish/jni/stockfish/movepick.cpp @@ -0,0 +1,350 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "movegen.h" +#include "movepick.h" +#include "search.h" +#include "types.h" + +namespace { + + enum MovegenPhase { + PH_TT_MOVES, // Transposition table move and mate killer + PH_GOOD_CAPTURES, // Queen promotions and captures with SEE values >= 0 + PH_KILLERS, // Killer moves from the current ply + PH_NONCAPTURES, // Non-captures and underpromotions + PH_BAD_CAPTURES, // Queen promotions and captures with SEE values < 0 + PH_EVASIONS, // Check evasions + PH_QCAPTURES, // Captures in quiescence search + PH_QCHECKS, // Non-capture checks in quiescence search + PH_STOP + }; + + CACHE_LINE_ALIGNMENT + const uint8_t MainSearchTable[] = { PH_TT_MOVES, PH_GOOD_CAPTURES, PH_KILLERS, PH_NONCAPTURES, PH_BAD_CAPTURES, PH_STOP }; + const uint8_t EvasionTable[] = { PH_TT_MOVES, PH_EVASIONS, PH_STOP }; + const uint8_t QsearchWithChecksTable[] = { PH_TT_MOVES, PH_QCAPTURES, PH_QCHECKS, PH_STOP }; + const uint8_t QsearchWithoutChecksTable[] = { PH_TT_MOVES, PH_QCAPTURES, PH_STOP }; +} + + +/// Constructor for the MovePicker class. Apart from the position for which +/// it is asked to pick legal moves, MovePicker also wants some information +/// to help it to return the presumably good moves first, to decide which +/// moves to return (in the quiescence search, for instance, we only want to +/// search captures, promotions and some checks) and about how important good +/// move ordering is at the current node. + +MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const History& h, + SearchStack* ss, Value beta) : pos(p), H(h) { + int searchTT = ttm; + ttMoves[0].move = ttm; + badCaptureThreshold = 0; + badCaptures = moves + MAX_MOVES; + + assert(d > DEPTH_ZERO); + + pinned = p.pinned_pieces(pos.side_to_move()); + + if (p.in_check()) + { + ttMoves[1].move = killers[0].move = killers[1].move = MOVE_NONE; + phasePtr = EvasionTable; + } + else + { + ttMoves[1].move = (ss->mateKiller == ttm) ? MOVE_NONE : ss->mateKiller; + searchTT |= ttMoves[1].move; + killers[0].move = ss->killers[0]; + killers[1].move = ss->killers[1]; + + // Consider sligtly negative captures as good if at low + // depth and far from beta. + if (ss && ss->eval < beta - PawnValueMidgame && d < 3 * ONE_PLY) + badCaptureThreshold = -PawnValueMidgame; + + phasePtr = MainSearchTable; + } + + phasePtr += int(!searchTT) - 1; + go_next_phase(); +} + +MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const History& h) + : pos(p), H(h) { + int searchTT = ttm; + ttMoves[0].move = ttm; + ttMoves[1].move = MOVE_NONE; + + assert(d <= DEPTH_ZERO); + + pinned = p.pinned_pieces(pos.side_to_move()); + + if (p.in_check()) + phasePtr = EvasionTable; + else if (d >= DEPTH_QS_CHECKS) + phasePtr = QsearchWithChecksTable; + else + { + phasePtr = QsearchWithoutChecksTable; + + // Skip TT move if is not a capture or a promotion, this avoids + // qsearch tree explosion due to a possible perpetual check or + // similar rare cases when TT table is full. + if (ttm != MOVE_NONE && !pos.move_is_capture_or_promotion(ttm)) + searchTT = ttMoves[0].move = MOVE_NONE; + } + + phasePtr += int(!searchTT) - 1; + go_next_phase(); +} + + +/// MovePicker::go_next_phase() generates, scores and sorts the next bunch +/// of moves when there are no more moves to try for the current phase. + +void MovePicker::go_next_phase() { + + curMove = moves; + phase = *(++phasePtr); + switch (phase) { + + case PH_TT_MOVES: + curMove = ttMoves; + lastMove = curMove + 2; + return; + + case PH_GOOD_CAPTURES: + lastMove = generate(pos, moves); + score_captures(); + return; + + case PH_KILLERS: + curMove = killers; + lastMove = curMove + 2; + return; + + case PH_NONCAPTURES: + lastMove = generate(pos, moves); + score_noncaptures(); + sort_moves(moves, lastMove, &lastGoodNonCapture); + return; + + case PH_BAD_CAPTURES: + // Bad captures SEE value is already calculated so just pick + // them in order to get SEE move ordering. + curMove = badCaptures; + lastMove = moves + MAX_MOVES; + return; + + case PH_EVASIONS: + assert(pos.in_check()); + lastMove = generate(pos, moves); + score_evasions(); + return; + + case PH_QCAPTURES: + lastMove = generate(pos, moves); + score_captures(); + return; + + case PH_QCHECKS: + lastMove = generate(pos, moves); + return; + + case PH_STOP: + lastMove = curMove + 1; // Avoid another go_next_phase() call + return; + + default: + assert(false); + return; + } +} + + +/// MovePicker::score_captures(), MovePicker::score_noncaptures() and +/// MovePicker::score_evasions() assign a numerical move ordering score +/// to each move in a move list. The moves with highest scores will be +/// picked first by get_next_move(). + +void MovePicker::score_captures() { + // Winning and equal captures in the main search are ordered by MVV/LVA. + // Suprisingly, this appears to perform slightly better than SEE based + // move ordering. The reason is probably that in a position with a winning + // capture, capturing a more valuable (but sufficiently defended) piece + // first usually doesn't hurt. The opponent will have to recapture, and + // the hanging piece will still be hanging (except in the unusual cases + // where it is possible to recapture with the hanging piece). Exchanging + // big pieces before capturing a hanging piece probably helps to reduce + // the subtree size. + // In main search we want to push captures with negative SEE values to + // badCaptures[] array, but instead of doing it now we delay till when + // the move has been picked up in pick_move_from_list(), this way we save + // some SEE calls in case we get a cutoff (idea from Pablo Vazquez). + Move m; + + // Use MVV/LVA ordering + for (MoveStack* cur = moves; cur != lastMove; cur++) + { + m = cur->move; + if (move_is_promotion(m)) + cur->score = QueenValueMidgame; + else + cur->score = pos.midgame_value_of_piece_on(move_to(m)) + - pos.type_of_piece_on(move_from(m)); + } +} + +void MovePicker::score_noncaptures() { + + Move m; + Square from; + + for (MoveStack* cur = moves; cur != lastMove; cur++) + { + m = cur->move; + from = move_from(m); + cur->score = H.value(pos.piece_on(from), move_to(m)); + } +} + +void MovePicker::score_evasions() { + // Try good captures ordered by MVV/LVA, then non-captures if + // destination square is not under attack, ordered by history + // value, and at the end bad-captures and non-captures with a + // negative SEE. This last group is ordered by the SEE score. + Move m; + int seeScore; + + // Skip if we don't have at least two moves to order + if (lastMove < moves + 2) + return; + + for (MoveStack* cur = moves; cur != lastMove; cur++) + { + m = cur->move; + if ((seeScore = pos.see_sign(m)) < 0) + cur->score = seeScore - History::MaxValue; // Be sure we are at the bottom + else if (pos.move_is_capture(m)) + cur->score = pos.midgame_value_of_piece_on(move_to(m)) + - pos.type_of_piece_on(move_from(m)) + History::MaxValue; + else + cur->score = H.value(pos.piece_on(move_from(m)), move_to(m)); + } +} + +/// MovePicker::get_next_move() is the most important method of the MovePicker +/// class. It returns a new legal move every time it is called, until there +/// are no more moves left. It picks the move with the biggest score from a list +/// of generated moves taking care not to return the tt move if has already been +/// searched previously. Note that this function is not thread safe so should be +/// lock protected by caller when accessed through a shared MovePicker object. + +Move MovePicker::get_next_move() { + + Move move; + + while (true) + { + while (curMove == lastMove) + go_next_phase(); + + switch (phase) { + + case PH_TT_MOVES: + move = (curMove++)->move; + if ( move != MOVE_NONE + && pos.move_is_legal(move, pinned)) + return move; + break; + + case PH_GOOD_CAPTURES: + move = pick_best(curMove++, lastMove).move; + if ( move != ttMoves[0].move + && move != ttMoves[1].move + && pos.pl_move_is_legal(move, pinned)) + { + // Check for a non negative SEE now + int seeValue = pos.see_sign(move); + if (seeValue >= badCaptureThreshold) + return move; + + // Losing capture, move it to the tail of the array, note + // that move has now been already checked for legality. + (--badCaptures)->move = move; + badCaptures->score = seeValue; + } + break; + + case PH_KILLERS: + move = (curMove++)->move; + if ( move != MOVE_NONE + && pos.move_is_legal(move, pinned) + && move != ttMoves[0].move + && move != ttMoves[1].move + && !pos.move_is_capture(move)) + return move; + break; + + case PH_NONCAPTURES: + // Sort negative scored moves only when we get there + if (curMove == lastGoodNonCapture) + insertion_sort(lastGoodNonCapture, lastMove); + + move = (curMove++)->move; + if ( move != ttMoves[0].move + && move != ttMoves[1].move + && move != killers[0].move + && move != killers[1].move + && pos.pl_move_is_legal(move, pinned)) + return move; + break; + + case PH_BAD_CAPTURES: + move = pick_best(curMove++, lastMove).move; + return move; + + case PH_EVASIONS: + case PH_QCAPTURES: + move = pick_best(curMove++, lastMove).move; + if ( move != ttMoves[0].move + && pos.pl_move_is_legal(move, pinned)) + return move; + break; + + case PH_QCHECKS: + move = (curMove++)->move; + if ( move != ttMoves[0].move + && pos.pl_move_is_legal(move, pinned)) + return move; + break; + + case PH_STOP: + return MOVE_NONE; + + default: + assert(false); + break; + } + } +} diff --git a/DroidFish/jni/stockfish/movepick.h b/DroidFish/jni/stockfish/movepick.h new file mode 100644 index 0000000..765cae2 --- /dev/null +++ b/DroidFish/jni/stockfish/movepick.h @@ -0,0 +1,63 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined MOVEPICK_H_INCLUDED +#define MOVEPICK_H_INCLUDED + +#include "history.h" +#include "move.h" +#include "position.h" +#include "types.h" + +struct SearchStack; + +/// MovePicker is a class which is used to pick one legal move at a time from +/// the current position. It is initialized with a Position object and a few +/// moves we have reason to believe are good. The most important method is +/// MovePicker::get_next_move(), which returns a new legal move each time it +/// is called, until there are no legal moves left, when MOVE_NONE is returned. +/// In order to improve the efficiency of the alpha beta algorithm, MovePicker +/// attempts to return the moves which are most likely to get a cut-off first. + +class MovePicker { + + MovePicker& operator=(const MovePicker&); // Silence a warning under MSVC + +public: + MovePicker(const Position&, Move, Depth, const History&, SearchStack*, Value); + MovePicker(const Position&, Move, Depth, const History&); + Move get_next_move(); + +private: + void score_captures(); + void score_noncaptures(); + void score_evasions(); + void go_next_phase(); + + const Position& pos; + const History& H; + Bitboard pinned; + MoveStack ttMoves[2], killers[2]; + int badCaptureThreshold, phase; + const uint8_t* phasePtr; + MoveStack *curMove, *lastMove, *lastGoodNonCapture, *badCaptures; + MoveStack moves[MAX_MOVES]; +}; + +#endif // !defined(MOVEPICK_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/pawns.cpp b/DroidFish/jni/stockfish/pawns.cpp new file mode 100644 index 0000000..3ad88fb --- /dev/null +++ b/DroidFish/jni/stockfish/pawns.cpp @@ -0,0 +1,243 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bitboard.h" +#include "bitcount.h" +#include "pawns.h" +#include "position.h" + +namespace { + + #define S(mg, eg) make_score(mg, eg) + + // Doubled pawn penalty by opposed flag and file + const Score DoubledPawnPenalty[2][8] = { + { S(13, 43), S(20, 48), S(23, 48), S(23, 48), + S(23, 48), S(23, 48), S(20, 48), S(13, 43) }, + { S(13, 43), S(20, 48), S(23, 48), S(23, 48), + S(23, 48), S(23, 48), S(20, 48), S(13, 43) }}; + + // Isolated pawn penalty by opposed flag and file + const Score IsolatedPawnPenalty[2][8] = { + { S(37, 45), S(54, 52), S(60, 52), S(60, 52), + S(60, 52), S(60, 52), S(54, 52), S(37, 45) }, + { S(25, 30), S(36, 35), S(40, 35), S(40, 35), + S(40, 35), S(40, 35), S(36, 35), S(25, 30) }}; + + // Backward pawn penalty by opposed flag and file + const Score BackwardPawnPenalty[2][8] = { + { S(30, 42), S(43, 46), S(49, 46), S(49, 46), + S(49, 46), S(49, 46), S(43, 46), S(30, 42) }, + { S(20, 28), S(29, 31), S(33, 31), S(33, 31), + S(33, 31), S(33, 31), S(29, 31), S(20, 28) }}; + + // Pawn chain membership bonus by file + const Score ChainBonus[8] = { + S(11,-1), S(13,-1), S(13,-1), S(14,-1), + S(14,-1), S(13,-1), S(13,-1), S(11,-1) + }; + + // Candidate passed pawn bonus by rank + const Score CandidateBonus[8] = { + S( 0, 0), S( 6, 13), S(6,13), S(14,29), + S(34,68), S(83,166), S(0, 0), S( 0, 0) + }; + + const Score PawnStructureWeight = S(233, 201); + + #undef S + + inline Score apply_weight(Score v, Score w) { + return make_score((int(mg_value(v)) * mg_value(w)) / 0x100, + (int(eg_value(v)) * eg_value(w)) / 0x100); + } +} + + +/// PawnInfoTable::get_pawn_info() takes a position object as input, computes +/// a PawnInfo object, and returns a pointer to it. The result is also stored +/// in an hash table, so we don't have to recompute everything when the same +/// pawn structure occurs again. + +PawnInfo* PawnInfoTable::get_pawn_info(const Position& pos) const { + + assert(pos.is_ok()); + + Key key = pos.get_pawn_key(); + PawnInfo* pi = probe(key); + + // If pi->key matches the position's pawn hash key, it means that we + // have analysed this pawn structure before, and we can simply return + // the information we found the last time instead of recomputing it. + if (pi->key == key) + return pi; + + // Initialize PawnInfo entry + pi->key = key; + pi->passedPawns[WHITE] = pi->passedPawns[BLACK] = 0; + pi->kingSquares[WHITE] = pi->kingSquares[BLACK] = SQ_NONE; + pi->halfOpenFiles[WHITE] = pi->halfOpenFiles[BLACK] = 0xFF; + + // Calculate pawn attacks + Bitboard wPawns = pos.pieces(PAWN, WHITE); + Bitboard bPawns = pos.pieces(PAWN, BLACK); + pi->pawnAttacks[WHITE] = ((wPawns << 9) & ~FileABB) | ((wPawns << 7) & ~FileHBB); + pi->pawnAttacks[BLACK] = ((bPawns >> 7) & ~FileABB) | ((bPawns >> 9) & ~FileHBB); + + // Evaluate pawns for both colors and weight the result + pi->value = evaluate_pawns(pos, wPawns, bPawns, pi) + - evaluate_pawns(pos, bPawns, wPawns, pi); + + pi->value = apply_weight(pi->value, PawnStructureWeight); + + return pi; +} + + +/// PawnInfoTable::evaluate_pawns() evaluates each pawn of the given color + +template +Score PawnInfoTable::evaluate_pawns(const Position& pos, Bitboard ourPawns, + Bitboard theirPawns, PawnInfo* pi) { + + const BitCountType Max15 = CpuIs64Bit ? CNT64_MAX15 : CNT32_MAX15; + const Color Them = (Us == WHITE ? BLACK : WHITE); + + Bitboard b; + Square s; + File f; + Rank r; + bool passed, isolated, doubled, opposed, chain, backward, candidate; + Score value = SCORE_ZERO; + const Square* ptr = pos.piece_list_begin(Us, PAWN); + + // Loop through all pawns of the current color and score each pawn + while ((s = *ptr++) != SQ_NONE) + { + assert(pos.piece_on(s) == make_piece(Us, PAWN)); + + f = square_file(s); + r = square_rank(s); + + // This file cannot be half open + pi->halfOpenFiles[Us] &= ~(1 << f); + + // Our rank plus previous one. Used for chain detection + b = rank_bb(r) | rank_bb(Us == WHITE ? r - Rank(1) : r + Rank(1)); + + // Flag the pawn as passed, isolated, doubled or member of a pawn + // chain (but not the backward one). + passed = !(theirPawns & passed_pawn_mask(Us, s)); + doubled = ourPawns & squares_in_front_of(Us, s); + opposed = theirPawns & squares_in_front_of(Us, s); + isolated = !(ourPawns & neighboring_files_bb(f)); + chain = ourPawns & neighboring_files_bb(f) & b; + + // Test for backward pawn + backward = false; + + // If the pawn is passed, isolated, or member of a pawn chain it cannot + // be backward. If there are friendly pawns behind on neighboring files + // or if can capture an enemy pawn it cannot be backward either. + if ( !(passed | isolated | chain) + && !(ourPawns & attack_span_mask(Them, s)) + && !(pos.attacks_from(s, Us) & theirPawns)) + { + // We now know that there are no friendly pawns beside or behind this + // pawn on neighboring files. We now check whether the pawn is + // backward by looking in the forward direction on the neighboring + // files, and seeing whether we meet a friendly or an enemy pawn first. + b = pos.attacks_from(s, Us); + + // Note that we are sure to find something because pawn is not passed + // nor isolated, so loop is potentially infinite, but it isn't. + while (!(b & (ourPawns | theirPawns))) + Us == WHITE ? b <<= 8 : b >>= 8; + + // The friendly pawn needs to be at least two ranks closer than the + // enemy pawn in order to help the potentially backward pawn advance. + backward = (b | (Us == WHITE ? b << 8 : b >> 8)) & theirPawns; + } + + assert(opposed | passed | (attack_span_mask(Us, s) & theirPawns)); + + // A not passed pawn is a candidate to become passed if it is free to + // advance and if the number of friendly pawns beside or behind this + // pawn on neighboring files is higher or equal than the number of + // enemy pawns in the forward direction on the neighboring files. + candidate = !(opposed | passed | backward | isolated) + && (b = attack_span_mask(Them, s + pawn_push(Us)) & ourPawns) != EmptyBoardBB + && count_1s(b) >= count_1s(attack_span_mask(Us, s) & theirPawns); + + // Passed pawns will be properly scored in evaluation because we need + // full attack info to evaluate passed pawns. Only the frontmost passed + // pawn on each file is considered a true passed pawn. + if (passed && !doubled) + set_bit(&(pi->passedPawns[Us]), s); + + // Score this pawn + if (isolated) + value -= IsolatedPawnPenalty[opposed][f]; + + if (doubled) + value -= DoubledPawnPenalty[opposed][f]; + + if (backward) + value -= BackwardPawnPenalty[opposed][f]; + + if (chain) + value += ChainBonus[f]; + + if (candidate) + value += CandidateBonus[relative_rank(Us, s)]; + } + return value; +} + + +/// PawnInfo::updateShelter() calculates and caches king shelter. It is called +/// only when king square changes, about 20% of total king_shelter() calls. +template +Score PawnInfo::updateShelter(const Position& pos, Square ksq) { + + const int Shift = (Us == WHITE ? 8 : -8); + + Bitboard pawns; + int r, shelter = 0; + + if (relative_rank(Us, ksq) <= RANK_4) + { + pawns = pos.pieces(PAWN, Us) & this_and_neighboring_files_bb(ksq); + r = ksq & (7 << 3); + for (int i = 0; i < 3; i++) + { + r += Shift; + shelter += BitCount8Bit[(pawns >> r) & 0xFF] * (64 >> i); + } + } + kingSquares[Us] = ksq; + kingShelters[Us] = make_score(shelter, 0); + return kingShelters[Us]; +} + +// Explicit template instantiation +template Score PawnInfo::updateShelter(const Position& pos, Square ksq); +template Score PawnInfo::updateShelter(const Position& pos, Square ksq); diff --git a/DroidFish/jni/stockfish/pawns.h b/DroidFish/jni/stockfish/pawns.h new file mode 100644 index 0000000..fec0395 --- /dev/null +++ b/DroidFish/jni/stockfish/pawns.h @@ -0,0 +1,107 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(PAWNS_H_INCLUDED) +#define PAWNS_H_INCLUDED + +#include "position.h" +#include "tt.h" +#include "types.h" + +const int PawnTableSize = 16384; + +/// PawnInfo is a class which contains various information about a pawn +/// structure. Currently, it only includes a middle game and an end game +/// pawn structure evaluation, and a bitboard of passed pawns. We may want +/// to add further information in the future. A lookup to the pawn hash table +/// (performed by calling the get_pawn_info method in a PawnInfoTable object) +/// returns a pointer to a PawnInfo object. + +class PawnInfo { + + friend class PawnInfoTable; + +public: + Score pawns_value() const; + Bitboard pawn_attacks(Color c) const; + Bitboard passed_pawns(Color c) const; + int file_is_half_open(Color c, File f) const; + int has_open_file_to_left(Color c, File f) const; + int has_open_file_to_right(Color c, File f) const; + + template + Score king_shelter(const Position& pos, Square ksq); + +private: + template + Score updateShelter(const Position& pos, Square ksq); + + Key key; + Bitboard passedPawns[2]; + Bitboard pawnAttacks[2]; + Square kingSquares[2]; + Score value; + int halfOpenFiles[2]; + Score kingShelters[2]; +}; + + +/// The PawnInfoTable class represents a pawn hash table. The most important +/// method is get_pawn_info, which returns a pointer to a PawnInfo object. + +class PawnInfoTable : public SimpleHash { +public: + PawnInfo* get_pawn_info(const Position& pos) const; + +private: + template + static Score evaluate_pawns(const Position& pos, Bitboard ourPawns, Bitboard theirPawns, PawnInfo* pi); +}; + + +inline Score PawnInfo::pawns_value() const { + return value; +} + +inline Bitboard PawnInfo::pawn_attacks(Color c) const { + return pawnAttacks[c]; +} + +inline Bitboard PawnInfo::passed_pawns(Color c) const { + return passedPawns[c]; +} + +inline int PawnInfo::file_is_half_open(Color c, File f) const { + return halfOpenFiles[c] & (1 << int(f)); +} + +inline int PawnInfo::has_open_file_to_left(Color c, File f) const { + return halfOpenFiles[c] & ((1 << int(f)) - 1); +} + +inline int PawnInfo::has_open_file_to_right(Color c, File f) const { + return halfOpenFiles[c] & ~((1 << int(f+1)) - 1); +} + +template +inline Score PawnInfo::king_shelter(const Position& pos, Square ksq) { + return kingSquares[Us] == ksq ? kingShelters[Us] : updateShelter(pos, ksq); +} + +#endif // !defined(PAWNS_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/position.cpp b/DroidFish/jni/stockfish/position.cpp new file mode 100644 index 0000000..e71499c --- /dev/null +++ b/DroidFish/jni/stockfish/position.cpp @@ -0,0 +1,2084 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include +#include + +#include "bitcount.h" +#include "movegen.h" +#include "position.h" +#include "psqtab.h" +#include "rkiss.h" +#include "thread.h" +#include "tt.h" +#include "ucioption.h" + +using std::string; +using std::cout; +using std::endl; + +Key Position::zobrist[2][8][64]; +Key Position::zobEp[64]; +Key Position::zobCastle[16]; +Key Position::zobSideToMove; +Key Position::zobExclusion; + +Score Position::PieceSquareTable[16][64]; + +// Material values arrays, indexed by Piece +const Value Position::PieceValueMidgame[17] = { + VALUE_ZERO, + PawnValueMidgame, KnightValueMidgame, BishopValueMidgame, + RookValueMidgame, QueenValueMidgame, VALUE_ZERO, + VALUE_ZERO, VALUE_ZERO, + PawnValueMidgame, KnightValueMidgame, BishopValueMidgame, + RookValueMidgame, QueenValueMidgame +}; + +const Value Position::PieceValueEndgame[17] = { + VALUE_ZERO, + PawnValueEndgame, KnightValueEndgame, BishopValueEndgame, + RookValueEndgame, QueenValueEndgame, VALUE_ZERO, + VALUE_ZERO, VALUE_ZERO, + PawnValueEndgame, KnightValueEndgame, BishopValueEndgame, + RookValueEndgame, QueenValueEndgame +}; + +// Material values array used by SEE, indexed by PieceType +const Value Position::seeValues[] = { + VALUE_ZERO, + PawnValueMidgame, KnightValueMidgame, BishopValueMidgame, + RookValueMidgame, QueenValueMidgame, QueenValueMidgame*10 +}; + + +namespace { + + // Bonus for having the side to move (modified by Joona Kiiski) + const Score TempoValue = make_score(48, 22); + + struct PieceLetters : public std::map { + + PieceLetters() { + + operator[]('K') = WK; operator[]('k') = BK; + operator[]('Q') = WQ; operator[]('q') = BQ; + operator[]('R') = WR; operator[]('r') = BR; + operator[]('B') = WB; operator[]('b') = BB; + operator[]('N') = WN; operator[]('n') = BN; + operator[]('P') = WP; operator[]('p') = BP; + operator[](' ') = PIECE_NONE; + operator[]('.') = PIECE_NONE_DARK_SQ; + } + + char from_piece(Piece p) const { + + std::map::const_iterator it; + for (it = begin(); it != end(); ++it) + if (it->second == p) + return it->first; + + assert(false); + return 0; + } + }; + + PieceLetters pieceLetters; +} + + +/// CheckInfo c'tor + +CheckInfo::CheckInfo(const Position& pos) { + + Color us = pos.side_to_move(); + Color them = opposite_color(us); + + ksq = pos.king_square(them); + dcCandidates = pos.discovered_check_candidates(us); + + checkSq[PAWN] = pos.attacks_from(ksq, them); + checkSq[KNIGHT] = pos.attacks_from(ksq); + checkSq[BISHOP] = pos.attacks_from(ksq); + checkSq[ROOK] = pos.attacks_from(ksq); + checkSq[QUEEN] = checkSq[BISHOP] | checkSq[ROOK]; + checkSq[KING] = EmptyBoardBB; +} + + +/// Position c'tors. Here we always create a copy of the original position +/// or the FEN string, we want the new born Position object do not depend +/// on any external data so we detach state pointer from the source one. + +Position::Position(const Position& pos, int th) { + + memcpy(this, &pos, sizeof(Position)); + detach(); // Always detach() in copy c'tor to avoid surprises + threadID = th; + nodes = 0; +} + +Position::Position(const string& fen, bool isChess960, int th) { + + from_fen(fen, isChess960); + threadID = th; +} + + +/// Position::detach() copies the content of the current state and castling +/// masks inside the position itself. This is needed when the st pointee could +/// become stale, as example because the caller is about to going out of scope. + +void Position::detach() { + + startState = *st; + st = &startState; + st->previous = NULL; // as a safe guard +} + + +/// Position::from_fen() initializes the position object with the given FEN +/// string. This function is not very robust - make sure that input FENs are +/// correct (this is assumed to be the responsibility of the GUI). + +void Position::from_fen(const string& fen, bool isChess960) { +/* + A FEN string defines a particular position using only the ASCII character set. + + A FEN string contains six fields. The separator between fields is a space. The fields are: + + 1) Piece placement (from white's perspective). Each rank is described, starting with rank 8 and ending + with rank 1; within each rank, the contents of each square are described from file A through file H. + Following the Standard Algebraic Notation (SAN), each piece is identified by a single letter taken + from the standard English names. White pieces are designated using upper-case letters ("PNBRQK") + while Black take lowercase ("pnbrqk"). Blank squares are noted using digits 1 through 8 (the number + of blank squares), and "/" separate ranks. + + 2) Active color. "w" means white moves next, "b" means black. + + 3) Castling availability. If neither side can castle, this is "-". Otherwise, this has one or more + letters: "K" (White can castle kingside), "Q" (White can castle queenside), "k" (Black can castle + kingside), and/or "q" (Black can castle queenside). + + 4) En passant target square in algebraic notation. If there's no en passant target square, this is "-". + If a pawn has just made a 2-square move, this is the position "behind" the pawn. This is recorded + regardless of whether there is a pawn in position to make an en passant capture. + + 5) Halfmove clock: This is the number of halfmoves since the last pawn advance or capture. This is used + to determine if a draw can be claimed under the fifty-move rule. + + 6) Fullmove number: The number of the full move. It starts at 1, and is incremented after Black's move. +*/ + + char token; + int hmc, fmn; + std::istringstream ss(fen); + Square sq = SQ_A8; + + clear(); + + // 1. Piece placement field + while (ss.get(token) && token != ' ') + { + if (pieceLetters.find(token) != pieceLetters.end()) + { + put_piece(pieceLetters[token], sq); + sq++; + } + else if (isdigit(token)) + sq += Square(token - '0'); // Skip the given number of files + else if (token == '/') + sq -= SQ_A3; // Jump back of 2 rows + else + goto incorrect_fen; + } + + // 2. Active color + if (!ss.get(token) || (token != 'w' && token != 'b')) + goto incorrect_fen; + + sideToMove = (token == 'w' ? WHITE : BLACK); + + if (!ss.get(token) || token != ' ') + goto incorrect_fen; + + // 3. Castling availability + while (ss.get(token) && token != ' ') + if (!set_castling_rights(token)) + goto incorrect_fen; + + // 4. En passant square + char col, row; + if ( (ss.get(col) && (col >= 'a' && col <= 'h')) + && (ss.get(row) && (row == '3' || row == '6'))) + { + st->epSquare = make_square(file_from_char(col), rank_from_char(row)); + + // Ignore if no capture is possible + Color them = opposite_color(sideToMove); + if (!(attacks_from(st->epSquare, them) & pieces(PAWN, sideToMove))) + st->epSquare = SQ_NONE; + } + + // 5. Halfmove clock + if (ss >> hmc) + st->rule50 = hmc; + + // 6. Fullmove number + if (ss >> fmn) + startPosPlyCounter = (fmn - 1) * 2 + int(sideToMove == BLACK); + + // Various initialisations + castleRightsMask[make_square(initialKFile, RANK_1)] ^= WHITE_OO | WHITE_OOO; + castleRightsMask[make_square(initialKFile, RANK_8)] ^= BLACK_OO | BLACK_OOO; + castleRightsMask[make_square(initialKRFile, RANK_1)] ^= WHITE_OO; + castleRightsMask[make_square(initialKRFile, RANK_8)] ^= BLACK_OO; + castleRightsMask[make_square(initialQRFile, RANK_1)] ^= WHITE_OOO; + castleRightsMask[make_square(initialQRFile, RANK_8)] ^= BLACK_OOO; + + chess960 = isChess960; + find_checkers(); + + st->key = compute_key(); + st->pawnKey = compute_pawn_key(); + st->materialKey = compute_material_key(); + st->value = compute_value(); + st->npMaterial[WHITE] = compute_non_pawn_material(WHITE); + st->npMaterial[BLACK] = compute_non_pawn_material(BLACK); + return; + +incorrect_fen: + cout << "Error in FEN string: " << fen << endl; +} + + +/// Position::set_castling_rights() sets castling parameters castling avaiability. +/// This function is compatible with 3 standards: Normal FEN standard, Shredder-FEN +/// that uses the letters of the columns on which the rooks began the game instead +/// of KQkq and also X-FEN standard that, in case of Chess960, if an inner Rook is +/// associated with the castling right, the traditional castling tag will be replaced +/// by the file letter of the involved rook as for the Shredder-FEN. + +bool Position::set_castling_rights(char token) { + + Color c = token >= 'a' ? BLACK : WHITE; + Square sqA = (c == WHITE ? SQ_A1 : SQ_A8); + Square sqH = (c == WHITE ? SQ_H1 : SQ_H8); + Piece rook = (c == WHITE ? WR : BR); + + initialKFile = square_file(king_square(c)); + token = char(toupper(token)); + + if (token == 'K') + { + for (Square sq = sqH; sq >= sqA; sq--) + if (piece_on(sq) == rook) + { + do_allow_oo(c); + initialKRFile = square_file(sq); + break; + } + } + else if (token == 'Q') + { + for (Square sq = sqA; sq <= sqH; sq++) + if (piece_on(sq) == rook) + { + do_allow_ooo(c); + initialQRFile = square_file(sq); + break; + } + } + else if (token >= 'A' && token <= 'H') + { + File rookFile = File(token - 'A') + FILE_A; + if (rookFile < initialKFile) + { + do_allow_ooo(c); + initialQRFile = rookFile; + } + else + { + do_allow_oo(c); + initialKRFile = rookFile; + } + } + else + return token == '-'; + + return true; +} + + +/// Position::to_fen() returns a FEN representation of the position. In case +/// of Chess960 the Shredder-FEN notation is used. Mainly a debugging function. + +const string Position::to_fen() const { + + string fen; + Square sq; + char emptyCnt = '0'; + + for (Rank rank = RANK_8; rank >= RANK_1; rank--, fen += '/') + { + for (File file = FILE_A; file <= FILE_H; file++) + { + sq = make_square(file, rank); + + if (square_is_occupied(sq)) + { + if (emptyCnt != '0') + { + fen += emptyCnt; + emptyCnt = '0'; + } + fen += pieceLetters.from_piece(piece_on(sq)); + } else + emptyCnt++; + } + + if (emptyCnt != '0') + { + fen += emptyCnt; + emptyCnt = '0'; + } + } + + fen += (sideToMove == WHITE ? " w " : " b "); + + if (st->castleRights != CASTLES_NONE) + { + if (can_castle_kingside(WHITE)) + fen += chess960 ? char(toupper(file_to_char(initialKRFile))) : 'K'; + + if (can_castle_queenside(WHITE)) + fen += chess960 ? char(toupper(file_to_char(initialQRFile))) : 'Q'; + + if (can_castle_kingside(BLACK)) + fen += chess960 ? file_to_char(initialKRFile) : 'k'; + + if (can_castle_queenside(BLACK)) + fen += chess960 ? file_to_char(initialQRFile) : 'q'; + } else + fen += '-'; + + fen += (ep_square() == SQ_NONE ? " -" : " " + square_to_string(ep_square())); + return fen; +} + + +/// Position::print() prints an ASCII representation of the position to +/// the standard output. If a move is given then also the san is printed. + +void Position::print(Move move) const { + + const char* dottedLine = "\n+---+---+---+---+---+---+---+---+\n"; + + if (move) + { + Position p(*this, thread()); + string dd = (color_of_piece_on(move_from(move)) == BLACK ? ".." : ""); + cout << "\nMove is: " << dd << move_to_san(p, move); + } + + for (Rank rank = RANK_8; rank >= RANK_1; rank--) + { + cout << dottedLine << '|'; + for (File file = FILE_A; file <= FILE_H; file++) + { + Square sq = make_square(file, rank); + Piece piece = piece_on(sq); + + if (piece == PIECE_NONE && square_color(sq) == DARK) + piece = PIECE_NONE_DARK_SQ; + + char c = (color_of_piece_on(sq) == BLACK ? '=' : ' '); + cout << c << pieceLetters.from_piece(piece) << c << '|'; + } + } + cout << dottedLine << "Fen is: " << to_fen() << "\nKey is: " << st->key << endl; +} + + +/// Position:hidden_checkers<>() returns a bitboard of all pinned (against the +/// king) pieces for the given color and for the given pinner type. Or, when +/// template parameter FindPinned is false, the pieces of the given color +/// candidate for a discovery check against the enemy king. +/// Bitboard checkersBB must be already updated when looking for pinners. + +template +Bitboard Position::hidden_checkers(Color c) const { + + Bitboard result = EmptyBoardBB; + Bitboard pinners = pieces_of_color(FindPinned ? opposite_color(c) : c); + + // Pinned pieces protect our king, dicovery checks attack + // the enemy king. + Square ksq = king_square(FindPinned ? c : opposite_color(c)); + + // Pinners are sliders, not checkers, that give check when candidate pinned is removed + pinners &= (pieces(ROOK, QUEEN) & RookPseudoAttacks[ksq]) | (pieces(BISHOP, QUEEN) & BishopPseudoAttacks[ksq]); + + if (FindPinned && pinners) + pinners &= ~st->checkersBB; + + while (pinners) + { + Square s = pop_1st_bit(&pinners); + Bitboard b = squares_between(s, ksq) & occupied_squares(); + + assert(b); + + if ( !(b & (b - 1)) // Only one bit set? + && (b & pieces_of_color(c))) // Is an our piece? + result |= b; + } + return result; +} + + +/// Position:pinned_pieces() returns a bitboard of all pinned (against the +/// king) pieces for the given color. Note that checkersBB bitboard must +/// be already updated. + +Bitboard Position::pinned_pieces(Color c) const { + + return hidden_checkers(c); +} + + +/// Position:discovered_check_candidates() returns a bitboard containing all +/// pieces for the given side which are candidates for giving a discovered +/// check. Contrary to pinned_pieces() here there is no need of checkersBB +/// to be already updated. + +Bitboard Position::discovered_check_candidates(Color c) const { + + return hidden_checkers(c); +} + +/// Position::attackers_to() computes a bitboard containing all pieces which +/// attacks a given square. + +Bitboard Position::attackers_to(Square s) const { + + return (attacks_from(s, BLACK) & pieces(PAWN, WHITE)) + | (attacks_from(s, WHITE) & pieces(PAWN, BLACK)) + | (attacks_from(s) & pieces(KNIGHT)) + | (attacks_from(s) & pieces(ROOK, QUEEN)) + | (attacks_from(s) & pieces(BISHOP, QUEEN)) + | (attacks_from(s) & pieces(KING)); +} + +/// Position::attacks_from() computes a bitboard of all attacks +/// of a given piece put in a given square. + +Bitboard Position::attacks_from(Piece p, Square s) const { + + assert(square_is_ok(s)); + + switch (p) + { + case WB: case BB: return attacks_from(s); + case WR: case BR: return attacks_from(s); + case WQ: case BQ: return attacks_from(s); + default: return StepAttacksBB[p][s]; + } +} + +Bitboard Position::attacks_from(Piece p, Square s, Bitboard occ) { + + assert(square_is_ok(s)); + + switch (p) + { + case WB: case BB: return bishop_attacks_bb(s, occ); + case WR: case BR: return rook_attacks_bb(s, occ); + case WQ: case BQ: return bishop_attacks_bb(s, occ) | rook_attacks_bb(s, occ); + default: return StepAttacksBB[p][s]; + } +} + + +/// Position::move_attacks_square() tests whether a move from the current +/// position attacks a given square. + +bool Position::move_attacks_square(Move m, Square s) const { + + assert(move_is_ok(m)); + assert(square_is_ok(s)); + + Bitboard occ, xray; + Square f = move_from(m), t = move_to(m); + + assert(square_is_occupied(f)); + + if (bit_is_set(attacks_from(piece_on(f), t), s)) + return true; + + // Move the piece and scan for X-ray attacks behind it + occ = occupied_squares(); + do_move_bb(&occ, make_move_bb(f, t)); + xray = ( (rook_attacks_bb(s, occ) & pieces(ROOK, QUEEN)) + |(bishop_attacks_bb(s, occ) & pieces(BISHOP, QUEEN))) + & pieces_of_color(color_of_piece_on(f)); + + // If we have attacks we need to verify that are caused by our move + // and are not already existent ones. + return xray && (xray ^ (xray & attacks_from(s))); +} + + +/// Position::find_checkers() computes the checkersBB bitboard, which +/// contains a nonzero bit for each checking piece (0, 1 or 2). It +/// currently works by calling Position::attackers_to, which is probably +/// inefficient. Consider rewriting this function to use the last move +/// played, like in non-bitboard versions of Glaurung. + +void Position::find_checkers() { + + Color us = side_to_move(); + st->checkersBB = attackers_to(king_square(us)) & pieces_of_color(opposite_color(us)); +} + + +/// Position::pl_move_is_legal() tests whether a pseudo-legal move is legal + +bool Position::pl_move_is_legal(Move m, Bitboard pinned) const { + + assert(is_ok()); + assert(move_is_ok(m)); + assert(pinned == pinned_pieces(side_to_move())); + + // Castling moves are checked for legality during move generation. + if (move_is_castle(m)) + return true; + + // En passant captures are a tricky special case. Because they are + // rather uncommon, we do it simply by testing whether the king is attacked + // after the move is made + if (move_is_ep(m)) + { + Color us = side_to_move(); + Color them = opposite_color(us); + Square from = move_from(m); + Square to = move_to(m); + Square capsq = make_square(square_file(to), square_rank(from)); + Square ksq = king_square(us); + Bitboard b = occupied_squares(); + + assert(to == ep_square()); + assert(piece_on(from) == make_piece(us, PAWN)); + assert(piece_on(capsq) == make_piece(them, PAWN)); + assert(piece_on(to) == PIECE_NONE); + + clear_bit(&b, from); + clear_bit(&b, capsq); + set_bit(&b, to); + + return !(rook_attacks_bb(ksq, b) & pieces(ROOK, QUEEN, them)) + && !(bishop_attacks_bb(ksq, b) & pieces(BISHOP, QUEEN, them)); + } + + Color us = side_to_move(); + Square from = move_from(m); + + assert(color_of_piece_on(from) == us); + assert(piece_on(king_square(us)) == make_piece(us, KING)); + + // If the moving piece is a king, check whether the destination + // square is attacked by the opponent. + if (type_of_piece_on(from) == KING) + return !(attackers_to(move_to(m)) & pieces_of_color(opposite_color(us))); + + // A non-king move is legal if and only if it is not pinned or it + // is moving along the ray towards or away from the king. + return !pinned + || !bit_is_set(pinned, from) + || squares_aligned(from, move_to(m), king_square(us)); +} + + +/// Position::pl_move_is_evasion() tests whether a pseudo-legal move is a legal evasion + +bool Position::pl_move_is_evasion(Move m, Bitboard pinned) const +{ + assert(in_check()); + + Color us = side_to_move(); + Square from = move_from(m); + Square to = move_to(m); + + // King moves and en-passant captures are verified in pl_move_is_legal() + if (type_of_piece_on(from) == KING || move_is_ep(m)) + return pl_move_is_legal(m, pinned); + + Bitboard target = checkers(); + Square checksq = pop_1st_bit(&target); + + if (target) // double check ? + return false; + + // Our move must be a blocking evasion or a capture of the checking piece + target = squares_between(checksq, king_square(us)) | checkers(); + return bit_is_set(target, to) && pl_move_is_legal(m, pinned); +} + +/// Position::move_is_legal() takes a position and a (not necessarily pseudo-legal) +/// move and tests whether the move is legal. This version is not very fast and +/// should be used only in non time-critical paths. + +bool Position::move_is_legal(const Move m) const { + + MoveStack mlist[MAX_MOVES]; + MoveStack *cur, *last = generate(*this, mlist); + + for (cur = mlist; cur != last; cur++) + if (cur->move == m) + return pl_move_is_legal(m, pinned_pieces(sideToMove)); + + return false; +} + + +/// Fast version of Position::move_is_legal() that takes a position a move and +/// a bitboard of pinned pieces as input, and tests whether the move is legal. + +bool Position::move_is_legal(const Move m, Bitboard pinned) const { + + assert(is_ok()); + assert(pinned == pinned_pieces(sideToMove)); + + Color us = sideToMove; + Color them = opposite_color(sideToMove); + Square from = move_from(m); + Square to = move_to(m); + Piece pc = piece_on(from); + + // Use a slower but simpler function for uncommon cases + if (move_is_special(m)) + return move_is_legal(m); + + // If the from square is not occupied by a piece belonging to the side to + // move, the move is obviously not legal. + if (color_of_piece(pc) != us) + return false; + + // The destination square cannot be occupied by a friendly piece + if (color_of_piece_on(to) == us) + return false; + + // Handle the special case of a pawn move + if (type_of_piece(pc) == PAWN) + { + // Move direction must be compatible with pawn color + int direction = to - from; + if ((us == WHITE) != (direction > 0)) + return false; + + // We have already handled promotion moves, so destination + // cannot be on the 8/1th rank. + if (square_rank(to) == RANK_8 || square_rank(to) == RANK_1) + return false; + + // Proceed according to the square delta between the origin and + // destination squares. + switch (direction) + { + case DELTA_NW: + case DELTA_NE: + case DELTA_SW: + case DELTA_SE: + // Capture. The destination square must be occupied by an enemy + // piece (en passant captures was handled earlier). + if (color_of_piece_on(to) != them) + return false; + break; + + case DELTA_N: + case DELTA_S: + // Pawn push. The destination square must be empty. + if (!square_is_empty(to)) + return false; + break; + + case DELTA_NN: + // Double white pawn push. The destination square must be on the fourth + // rank, and both the destination square and the square between the + // source and destination squares must be empty. + if ( square_rank(to) != RANK_4 + || !square_is_empty(to) + || !square_is_empty(from + DELTA_N)) + return false; + break; + + case DELTA_SS: + // Double black pawn push. The destination square must be on the fifth + // rank, and both the destination square and the square between the + // source and destination squares must be empty. + if ( square_rank(to) != RANK_5 + || !square_is_empty(to) + || !square_is_empty(from + DELTA_S)) + return false; + break; + + default: + return false; + } + } + else if (!bit_is_set(attacks_from(pc, from), to)) + return false; + + // The move is pseudo-legal, check if it is also legal + return in_check() ? pl_move_is_evasion(m, pinned) : pl_move_is_legal(m, pinned); +} + + +/// Position::move_gives_check() tests whether a pseudo-legal move is a check + +bool Position::move_gives_check(Move m) const { + + return move_gives_check(m, CheckInfo(*this)); +} + +bool Position::move_gives_check(Move m, const CheckInfo& ci) const { + + assert(is_ok()); + assert(move_is_ok(m)); + assert(ci.dcCandidates == discovered_check_candidates(side_to_move())); + assert(color_of_piece_on(move_from(m)) == side_to_move()); + assert(piece_on(ci.ksq) == make_piece(opposite_color(side_to_move()), KING)); + + Square from = move_from(m); + Square to = move_to(m); + PieceType pt = type_of_piece_on(from); + + // Direct check ? + if (bit_is_set(ci.checkSq[pt], to)) + return true; + + // Discovery check ? + if (ci.dcCandidates && bit_is_set(ci.dcCandidates, from)) + { + // For pawn and king moves we need to verify also direction + if ( (pt != PAWN && pt != KING) + || !squares_aligned(from, to, ci.ksq)) + return true; + } + + // Can we skip the ugly special cases ? + if (!move_is_special(m)) + return false; + + Color us = side_to_move(); + Bitboard b = occupied_squares(); + + // Promotion with check ? + if (move_is_promotion(m)) + { + clear_bit(&b, from); + + switch (move_promotion_piece(m)) + { + case KNIGHT: + return bit_is_set(attacks_from(to), ci.ksq); + case BISHOP: + return bit_is_set(bishop_attacks_bb(to, b), ci.ksq); + case ROOK: + return bit_is_set(rook_attacks_bb(to, b), ci.ksq); + case QUEEN: + return bit_is_set(queen_attacks_bb(to, b), ci.ksq); + default: + assert(false); + } + } + + // En passant capture with check ? We have already handled the case + // of direct checks and ordinary discovered check, the only case we + // need to handle is the unusual case of a discovered check through + // the captured pawn. + if (move_is_ep(m)) + { + Square capsq = make_square(square_file(to), square_rank(from)); + clear_bit(&b, from); + clear_bit(&b, capsq); + set_bit(&b, to); + return (rook_attacks_bb(ci.ksq, b) & pieces(ROOK, QUEEN, us)) + ||(bishop_attacks_bb(ci.ksq, b) & pieces(BISHOP, QUEEN, us)); + } + + // Castling with check ? + if (move_is_castle(m)) + { + Square kfrom, kto, rfrom, rto; + kfrom = from; + rfrom = to; + + if (rfrom > kfrom) + { + kto = relative_square(us, SQ_G1); + rto = relative_square(us, SQ_F1); + } else { + kto = relative_square(us, SQ_C1); + rto = relative_square(us, SQ_D1); + } + clear_bit(&b, kfrom); + clear_bit(&b, rfrom); + set_bit(&b, rto); + set_bit(&b, kto); + return bit_is_set(rook_attacks_bb(rto, b), ci.ksq); + } + + return false; +} + + +/// Position::do_setup_move() makes a permanent move on the board. It should +/// be used when setting up a position on board. You can't undo the move. + +void Position::do_setup_move(Move m) { + + StateInfo newSt; + + do_move(m, newSt); + + // Reset "game ply" in case we made a non-reversible move. + // "game ply" is used for repetition detection. + if (st->rule50 == 0) + st->gamePly = 0; + + // Update the number of plies played from the starting position + startPosPlyCounter++; + + // Our StateInfo newSt is about going out of scope so copy + // its content before it disappears. + detach(); +} + + +/// Position::do_move() makes a move, and saves all information necessary +/// to a StateInfo object. The move is assumed to be legal. Pseudo-legal +/// moves should be filtered out before this function is called. + +void Position::do_move(Move m, StateInfo& newSt) { + + CheckInfo ci(*this); + do_move(m, newSt, ci, move_gives_check(m, ci)); +} + +void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveIsCheck) { + + assert(is_ok()); + assert(move_is_ok(m)); + assert(&newSt != st); + + nodes++; + Key key = st->key; + + // Copy some fields of old state to our new StateInfo object except the + // ones which are recalculated from scratch anyway, then switch our state + // pointer to point to the new, ready to be updated, state. + struct ReducedStateInfo { + Key pawnKey, materialKey; + int castleRights, rule50, gamePly, pliesFromNull; + Square epSquare; + Score value; + Value npMaterial[2]; + }; + + memcpy(&newSt, st, sizeof(ReducedStateInfo)); + + newSt.previous = st; + st = &newSt; + + // Save the current key to the history[] array, in order to be able to + // detect repetition draws. + history[st->gamePly++] = key; + + // Update side to move + key ^= zobSideToMove; + + // Increment the 50 moves rule draw counter. Resetting it to zero in the + // case of non-reversible moves is taken care of later. + st->rule50++; + st->pliesFromNull++; + + if (move_is_castle(m)) + { + st->key = key; + do_castle_move(m); + return; + } + + Color us = side_to_move(); + Color them = opposite_color(us); + Square from = move_from(m); + Square to = move_to(m); + bool ep = move_is_ep(m); + bool pm = move_is_promotion(m); + + Piece piece = piece_on(from); + PieceType pt = type_of_piece(piece); + PieceType capture = ep ? PAWN : type_of_piece_on(to); + + assert(color_of_piece_on(from) == us); + assert(color_of_piece_on(to) == them || square_is_empty(to)); + assert(!(ep || pm) || piece == make_piece(us, PAWN)); + assert(!pm || relative_rank(us, to) == RANK_8); + + if (capture) + do_capture_move(key, capture, them, to, ep); + + // Update hash key + key ^= zobrist[us][pt][from] ^ zobrist[us][pt][to]; + + // Reset en passant square + if (st->epSquare != SQ_NONE) + { + key ^= zobEp[st->epSquare]; + st->epSquare = SQ_NONE; + } + + // Update castle rights, try to shortcut a common case + int cm = castleRightsMask[from] & castleRightsMask[to]; + if (cm != ALL_CASTLES && ((cm & st->castleRights) != st->castleRights)) + { + key ^= zobCastle[st->castleRights]; + st->castleRights &= castleRightsMask[from]; + st->castleRights &= castleRightsMask[to]; + key ^= zobCastle[st->castleRights]; + } + + // Prefetch TT access as soon as we know key is updated + prefetch((char*)TT.first_entry(key)); + + // Move the piece + Bitboard move_bb = make_move_bb(from, to); + do_move_bb(&(byColorBB[us]), move_bb); + do_move_bb(&(byTypeBB[pt]), move_bb); + do_move_bb(&(byTypeBB[0]), move_bb); // HACK: byTypeBB[0] == occupied squares + + board[to] = board[from]; + board[from] = PIECE_NONE; + + // Update piece lists, note that index[from] is not updated and + // becomes stale. This works as long as index[] is accessed just + // by known occupied squares. + index[to] = index[from]; + pieceList[us][pt][index[to]] = to; + + // If the moving piece was a pawn do some special extra work + if (pt == PAWN) + { + // Reset rule 50 draw counter + st->rule50 = 0; + + // Update pawn hash key and prefetch in L1/L2 cache + st->pawnKey ^= zobrist[us][PAWN][from] ^ zobrist[us][PAWN][to]; + + // Set en passant square, only if moved pawn can be captured + if ((to ^ from) == 16) + { + if (attacks_from(from + (us == WHITE ? DELTA_N : DELTA_S), us) & pieces(PAWN, them)) + { + st->epSquare = Square((int(from) + int(to)) / 2); + key ^= zobEp[st->epSquare]; + } + } + + if (pm) // promotion ? + { + PieceType promotion = move_promotion_piece(m); + + assert(promotion >= KNIGHT && promotion <= QUEEN); + + // Insert promoted piece instead of pawn + clear_bit(&(byTypeBB[PAWN]), to); + set_bit(&(byTypeBB[promotion]), to); + board[to] = make_piece(us, promotion); + + // Update piece counts + pieceCount[us][promotion]++; + pieceCount[us][PAWN]--; + + // Update material key + st->materialKey ^= zobrist[us][PAWN][pieceCount[us][PAWN]]; + st->materialKey ^= zobrist[us][promotion][pieceCount[us][promotion]-1]; + + // Update piece lists, move the last pawn at index[to] position + // and shrink the list. Add a new promotion piece to the list. + Square lastPawnSquare = pieceList[us][PAWN][pieceCount[us][PAWN]]; + index[lastPawnSquare] = index[to]; + pieceList[us][PAWN][index[lastPawnSquare]] = lastPawnSquare; + pieceList[us][PAWN][pieceCount[us][PAWN]] = SQ_NONE; + index[to] = pieceCount[us][promotion] - 1; + pieceList[us][promotion][index[to]] = to; + + // Partially revert hash keys update + key ^= zobrist[us][PAWN][to] ^ zobrist[us][promotion][to]; + st->pawnKey ^= zobrist[us][PAWN][to]; + + // Partially revert and update incremental scores + st->value -= pst(us, PAWN, to); + st->value += pst(us, promotion, to); + + // Update material + st->npMaterial[us] += PieceValueMidgame[promotion]; + } + } + + // Prefetch pawn and material hash tables + Threads[threadID].pawnTable.prefetch(st->pawnKey); + Threads[threadID].materialTable.prefetch(st->materialKey); + + // Update incremental scores + st->value += pst_delta(piece, from, to); + + // Set capture piece + st->capturedType = capture; + + // Update the key with the final value + st->key = key; + + // Update checkers bitboard, piece must be already moved + st->checkersBB = EmptyBoardBB; + + if (moveIsCheck) + { + if (ep | pm) + st->checkersBB = attackers_to(king_square(them)) & pieces_of_color(us); + else + { + // Direct checks + if (bit_is_set(ci.checkSq[pt], to)) + st->checkersBB = SetMaskBB[to]; + + // Discovery checks + if (ci.dcCandidates && bit_is_set(ci.dcCandidates, from)) + { + if (pt != ROOK) + st->checkersBB |= (attacks_from(ci.ksq) & pieces(ROOK, QUEEN, us)); + + if (pt != BISHOP) + st->checkersBB |= (attacks_from(ci.ksq) & pieces(BISHOP, QUEEN, us)); + } + } + } + + // Finish + sideToMove = opposite_color(sideToMove); + st->value += (sideToMove == WHITE ? TempoValue : -TempoValue); + + assert(is_ok()); +} + + +/// Position::do_capture_move() is a private method used to update captured +/// piece info. It is called from the main Position::do_move function. + +void Position::do_capture_move(Key& key, PieceType capture, Color them, Square to, bool ep) { + + assert(capture != KING); + + Square capsq = to; + + // If the captured piece was a pawn, update pawn hash key, + // otherwise update non-pawn material. + if (capture == PAWN) + { + if (ep) // en passant ? + { + capsq = (them == BLACK)? (to - DELTA_N) : (to - DELTA_S); + + assert(to == st->epSquare); + assert(relative_rank(opposite_color(them), to) == RANK_6); + assert(piece_on(to) == PIECE_NONE); + assert(piece_on(capsq) == make_piece(them, PAWN)); + + board[capsq] = PIECE_NONE; + } + st->pawnKey ^= zobrist[them][PAWN][capsq]; + } + else + st->npMaterial[them] -= PieceValueMidgame[capture]; + + // Remove captured piece + clear_bit(&(byColorBB[them]), capsq); + clear_bit(&(byTypeBB[capture]), capsq); + clear_bit(&(byTypeBB[0]), capsq); + + // Update hash key + key ^= zobrist[them][capture][capsq]; + + // Update incremental scores + st->value -= pst(them, capture, capsq); + + // Update piece count + pieceCount[them][capture]--; + + // Update material hash key + st->materialKey ^= zobrist[them][capture][pieceCount[them][capture]]; + + // Update piece list, move the last piece at index[capsq] position + // + // WARNING: This is a not perfectly revresible operation. When we + // will reinsert the captured piece in undo_move() we will put it + // at the end of the list and not in its original place, it means + // index[] and pieceList[] are not guaranteed to be invariant to a + // do_move() + undo_move() sequence. + Square lastPieceSquare = pieceList[them][capture][pieceCount[them][capture]]; + index[lastPieceSquare] = index[capsq]; + pieceList[them][capture][index[lastPieceSquare]] = lastPieceSquare; + pieceList[them][capture][pieceCount[them][capture]] = SQ_NONE; + + // Reset rule 50 counter + st->rule50 = 0; +} + + +/// Position::do_castle_move() is a private method used to make a castling +/// move. It is called from the main Position::do_move function. Note that +/// castling moves are encoded as "king captures friendly rook" moves, for +/// instance white short castling in a non-Chess960 game is encoded as e1h1. + +void Position::do_castle_move(Move m) { + + assert(move_is_ok(m)); + assert(move_is_castle(m)); + + Color us = side_to_move(); + Color them = opposite_color(us); + + // Reset capture field + st->capturedType = PIECE_TYPE_NONE; + + // Find source squares for king and rook + Square kfrom = move_from(m); + Square rfrom = move_to(m); // HACK: See comment at beginning of function + Square kto, rto; + + assert(piece_on(kfrom) == make_piece(us, KING)); + assert(piece_on(rfrom) == make_piece(us, ROOK)); + + // Find destination squares for king and rook + if (rfrom > kfrom) // O-O + { + kto = relative_square(us, SQ_G1); + rto = relative_square(us, SQ_F1); + } else { // O-O-O + kto = relative_square(us, SQ_C1); + rto = relative_square(us, SQ_D1); + } + + // Remove pieces from source squares: + clear_bit(&(byColorBB[us]), kfrom); + clear_bit(&(byTypeBB[KING]), kfrom); + clear_bit(&(byTypeBB[0]), kfrom); // HACK: byTypeBB[0] == occupied squares + clear_bit(&(byColorBB[us]), rfrom); + clear_bit(&(byTypeBB[ROOK]), rfrom); + clear_bit(&(byTypeBB[0]), rfrom); // HACK: byTypeBB[0] == occupied squares + + // Put pieces on destination squares: + set_bit(&(byColorBB[us]), kto); + set_bit(&(byTypeBB[KING]), kto); + set_bit(&(byTypeBB[0]), kto); // HACK: byTypeBB[0] == occupied squares + set_bit(&(byColorBB[us]), rto); + set_bit(&(byTypeBB[ROOK]), rto); + set_bit(&(byTypeBB[0]), rto); // HACK: byTypeBB[0] == occupied squares + + // Update board array + Piece king = make_piece(us, KING); + Piece rook = make_piece(us, ROOK); + board[kfrom] = board[rfrom] = PIECE_NONE; + board[kto] = king; + board[rto] = rook; + + // Update piece lists + pieceList[us][KING][index[kfrom]] = kto; + pieceList[us][ROOK][index[rfrom]] = rto; + int tmp = index[rfrom]; // In Chess960 could be rto == kfrom + index[kto] = index[kfrom]; + index[rto] = tmp; + + // Update incremental scores + st->value += pst_delta(king, kfrom, kto); + st->value += pst_delta(rook, rfrom, rto); + + // Update hash key + st->key ^= zobrist[us][KING][kfrom] ^ zobrist[us][KING][kto]; + st->key ^= zobrist[us][ROOK][rfrom] ^ zobrist[us][ROOK][rto]; + + // Clear en passant square + if (st->epSquare != SQ_NONE) + { + st->key ^= zobEp[st->epSquare]; + st->epSquare = SQ_NONE; + } + + // Update castling rights + st->key ^= zobCastle[st->castleRights]; + st->castleRights &= castleRightsMask[kfrom]; + st->key ^= zobCastle[st->castleRights]; + + // Reset rule 50 counter + st->rule50 = 0; + + // Update checkers BB + st->checkersBB = attackers_to(king_square(them)) & pieces_of_color(us); + + // Finish + sideToMove = opposite_color(sideToMove); + st->value += (sideToMove == WHITE ? TempoValue : -TempoValue); + + assert(is_ok()); +} + + +/// Position::undo_move() unmakes a move. When it returns, the position should +/// be restored to exactly the same state as before the move was made. + +void Position::undo_move(Move m) { + + assert(is_ok()); + assert(move_is_ok(m)); + + sideToMove = opposite_color(sideToMove); + + if (move_is_castle(m)) + { + undo_castle_move(m); + return; + } + + Color us = side_to_move(); + Color them = opposite_color(us); + Square from = move_from(m); + Square to = move_to(m); + bool ep = move_is_ep(m); + bool pm = move_is_promotion(m); + + PieceType pt = type_of_piece_on(to); + + assert(square_is_empty(from)); + assert(color_of_piece_on(to) == us); + assert(!pm || relative_rank(us, to) == RANK_8); + assert(!ep || to == st->previous->epSquare); + assert(!ep || relative_rank(us, to) == RANK_6); + assert(!ep || piece_on(to) == make_piece(us, PAWN)); + + if (pm) // promotion ? + { + PieceType promotion = move_promotion_piece(m); + pt = PAWN; + + assert(promotion >= KNIGHT && promotion <= QUEEN); + assert(piece_on(to) == make_piece(us, promotion)); + + // Replace promoted piece with a pawn + clear_bit(&(byTypeBB[promotion]), to); + set_bit(&(byTypeBB[PAWN]), to); + + // Update piece counts + pieceCount[us][promotion]--; + pieceCount[us][PAWN]++; + + // Update piece list replacing promotion piece with a pawn + Square lastPromotionSquare = pieceList[us][promotion][pieceCount[us][promotion]]; + index[lastPromotionSquare] = index[to]; + pieceList[us][promotion][index[lastPromotionSquare]] = lastPromotionSquare; + pieceList[us][promotion][pieceCount[us][promotion]] = SQ_NONE; + index[to] = pieceCount[us][PAWN] - 1; + pieceList[us][PAWN][index[to]] = to; + } + + // Put the piece back at the source square + Bitboard move_bb = make_move_bb(to, from); + do_move_bb(&(byColorBB[us]), move_bb); + do_move_bb(&(byTypeBB[pt]), move_bb); + do_move_bb(&(byTypeBB[0]), move_bb); // HACK: byTypeBB[0] == occupied squares + + board[from] = make_piece(us, pt); + board[to] = PIECE_NONE; + + // Update piece list + index[from] = index[to]; + pieceList[us][pt][index[from]] = from; + + if (st->capturedType) + { + Square capsq = to; + + if (ep) + capsq = (us == WHITE)? (to - DELTA_N) : (to - DELTA_S); + + assert(st->capturedType != KING); + assert(!ep || square_is_empty(capsq)); + + // Restore the captured piece + set_bit(&(byColorBB[them]), capsq); + set_bit(&(byTypeBB[st->capturedType]), capsq); + set_bit(&(byTypeBB[0]), capsq); + + board[capsq] = make_piece(them, st->capturedType); + + // Update piece count + pieceCount[them][st->capturedType]++; + + // Update piece list, add a new captured piece in capsq square + index[capsq] = pieceCount[them][st->capturedType] - 1; + pieceList[them][st->capturedType][index[capsq]] = capsq; + } + + // Finally point our state pointer back to the previous state + st = st->previous; + + assert(is_ok()); +} + + +/// Position::undo_castle_move() is a private method used to unmake a castling +/// move. It is called from the main Position::undo_move function. Note that +/// castling moves are encoded as "king captures friendly rook" moves, for +/// instance white short castling in a non-Chess960 game is encoded as e1h1. + +void Position::undo_castle_move(Move m) { + + assert(move_is_ok(m)); + assert(move_is_castle(m)); + + // When we have arrived here, some work has already been done by + // Position::undo_move. In particular, the side to move has been switched, + // so the code below is correct. + Color us = side_to_move(); + + // Find source squares for king and rook + Square kfrom = move_from(m); + Square rfrom = move_to(m); // HACK: See comment at beginning of function + Square kto, rto; + + // Find destination squares for king and rook + if (rfrom > kfrom) // O-O + { + kto = relative_square(us, SQ_G1); + rto = relative_square(us, SQ_F1); + } else { // O-O-O + kto = relative_square(us, SQ_C1); + rto = relative_square(us, SQ_D1); + } + + assert(piece_on(kto) == make_piece(us, KING)); + assert(piece_on(rto) == make_piece(us, ROOK)); + + // Remove pieces from destination squares: + clear_bit(&(byColorBB[us]), kto); + clear_bit(&(byTypeBB[KING]), kto); + clear_bit(&(byTypeBB[0]), kto); // HACK: byTypeBB[0] == occupied squares + clear_bit(&(byColorBB[us]), rto); + clear_bit(&(byTypeBB[ROOK]), rto); + clear_bit(&(byTypeBB[0]), rto); // HACK: byTypeBB[0] == occupied squares + + // Put pieces on source squares: + set_bit(&(byColorBB[us]), kfrom); + set_bit(&(byTypeBB[KING]), kfrom); + set_bit(&(byTypeBB[0]), kfrom); // HACK: byTypeBB[0] == occupied squares + set_bit(&(byColorBB[us]), rfrom); + set_bit(&(byTypeBB[ROOK]), rfrom); + set_bit(&(byTypeBB[0]), rfrom); // HACK: byTypeBB[0] == occupied squares + + // Update board + board[rto] = board[kto] = PIECE_NONE; + board[rfrom] = make_piece(us, ROOK); + board[kfrom] = make_piece(us, KING); + + // Update piece lists + pieceList[us][KING][index[kto]] = kfrom; + pieceList[us][ROOK][index[rto]] = rfrom; + int tmp = index[rto]; // In Chess960 could be rto == kfrom + index[kfrom] = index[kto]; + index[rfrom] = tmp; + + // Finally point our state pointer back to the previous state + st = st->previous; + + assert(is_ok()); +} + + +/// Position::do_null_move makes() a "null move": It switches the side to move +/// and updates the hash key without executing any move on the board. + +void Position::do_null_move(StateInfo& backupSt) { + + assert(is_ok()); + assert(!in_check()); + + // Back up the information necessary to undo the null move to the supplied + // StateInfo object. + // Note that differently from normal case here backupSt is actually used as + // a backup storage not as a new state to be used. + backupSt.key = st->key; + backupSt.epSquare = st->epSquare; + backupSt.value = st->value; + backupSt.previous = st->previous; + backupSt.pliesFromNull = st->pliesFromNull; + st->previous = &backupSt; + + // Save the current key to the history[] array, in order to be able to + // detect repetition draws. + history[st->gamePly++] = st->key; + + // Update the necessary information + if (st->epSquare != SQ_NONE) + st->key ^= zobEp[st->epSquare]; + + st->key ^= zobSideToMove; + prefetch((char*)TT.first_entry(st->key)); + + sideToMove = opposite_color(sideToMove); + st->epSquare = SQ_NONE; + st->rule50++; + st->pliesFromNull = 0; + st->value += (sideToMove == WHITE) ? TempoValue : -TempoValue; +} + + +/// Position::undo_null_move() unmakes a "null move". + +void Position::undo_null_move() { + + assert(is_ok()); + assert(!in_check()); + + // Restore information from the our backup StateInfo object + StateInfo* backupSt = st->previous; + st->key = backupSt->key; + st->epSquare = backupSt->epSquare; + st->value = backupSt->value; + st->previous = backupSt->previous; + st->pliesFromNull = backupSt->pliesFromNull; + + // Update the necessary information + sideToMove = opposite_color(sideToMove); + st->rule50--; + st->gamePly--; +} + + +/// Position::see() is a static exchange evaluator: It tries to estimate the +/// material gain or loss resulting from a move. There are three versions of +/// this function: One which takes a destination square as input, one takes a +/// move, and one which takes a 'from' and a 'to' square. The function does +/// not yet understand promotions captures. + +int Position::see(Move m) const { + + assert(move_is_ok(m)); + return see(move_from(m), move_to(m)); +} + +int Position::see_sign(Move m) const { + + assert(move_is_ok(m)); + + Square from = move_from(m); + Square to = move_to(m); + + // Early return if SEE cannot be negative because captured piece value + // is not less then capturing one. Note that king moves always return + // here because king midgame value is set to 0. + if (midgame_value_of_piece_on(to) >= midgame_value_of_piece_on(from)) + return 1; + + return see(from, to); +} + +int Position::see(Square from, Square to) const { + + Bitboard occupied, attackers, stmAttackers, b; + int swapList[32], slIndex = 1; + PieceType capturedType, pt; + Color stm; + + assert(square_is_ok(from)); + assert(square_is_ok(to)); + + capturedType = type_of_piece_on(to); + + // King cannot be recaptured + if (capturedType == KING) + return seeValues[capturedType]; + + occupied = occupied_squares(); + + // Handle en passant moves + if (st->epSquare == to && type_of_piece_on(from) == PAWN) + { + Square capQq = (side_to_move() == WHITE ? to - DELTA_N : to - DELTA_S); + + assert(capturedType == PIECE_TYPE_NONE); + assert(type_of_piece_on(capQq) == PAWN); + + // Remove the captured pawn + clear_bit(&occupied, capQq); + capturedType = PAWN; + } + + // Find all attackers to the destination square, with the moving piece + // removed, but possibly an X-ray attacker added behind it. + clear_bit(&occupied, from); + attackers = (rook_attacks_bb(to, occupied) & pieces(ROOK, QUEEN)) + | (bishop_attacks_bb(to, occupied)& pieces(BISHOP, QUEEN)) + | (attacks_from(to) & pieces(KNIGHT)) + | (attacks_from(to) & pieces(KING)) + | (attacks_from(to, WHITE) & pieces(PAWN, BLACK)) + | (attacks_from(to, BLACK) & pieces(PAWN, WHITE)); + + // If the opponent has no attackers we are finished + stm = opposite_color(color_of_piece_on(from)); + stmAttackers = attackers & pieces_of_color(stm); + if (!stmAttackers) + return seeValues[capturedType]; + + // The destination square is defended, which makes things rather more + // difficult to compute. We proceed by building up a "swap list" containing + // the material gain or loss at each stop in a sequence of captures to the + // destination square, where the sides alternately capture, and always + // capture with the least valuable piece. After each capture, we look for + // new X-ray attacks from behind the capturing piece. + swapList[0] = seeValues[capturedType]; + capturedType = type_of_piece_on(from); + + do { + // Locate the least valuable attacker for the side to move. The loop + // below looks like it is potentially infinite, but it isn't. We know + // that the side to move still has at least one attacker left. + for (pt = PAWN; !(stmAttackers & pieces(pt)); pt++) + assert(pt < KING); + + // Remove the attacker we just found from the 'occupied' bitboard, + // and scan for new X-ray attacks behind the attacker. + b = stmAttackers & pieces(pt); + occupied ^= (b & (~b + 1)); + attackers |= (rook_attacks_bb(to, occupied) & pieces(ROOK, QUEEN)) + | (bishop_attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)); + + attackers &= occupied; // Cut out pieces we've already done + + // Add the new entry to the swap list + assert(slIndex < 32); + swapList[slIndex] = -swapList[slIndex - 1] + seeValues[capturedType]; + slIndex++; + + // Remember the value of the capturing piece, and change the side to + // move before beginning the next iteration. + capturedType = pt; + stm = opposite_color(stm); + stmAttackers = attackers & pieces_of_color(stm); + + // Stop before processing a king capture + if (capturedType == KING && stmAttackers) + { + assert(slIndex < 32); + swapList[slIndex++] = QueenValueMidgame*10; + break; + } + } while (stmAttackers); + + // Having built the swap list, we negamax through it to find the best + // achievable score from the point of view of the side to move. + while (--slIndex) + swapList[slIndex-1] = Min(-swapList[slIndex], swapList[slIndex-1]); + + return swapList[0]; +} + + +/// Position::clear() erases the position object to a pristine state, with an +/// empty board, white to move, and no castling rights. + +void Position::clear() { + + st = &startState; + memset(st, 0, sizeof(StateInfo)); + st->epSquare = SQ_NONE; + startPosPlyCounter = 0; + nodes = 0; + + memset(byColorBB, 0, sizeof(Bitboard) * 2); + memset(byTypeBB, 0, sizeof(Bitboard) * 8); + memset(pieceCount, 0, sizeof(int) * 2 * 8); + memset(index, 0, sizeof(int) * 64); + + for (int i = 0; i < 64; i++) + board[i] = PIECE_NONE; + + for (int i = 0; i < 8; i++) + for (int j = 0; j < 16; j++) + pieceList[0][i][j] = pieceList[1][i][j] = SQ_NONE; + + for (Square sq = SQ_A1; sq <= SQ_H8; sq++) + castleRightsMask[sq] = ALL_CASTLES; + + sideToMove = WHITE; + initialKFile = FILE_E; + initialKRFile = FILE_H; + initialQRFile = FILE_A; +} + + +/// Position::put_piece() puts a piece on the given square of the board, +/// updating the board array, pieces list, bitboards, and piece counts. + +void Position::put_piece(Piece p, Square s) { + + Color c = color_of_piece(p); + PieceType pt = type_of_piece(p); + + board[s] = p; + index[s] = pieceCount[c][pt]++; + pieceList[c][pt][index[s]] = s; + + set_bit(&(byTypeBB[pt]), s); + set_bit(&(byColorBB[c]), s); + set_bit(&(byTypeBB[0]), s); // HACK: byTypeBB[0] contains all occupied squares. +} + + +/// Position::compute_key() computes the hash key of the position. The hash +/// key is usually updated incrementally as moves are made and unmade, the +/// compute_key() function is only used when a new position is set up, and +/// to verify the correctness of the hash key when running in debug mode. + +Key Position::compute_key() const { + + Key result = zobCastle[st->castleRights]; + + for (Square s = SQ_A1; s <= SQ_H8; s++) + if (square_is_occupied(s)) + result ^= zobrist[color_of_piece_on(s)][type_of_piece_on(s)][s]; + + if (ep_square() != SQ_NONE) + result ^= zobEp[ep_square()]; + + if (side_to_move() == BLACK) + result ^= zobSideToMove; + + return result; +} + + +/// Position::compute_pawn_key() computes the hash key of the position. The +/// hash key is usually updated incrementally as moves are made and unmade, +/// the compute_pawn_key() function is only used when a new position is set +/// up, and to verify the correctness of the pawn hash key when running in +/// debug mode. + +Key Position::compute_pawn_key() const { + + Bitboard b; + Key result = 0; + + for (Color c = WHITE; c <= BLACK; c++) + { + b = pieces(PAWN, c); + while (b) + result ^= zobrist[c][PAWN][pop_1st_bit(&b)]; + } + return result; +} + + +/// Position::compute_material_key() computes the hash key of the position. +/// The hash key is usually updated incrementally as moves are made and unmade, +/// the compute_material_key() function is only used when a new position is set +/// up, and to verify the correctness of the material hash key when running in +/// debug mode. + +Key Position::compute_material_key() const { + + int count; + Key result = 0; + + for (Color c = WHITE; c <= BLACK; c++) + for (PieceType pt = PAWN; pt <= QUEEN; pt++) + { + count = piece_count(c, pt); + for (int i = 0; i < count; i++) + result ^= zobrist[c][pt][i]; + } + return result; +} + + +/// Position::compute_value() compute the incremental scores for the middle +/// game and the endgame. These functions are used to initialize the incremental +/// scores when a new position is set up, and to verify that the scores are correctly +/// updated by do_move and undo_move when the program is running in debug mode. +Score Position::compute_value() const { + + Bitboard b; + Score result = SCORE_ZERO; + + for (Color c = WHITE; c <= BLACK; c++) + for (PieceType pt = PAWN; pt <= KING; pt++) + { + b = pieces(pt, c); + while (b) + result += pst(c, pt, pop_1st_bit(&b)); + } + + result += (side_to_move() == WHITE ? TempoValue / 2 : -TempoValue / 2); + return result; +} + + +/// Position::compute_non_pawn_material() computes the total non-pawn middle +/// game material value for the given side. Material values are updated +/// incrementally during the search, this function is only used while +/// initializing a new Position object. + +Value Position::compute_non_pawn_material(Color c) const { + + Value result = VALUE_ZERO; + + for (PieceType pt = KNIGHT; pt <= QUEEN; pt++) + result += piece_count(c, pt) * PieceValueMidgame[pt]; + + return result; +} + + +/// Position::is_draw() tests whether the position is drawn by material, +/// repetition, or the 50 moves rule. It does not detect stalemates, this +/// must be done by the search. + +bool Position::is_draw() const { + + // Draw by material? + if ( !pieces(PAWN) + && (non_pawn_material(WHITE) + non_pawn_material(BLACK) <= BishopValueMidgame)) + return true; + + // Draw by the 50 moves rule? + if (st->rule50 > 99 && !is_mate()) + return true; + + // Draw by repetition? + for (int i = 4, e = Min(Min(st->gamePly, st->rule50), st->pliesFromNull); i <= e; i += 2) + if (history[st->gamePly - i] == st->key) + return true; + + return false; +} + + +/// Position::is_mate() returns true or false depending on whether the +/// side to move is checkmated. + +bool Position::is_mate() const { + + MoveStack moves[MAX_MOVES]; + return in_check() && generate(*this, moves) == moves; +} + + +/// Position::init_zobrist() is a static member function which initializes at +/// startup the various arrays used to compute hash keys. + +void Position::init_zobrist() { + + int i,j, k; + RKISS rk; + + for (i = 0; i < 2; i++) for (j = 0; j < 8; j++) for (k = 0; k < 64; k++) + zobrist[i][j][k] = rk.rand(); + + for (i = 0; i < 64; i++) + zobEp[i] = rk.rand(); + + for (i = 0; i < 16; i++) + zobCastle[i] = rk.rand(); + + zobSideToMove = rk.rand(); + zobExclusion = rk.rand(); +} + + +/// Position::init_piece_square_tables() initializes the piece square tables. +/// This is a two-step operation: First, the white halves of the tables are +/// copied from the MgPST[][] and EgPST[][] arrays. Second, the black halves +/// of the tables are initialized by mirroring and changing the sign of the +/// corresponding white scores. + +void Position::init_piece_square_tables() { + + for (Square s = SQ_A1; s <= SQ_H8; s++) + for (Piece p = WP; p <= WK; p++) + PieceSquareTable[p][s] = make_score(MgPST[p][s], EgPST[p][s]); + + for (Square s = SQ_A1; s <= SQ_H8; s++) + for (Piece p = BP; p <= BK; p++) + PieceSquareTable[p][s] = -PieceSquareTable[p-8][flip_square(s)]; +} + + +/// Position::flip() flips position with the white and black sides reversed. This +/// is only useful for debugging especially for finding evaluation symmetry bugs. + +void Position::flip() { + + assert(is_ok()); + + // Make a copy of current position before to start changing + const Position pos(*this, threadID); + + clear(); + threadID = pos.thread(); + + // Board + for (Square s = SQ_A1; s <= SQ_H8; s++) + if (!pos.square_is_empty(s)) + put_piece(Piece(pos.piece_on(s) ^ 8), flip_square(s)); + + // Side to move + sideToMove = opposite_color(pos.side_to_move()); + + // Castling rights + if (pos.can_castle_kingside(WHITE)) do_allow_oo(BLACK); + if (pos.can_castle_queenside(WHITE)) do_allow_ooo(BLACK); + if (pos.can_castle_kingside(BLACK)) do_allow_oo(WHITE); + if (pos.can_castle_queenside(BLACK)) do_allow_ooo(WHITE); + + initialKFile = pos.initialKFile; + initialKRFile = pos.initialKRFile; + initialQRFile = pos.initialQRFile; + + castleRightsMask[make_square(initialKFile, RANK_1)] ^= (WHITE_OO | WHITE_OOO); + castleRightsMask[make_square(initialKFile, RANK_8)] ^= (BLACK_OO | BLACK_OOO); + castleRightsMask[make_square(initialKRFile, RANK_1)] ^= WHITE_OO; + castleRightsMask[make_square(initialKRFile, RANK_8)] ^= BLACK_OO; + castleRightsMask[make_square(initialQRFile, RANK_1)] ^= WHITE_OOO; + castleRightsMask[make_square(initialQRFile, RANK_8)] ^= BLACK_OOO; + + // En passant square + if (pos.st->epSquare != SQ_NONE) + st->epSquare = flip_square(pos.st->epSquare); + + // Checkers + find_checkers(); + + // Hash keys + st->key = compute_key(); + st->pawnKey = compute_pawn_key(); + st->materialKey = compute_material_key(); + + // Incremental scores + st->value = compute_value(); + + // Material + st->npMaterial[WHITE] = compute_non_pawn_material(WHITE); + st->npMaterial[BLACK] = compute_non_pawn_material(BLACK); + + assert(is_ok()); +} + + +/// Position::is_ok() performs some consitency checks for the position object. +/// This is meant to be helpful when debugging. + +bool Position::is_ok(int* failedStep) const { + + // What features of the position should be verified? + const bool debugAll = false; + + const bool debugBitboards = debugAll || false; + const bool debugKingCount = debugAll || false; + const bool debugKingCapture = debugAll || false; + const bool debugCheckerCount = debugAll || false; + const bool debugKey = debugAll || false; + const bool debugMaterialKey = debugAll || false; + const bool debugPawnKey = debugAll || false; + const bool debugIncrementalEval = debugAll || false; + const bool debugNonPawnMaterial = debugAll || false; + const bool debugPieceCounts = debugAll || false; + const bool debugPieceList = debugAll || false; + const bool debugCastleSquares = debugAll || false; + + if (failedStep) *failedStep = 1; + + // Side to move OK? + if (!color_is_ok(side_to_move())) + return false; + + // Are the king squares in the position correct? + if (failedStep) (*failedStep)++; + if (piece_on(king_square(WHITE)) != WK) + return false; + + if (failedStep) (*failedStep)++; + if (piece_on(king_square(BLACK)) != BK) + return false; + + // Castle files OK? + if (failedStep) (*failedStep)++; + if (!file_is_ok(initialKRFile)) + return false; + + if (!file_is_ok(initialQRFile)) + return false; + + // Do both sides have exactly one king? + if (failedStep) (*failedStep)++; + if (debugKingCount) + { + int kingCount[2] = {0, 0}; + for (Square s = SQ_A1; s <= SQ_H8; s++) + if (type_of_piece_on(s) == KING) + kingCount[color_of_piece_on(s)]++; + + if (kingCount[0] != 1 || kingCount[1] != 1) + return false; + } + + // Can the side to move capture the opponent's king? + if (failedStep) (*failedStep)++; + if (debugKingCapture) + { + Color us = side_to_move(); + Color them = opposite_color(us); + Square ksq = king_square(them); + if (attackers_to(ksq) & pieces_of_color(us)) + return false; + } + + // Is there more than 2 checkers? + if (failedStep) (*failedStep)++; + if (debugCheckerCount && count_1s(st->checkersBB) > 2) + return false; + + // Bitboards OK? + if (failedStep) (*failedStep)++; + if (debugBitboards) + { + // The intersection of the white and black pieces must be empty + if ((pieces_of_color(WHITE) & pieces_of_color(BLACK)) != EmptyBoardBB) + return false; + + // The union of the white and black pieces must be equal to all + // occupied squares + if ((pieces_of_color(WHITE) | pieces_of_color(BLACK)) != occupied_squares()) + return false; + + // Separate piece type bitboards must have empty intersections + for (PieceType p1 = PAWN; p1 <= KING; p1++) + for (PieceType p2 = PAWN; p2 <= KING; p2++) + if (p1 != p2 && (pieces(p1) & pieces(p2))) + return false; + } + + // En passant square OK? + if (failedStep) (*failedStep)++; + if (ep_square() != SQ_NONE) + { + // The en passant square must be on rank 6, from the point of view of the + // side to move. + if (relative_rank(side_to_move(), ep_square()) != RANK_6) + return false; + } + + // Hash key OK? + if (failedStep) (*failedStep)++; + if (debugKey && st->key != compute_key()) + return false; + + // Pawn hash key OK? + if (failedStep) (*failedStep)++; + if (debugPawnKey && st->pawnKey != compute_pawn_key()) + return false; + + // Material hash key OK? + if (failedStep) (*failedStep)++; + if (debugMaterialKey && st->materialKey != compute_material_key()) + return false; + + // Incremental eval OK? + if (failedStep) (*failedStep)++; + if (debugIncrementalEval && st->value != compute_value()) + return false; + + // Non-pawn material OK? + if (failedStep) (*failedStep)++; + if (debugNonPawnMaterial) + { + if (st->npMaterial[WHITE] != compute_non_pawn_material(WHITE)) + return false; + + if (st->npMaterial[BLACK] != compute_non_pawn_material(BLACK)) + return false; + } + + // Piece counts OK? + if (failedStep) (*failedStep)++; + if (debugPieceCounts) + for (Color c = WHITE; c <= BLACK; c++) + for (PieceType pt = PAWN; pt <= KING; pt++) + if (pieceCount[c][pt] != count_1s(pieces(pt, c))) + return false; + + if (failedStep) (*failedStep)++; + if (debugPieceList) + for (Color c = WHITE; c <= BLACK; c++) + for (PieceType pt = PAWN; pt <= KING; pt++) + for (int i = 0; i < pieceCount[c][pt]; i++) + { + if (piece_on(piece_list(c, pt, i)) != make_piece(c, pt)) + return false; + + if (index[piece_list(c, pt, i)] != i) + return false; + } + + if (failedStep) (*failedStep)++; + if (debugCastleSquares) + { + for (Color c = WHITE; c <= BLACK; c++) + { + if (can_castle_kingside(c) && piece_on(initial_kr_square(c)) != make_piece(c, ROOK)) + return false; + + if (can_castle_queenside(c) && piece_on(initial_qr_square(c)) != make_piece(c, ROOK)) + return false; + } + if (castleRightsMask[initial_kr_square(WHITE)] != (ALL_CASTLES ^ WHITE_OO)) + return false; + if (castleRightsMask[initial_qr_square(WHITE)] != (ALL_CASTLES ^ WHITE_OOO)) + return false; + if (castleRightsMask[initial_kr_square(BLACK)] != (ALL_CASTLES ^ BLACK_OO)) + return false; + if (castleRightsMask[initial_qr_square(BLACK)] != (ALL_CASTLES ^ BLACK_OOO)) + return false; + } + + if (failedStep) *failedStep = 0; + return true; +} diff --git a/DroidFish/jni/stockfish/position.h b/DroidFish/jni/stockfish/position.h new file mode 100644 index 0000000..de2afbf --- /dev/null +++ b/DroidFish/jni/stockfish/position.h @@ -0,0 +1,554 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(POSITION_H_INCLUDED) +#define POSITION_H_INCLUDED + +#include "bitboard.h" +#include "move.h" +#include "types.h" + +/// Maximum number of plies per game (220 should be enough, because the +/// maximum search depth is 100, and during position setup we reset the +/// move counter for every non-reversible move). +const int MaxGameLength = 220; + +class Position; + +/// struct checkInfo is initialized at c'tor time and keeps +/// info used to detect if a move gives check. + +struct CheckInfo { + + explicit CheckInfo(const Position&); + + Bitboard dcCandidates; + Bitboard checkSq[8]; + Square ksq; +}; + +/// Castle rights, encoded as bit fields + +enum CastleRights { + CASTLES_NONE = 0, + WHITE_OO = 1, + BLACK_OO = 2, + WHITE_OOO = 4, + BLACK_OOO = 8, + ALL_CASTLES = 15 +}; + +/// Game phase +enum Phase { + PHASE_ENDGAME = 0, + PHASE_MIDGAME = 128 +}; + + +/// The StateInfo struct stores information we need to restore a Position +/// object to its previous state when we retract a move. Whenever a move +/// is made on the board (by calling Position::do_move), an StateInfo object +/// must be passed as a parameter. + +struct StateInfo { + Key pawnKey, materialKey; + int castleRights, rule50, gamePly, pliesFromNull; + Square epSquare; + Score value; + Value npMaterial[2]; + + PieceType capturedType; + Key key; + Bitboard checkersBB; + StateInfo* previous; +}; + + +/// The position data structure. A position consists of the following data: +/// +/// * For each piece type, a bitboard representing the squares occupied +/// by pieces of that type. +/// * For each color, a bitboard representing the squares occupied by +/// pieces of that color. +/// * A bitboard of all occupied squares. +/// * A bitboard of all checking pieces. +/// * A 64-entry array of pieces, indexed by the squares of the board. +/// * The current side to move. +/// * Information about the castling rights for both sides. +/// * The initial files of the kings and both pairs of rooks. This is +/// used to implement the Chess960 castling rules. +/// * The en passant square (which is SQ_NONE if no en passant capture is +/// possible). +/// * The squares of the kings for both sides. +/// * Hash keys for the position itself, the current pawn structure, and +/// the current material situation. +/// * Hash keys for all previous positions in the game for detecting +/// repetition draws. +/// * A counter for detecting 50 move rule draws. + +class Position { + + Position(); // No default or copy c'tor allowed + Position(const Position& pos); + +public: + enum GamePhase { + MidGame, + EndGame + }; + + // Constructors + Position(const Position& pos, int threadID); + Position(const std::string& fen, bool isChess960, int threadID); + + // Text input/output + void from_fen(const std::string& fen, bool isChess960); + const std::string to_fen() const; + void print(Move m = MOVE_NONE) const; + + // Copying + void flip(); + + // The piece on a given square + Piece piece_on(Square s) const; + PieceType type_of_piece_on(Square s) const; + Color color_of_piece_on(Square s) const; + bool square_is_empty(Square s) const; + bool square_is_occupied(Square s) const; + Value midgame_value_of_piece_on(Square s) const; + Value endgame_value_of_piece_on(Square s) const; + + // Side to move + Color side_to_move() const; + + // Bitboard representation of the position + Bitboard empty_squares() const; + Bitboard occupied_squares() const; + Bitboard pieces_of_color(Color c) const; + Bitboard pieces(PieceType pt) const; + Bitboard pieces(PieceType pt, Color c) const; + Bitboard pieces(PieceType pt1, PieceType pt2) const; + Bitboard pieces(PieceType pt1, PieceType pt2, Color c) const; + + // Number of pieces of each color and type + int piece_count(Color c, PieceType pt) const; + + // The en passant square + Square ep_square() const; + + // Current king position for each color + Square king_square(Color c) const; + + // Castling rights + bool can_castle_kingside(Color c) const; + bool can_castle_queenside(Color c) const; + bool can_castle(Color c) const; + Square initial_kr_square(Color c) const; + Square initial_qr_square(Color c) const; + + // Bitboards for pinned pieces and discovered check candidates + Bitboard discovered_check_candidates(Color c) const; + Bitboard pinned_pieces(Color c) const; + + // Checking pieces and under check information + Bitboard checkers() const; + bool in_check() const; + + // Piece lists + Square piece_list(Color c, PieceType pt, int index) const; + const Square* piece_list_begin(Color c, PieceType pt) const; + + // Information about attacks to or from a given square + Bitboard attackers_to(Square s) const; + Bitboard attacks_from(Piece p, Square s) const; + static Bitboard attacks_from(Piece p, Square s, Bitboard occ); + template Bitboard attacks_from(Square s) const; + template Bitboard attacks_from(Square s, Color c) const; + + // Properties of moves + bool pl_move_is_legal(Move m, Bitboard pinned) const; + bool pl_move_is_evasion(Move m, Bitboard pinned) const; + bool move_is_legal(const Move m) const; + bool move_is_legal(const Move m, Bitboard pinned) const; + bool move_gives_check(Move m) const; + bool move_gives_check(Move m, const CheckInfo& ci) const; + bool move_is_capture(Move m) const; + bool move_is_capture_or_promotion(Move m) const; + bool move_is_passed_pawn_push(Move m) const; + bool move_attacks_square(Move m, Square s) const; + + // Piece captured with previous moves + PieceType captured_piece_type() const; + + // Information about pawns + bool pawn_is_passed(Color c, Square s) const; + + // Weak squares + bool square_is_weak(Square s, Color c) const; + + // Doing and undoing moves + void do_setup_move(Move m); + void do_move(Move m, StateInfo& st); + void do_move(Move m, StateInfo& st, const CheckInfo& ci, bool moveIsCheck); + void undo_move(Move m); + void do_null_move(StateInfo& st); + void undo_null_move(); + + // Static exchange evaluation + int see(Square from, Square to) const; + int see(Move m) const; + int see_sign(Move m) const; + + // Accessing hash keys + Key get_key() const; + Key get_exclusion_key() const; + Key get_pawn_key() const; + Key get_material_key() const; + + // Incremental evaluation + Score value() const; + Value non_pawn_material(Color c) const; + static Score pst_delta(Piece piece, Square from, Square to); + + // Game termination checks + bool is_mate() const; + bool is_draw() const; + + // Number of plies from starting position + int startpos_ply_counter() const; + + // Other properties of the position + bool opposite_colored_bishops() const; + bool has_pawn_on_7th(Color c) const; + bool is_chess960() const; + + // Current thread ID searching on the position + int thread() const; + + int64_t nodes_searched() const; + void set_nodes_searched(int64_t n); + + // Position consistency check, for debugging + bool is_ok(int* failedStep = NULL) const; + + // Static member functions + static void init_zobrist(); + static void init_piece_square_tables(); + +private: + + // Initialization helper functions (used while setting up a position) + void clear(); + void detach(); + void put_piece(Piece p, Square s); + void do_allow_oo(Color c); + void do_allow_ooo(Color c); + bool set_castling_rights(char token); + + // Helper functions for doing and undoing moves + void do_capture_move(Key& key, PieceType capture, Color them, Square to, bool ep); + void do_castle_move(Move m); + void undo_castle_move(Move m); + void find_checkers(); + + template + Bitboard hidden_checkers(Color c) const; + + // Computing hash keys from scratch (for initialization and debugging) + Key compute_key() const; + Key compute_pawn_key() const; + Key compute_material_key() const; + + // Computing incremental evaluation scores and material counts + static Score pst(Color c, PieceType pt, Square s); + Score compute_value() const; + Value compute_non_pawn_material(Color c) const; + + // Board + Piece board[64]; + + // Bitboards + Bitboard byTypeBB[8], byColorBB[2]; + + // Piece counts + int pieceCount[2][8]; // [color][pieceType] + + // Piece lists + Square pieceList[2][8][16]; // [color][pieceType][index] + int index[64]; // [square] + + // Other info + Color sideToMove; + Key history[MaxGameLength]; + int castleRightsMask[64]; + StateInfo startState; + File initialKFile, initialKRFile, initialQRFile; + bool chess960; + int startPosPlyCounter; + int threadID; + int64_t nodes; + StateInfo* st; + + // Static variables + static Key zobrist[2][8][64]; + static Key zobEp[64]; + static Key zobCastle[16]; + static Key zobSideToMove; + static Score PieceSquareTable[16][64]; + static Key zobExclusion; + static const Value seeValues[8]; + static const Value PieceValueMidgame[17]; + static const Value PieceValueEndgame[17]; +}; + +inline int64_t Position::nodes_searched() const { + return nodes; +} + +inline void Position::set_nodes_searched(int64_t n) { + nodes = n; +} + +inline Piece Position::piece_on(Square s) const { + return board[s]; +} + +inline Color Position::color_of_piece_on(Square s) const { + return color_of_piece(piece_on(s)); +} + +inline PieceType Position::type_of_piece_on(Square s) const { + return type_of_piece(piece_on(s)); +} + +inline bool Position::square_is_empty(Square s) const { + return piece_on(s) == PIECE_NONE; +} + +inline bool Position::square_is_occupied(Square s) const { + return !square_is_empty(s); +} + +inline Value Position::midgame_value_of_piece_on(Square s) const { + return PieceValueMidgame[piece_on(s)]; +} + +inline Value Position::endgame_value_of_piece_on(Square s) const { + return PieceValueEndgame[piece_on(s)]; +} + +inline Color Position::side_to_move() const { + return sideToMove; +} + +inline Bitboard Position::occupied_squares() const { + return byTypeBB[0]; +} + +inline Bitboard Position::empty_squares() const { + return ~occupied_squares(); +} + +inline Bitboard Position::pieces_of_color(Color c) const { + return byColorBB[c]; +} + +inline Bitboard Position::pieces(PieceType pt) const { + return byTypeBB[pt]; +} + +inline Bitboard Position::pieces(PieceType pt, Color c) const { + return byTypeBB[pt] & byColorBB[c]; +} + +inline Bitboard Position::pieces(PieceType pt1, PieceType pt2) const { + return byTypeBB[pt1] | byTypeBB[pt2]; +} + +inline Bitboard Position::pieces(PieceType pt1, PieceType pt2, Color c) const { + return (byTypeBB[pt1] | byTypeBB[pt2]) & byColorBB[c]; +} + +inline int Position::piece_count(Color c, PieceType pt) const { + return pieceCount[c][pt]; +} + +inline Square Position::piece_list(Color c, PieceType pt, int idx) const { + return pieceList[c][pt][idx]; +} + +inline const Square* Position::piece_list_begin(Color c, PieceType pt) const { + return pieceList[c][pt]; +} + +inline Square Position::ep_square() const { + return st->epSquare; +} + +inline Square Position::king_square(Color c) const { + return pieceList[c][KING][0]; +} + +inline bool Position::can_castle_kingside(Color side) const { + return st->castleRights & (1+int(side)); +} + +inline bool Position::can_castle_queenside(Color side) const { + return st->castleRights & (4+4*int(side)); +} + +inline bool Position::can_castle(Color side) const { + return can_castle_kingside(side) || can_castle_queenside(side); +} + +inline Square Position::initial_kr_square(Color c) const { + return relative_square(c, make_square(initialKRFile, RANK_1)); +} + +inline Square Position::initial_qr_square(Color c) const { + return relative_square(c, make_square(initialQRFile, RANK_1)); +} + +template<> +inline Bitboard Position::attacks_from(Square s, Color c) const { + return StepAttacksBB[make_piece(c, PAWN)][s]; +} + +template // Knight and King and white pawns +inline Bitboard Position::attacks_from(Square s) const { + return StepAttacksBB[Piece][s]; +} + +template<> +inline Bitboard Position::attacks_from(Square s) const { + return bishop_attacks_bb(s, occupied_squares()); +} + +template<> +inline Bitboard Position::attacks_from(Square s) const { + return rook_attacks_bb(s, occupied_squares()); +} + +template<> +inline Bitboard Position::attacks_from(Square s) const { + return attacks_from(s) | attacks_from(s); +} + +inline Bitboard Position::checkers() const { + return st->checkersBB; +} + +inline bool Position::in_check() const { + return st->checkersBB != EmptyBoardBB; +} + +inline bool Position::pawn_is_passed(Color c, Square s) const { + return !(pieces(PAWN, opposite_color(c)) & passed_pawn_mask(c, s)); +} + +inline bool Position::square_is_weak(Square s, Color c) const { + return !(pieces(PAWN, opposite_color(c)) & attack_span_mask(c, s)); +} + +inline Key Position::get_key() const { + return st->key; +} + +inline Key Position::get_exclusion_key() const { + return st->key ^ zobExclusion; +} + +inline Key Position::get_pawn_key() const { + return st->pawnKey; +} + +inline Key Position::get_material_key() const { + return st->materialKey; +} + +inline Score Position::pst(Color c, PieceType pt, Square s) { + return PieceSquareTable[make_piece(c, pt)][s]; +} + +inline Score Position::pst_delta(Piece piece, Square from, Square to) { + return PieceSquareTable[piece][to] - PieceSquareTable[piece][from]; +} + +inline Score Position::value() const { + return st->value; +} + +inline Value Position::non_pawn_material(Color c) const { + return st->npMaterial[c]; +} + +inline bool Position::move_is_passed_pawn_push(Move m) const { + + Color c = side_to_move(); + return piece_on(move_from(m)) == make_piece(c, PAWN) + && pawn_is_passed(c, move_to(m)); +} + +inline int Position::startpos_ply_counter() const { + return startPosPlyCounter; +} + +inline bool Position::opposite_colored_bishops() const { + + return piece_count(WHITE, BISHOP) == 1 && piece_count(BLACK, BISHOP) == 1 + && opposite_color_squares(piece_list(WHITE, BISHOP, 0), piece_list(BLACK, BISHOP, 0)); +} + +inline bool Position::has_pawn_on_7th(Color c) const { + return pieces(PAWN, c) & rank_bb(relative_rank(c, RANK_7)); +} + +inline bool Position::is_chess960() const { + return chess960; +} + +inline bool Position::move_is_capture(Move m) const { + + // Move must not be MOVE_NONE ! + return (m & (3 << 15)) ? !move_is_castle(m) : !square_is_empty(move_to(m)); +} + +inline bool Position::move_is_capture_or_promotion(Move m) const { + + // Move must not be MOVE_NONE ! + return (m & (0x1F << 12)) ? !move_is_castle(m) : !square_is_empty(move_to(m)); +} + +inline PieceType Position::captured_piece_type() const { + return st->capturedType; +} + +inline int Position::thread() const { + return threadID; +} + +inline void Position::do_allow_oo(Color c) { + st->castleRights |= (1 + int(c)); +} + +inline void Position::do_allow_ooo(Color c) { + st->castleRights |= (4 + 4*int(c)); +} + +#endif // !defined(POSITION_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/psqtab.h b/DroidFish/jni/stockfish/psqtab.h new file mode 100644 index 0000000..f56ba9c --- /dev/null +++ b/DroidFish/jni/stockfish/psqtab.h @@ -0,0 +1,185 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(PSQTAB_H_INCLUDED) +#define PSQTAB_H_INCLUDED + +#include "types.h" + +namespace { + +//// +//// Constants modified by Joona Kiiski +//// + +const Value MP = PawnValueMidgame; +const Value MK = KnightValueMidgame; +const Value MB = BishopValueMidgame; +const Value MR = RookValueMidgame; +const Value MQ = QueenValueMidgame; + +const Value EP = PawnValueEndgame; +const Value EK = KnightValueEndgame; +const Value EB = BishopValueEndgame; +const Value ER = RookValueEndgame; +const Value EQ = QueenValueEndgame; + +const int MgPST[][64] = { + { }, + {// Pawn + // A B C D E F G H + 0, 0, 0, 0, 0, 0, 0, 0, + MP-28, MP-6, MP+ 4, MP+14, MP+14, MP+ 4, MP-6, MP-28, + MP-28, MP-6, MP+ 9, MP+36, MP+36, MP+ 9, MP-6, MP-28, + MP-28, MP-6, MP+17, MP+58, MP+58, MP+17, MP-6, MP-28, + MP-28, MP-6, MP+17, MP+36, MP+36, MP+17, MP-6, MP-28, + MP-28, MP-6, MP+ 9, MP+14, MP+14, MP+ 9, MP-6, MP-28, + MP-28, MP-6, MP+ 4, MP+14, MP+14, MP+ 4, MP-6, MP-28, + 0, 0, 0, 0, 0, 0, 0, 0 + }, + {// Knight + // A B C D E F G H + MK-135, MK-107, MK-80, MK-67, MK-67, MK-80, MK-107, MK-135, + MK- 93, MK- 67, MK-39, MK-25, MK-25, MK-39, MK- 67, MK- 93, + MK- 53, MK- 25, MK+ 1, MK+13, MK+13, MK+ 1, MK- 25, MK- 53, + MK- 25, MK+ 1, MK+27, MK+41, MK+41, MK+27, MK+ 1, MK- 25, + MK- 11, MK+ 13, MK+41, MK+55, MK+55, MK+41, MK+ 13, MK- 11, + MK- 11, MK+ 13, MK+41, MK+55, MK+55, MK+41, MK+ 13, MK- 11, + MK- 53, MK- 25, MK+ 1, MK+13, MK+13, MK+ 1, MK- 25, MK- 53, + MK-193, MK- 67, MK-39, MK-25, MK-25, MK-39, MK- 67, MK-193 + }, + {// Bishop + // A B C D E F G H + MB-40, MB-40, MB-35, MB-30, MB-30, MB-35, MB-40, MB-40, + MB-17, MB+ 0, MB- 4, MB+ 0, MB+ 0, MB- 4, MB+ 0, MB-17, + MB-13, MB- 4, MB+ 8, MB+ 4, MB+ 4, MB+ 8, MB- 4, MB-13, + MB- 8, MB+ 0, MB+ 4, MB+17, MB+17, MB+ 4, MB+ 0, MB- 8, + MB- 8, MB+ 0, MB+ 4, MB+17, MB+17, MB+ 4, MB+ 0, MB- 8, + MB-13, MB- 4, MB+ 8, MB+ 4, MB+ 4, MB+ 8, MB- 4, MB-13, + MB-17, MB+ 0, MB- 4, MB+ 0, MB+ 0, MB- 4, MB+ 0, MB-17, + MB-17, MB-17, MB-13, MB- 8, MB- 8, MB-13, MB-17, MB-17 + }, + {// Rook + // A B C D E F G H + MR-12, MR-7, MR-2, MR+2, MR+2, MR-2, MR-7, MR-12, + MR-12, MR-7, MR-2, MR+2, MR+2, MR-2, MR-7, MR-12, + MR-12, MR-7, MR-2, MR+2, MR+2, MR-2, MR-7, MR-12, + MR-12, MR-7, MR-2, MR+2, MR+2, MR-2, MR-7, MR-12, + MR-12, MR-7, MR-2, MR+2, MR+2, MR-2, MR-7, MR-12, + MR-12, MR-7, MR-2, MR+2, MR+2, MR-2, MR-7, MR-12, + MR-12, MR-7, MR-2, MR+2, MR+2, MR-2, MR-7, MR-12, + MR-12, MR-7, MR-2, MR+2, MR+2, MR-2, MR-7, MR-12 + }, + {// Queen + // A B C D E F G H + MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, + MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, + MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, + MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, + MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, + MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, + MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, + MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8, MQ+8 + }, + {// King + //A B C D E F G H + 287, 311, 262, 214, 214, 262, 311, 287, + 262, 287, 238, 190, 190, 238, 287, 262, + 214, 238, 190, 142, 142, 190, 238, 214, + 190, 214, 167, 119, 119, 167, 214, 190, + 167, 190, 142, 94, 94, 142, 190, 167, + 142, 167, 119, 69, 69, 119, 167, 142, + 119, 142, 94, 46, 46, 94, 142, 119, + 94, 119, 69, 21, 21, 69, 119, 94 + } +}; + +const int EgPST[][64] = { + { }, + {// Pawn + // A B C D E F G H + 0, 0, 0, 0, 0, 0, 0, 0, + EP-8, EP-8, EP-8, EP-8, EP-8, EP-8, EP-8, EP-8, + EP-8, EP-8, EP-8, EP-8, EP-8, EP-8, EP-8, EP-8, + EP-8, EP-8, EP-8, EP-8, EP-8, EP-8, EP-8, EP-8, + EP-8, EP-8, EP-8, EP-8, EP-8, EP-8, EP-8, EP-8, + EP-8, EP-8, EP-8, EP-8, EP-8, EP-8, EP-8, EP-8, + EP-8, EP-8, EP-8, EP-8, EP-8, EP-8, EP-8, EP-8, + 0, 0, 0, 0, 0, 0, 0, 0 + }, + {// Knight + // A B C D E F G H + EK-104, EK-79, EK-55, EK-42, EK-42, EK-55, EK-79, EK-104, + EK- 79, EK-55, EK-30, EK-17, EK-17, EK-30, EK-55, EK- 79, + EK- 55, EK-30, EK- 6, EK+ 5, EK+ 5, EK- 6, EK-30, EK- 55, + EK- 42, EK-17, EK+ 5, EK+18, EK+18, EK+ 5, EK-17, EK- 42, + EK- 42, EK-17, EK+ 5, EK+18, EK+18, EK+ 5, EK-17, EK- 42, + EK- 55, EK-30, EK- 6, EK+ 5, EK+ 5, EK- 6, EK-30, EK- 55, + EK- 79, EK-55, EK-30, EK-17, EK-17, EK-30, EK-55, EK- 79, + EK-104, EK-79, EK-55, EK-42, EK-42, EK-55, EK-79, EK-104 + }, + {// Bishop + // A B C D E F G H + EB-59, EB-42, EB-35, EB-26, EB-26, EB-35, EB-42, EB-59, + EB-42, EB-26, EB-18, EB-11, EB-11, EB-18, EB-26, EB-42, + EB-35, EB-18, EB-11, EB- 4, EB- 4, EB-11, EB-18, EB-35, + EB-26, EB-11, EB- 4, EB+ 4, EB+ 4, EB- 4, EB-11, EB-26, + EB-26, EB-11, EB- 4, EB+ 4, EB+ 4, EB- 4, EB-11, EB-26, + EB-35, EB-18, EB-11, EB- 4, EB- 4, EB-11, EB-18, EB-35, + EB-42, EB-26, EB-18, EB-11, EB-11, EB-18, EB-26, EB-42, + EB-59, EB-42, EB-35, EB-26, EB-26, EB-35, EB-42, EB-59 + }, + {// Rook + // A B C D E F G H + ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, + ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, + ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, + ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, + ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, + ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, + ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, + ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, ER+3, ER+3 + }, + {// Queen + // A B C D E F G H + EQ-80, EQ-54, EQ-42, EQ-30, EQ-30, EQ-42, EQ-54, EQ-80, + EQ-54, EQ-30, EQ-18, EQ- 6, EQ- 6, EQ-18, EQ-30, EQ-54, + EQ-42, EQ-18, EQ- 6, EQ+ 6, EQ+ 6, EQ- 6, EQ-18, EQ-42, + EQ-30, EQ- 6, EQ+ 6, EQ+18, EQ+18, EQ+ 6, EQ- 6, EQ-30, + EQ-30, EQ- 6, EQ+ 6, EQ+18, EQ+18, EQ+ 6, EQ- 6, EQ-30, + EQ-42, EQ-18, EQ- 6, EQ+ 6, EQ+ 6, EQ- 6, EQ-18, EQ-42, + EQ-54, EQ-30, EQ-18, EQ- 6, EQ- 6, EQ-18, EQ-30, EQ-54, + EQ-80, EQ-54, EQ-42, EQ-30, EQ-30, EQ-42, EQ-54, EQ-80 + }, + {// King + //A B C D E F G H + 18, 77, 105, 135, 135, 105, 77, 18, + 77, 135, 165, 193, 193, 165, 135, 77, + 105, 165, 193, 222, 222, 193, 165, 105, + 135, 193, 222, 251, 251, 222, 193, 135, + 135, 193, 222, 251, 251, 222, 193, 135, + 105, 165, 193, 222, 222, 193, 165, 105, + 77, 135, 165, 193, 193, 165, 135, 77, + 18, 77, 105, 135, 135, 105, 77, 18 + } +}; + +} // namespace + +#endif // !defined(PSQTAB_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/rkiss.h b/DroidFish/jni/stockfish/rkiss.h new file mode 100644 index 0000000..5c01a0e --- /dev/null +++ b/DroidFish/jni/stockfish/rkiss.h @@ -0,0 +1,77 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + This file is based on original code by Heinz van Saanen and is + available under the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ** George Marsaglia invented the RNG-Kiss-family in the early 90's. + ** This is a specific version that Heinz van Saanen derived and + ** tested from some public domain code by Bob Jenkins: + ** + ** Quite platform independent + ** Passes ALL dieharder tests! Here *nix sys-rand() e.g. fails miserably:-) + ** ~12 times faster than my *nix sys-rand() + ** ~4 times faster than SSE2-version of Mersenne twister + ** Average cycle length: ~2^126 + ** 64 bit seed + ** Return doubles with a full 53 bit mantissa + ** Thread safe +*/ + +#if !defined(RKISS_H_INCLUDED) +#define RKISS_H_INCLUDED + +#include "types.h" + +class RKISS { + + // Keep variables always together + struct S { uint64_t a, b, c, d; } s; + + uint64_t rotate(uint64_t x, uint64_t k) const { + return (x << k) | (x >> (64 - k)); + } + + // Return 64 bit unsigned integer in between [0, 2^64 - 1] + uint64_t rand64() { + + const uint64_t + e = s.a - rotate(s.b, 7); + s.a = s.b ^ rotate(s.c, 13); + s.b = s.c + rotate(s.d, 37); + s.c = s.d + e; + return s.d = e + s.a; + } + + // Init seed and scramble a few rounds + void raninit() { + + s.a = 0xf1ea5eed; + s.b = s.c = s.d = 0xd4e12c77; + for (int i = 0; i < 73; i++) + rand64(); + } + +public: + RKISS() { raninit(); } + template T rand() { return T(rand64()); } +}; + +#endif // !defined(RKISS_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/search.cpp b/DroidFish/jni/stockfish/search.cpp new file mode 100644 index 0000000..49473a2 --- /dev/null +++ b/DroidFish/jni/stockfish/search.cpp @@ -0,0 +1,2200 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "book.h" +#include "evaluate.h" +#include "history.h" +#include "misc.h" +#include "move.h" +#include "movegen.h" +#include "movepick.h" +#include "search.h" +#include "timeman.h" +#include "thread.h" +#include "tt.h" +#include "ucioption.h" + +using std::cout; +using std::endl; + +namespace { + + // Set to true to force running with one thread. Used for debugging + const bool FakeSplit = false; + + // Different node types, used as template parameter + enum NodeType { NonPV, PV }; + + // RootMove struct is used for moves at the root of the tree. For each root + // move, we store two scores, a node count, and a PV (really a refutation + // in the case of moves which fail low). Value pv_score is normally set at + // -VALUE_INFINITE for all non-pv moves, while non_pv_score is computed + // according to the order in which moves are returned by MovePicker. + struct RootMove { + + RootMove(); + RootMove(const RootMove& rm) { *this = rm; } + RootMove& operator=(const RootMove& rm); + + // RootMove::operator<() is the comparison function used when + // sorting the moves. A move m1 is considered to be better + // than a move m2 if it has an higher pv_score, or if it has + // equal pv_score but m1 has the higher non_pv_score. In this way + // we are guaranteed that PV moves are always sorted as first. + bool operator<(const RootMove& m) const { + return pv_score != m.pv_score ? pv_score < m.pv_score + : non_pv_score < m.non_pv_score; + } + + void extract_pv_from_tt(Position& pos); + void insert_pv_in_tt(Position& pos); + std::string pv_info_to_uci(Position& pos, int depth, int selDepth, + Value alpha, Value beta, int pvIdx); + int64_t nodes; + Value pv_score; + Value non_pv_score; + Move pv[PLY_MAX_PLUS_2]; + }; + + // RootMoveList struct is just a vector of RootMove objects, + // with an handful of methods above the standard ones. + struct RootMoveList : public std::vector { + + typedef std::vector Base; + + void init(Position& pos, Move searchMoves[]); + void sort() { insertion_sort(begin(), end()); } + void sort_multipv(int n) { insertion_sort(begin(), begin() + n); } + + int bestMoveChanges; + }; + + // MovePickerExt template class extends MovePicker and allows to choose at compile + // time the proper moves source according to the type of node. In the default case + // we simply create and use a standard MovePicker object. + template struct MovePickerExt : public MovePicker { + + MovePickerExt(const Position& p, Move ttm, Depth d, const History& h, SearchStack* ss, Value b) + : MovePicker(p, ttm, d, h, ss, b) {} + + RootMoveList::iterator rm; // Dummy, needed to compile + }; + + // In case of a SpNode we use split point's shared MovePicker object as moves source + template<> struct MovePickerExt : public MovePicker { + + MovePickerExt(const Position& p, Move ttm, Depth d, const History& h, SearchStack* ss, Value b) + : MovePicker(p, ttm, d, h, ss, b), mp(ss->sp->mp) {} + + Move get_next_move() { return mp->get_next_move(); } + + RootMoveList::iterator rm; // Dummy, needed to compile + MovePicker* mp; + }; + + // In case of a Root node we use RootMoveList as moves source + template<> struct MovePickerExt : public MovePicker { + + MovePickerExt(const Position&, Move, Depth, const History&, SearchStack*, Value); + Move get_next_move(); + + RootMoveList::iterator rm; + bool firstCall; + }; + + + /// Constants + + // Lookup table to check if a Piece is a slider and its access function + const bool Slidings[18] = { 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1 }; + inline bool piece_is_slider(Piece p) { return Slidings[p]; } + + // Step 6. Razoring + + // Maximum depth for razoring + const Depth RazorDepth = 4 * ONE_PLY; + + // Dynamic razoring margin based on depth + inline Value razor_margin(Depth d) { return Value(0x200 + 0x10 * int(d)); } + + // Maximum depth for use of dynamic threat detection when null move fails low + const Depth ThreatDepth = 5 * ONE_PLY; + + // Step 9. Internal iterative deepening + + // Minimum depth for use of internal iterative deepening + const Depth IIDDepth[] = { 8 * ONE_PLY, 5 * ONE_PLY }; + + // At Non-PV nodes we do an internal iterative deepening search + // when the static evaluation is bigger then beta - IIDMargin. + const Value IIDMargin = Value(0x100); + + // Step 11. Decide the new search depth + + // Extensions. Array index 0 is used for non-PV nodes, index 1 for PV nodes + const Depth CheckExtension[] = { ONE_PLY / 2, ONE_PLY / 1 }; + const Depth PawnEndgameExtension[] = { ONE_PLY / 1, ONE_PLY / 1 }; + const Depth PawnPushTo7thExtension[] = { ONE_PLY / 2, ONE_PLY / 2 }; + const Depth PassedPawnExtension[] = { DEPTH_ZERO, ONE_PLY / 2 }; + + // Minimum depth for use of singular extension + const Depth SingularExtensionDepth[] = { 8 * ONE_PLY, 6 * ONE_PLY }; + + // Step 12. Futility pruning + + // Futility margin for quiescence search + const Value FutilityMarginQS = Value(0x80); + + // Futility lookup tables (initialized at startup) and their access functions + Value FutilityMargins[16][64]; // [depth][moveNumber] + int FutilityMoveCounts[32]; // [depth] + + inline Value futility_margin(Depth d, int mn) { + + return d < 7 * ONE_PLY ? FutilityMargins[Max(d, 1)][Min(mn, 63)] + : 2 * VALUE_INFINITE; + } + + inline int futility_move_count(Depth d) { + + return d < 16 * ONE_PLY ? FutilityMoveCounts[d] : MAX_MOVES; + } + + // Step 14. Reduced search + + // Reduction lookup tables (initialized at startup) and their access function + int8_t Reductions[2][64][64]; // [pv][depth][moveNumber] + + template inline Depth reduction(Depth d, int mn) { + + return (Depth) Reductions[PV][Min(d / ONE_PLY, 63)][Min(mn, 63)]; + } + + // Easy move margin. An easy move candidate must be at least this much + // better than the second best move. + const Value EasyMoveMargin = Value(0x200); + + + /// Namespace variables + + // Root move list + RootMoveList Rml; + + // MultiPV mode + int MultiPV, UCIMultiPV; + + // Time management variables + bool StopOnPonderhit, FirstRootMove, StopRequest, QuitRequest, AspirationFailLow; + TimeManager TimeMgr; + SearchLimits Limits; + + // Log file + std::ofstream LogFile; + + // Skill level adjustment + int SkillLevel; + bool SkillLevelEnabled; + + // Node counters, used only by thread[0] but try to keep in different cache + // lines (64 bytes each) from the heavy multi-thread read accessed variables. + bool SendSearchedNodes; + int NodesSincePoll; + int NodesBetweenPolls = 30000; + + // History table + History H; + + + /// Local functions + + Move id_loop(Position& pos, Move searchMoves[], Move* ponderMove); + + template + Value search(Position& pos, SearchStack* ss, Value alpha, Value beta, Depth depth); + + template + Value qsearch(Position& pos, SearchStack* ss, Value alpha, Value beta, Depth depth); + + template + inline Value search(Position& pos, SearchStack* ss, Value alpha, Value beta, Depth depth) { + + return depth < ONE_PLY ? qsearch(pos, ss, alpha, beta, DEPTH_ZERO) + : search(pos, ss, alpha, beta, depth); + } + + template + Depth extension(const Position& pos, Move m, bool captureOrPromotion, bool moveIsCheck, bool* dangerous); + + bool check_is_dangerous(Position &pos, Move move, Value futilityBase, Value beta, Value *bValue); + bool connected_moves(const Position& pos, Move m1, Move m2); + Value value_to_tt(Value v, int ply); + Value value_from_tt(Value v, int ply); + bool ok_to_use_TT(const TTEntry* tte, Depth depth, Value beta, int ply); + bool connected_threat(const Position& pos, Move m, Move threat); + Value refine_eval(const TTEntry* tte, Value defaultEval, int ply); + void update_history(const Position& pos, Move move, Depth depth, Move movesSearched[], int moveCount); + void update_gains(const Position& pos, Move move, Value before, Value after); + void do_skill_level(Move* best, Move* ponder); + + int current_search_time(int set = 0); + std::string value_to_uci(Value v); + std::string speed_to_uci(int64_t nodes); + void poll(const Position& pos); + void wait_for_stop_or_ponderhit(); + + // Overload operator<<() to make it easier to print moves in a coordinate + // notation compatible with UCI protocol. + std::ostream& operator<<(std::ostream& os, Move m) { + + bool chess960 = (os.iword(0) != 0); // See set960() + return os << move_to_uci(m, chess960); + } + + // When formatting a move for std::cout we must know if we are in Chess960 + // or not. To keep using the handy operator<<() on the move the trick is to + // embed this flag in the stream itself. Function-like named enum set960 is + // used as a custom manipulator and the stream internal general-purpose array, + // accessed through ios_base::iword(), is used to pass the flag to the move's + // operator<<() that will read it to properly format castling moves. + enum set960 {}; + + std::ostream& operator<< (std::ostream& os, const set960& f) { + + os.iword(0) = int(f); + return os; + } + +} // namespace + + +/// init_search() is called during startup to initialize various lookup tables + +void init_search() { + + int d; // depth (ONE_PLY == 2) + int hd; // half depth (ONE_PLY == 1) + int mc; // moveCount + + // Init reductions array + for (hd = 1; hd < 64; hd++) for (mc = 1; mc < 64; mc++) + { + double pvRed = log(double(hd)) * log(double(mc)) / 3.0; + double nonPVRed = 0.33 + log(double(hd)) * log(double(mc)) / 2.25; + Reductions[PV][hd][mc] = (int8_t) ( pvRed >= 1.0 ? floor( pvRed * int(ONE_PLY)) : 0); + Reductions[NonPV][hd][mc] = (int8_t) (nonPVRed >= 1.0 ? floor(nonPVRed * int(ONE_PLY)) : 0); + } + + // Init futility margins array + for (d = 1; d < 16; d++) for (mc = 0; mc < 64; mc++) + FutilityMargins[d][mc] = Value(112 * int(log(double(d * d) / 2) / log(2.0) + 1.001) - 8 * mc + 45); + + // Init futility move count array + for (d = 0; d < 32; d++) + FutilityMoveCounts[d] = int(3.001 + 0.25 * pow(d, 2.0)); +} + + +/// perft() is our utility to verify move generation. All the leaf nodes up to +/// the given depth are generated and counted and the sum returned. + +int64_t perft(Position& pos, Depth depth) { + + MoveStack mlist[MAX_MOVES]; + StateInfo st; + Move m; + int64_t sum = 0; + + // Generate all legal moves + MoveStack* last = generate(pos, mlist); + + // If we are at the last ply we don't need to do and undo + // the moves, just to count them. + if (depth <= ONE_PLY) + return int(last - mlist); + + // Loop through all legal moves + CheckInfo ci(pos); + for (MoveStack* cur = mlist; cur != last; cur++) + { + m = cur->move; + pos.do_move(m, st, ci, pos.move_gives_check(m, ci)); + sum += perft(pos, depth - ONE_PLY); + pos.undo_move(m); + } + return sum; +} + + +/// think() is the external interface to Stockfish's search, and is called when +/// the program receives the UCI 'go' command. It initializes various global +/// variables, and calls id_loop(). It returns false when a "quit" command is +/// received during the search. + +bool think(Position& pos, const SearchLimits& limits, Move searchMoves[]) { + + static Book book; + + // Initialize global search-related variables + StopOnPonderhit = StopRequest = QuitRequest = AspirationFailLow = SendSearchedNodes = false; + NodesSincePoll = 0; + current_search_time(get_system_time()); + Limits = limits; + TimeMgr.init(Limits, pos.startpos_ply_counter()); + + // Set best NodesBetweenPolls interval to avoid lagging under time pressure + if (Limits.maxNodes) + NodesBetweenPolls = Min(Limits.maxNodes, 30000); + else if (Limits.time && Limits.time < 1000) + NodesBetweenPolls = 1000; + else if (Limits.time && Limits.time < 5000) + NodesBetweenPolls = 5000; + else + NodesBetweenPolls = 30000; + NodesBetweenPolls /= 16; + + // Look for a book move + if (Options["OwnBook"].value()) + { + if (Options["Book File"].value() != book.name()) + book.open(Options["Book File"].value()); + + Move bookMove = book.get_move(pos, Options["Best Book Move"].value()); + if (bookMove != MOVE_NONE) + { + if (Limits.ponder) + wait_for_stop_or_ponderhit(); + + cout << "bestmove " << bookMove << endl; + return !QuitRequest; + } + } + + // Read UCI options + UCIMultiPV = Options["MultiPV"].value(); + SkillLevel = Options["Skill Level"].value(); + + read_evaluation_uci_options(pos.side_to_move()); + Threads.read_uci_options(); + + // If needed allocate pawn and material hash tables and adjust TT size + Threads.init_hash_tables(); + TT.set_size(Options["Hash"].value()); + + if (Options["Clear Hash"].value()) + { + Options["Clear Hash"].set_value("false"); + TT.clear(); + } + + // Do we have to play with skill handicap? In this case enable MultiPV that + // we will use behind the scenes to retrieve a set of possible moves. + SkillLevelEnabled = (SkillLevel < 20); + MultiPV = (SkillLevelEnabled ? Max(UCIMultiPV, 4) : UCIMultiPV); + + // Wake up needed threads and reset maxPly counter + for (int i = 0; i < Threads.size(); i++) + { + Threads[i].wake_up(); + Threads[i].maxPly = 0; + } + + // Write to log file and keep it open to be accessed during the search + if (Options["Use Search Log"].value()) + { + std::string name = Options["Search Log Filename"].value(); + LogFile.open(name.c_str(), std::ios::out | std::ios::app); + + if (LogFile.is_open()) + LogFile << "\nSearching: " << pos.to_fen() + << "\ninfinite: " << Limits.infinite + << " ponder: " << Limits.ponder + << " time: " << Limits.time + << " increment: " << Limits.increment + << " moves to go: " << Limits.movesToGo + << endl; + } + + // We're ready to start thinking. Call the iterative deepening loop function + Move ponderMove = MOVE_NONE; + Move bestMove = id_loop(pos, searchMoves, &ponderMove); + + cout << "info" << speed_to_uci(pos.nodes_searched()) << endl; + + // Write final search statistics and close log file + if (LogFile.is_open()) + { + int t = current_search_time(); + + LogFile << "Nodes: " << pos.nodes_searched() + << "\nNodes/second: " << (t > 0 ? pos.nodes_searched() * 1000 / t : 0) + << "\nBest move: " << move_to_san(pos, bestMove); + + StateInfo st; + pos.do_move(bestMove, st); + LogFile << "\nPonder move: " << move_to_san(pos, ponderMove) << endl; + pos.undo_move(bestMove); // Return from think() with unchanged position + LogFile.close(); + } + + // This makes all the threads to go to sleep + Threads.set_size(1); + + // If we are pondering or in infinite search, we shouldn't print the + // best move before we are told to do so. + if (!StopRequest && (Limits.ponder || Limits.infinite)) + wait_for_stop_or_ponderhit(); + + // Could be MOVE_NONE when searching on a stalemate position + cout << "bestmove " << bestMove; + + // UCI protol is not clear on allowing sending an empty ponder move, instead + // it is clear that ponder move is optional. So skip it if empty. + if (ponderMove != MOVE_NONE) + cout << " ponder " << ponderMove; + + cout << endl; + + return !QuitRequest; +} + + +namespace { + + // id_loop() is the main iterative deepening loop. It calls search() repeatedly + // with increasing depth until the allocated thinking time has been consumed, + // user stops the search, or the maximum search depth is reached. + + Move id_loop(Position& pos, Move searchMoves[], Move* ponderMove) { + + SearchStack ss[PLY_MAX_PLUS_2]; + Value bestValues[PLY_MAX_PLUS_2]; + int bestMoveChanges[PLY_MAX_PLUS_2]; + int depth, selDepth, aspirationDelta; + Value value, alpha, beta; + Move bestMove, easyMove, skillBest, skillPonder; + + // Initialize stuff before a new search + memset(ss, 0, 4 * sizeof(SearchStack)); + TT.new_search(); + H.clear(); + *ponderMove = bestMove = easyMove = skillBest = skillPonder = MOVE_NONE; + depth = aspirationDelta = 0; + alpha = -VALUE_INFINITE, beta = VALUE_INFINITE; + ss->currentMove = MOVE_NULL; // Hack to skip update_gains() + + // Moves to search are verified and copied + Rml.init(pos, searchMoves); + + // Handle special case of searching on a mate/stalemate position + if (Rml.size() == 0) + { + cout << "info depth 0 score " + << value_to_uci(pos.in_check() ? -VALUE_MATE : VALUE_DRAW) + << endl; + + return MOVE_NONE; + } + + // Iterative deepening loop until requested to stop or target depth reached + while (!StopRequest && ++depth <= PLY_MAX && (!Limits.maxDepth || depth <= Limits.maxDepth)) + { + Rml.bestMoveChanges = 0; + cout << set960(pos.is_chess960()) << "info depth " << depth << endl; + + // Calculate dynamic aspiration window based on previous iterations + if (MultiPV == 1 && depth >= 5 && abs(bestValues[depth - 1]) < VALUE_KNOWN_WIN) + { + int prevDelta1 = bestValues[depth - 1] - bestValues[depth - 2]; + int prevDelta2 = bestValues[depth - 2] - bestValues[depth - 3]; + + aspirationDelta = Min(Max(abs(prevDelta1) + abs(prevDelta2) / 2, 16), 24); + aspirationDelta = (aspirationDelta + 7) / 8 * 8; // Round to match grainSize + + alpha = Max(bestValues[depth - 1] - aspirationDelta, -VALUE_INFINITE); + beta = Min(bestValues[depth - 1] + aspirationDelta, VALUE_INFINITE); + } + + // Start with a small aspiration window and, in case of fail high/low, + // research with bigger window until not failing high/low anymore. + do { + // Search starting from ss+1 to allow calling update_gains() + value = search(pos, ss+1, alpha, beta, depth * ONE_PLY); + + // Write PV back to transposition table in case the relevant entries + // have been overwritten during the search. + for (int i = 0; i < Min(MultiPV, (int)Rml.size()); i++) + Rml[i].insert_pv_in_tt(pos); + + // Value cannot be trusted. Break out immediately! + if (StopRequest) + break; + + assert(value >= alpha); + + // In case of failing high/low increase aspiration window and research, + // otherwise exit the fail high/low loop. + if (value >= beta) + { + beta = Min(beta + aspirationDelta, VALUE_INFINITE); + aspirationDelta += aspirationDelta / 2; + } + else if (value <= alpha) + { + AspirationFailLow = true; + StopOnPonderhit = false; + + alpha = Max(alpha - aspirationDelta, -VALUE_INFINITE); + aspirationDelta += aspirationDelta / 2; + } + else + break; + + } while (abs(value) < VALUE_KNOWN_WIN); + + // Collect info about search result + bestMove = Rml[0].pv[0]; + *ponderMove = Rml[0].pv[1]; + bestValues[depth] = value; + bestMoveChanges[depth] = Rml.bestMoveChanges; + + // Do we need to pick now the best and the ponder moves ? + if (SkillLevelEnabled && depth == 1 + SkillLevel) + do_skill_level(&skillBest, &skillPonder); + + // Retrieve max searched depth among threads + selDepth = 0; + for (int i = 0; i < Threads.size(); i++) + if (Threads[i].maxPly > selDepth) + selDepth = Threads[i].maxPly; + + // Send PV line to GUI and to log file + for (int i = 0; i < Min(UCIMultiPV, (int)Rml.size()); i++) + cout << Rml[i].pv_info_to_uci(pos, depth, selDepth, alpha, beta, i) << endl; + + if (LogFile.is_open()) + LogFile << pretty_pv(pos, depth, value, current_search_time(), Rml[0].pv) << endl; + + // Init easyMove after first iteration or drop if differs from the best move + if (depth == 1 && (Rml.size() == 1 || Rml[0].pv_score > Rml[1].pv_score + EasyMoveMargin)) + easyMove = bestMove; + else if (bestMove != easyMove) + easyMove = MOVE_NONE; + + // Check for some early stop condition + if (!StopRequest && Limits.useTimeManagement()) + { + // Stop search early when the last two iterations returned a mate score + if ( depth >= 5 + && abs(bestValues[depth]) >= VALUE_MATE_IN_PLY_MAX + && abs(bestValues[depth - 1]) >= VALUE_MATE_IN_PLY_MAX) + StopRequest = true; + + // Stop search early if one move seems to be much better than the + // others or if there is only a single legal move. Also in the latter + // case we search up to some depth anyway to get a proper score. + if ( depth >= 7 + && easyMove == bestMove + && ( Rml.size() == 1 + ||( Rml[0].nodes > (pos.nodes_searched() * 85) / 100 + && current_search_time() > TimeMgr.available_time() / 16) + ||( Rml[0].nodes > (pos.nodes_searched() * 98) / 100 + && current_search_time() > TimeMgr.available_time() / 32))) + StopRequest = true; + + // Take in account some extra time if the best move has changed + if (depth > 4 && depth < 50) + TimeMgr.pv_instability(bestMoveChanges[depth], bestMoveChanges[depth - 1]); + + // Stop search if most of available time is already consumed. We probably don't + // have enough time to search the first move at the next iteration anyway. + if (current_search_time() > (TimeMgr.available_time() * 62) / 100) + StopRequest = true; + + // If we are allowed to ponder do not stop the search now but keep pondering + if (StopRequest && Limits.ponder) + { + StopRequest = false; + StopOnPonderhit = true; + } + } + } + + // When using skills overwrite best and ponder moves with the sub-optimal ones + if (SkillLevelEnabled) + { + if (skillBest == MOVE_NONE) // Still unassigned ? + do_skill_level(&skillBest, &skillPonder); + + bestMove = skillBest; + *ponderMove = skillPonder; + } + + return bestMove; + } + + + // search<>() is the main search function for both PV and non-PV nodes and for + // normal and SplitPoint nodes. When called just after a split point the search + // is simpler because we have already probed the hash table, done a null move + // search, and searched the first move before splitting, we don't have to repeat + // all this work again. We also don't need to store anything to the hash table + // here: This is taken care of after we return from the split point. + + template + Value search(Position& pos, SearchStack* ss, Value alpha, Value beta, Depth depth) { + + assert(alpha >= -VALUE_INFINITE && alpha <= VALUE_INFINITE); + assert(beta > alpha && beta <= VALUE_INFINITE); + assert(PvNode || alpha == beta - 1); + assert(pos.thread() >= 0 && pos.thread() < Threads.size()); + + Move movesSearched[MAX_MOVES]; + int64_t nodes; + StateInfo st; + const TTEntry *tte; + Key posKey; + Move ttMove, move, excludedMove, threatMove; + Depth ext, newDepth; + ValueType vt; + Value bestValue, value, oldAlpha; + Value refinedValue, nullValue, futilityBase, futilityValueScaled; // Non-PV specific + bool isPvMove, inCheck, singularExtensionNode, givesCheck, captureOrPromotion, dangerous, isBadCap; + int moveCount = 0, playedMoveCount = 0; + int threadID = pos.thread(); + SplitPoint* sp = NULL; + + refinedValue = bestValue = value = -VALUE_INFINITE; + oldAlpha = alpha; + inCheck = pos.in_check(); + ss->ply = (ss-1)->ply + 1; + + // Used to send selDepth info to GUI + if (PvNode && Threads[threadID].maxPly < ss->ply) + Threads[threadID].maxPly = ss->ply; + + if (SpNode) + { + sp = ss->sp; + tte = NULL; + ttMove = excludedMove = MOVE_NONE; + threatMove = sp->threatMove; + goto split_point_start; + } + else if (Root) + bestValue = alpha; + + // Step 1. Initialize node and poll. Polling can abort search + ss->currentMove = ss->bestMove = threatMove = (ss+1)->excludedMove = MOVE_NONE; + (ss+1)->skipNullMove = false; (ss+1)->reduction = DEPTH_ZERO; + (ss+2)->killers[0] = (ss+2)->killers[1] = (ss+2)->mateKiller = MOVE_NONE; + + if (threadID == 0 && ++NodesSincePoll > NodesBetweenPolls) + { + NodesSincePoll = 0; + poll(pos); + } + + // Step 2. Check for aborted search and immediate draw + if (( StopRequest + || Threads[threadID].cutoff_occurred() + || pos.is_draw() + || ss->ply > PLY_MAX) && !Root) + return VALUE_DRAW; + + // Step 3. Mate distance pruning + alpha = Max(value_mated_in(ss->ply), alpha); + beta = Min(value_mate_in(ss->ply+1), beta); + if (alpha >= beta) + return alpha; + + // Step 4. Transposition table lookup + // We don't want the score of a partial search to overwrite a previous full search + // TT value, so we use a different position key in case of an excluded move. + excludedMove = ss->excludedMove; + posKey = excludedMove ? pos.get_exclusion_key() : pos.get_key(); + + tte = TT.probe(posKey); + ttMove = tte ? tte->move() : MOVE_NONE; + + // At PV nodes we check for exact scores, while at non-PV nodes we check for + // a fail high/low. Biggest advantage at probing at PV nodes is to have a + // smooth experience in analysis mode. + if ( !Root + && tte + && (PvNode ? tte->depth() >= depth && tte->type() == VALUE_TYPE_EXACT + : ok_to_use_TT(tte, depth, beta, ss->ply))) + { + TT.refresh(tte); + ss->bestMove = ttMove; // Can be MOVE_NONE + return value_from_tt(tte->value(), ss->ply); + } + + // Step 5. Evaluate the position statically and update parent's gain statistics + if (inCheck) + ss->eval = ss->evalMargin = VALUE_NONE; + else if (tte) + { + assert(tte->static_value() != VALUE_NONE); + + ss->eval = tte->static_value(); + ss->evalMargin = tte->static_value_margin(); + refinedValue = refine_eval(tte, ss->eval, ss->ply); + } + else + { + refinedValue = ss->eval = evaluate(pos, ss->evalMargin); + TT.store(posKey, VALUE_NONE, VALUE_TYPE_NONE, DEPTH_NONE, MOVE_NONE, ss->eval, ss->evalMargin); + } + + // Save gain for the parent non-capture move + update_gains(pos, (ss-1)->currentMove, (ss-1)->eval, ss->eval); + + // Step 6. Razoring (is omitted in PV nodes) + if ( !PvNode + && depth < RazorDepth + && !inCheck + && refinedValue + razor_margin(depth) < beta + && ttMove == MOVE_NONE + && abs(beta) < VALUE_MATE_IN_PLY_MAX + && !pos.has_pawn_on_7th(pos.side_to_move())) + { + Value rbeta = beta - razor_margin(depth); + Value v = qsearch(pos, ss, rbeta-1, rbeta, DEPTH_ZERO); + if (v < rbeta) + // Logically we should return (v + razor_margin(depth)), but + // surprisingly this did slightly weaker in tests. + return v; + } + + // Step 7. Static null move pruning (is omitted in PV nodes) + // We're betting that the opponent doesn't have a move that will reduce + // the score by more than futility_margin(depth) if we do a null move. + if ( !PvNode + && !ss->skipNullMove + && depth < RazorDepth + && !inCheck + && refinedValue - futility_margin(depth, 0) >= beta + && abs(beta) < VALUE_MATE_IN_PLY_MAX + && pos.non_pawn_material(pos.side_to_move())) + return refinedValue - futility_margin(depth, 0); + + // Step 8. Null move search with verification search (is omitted in PV nodes) + if ( !PvNode + && !ss->skipNullMove + && depth > ONE_PLY + && !inCheck + && refinedValue >= beta + && abs(beta) < VALUE_MATE_IN_PLY_MAX + && pos.non_pawn_material(pos.side_to_move())) + { + ss->currentMove = MOVE_NULL; + + // Null move dynamic reduction based on depth + int R = 3 + (depth >= 5 * ONE_PLY ? depth / 8 : 0); + + // Null move dynamic reduction based on value + if (refinedValue - PawnValueMidgame > beta) + R++; + + pos.do_null_move(st); + (ss+1)->skipNullMove = true; + nullValue = -search(pos, ss+1, -beta, -alpha, depth-R*ONE_PLY); + (ss+1)->skipNullMove = false; + pos.undo_null_move(); + + if (nullValue >= beta) + { + // Do not return unproven mate scores + if (nullValue >= VALUE_MATE_IN_PLY_MAX) + nullValue = beta; + + if (depth < 6 * ONE_PLY) + return nullValue; + + // Do verification search at high depths + ss->skipNullMove = true; + Value v = search(pos, ss, alpha, beta, depth-R*ONE_PLY); + ss->skipNullMove = false; + + if (v >= beta) + return nullValue; + } + else + { + // The null move failed low, which means that we may be faced with + // some kind of threat. If the previous move was reduced, check if + // the move that refuted the null move was somehow connected to the + // move which was reduced. If a connection is found, return a fail + // low score (which will cause the reduced move to fail high in the + // parent node, which will trigger a re-search with full depth). + threatMove = (ss+1)->bestMove; + + if ( depth < ThreatDepth + && (ss-1)->reduction + && threatMove != MOVE_NONE + && connected_moves(pos, (ss-1)->currentMove, threatMove)) + return beta - 1; + } + } + + // Step 9. Internal iterative deepening + if ( depth >= IIDDepth[PvNode] + && ttMove == MOVE_NONE + && (PvNode || (!inCheck && ss->eval + IIDMargin >= beta))) + { + Depth d = (PvNode ? depth - 2 * ONE_PLY : depth / 2); + + ss->skipNullMove = true; + search(pos, ss, alpha, beta, d); + ss->skipNullMove = false; + + ttMove = ss->bestMove; + tte = TT.probe(posKey); + } + +split_point_start: // At split points actual search starts from here + + // Initialize a MovePicker object for the current position + MovePickerExt mp(pos, ttMove, depth, H, ss, (PvNode ? -VALUE_INFINITE : beta)); + CheckInfo ci(pos); + ss->bestMove = MOVE_NONE; + futilityBase = ss->eval + ss->evalMargin; + singularExtensionNode = !Root + && !SpNode + && depth >= SingularExtensionDepth[PvNode] + && tte + && tte->move() + && !excludedMove // Do not allow recursive singular extension search + && (tte->type() & VALUE_TYPE_LOWER) + && tte->depth() >= depth - 3 * ONE_PLY; + if (SpNode) + { + lock_grab(&(sp->lock)); + bestValue = sp->bestValue; + } + + // Step 10. Loop through moves + // Loop through all legal moves until no moves remain or a beta cutoff occurs + while ( bestValue < beta + && (move = mp.get_next_move()) != MOVE_NONE + && !Threads[threadID].cutoff_occurred()) + { + assert(move_is_ok(move)); + + if (SpNode) + { + moveCount = ++sp->moveCount; + lock_release(&(sp->lock)); + } + else if (move == excludedMove) + continue; + else + moveCount++; + + if (Root) + { + // This is used by time management + FirstRootMove = (moveCount == 1); + + // Save the current node count before the move is searched + nodes = pos.nodes_searched(); + + // If it's time to send nodes info, do it here where we have the + // correct accumulated node counts searched by each thread. + if (SendSearchedNodes) + { + SendSearchedNodes = false; + cout << "info" << speed_to_uci(pos.nodes_searched()) << endl; + } + + if (current_search_time() > 2000) + cout << "info currmove " << move + << " currmovenumber " << moveCount << endl; + } + + // At Root and at first iteration do a PV search on all the moves to score root moves + isPvMove = (PvNode && moveCount <= (Root ? depth <= ONE_PLY ? 1000 : MultiPV : 1)); + givesCheck = pos.move_gives_check(move, ci); + captureOrPromotion = pos.move_is_capture_or_promotion(move); + + // Step 11. Decide the new search depth + ext = extension(pos, move, captureOrPromotion, givesCheck, &dangerous); + + // Singular extension search. If all moves but one fail low on a search of + // (alpha-s, beta-s), and just one fails high on (alpha, beta), then that move + // is singular and should be extended. To verify this we do a reduced search + // on all the other moves but the ttMove, if result is lower than ttValue minus + // a margin then we extend ttMove. + if ( singularExtensionNode + && move == tte->move() + && ext < ONE_PLY) + { + Value ttValue = value_from_tt(tte->value(), ss->ply); + + if (abs(ttValue) < VALUE_KNOWN_WIN) + { + Value rBeta = ttValue - int(depth); + ss->excludedMove = move; + ss->skipNullMove = true; + Value v = search(pos, ss, rBeta - 1, rBeta, depth / 2); + ss->skipNullMove = false; + ss->excludedMove = MOVE_NONE; + ss->bestMove = MOVE_NONE; + if (v < rBeta) + ext = ONE_PLY; + } + } + + // Update current move (this must be done after singular extension search) + ss->currentMove = move; + newDepth = depth - ONE_PLY + ext; + + // Step 12. Futility pruning (is omitted in PV nodes) + if ( !PvNode + && !captureOrPromotion + && !inCheck + && !dangerous + && move != ttMove + && !move_is_castle(move)) + { + // Move count based pruning + if ( moveCount >= futility_move_count(depth) + && (!threatMove || !connected_threat(pos, move, threatMove)) + && bestValue > VALUE_MATED_IN_PLY_MAX) // FIXME bestValue is racy + { + if (SpNode) + lock_grab(&(sp->lock)); + + continue; + } + + // Value based pruning + // We illogically ignore reduction condition depth >= 3*ONE_PLY for predicted depth, + // but fixing this made program slightly weaker. + Depth predictedDepth = newDepth - reduction(depth, moveCount); + futilityValueScaled = futilityBase + futility_margin(predictedDepth, moveCount) + + H.gain(pos.piece_on(move_from(move)), move_to(move)); + + if (futilityValueScaled < beta) + { + if (SpNode) + { + lock_grab(&(sp->lock)); + if (futilityValueScaled > sp->bestValue) + sp->bestValue = bestValue = futilityValueScaled; + } + else if (futilityValueScaled > bestValue) + bestValue = futilityValueScaled; + + continue; + } + + // Prune moves with negative SEE at low depths + if ( predictedDepth < 2 * ONE_PLY + && bestValue > VALUE_MATED_IN_PLY_MAX + && pos.see_sign(move) < 0) + { + if (SpNode) + lock_grab(&(sp->lock)); + + continue; + } + } + + // Bad capture detection. Will be used by prob-cut search + isBadCap = depth >= 3 * ONE_PLY + && depth < 8 * ONE_PLY + && captureOrPromotion + && move != ttMove + && !dangerous + && !move_is_promotion(move) + && abs(alpha) < VALUE_MATE_IN_PLY_MAX + && pos.see_sign(move) < 0; + + // Step 13. Make the move + pos.do_move(move, st, ci, givesCheck); + + if (!SpNode && !captureOrPromotion) + movesSearched[playedMoveCount++] = move; + + // Step extra. pv search (only in PV nodes) + // The first move in list is the expected PV + if (isPvMove) + { + // Aspiration window is disabled in multi-pv case + if (Root && MultiPV > 1) + alpha = -VALUE_INFINITE; + + value = -search(pos, ss+1, -beta, -alpha, newDepth); + } + else + { + // Step 14. Reduced depth search + // If the move fails high will be re-searched at full depth. + bool doFullDepthSearch = true; + alpha = SpNode ? sp->alpha : alpha; + + if ( depth >= 3 * ONE_PLY + && !captureOrPromotion + && !dangerous + && !move_is_castle(move) + && ss->killers[0] != move + && ss->killers[1] != move) + { + ss->reduction = reduction(depth, moveCount); + if (ss->reduction) + { + alpha = SpNode ? sp->alpha : alpha; + Depth d = newDepth - ss->reduction; + value = -search(pos, ss+1, -(alpha+1), -alpha, d); + + doFullDepthSearch = (value > alpha); + } + ss->reduction = DEPTH_ZERO; // Restore original reduction + } + + // Probcut search for bad captures. If a reduced search returns a value + // very below beta then we can (almost) safely prune the bad capture. + if (isBadCap) + { + ss->reduction = 3 * ONE_PLY; + Value rAlpha = alpha - 300; + Depth d = newDepth - ss->reduction; + value = -search(pos, ss+1, -(rAlpha+1), -rAlpha, d); + doFullDepthSearch = (value > rAlpha); + ss->reduction = DEPTH_ZERO; // Restore original reduction + } + + // Step 15. Full depth search + if (doFullDepthSearch) + { + alpha = SpNode ? sp->alpha : alpha; + value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth); + + // Step extra. pv search (only in PV nodes) + // Search only for possible new PV nodes, if instead value >= beta then + // parent node fails low with value <= alpha and tries another move. + if (PvNode && value > alpha && (Root || value < beta)) + value = -search(pos, ss+1, -beta, -alpha, newDepth); + } + } + + // Step 16. Undo move + pos.undo_move(move); + + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); + + // Step 17. Check for new best move + if (SpNode) + { + lock_grab(&(sp->lock)); + bestValue = sp->bestValue; + alpha = sp->alpha; + } + + if (value > bestValue && !(SpNode && Threads[threadID].cutoff_occurred())) + { + bestValue = value; + + if (SpNode) + sp->bestValue = value; + + if (!Root && value > alpha) + { + if (PvNode && value < beta) // We want always alpha < beta + { + alpha = value; + + if (SpNode) + sp->alpha = value; + } + else if (SpNode) + sp->is_betaCutoff = true; + + if (value == value_mate_in(ss->ply + 1)) + ss->mateKiller = move; + + ss->bestMove = move; + + if (SpNode) + sp->ss->bestMove = move; + } + } + + if (Root) + { + // Finished searching the move. If StopRequest is true, the search + // was aborted because the user interrupted the search or because we + // ran out of time. In this case, the return value of the search cannot + // be trusted, and we break out of the loop without updating the best + // move and/or PV. + if (StopRequest) + break; + + // Remember searched nodes counts for this move + mp.rm->nodes += pos.nodes_searched() - nodes; + + // PV move or new best move ? + if (isPvMove || value > alpha) + { + // Update PV + ss->bestMove = move; + mp.rm->pv_score = value; + mp.rm->extract_pv_from_tt(pos); + + // We record how often the best move has been changed in each + // iteration. This information is used for time management: When + // the best move changes frequently, we allocate some more time. + if (!isPvMove && MultiPV == 1) + Rml.bestMoveChanges++; + + Rml.sort_multipv(moveCount); + + // Update alpha. In multi-pv we don't use aspiration window, so + // set alpha equal to minimum score among the PV lines. + if (MultiPV > 1) + alpha = Rml[Min(moveCount, MultiPV) - 1].pv_score; // FIXME why moveCount? + else if (value > alpha) + alpha = value; + } + else + mp.rm->pv_score = -VALUE_INFINITE; + + } // Root + + // Step 18. Check for split + if ( !Root + && !SpNode + && depth >= Threads.min_split_depth() + && bestValue < beta + && Threads.available_slave_exists(threadID) + && !StopRequest + && !Threads[threadID].cutoff_occurred()) + Threads.split(pos, ss, &alpha, beta, &bestValue, depth, + threatMove, moveCount, &mp, PvNode); + } + + // Step 19. Check for mate and stalemate + // All legal moves have been searched and if there are + // no legal moves, it must be mate or stalemate. + // If one move was excluded return fail low score. + if (!SpNode && !moveCount) + return excludedMove ? oldAlpha : inCheck ? value_mated_in(ss->ply) : VALUE_DRAW; + + // Step 20. Update tables + // If the search is not aborted, update the transposition table, + // history counters, and killer moves. + if (!SpNode && !StopRequest && !Threads[threadID].cutoff_occurred()) + { + move = bestValue <= oldAlpha ? MOVE_NONE : ss->bestMove; + vt = bestValue <= oldAlpha ? VALUE_TYPE_UPPER + : bestValue >= beta ? VALUE_TYPE_LOWER : VALUE_TYPE_EXACT; + + TT.store(posKey, value_to_tt(bestValue, ss->ply), vt, depth, move, ss->eval, ss->evalMargin); + + // Update killers and history only for non capture moves that fails high + if ( bestValue >= beta + && !pos.move_is_capture_or_promotion(move)) + { + if (move != ss->killers[0]) + { + ss->killers[1] = ss->killers[0]; + ss->killers[0] = move; + } + update_history(pos, move, depth, movesSearched, playedMoveCount); + } + } + + if (SpNode) + { + // Here we have the lock still grabbed + sp->is_slave[threadID] = false; + sp->nodes += pos.nodes_searched(); + lock_release(&(sp->lock)); + } + + assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); + + return bestValue; + } + + // qsearch() is the quiescence search function, which is called by the main + // search function when the remaining depth is zero (or, to be more precise, + // less than ONE_PLY). + + template + Value qsearch(Position& pos, SearchStack* ss, Value alpha, Value beta, Depth depth) { + + assert(alpha >= -VALUE_INFINITE && alpha <= VALUE_INFINITE); + assert(beta >= -VALUE_INFINITE && beta <= VALUE_INFINITE); + assert(PvNode || alpha == beta - 1); + assert(depth <= 0); + assert(pos.thread() >= 0 && pos.thread() < Threads.size()); + + StateInfo st; + Move ttMove, move; + Value bestValue, value, evalMargin, futilityValue, futilityBase; + bool inCheck, enoughMaterial, givesCheck, evasionPrunable; + const TTEntry* tte; + Depth ttDepth; + Value oldAlpha = alpha; + + ss->bestMove = ss->currentMove = MOVE_NONE; + ss->ply = (ss-1)->ply + 1; + + // Check for an instant draw or maximum ply reached + if (ss->ply > PLY_MAX || pos.is_draw()) + return VALUE_DRAW; + + // Decide whether or not to include checks, this fixes also the type of + // TT entry depth that we are going to use. Note that in qsearch we use + // only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS. + inCheck = pos.in_check(); + ttDepth = (inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NO_CHECKS); + + // Transposition table lookup. At PV nodes, we don't use the TT for + // pruning, but only for move ordering. + tte = TT.probe(pos.get_key()); + ttMove = (tte ? tte->move() : MOVE_NONE); + + if (!PvNode && tte && ok_to_use_TT(tte, ttDepth, beta, ss->ply)) + { + ss->bestMove = ttMove; // Can be MOVE_NONE + return value_from_tt(tte->value(), ss->ply); + } + + // Evaluate the position statically + if (inCheck) + { + bestValue = futilityBase = -VALUE_INFINITE; + ss->eval = evalMargin = VALUE_NONE; + enoughMaterial = false; + } + else + { + if (tte) + { + assert(tte->static_value() != VALUE_NONE); + + evalMargin = tte->static_value_margin(); + ss->eval = bestValue = tte->static_value(); + } + else + ss->eval = bestValue = evaluate(pos, evalMargin); + + update_gains(pos, (ss-1)->currentMove, (ss-1)->eval, ss->eval); + + // Stand pat. Return immediately if static value is at least beta + if (bestValue >= beta) + { + if (!tte) + TT.store(pos.get_key(), value_to_tt(bestValue, ss->ply), VALUE_TYPE_LOWER, DEPTH_NONE, MOVE_NONE, ss->eval, evalMargin); + + return bestValue; + } + + if (PvNode && bestValue > alpha) + alpha = bestValue; + + // Futility pruning parameters, not needed when in check + futilityBase = ss->eval + evalMargin + FutilityMarginQS; + enoughMaterial = pos.non_pawn_material(pos.side_to_move()) > RookValueMidgame; + } + + // Initialize a MovePicker object for the current position, and prepare + // to search the moves. Because the depth is <= 0 here, only captures, + // queen promotions and checks (only if depth >= DEPTH_QS_CHECKS) will + // be generated. + MovePicker mp(pos, ttMove, depth, H); + CheckInfo ci(pos); + + // Loop through the moves until no moves remain or a beta cutoff occurs + while ( alpha < beta + && (move = mp.get_next_move()) != MOVE_NONE) + { + assert(move_is_ok(move)); + + givesCheck = pos.move_gives_check(move, ci); + + // Futility pruning + if ( !PvNode + && !inCheck + && !givesCheck + && move != ttMove + && enoughMaterial + && !move_is_promotion(move) + && !pos.move_is_passed_pawn_push(move)) + { + futilityValue = futilityBase + + pos.endgame_value_of_piece_on(move_to(move)) + + (move_is_ep(move) ? PawnValueEndgame : VALUE_ZERO); + + if (futilityValue < alpha) + { + if (futilityValue > bestValue) + bestValue = futilityValue; + continue; + } + + // Prune moves with negative or equal SEE + if ( futilityBase < beta + && depth < DEPTH_ZERO + && pos.see(move) <= 0) + continue; + } + + // Detect non-capture evasions that are candidate to be pruned + evasionPrunable = inCheck + && bestValue > VALUE_MATED_IN_PLY_MAX + && !pos.move_is_capture(move) + && !pos.can_castle(pos.side_to_move()); + + // Don't search moves with negative SEE values + if ( !PvNode + && (!inCheck || evasionPrunable) + && move != ttMove + && !move_is_promotion(move) + && pos.see_sign(move) < 0) + continue; + + // Don't search useless checks + if ( !PvNode + && !inCheck + && givesCheck + && move != ttMove + && !pos.move_is_capture_or_promotion(move) + && ss->eval + PawnValueMidgame / 4 < beta + && !check_is_dangerous(pos, move, futilityBase, beta, &bestValue)) + { + if (ss->eval + PawnValueMidgame / 4 > bestValue) + bestValue = ss->eval + PawnValueMidgame / 4; + + continue; + } + + // Update current move + ss->currentMove = move; + + // Make and search the move + pos.do_move(move, st, ci, givesCheck); + value = -qsearch(pos, ss+1, -beta, -alpha, depth-ONE_PLY); + pos.undo_move(move); + + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); + + // New best move? + if (value > bestValue) + { + bestValue = value; + if (value > alpha) + { + alpha = value; + ss->bestMove = move; + } + } + } + + // All legal moves have been searched. A special case: If we're in check + // and no legal moves were found, it is checkmate. + if (inCheck && bestValue == -VALUE_INFINITE) + return value_mated_in(ss->ply); + + // Update transposition table + ValueType vt = (bestValue <= oldAlpha ? VALUE_TYPE_UPPER : bestValue >= beta ? VALUE_TYPE_LOWER : VALUE_TYPE_EXACT); + TT.store(pos.get_key(), value_to_tt(bestValue, ss->ply), vt, ttDepth, ss->bestMove, ss->eval, evalMargin); + + assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); + + return bestValue; + } + + + // check_is_dangerous() tests if a checking move can be pruned in qsearch(). + // bestValue is updated only when returning false because in that case move + // will be pruned. + + bool check_is_dangerous(Position &pos, Move move, Value futilityBase, Value beta, Value *bestValue) + { + Bitboard b, occ, oldAtt, newAtt, kingAtt; + Square from, to, ksq, victimSq; + Piece pc; + Color them; + Value futilityValue, bv = *bestValue; + + from = move_from(move); + to = move_to(move); + them = opposite_color(pos.side_to_move()); + ksq = pos.king_square(them); + kingAtt = pos.attacks_from(ksq); + pc = pos.piece_on(from); + + occ = pos.occupied_squares() & ~(1ULL << from) & ~(1ULL << ksq); + oldAtt = pos.attacks_from(pc, from, occ); + newAtt = pos.attacks_from(pc, to, occ); + + // Rule 1. Checks which give opponent's king at most one escape square are dangerous + b = kingAtt & ~pos.pieces_of_color(them) & ~newAtt & ~(1ULL << to); + + if (!(b && (b & (b - 1)))) + return true; + + // Rule 2. Queen contact check is very dangerous + if ( type_of_piece(pc) == QUEEN + && bit_is_set(kingAtt, to)) + return true; + + // Rule 3. Creating new double threats with checks + b = pos.pieces_of_color(them) & newAtt & ~oldAtt & ~(1ULL << ksq); + + while (b) + { + victimSq = pop_1st_bit(&b); + futilityValue = futilityBase + pos.endgame_value_of_piece_on(victimSq); + + // Note that here we generate illegal "double move"! + if ( futilityValue >= beta + && pos.see_sign(make_move(from, victimSq)) >= 0) + return true; + + if (futilityValue > bv) + bv = futilityValue; + } + + // Update bestValue only if check is not dangerous (because we will prune the move) + *bestValue = bv; + return false; + } + + + // connected_moves() tests whether two moves are 'connected' in the sense + // that the first move somehow made the second move possible (for instance + // if the moving piece is the same in both moves). The first move is assumed + // to be the move that was made to reach the current position, while the + // second move is assumed to be a move from the current position. + + bool connected_moves(const Position& pos, Move m1, Move m2) { + + Square f1, t1, f2, t2; + Piece p; + + assert(m1 && move_is_ok(m1)); + assert(m2 && move_is_ok(m2)); + + // Case 1: The moving piece is the same in both moves + f2 = move_from(m2); + t1 = move_to(m1); + if (f2 == t1) + return true; + + // Case 2: The destination square for m2 was vacated by m1 + t2 = move_to(m2); + f1 = move_from(m1); + if (t2 == f1) + return true; + + // Case 3: Moving through the vacated square + if ( piece_is_slider(pos.piece_on(f2)) + && bit_is_set(squares_between(f2, t2), f1)) + return true; + + // Case 4: The destination square for m2 is defended by the moving piece in m1 + p = pos.piece_on(t1); + if (bit_is_set(pos.attacks_from(p, t1), t2)) + return true; + + // Case 5: Discovered check, checking piece is the piece moved in m1 + if ( piece_is_slider(p) + && bit_is_set(squares_between(t1, pos.king_square(pos.side_to_move())), f2) + && !bit_is_set(squares_between(t1, pos.king_square(pos.side_to_move())), t2)) + { + // discovered_check_candidates() works also if the Position's side to + // move is the opposite of the checking piece. + Color them = opposite_color(pos.side_to_move()); + Bitboard dcCandidates = pos.discovered_check_candidates(them); + + if (bit_is_set(dcCandidates, f2)) + return true; + } + return false; + } + + + // value_to_tt() adjusts a mate score from "plies to mate from the root" to + // "plies to mate from the current ply". Non-mate scores are unchanged. + // The function is called before storing a value to the transposition table. + + Value value_to_tt(Value v, int ply) { + + if (v >= VALUE_MATE_IN_PLY_MAX) + return v + ply; + + if (v <= VALUE_MATED_IN_PLY_MAX) + return v - ply; + + return v; + } + + + // value_from_tt() is the inverse of value_to_tt(): It adjusts a mate score from + // the transposition table to a mate score corrected for the current ply. + + Value value_from_tt(Value v, int ply) { + + if (v >= VALUE_MATE_IN_PLY_MAX) + return v - ply; + + if (v <= VALUE_MATED_IN_PLY_MAX) + return v + ply; + + return v; + } + + + // extension() decides whether a move should be searched with normal depth, + // or with extended depth. Certain classes of moves (checking moves, in + // particular) are searched with bigger depth than ordinary moves and in + // any case are marked as 'dangerous'. Note that also if a move is not + // extended, as example because the corresponding UCI option is set to zero, + // the move is marked as 'dangerous' so, at least, we avoid to prune it. + template + Depth extension(const Position& pos, Move m, bool captureOrPromotion, + bool moveIsCheck, bool* dangerous) { + + assert(m != MOVE_NONE); + + Depth result = DEPTH_ZERO; + *dangerous = moveIsCheck; + + if (moveIsCheck && pos.see_sign(m) >= 0) + result += CheckExtension[PvNode]; + + if (pos.type_of_piece_on(move_from(m)) == PAWN) + { + Color c = pos.side_to_move(); + if (relative_rank(c, move_to(m)) == RANK_7) + { + result += PawnPushTo7thExtension[PvNode]; + *dangerous = true; + } + if (pos.pawn_is_passed(c, move_to(m))) + { + result += PassedPawnExtension[PvNode]; + *dangerous = true; + } + } + + if ( captureOrPromotion + && pos.type_of_piece_on(move_to(m)) != PAWN + && ( pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) + - pos.midgame_value_of_piece_on(move_to(m)) == VALUE_ZERO) + && !move_is_special(m)) + { + result += PawnEndgameExtension[PvNode]; + *dangerous = true; + } + + return Min(result, ONE_PLY); + } + + + // connected_threat() tests whether it is safe to forward prune a move or if + // is somehow connected to the threat move returned by null search. + + bool connected_threat(const Position& pos, Move m, Move threat) { + + assert(move_is_ok(m)); + assert(threat && move_is_ok(threat)); + assert(!pos.move_gives_check(m)); + assert(!pos.move_is_capture_or_promotion(m)); + assert(!pos.move_is_passed_pawn_push(m)); + + Square mfrom, mto, tfrom, tto; + + mfrom = move_from(m); + mto = move_to(m); + tfrom = move_from(threat); + tto = move_to(threat); + + // Case 1: Don't prune moves which move the threatened piece + if (mfrom == tto) + return true; + + // Case 2: If the threatened piece has value less than or equal to the + // value of the threatening piece, don't prune moves which defend it. + if ( pos.move_is_capture(threat) + && ( pos.midgame_value_of_piece_on(tfrom) >= pos.midgame_value_of_piece_on(tto) + || pos.type_of_piece_on(tfrom) == KING) + && pos.move_attacks_square(m, tto)) + return true; + + // Case 3: If the moving piece in the threatened move is a slider, don't + // prune safe moves which block its ray. + if ( piece_is_slider(pos.piece_on(tfrom)) + && bit_is_set(squares_between(tfrom, tto), mto) + && pos.see_sign(m) >= 0) + return true; + + return false; + } + + + // ok_to_use_TT() returns true if a transposition table score + // can be used at a given point in search. + + bool ok_to_use_TT(const TTEntry* tte, Depth depth, Value beta, int ply) { + + Value v = value_from_tt(tte->value(), ply); + + return ( tte->depth() >= depth + || v >= Max(VALUE_MATE_IN_PLY_MAX, beta) + || v < Min(VALUE_MATED_IN_PLY_MAX, beta)) + + && ( ((tte->type() & VALUE_TYPE_LOWER) && v >= beta) + || ((tte->type() & VALUE_TYPE_UPPER) && v < beta)); + } + + + // refine_eval() returns the transposition table score if + // possible otherwise falls back on static position evaluation. + + Value refine_eval(const TTEntry* tte, Value defaultEval, int ply) { + + assert(tte); + + Value v = value_from_tt(tte->value(), ply); + + if ( ((tte->type() & VALUE_TYPE_LOWER) && v >= defaultEval) + || ((tte->type() & VALUE_TYPE_UPPER) && v < defaultEval)) + return v; + + return defaultEval; + } + + + // update_history() registers a good move that produced a beta-cutoff + // in history and marks as failures all the other moves of that ply. + + void update_history(const Position& pos, Move move, Depth depth, + Move movesSearched[], int moveCount) { + Move m; + Value bonus = Value(int(depth) * int(depth)); + + H.update(pos.piece_on(move_from(move)), move_to(move), bonus); + + for (int i = 0; i < moveCount - 1; i++) + { + m = movesSearched[i]; + + assert(m != move); + + H.update(pos.piece_on(move_from(m)), move_to(m), -bonus); + } + } + + + // update_gains() updates the gains table of a non-capture move given + // the static position evaluation before and after the move. + + void update_gains(const Position& pos, Move m, Value before, Value after) { + + if ( m != MOVE_NULL + && before != VALUE_NONE + && after != VALUE_NONE + && pos.captured_piece_type() == PIECE_TYPE_NONE + && !move_is_special(m)) + H.update_gain(pos.piece_on(move_to(m)), move_to(m), -(before + after)); + } + + + // current_search_time() returns the number of milliseconds which have passed + // since the beginning of the current search. + + int current_search_time(int set) { + + static int searchStartTime; + + if (set) + searchStartTime = set; + + return get_system_time() - searchStartTime; + } + + + // value_to_uci() converts a value to a string suitable for use with the UCI + // protocol specifications: + // + // cp The score from the engine's point of view in centipawns. + // mate Mate in y moves, not plies. If the engine is getting mated + // use negative values for y. + + std::string value_to_uci(Value v) { + + std::stringstream s; + + if (abs(v) < VALUE_MATE - PLY_MAX * ONE_PLY) + s << "cp " << int(v) * 100 / int(PawnValueMidgame); // Scale to centipawns + else + s << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2; + + return s.str(); + } + + + // speed_to_uci() returns a string with time stats of current search suitable + // to be sent to UCI gui. + + std::string speed_to_uci(int64_t nodes) { + + std::stringstream s; + int t = current_search_time(); + + s << " nodes " << nodes + << " nps " << (t > 0 ? int(nodes * 1000 / t) : 0) + << " time " << t; + + return s.str(); + } + + + // poll() performs two different functions: It polls for user input, and it + // looks at the time consumed so far and decides if it's time to abort the + // search. + + void poll(const Position& pos) { + + static int lastInfoTime; + int t = current_search_time(); + + // Poll for input + if (input_available()) + { + // We are line oriented, don't read single chars + std::string command; + + if (!std::getline(std::cin, command) || command == "quit") + { + // Quit the program as soon as possible + Limits.ponder = false; + QuitRequest = StopRequest = true; + return; + } + else if (command == "stop") + { + // Stop calculating as soon as possible, but still send the "bestmove" + // and possibly the "ponder" token when finishing the search. + Limits.ponder = false; + StopRequest = true; + } + else if (command == "ponderhit") + { + // The opponent has played the expected move. GUI sends "ponderhit" if + // we were told to ponder on the same move the opponent has played. We + // should continue searching but switching from pondering to normal search. + Limits.ponder = false; + + if (StopOnPonderhit) + StopRequest = true; + } + } + + // Print search information + if (t < 1000) + lastInfoTime = 0; + + else if (lastInfoTime > t) + // HACK: Must be a new search where we searched less than + // NodesBetweenPolls nodes during the first second of search. + lastInfoTime = 0; + + else if (t - lastInfoTime >= 1000) + { + lastInfoTime = t; + + dbg_print_mean(); + dbg_print_hit_rate(); + + // Send info on searched nodes as soon as we return to root + SendSearchedNodes = true; + } + + // Should we stop the search? + if (Limits.ponder) + return; + + bool stillAtFirstMove = FirstRootMove + && !AspirationFailLow + && t > TimeMgr.available_time(); + + bool noMoreTime = t > TimeMgr.maximum_time() + || stillAtFirstMove; + + if ( (Limits.useTimeManagement() && noMoreTime) + || (Limits.maxTime && t >= Limits.maxTime) + || (Limits.maxNodes && pos.nodes_searched() >= Limits.maxNodes)) // FIXME + StopRequest = true; + } + + + // wait_for_stop_or_ponderhit() is called when the maximum depth is reached + // while the program is pondering. The point is to work around a wrinkle in + // the UCI protocol: When pondering, the engine is not allowed to give a + // "bestmove" before the GUI sends it a "stop" or "ponderhit" command. + // We simply wait here until one of these commands is sent, and return, + // after which the bestmove and pondermove will be printed. + + void wait_for_stop_or_ponderhit() { + + std::string command; + + // Wait for a command from stdin + while ( std::getline(std::cin, command) + && command != "ponderhit" && command != "stop" && command != "quit") {}; + + if (command != "ponderhit" && command != "stop") + QuitRequest = true; // Must be "quit" or getline() returned false + } + + + // When playing with strength handicap choose best move among the MultiPV set + // using a statistical rule dependent on SkillLevel. Idea by Heinz van Saanen. + void do_skill_level(Move* best, Move* ponder) { + + assert(MultiPV > 1); + + static RKISS rk; + + // Rml list is already sorted by pv_score in descending order + int s; + int max_s = -VALUE_INFINITE; + int size = Min(MultiPV, (int)Rml.size()); + int max = Rml[0].pv_score; + int var = Min(max - Rml[size - 1].pv_score, PawnValueMidgame); + int wk = 120 - 2 * SkillLevel; + + // PRNG sequence should be non deterministic + for (int i = abs(get_system_time() % 50); i > 0; i--) + rk.rand(); + + // Choose best move. For each move's score we add two terms both dependent + // on wk, one deterministic and bigger for weaker moves, and one random, + // then we choose the move with the resulting highest score. + for (int i = 0; i < size; i++) + { + s = Rml[i].pv_score; + + // Don't allow crazy blunders even at very low skills + if (i > 0 && Rml[i-1].pv_score > s + EasyMoveMargin) + break; + + // This is our magical formula + s += ((max - s) * wk + var * (rk.rand() % wk)) / 128; + + if (s > max_s) + { + max_s = s; + *best = Rml[i].pv[0]; + *ponder = Rml[i].pv[1]; + } + } + } + + + /// RootMove and RootMoveList method's definitions + + RootMove::RootMove() { + + nodes = 0; + pv_score = non_pv_score = -VALUE_INFINITE; + pv[0] = MOVE_NONE; + } + + RootMove& RootMove::operator=(const RootMove& rm) { + + const Move* src = rm.pv; + Move* dst = pv; + + // Avoid a costly full rm.pv[] copy + do *dst++ = *src; while (*src++ != MOVE_NONE); + + nodes = rm.nodes; + pv_score = rm.pv_score; + non_pv_score = rm.non_pv_score; + return *this; + } + + void RootMoveList::init(Position& pos, Move searchMoves[]) { + + MoveStack mlist[MAX_MOVES]; + Move* sm; + + clear(); + bestMoveChanges = 0; + + // Generate all legal moves and add them to RootMoveList + MoveStack* last = generate(pos, mlist); + for (MoveStack* cur = mlist; cur != last; cur++) + { + // If we have a searchMoves[] list then verify cur->move + // is in the list before to add it. + for (sm = searchMoves; *sm && *sm != cur->move; sm++) {} + + if (searchMoves[0] && *sm != cur->move) + continue; + + RootMove rm; + rm.pv[0] = cur->move; + rm.pv[1] = MOVE_NONE; + rm.pv_score = -VALUE_INFINITE; + push_back(rm); + } + } + + // extract_pv_from_tt() builds a PV by adding moves from the transposition table. + // We consider also failing high nodes and not only VALUE_TYPE_EXACT nodes. This + // allow to always have a ponder move even when we fail high at root and also a + // long PV to print that is important for position analysis. + + void RootMove::extract_pv_from_tt(Position& pos) { + + StateInfo state[PLY_MAX_PLUS_2], *st = state; + TTEntry* tte; + int ply = 1; + + assert(pv[0] != MOVE_NONE && pos.move_is_legal(pv[0])); + + pos.do_move(pv[0], *st++); + + while ( (tte = TT.probe(pos.get_key())) != NULL + && tte->move() != MOVE_NONE + && pos.move_is_legal(tte->move()) + && ply < PLY_MAX + && (!pos.is_draw() || ply < 2)) + { + pv[ply] = tte->move(); + pos.do_move(pv[ply++], *st++); + } + pv[ply] = MOVE_NONE; + + do pos.undo_move(pv[--ply]); while (ply); + } + + // insert_pv_in_tt() is called at the end of a search iteration, and inserts + // the PV back into the TT. This makes sure the old PV moves are searched + // first, even if the old TT entries have been overwritten. + + void RootMove::insert_pv_in_tt(Position& pos) { + + StateInfo state[PLY_MAX_PLUS_2], *st = state; + TTEntry* tte; + Key k; + Value v, m = VALUE_NONE; + int ply = 0; + + assert(pv[0] != MOVE_NONE && pos.move_is_legal(pv[0])); + + do { + k = pos.get_key(); + tte = TT.probe(k); + + // Don't overwrite existing correct entries + if (!tte || tte->move() != pv[ply]) + { + v = (pos.in_check() ? VALUE_NONE : evaluate(pos, m)); + TT.store(k, VALUE_NONE, VALUE_TYPE_NONE, DEPTH_NONE, pv[ply], v, m); + } + pos.do_move(pv[ply], *st++); + + } while (pv[++ply] != MOVE_NONE); + + do pos.undo_move(pv[--ply]); while (ply); + } + + // pv_info_to_uci() returns a string with information on the current PV line + // formatted according to UCI specification. + + std::string RootMove::pv_info_to_uci(Position& pos, int depth, int selDepth, Value alpha, + Value beta, int pvIdx) { + std::stringstream s; + + s << "info depth " << depth + << " seldepth " << selDepth + << " multipv " << pvIdx + 1 + << " score " << value_to_uci(pv_score) + << (pv_score >= beta ? " lowerbound" : pv_score <= alpha ? " upperbound" : "") + << speed_to_uci(pos.nodes_searched()) + << " pv "; + + for (Move* m = pv; *m != MOVE_NONE; m++) + s << *m << " "; + + return s.str(); + } + + // Specializations for MovePickerExt in case of Root node + MovePickerExt::MovePickerExt(const Position& p, Move ttm, Depth d, + const History& h, SearchStack* ss, Value b) + : MovePicker(p, ttm, d, h, ss, b), firstCall(true) { + Move move; + Value score = VALUE_ZERO; + + // Score root moves using standard ordering used in main search, the moves + // are scored according to the order in which they are returned by MovePicker. + // This is the second order score that is used to compare the moves when + // the first orders pv_score of both moves are equal. + while ((move = MovePicker::get_next_move()) != MOVE_NONE) + for (rm = Rml.begin(); rm != Rml.end(); ++rm) + if (rm->pv[0] == move) + { + rm->non_pv_score = score--; + break; + } + + Rml.sort(); + rm = Rml.begin(); + } + + Move MovePickerExt::get_next_move() { + + if (!firstCall) + ++rm; + else + firstCall = false; + + return rm != Rml.end() ? rm->pv[0] : MOVE_NONE; + } + +} // namespace + + +// ThreadsManager::idle_loop() is where the threads are parked when they have no work +// to do. The parameter 'sp', if non-NULL, is a pointer to an active SplitPoint +// object for which the current thread is the master. + +void ThreadsManager::idle_loop(int threadID, SplitPoint* sp) { + + assert(threadID >= 0 && threadID < MAX_THREADS); + + int i; + bool allFinished; + + while (true) + { + // Slave threads can exit as soon as AllThreadsShouldExit raises, + // master should exit as last one. + if (allThreadsShouldExit) + { + assert(!sp); + threads[threadID].state = Thread::TERMINATED; + return; + } + + // If we are not thinking, wait for a condition to be signaled + // instead of wasting CPU time polling for work. + while ( threadID >= activeThreads + || threads[threadID].state == Thread::INITIALIZING + || (useSleepingThreads && threads[threadID].state == Thread::AVAILABLE)) + { + assert(!sp || useSleepingThreads); + assert(threadID != 0 || useSleepingThreads); + + if (threads[threadID].state == Thread::INITIALIZING) + threads[threadID].state = Thread::AVAILABLE; + + // Grab the lock to avoid races with Thread::wake_up() + lock_grab(&threads[threadID].sleepLock); + + // If we are master and all slaves have finished do not go to sleep + for (i = 0; sp && i < activeThreads && !sp->is_slave[i]; i++) {} + allFinished = (i == activeThreads); + + if (allFinished || allThreadsShouldExit) + { + lock_release(&threads[threadID].sleepLock); + break; + } + + // Do sleep here after retesting sleep conditions + if (threadID >= activeThreads || threads[threadID].state == Thread::AVAILABLE) + cond_wait(&threads[threadID].sleepCond, &threads[threadID].sleepLock); + + lock_release(&threads[threadID].sleepLock); + } + + // If this thread has been assigned work, launch a search + if (threads[threadID].state == Thread::WORKISWAITING) + { + assert(!allThreadsShouldExit); + + threads[threadID].state = Thread::SEARCHING; + + // Copy split point position and search stack and call search() + // with SplitPoint template parameter set to true. + SearchStack ss[PLY_MAX_PLUS_2]; + SplitPoint* tsp = threads[threadID].splitPoint; + Position pos(*tsp->pos, threadID); + + memcpy(ss, tsp->ss - 1, 4 * sizeof(SearchStack)); + (ss+1)->sp = tsp; + + if (tsp->pvNode) + search(pos, ss+1, tsp->alpha, tsp->beta, tsp->depth); + else + search(pos, ss+1, tsp->alpha, tsp->beta, tsp->depth); + + assert(threads[threadID].state == Thread::SEARCHING); + + threads[threadID].state = Thread::AVAILABLE; + + // Wake up master thread so to allow it to return from the idle loop in + // case we are the last slave of the split point. + if ( useSleepingThreads + && threadID != tsp->master + && threads[tsp->master].state == Thread::AVAILABLE) + threads[tsp->master].wake_up(); + } + + // If this thread is the master of a split point and all slaves have + // finished their work at this split point, return from the idle loop. + for (i = 0; sp && i < activeThreads && !sp->is_slave[i]; i++) {} + allFinished = (i == activeThreads); + + if (allFinished) + { + // Because sp->slaves[] is reset under lock protection, + // be sure sp->lock has been released before to return. + lock_grab(&(sp->lock)); + lock_release(&(sp->lock)); + + // In helpful master concept a master can help only a sub-tree, and + // because here is all finished is not possible master is booked. + assert(threads[threadID].state == Thread::AVAILABLE); + + threads[threadID].state = Thread::SEARCHING; + return; + } + } +} diff --git a/DroidFish/jni/stockfish/search.h b/DroidFish/jni/stockfish/search.h new file mode 100644 index 0000000..b179c9c --- /dev/null +++ b/DroidFish/jni/stockfish/search.h @@ -0,0 +1,73 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(SEARCH_H_INCLUDED) +#define SEARCH_H_INCLUDED + +#include + +#include "move.h" +#include "types.h" + +class Position; +struct SplitPoint; + +/// The SearchStack struct keeps track of the information we need to remember +/// from nodes shallower and deeper in the tree during the search. Each +/// search thread has its own array of SearchStack objects, indexed by the +/// current ply. + +struct SearchStack { + int ply; + Move currentMove; + Move mateKiller; + Move excludedMove; + Move bestMove; + Move killers[2]; + Depth reduction; + Value eval; + Value evalMargin; + bool skipNullMove; + SplitPoint* sp; +}; + + +/// The SearchLimits struct stores information sent by GUI about available time +/// to search the current move, maximum depth/time, if we are in analysis mode +/// or if we have to ponder while is our opponent's side to move. + +struct SearchLimits { + + SearchLimits() { memset(this, 0, sizeof(SearchLimits)); } + + SearchLimits(int t, int i, int mtg, int mt, int md, int mn, bool inf, bool pon) + : time(t), increment(i), movesToGo(mtg), maxTime(mt), maxDepth(md), + maxNodes(mn), infinite(inf), ponder(pon) {} + + bool useTimeManagement() const { return !(maxTime | maxDepth | maxNodes | int(infinite)); } + + int time, increment, movesToGo, maxTime, maxDepth, maxNodes; + bool infinite, ponder; +}; + +extern void init_search(); +extern int64_t perft(Position& pos, Depth depth); +extern bool think(Position& pos, const SearchLimits& limits, Move searchMoves[]); + +#endif // !defined(SEARCH_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/thread.cpp b/DroidFish/jni/stockfish/thread.cpp new file mode 100644 index 0000000..c7cabeb --- /dev/null +++ b/DroidFish/jni/stockfish/thread.cpp @@ -0,0 +1,345 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "thread.h" +#include "ucioption.h" + +ThreadsManager Threads; // Global object definition + +namespace { extern "C" { + + // start_routine() is the C function which is called when a new thread + // is launched. It simply calls idle_loop() with the supplied threadID. + // There are two versions of this function; one for POSIX threads and + // one for Windows threads. + +#if defined(_MSC_VER) + + DWORD WINAPI start_routine(LPVOID threadID) { + + Threads.idle_loop(*(int*)threadID, NULL); + return 0; + } + +#else + + void* start_routine(void* threadID) { + + Threads.idle_loop(*(int*)threadID, NULL); + return NULL; + } + +#endif + +} } + + +// wake_up() wakes up the thread, normally at the beginning of the search or, +// if "sleeping threads" is used, when there is some work to do. + +void Thread::wake_up() { + + lock_grab(&sleepLock); + cond_signal(&sleepCond); + lock_release(&sleepLock); +} + + +// cutoff_occurred() checks whether a beta cutoff has occurred in +// the thread's currently active split point, or in some ancestor of +// the current split point. + +bool Thread::cutoff_occurred() const { + + for (SplitPoint* sp = splitPoint; sp; sp = sp->parent) + if (sp->is_betaCutoff) + return true; + return false; +} + + +// is_available_to() checks whether the thread is available to help the thread with +// threadID "master" at a split point. An obvious requirement is that thread must be +// idle. With more than two threads, this is not by itself sufficient: If the thread +// is the master of some active split point, it is only available as a slave to the +// threads which are busy searching the split point at the top of "slave"'s split +// point stack (the "helpful master concept" in YBWC terminology). + +bool Thread::is_available_to(int master) const { + + if (state != AVAILABLE) + return false; + + // Make a local copy to be sure doesn't become zero under our feet while + // testing next condition and so leading to an out of bound access. + int localActiveSplitPoints = activeSplitPoints; + + // No active split points means that the thread is available as a slave for any + // other thread otherwise apply the "helpful master" concept if possible. + if ( !localActiveSplitPoints + || splitPoints[localActiveSplitPoints - 1].is_slave[master]) + return true; + + return false; +} + + +// read_uci_options() updates number of active threads and other internal +// parameters according to the UCI options values. It is called before +// to start a new search. + +void ThreadsManager::read_uci_options() { + + maxThreadsPerSplitPoint = Options["Maximum Number of Threads per Split Point"].value(); + minimumSplitDepth = Options["Minimum Split Depth"].value() * ONE_PLY; + useSleepingThreads = Options["Use Sleeping Threads"].value(); + activeThreads = Options["Threads"].value(); +} + + +// init() is called during startup. Initializes locks and condition variables +// and launches all threads sending them immediately to sleep. + +void ThreadsManager::init() { + + int threadID[MAX_THREADS]; + + // This flag is needed to properly end the threads when program exits + allThreadsShouldExit = false; + + // Threads will sent to sleep as soon as created, only main thread is kept alive + activeThreads = 1; + threads[0].state = Thread::SEARCHING; + + // Allocate pawn and material hash tables for main thread + init_hash_tables(); + + lock_init(&mpLock); + + // Initialize thread and split point locks + for (int i = 0; i < MAX_THREADS; i++) + { + lock_init(&threads[i].sleepLock); + cond_init(&threads[i].sleepCond); + + for (int j = 0; j < MAX_ACTIVE_SPLIT_POINTS; j++) + lock_init(&(threads[i].splitPoints[j].lock)); + } + + // Create and startup all the threads but the main that is already running + for (int i = 1; i < MAX_THREADS; i++) + { + threads[i].state = Thread::INITIALIZING; + threadID[i] = i; + +#if defined(_MSC_VER) + bool ok = (CreateThread(NULL, 0, start_routine, (LPVOID)&threadID[i], 0, NULL) != NULL); +#else + pthread_t pthreadID; + bool ok = (pthread_create(&pthreadID, NULL, start_routine, (void*)&threadID[i]) == 0); + pthread_detach(pthreadID); +#endif + if (!ok) + { + std::cout << "Failed to create thread number " << i << std::endl; + ::exit(EXIT_FAILURE); + } + + // Wait until the thread has finished launching and is gone to sleep + while (threads[i].state == Thread::INITIALIZING) {} + } +} + + +// exit() is called to cleanly exit the threads when the program finishes + +void ThreadsManager::exit() { + + // Force the woken up threads to exit idle_loop() and hence terminate + allThreadsShouldExit = true; + + for (int i = 0; i < MAX_THREADS; i++) + { + // Wake up all the threads and waits for termination + if (i != 0) + { + threads[i].wake_up(); + while (threads[i].state != Thread::TERMINATED) {} + } + + // Now we can safely destroy the locks and wait conditions + lock_destroy(&threads[i].sleepLock); + cond_destroy(&threads[i].sleepCond); + + for (int j = 0; j < MAX_ACTIVE_SPLIT_POINTS; j++) + lock_destroy(&(threads[i].splitPoints[j].lock)); + } + + lock_destroy(&mpLock); +} + + +// init_hash_tables() dynamically allocates pawn and material hash tables +// according to the number of active threads. This avoids preallocating +// memory for all possible threads if only few are used as, for instance, +// on mobile devices where memory is scarce and allocating for MAX_THREADS +// threads could even result in a crash. + +void ThreadsManager::init_hash_tables() { + + for (int i = 0; i < activeThreads; i++) + { + threads[i].pawnTable.init(); + threads[i].materialTable.init(); + } +} + + +// available_slave_exists() tries to find an idle thread which is available as +// a slave for the thread with threadID "master". + +bool ThreadsManager::available_slave_exists(int master) const { + + assert(master >= 0 && master < activeThreads); + + for (int i = 0; i < activeThreads; i++) + if (i != master && threads[i].is_available_to(master)) + return true; + + return false; +} + + +// split() does the actual work of distributing the work at a node between +// several available threads. If it does not succeed in splitting the +// node (because no idle threads are available, or because we have no unused +// split point objects), the function immediately returns. If splitting is +// possible, a SplitPoint object is initialized with all the data that must be +// copied to the helper threads and we tell our helper threads that they have +// been assigned work. This will cause them to instantly leave their idle loops and +// call search().When all threads have returned from search() then split() returns. + +template +void ThreadsManager::split(Position& pos, SearchStack* ss, Value* alpha, const Value beta, + Value* bestValue, Depth depth, Move threatMove, + int moveCount, MovePicker* mp, bool pvNode) { + assert(pos.is_ok()); + assert(*bestValue >= -VALUE_INFINITE); + assert(*bestValue <= *alpha); + assert(*alpha < beta); + assert(beta <= VALUE_INFINITE); + assert(depth > DEPTH_ZERO); + assert(pos.thread() >= 0 && pos.thread() < activeThreads); + assert(activeThreads > 1); + + int i, master = pos.thread(); + Thread& masterThread = threads[master]; + + lock_grab(&mpLock); + + // If no other thread is available to help us, or if we have too many + // active split points, don't split. + if ( !available_slave_exists(master) + || masterThread.activeSplitPoints >= MAX_ACTIVE_SPLIT_POINTS) + { + lock_release(&mpLock); + return; + } + + // Pick the next available split point object from the split point stack + SplitPoint& splitPoint = masterThread.splitPoints[masterThread.activeSplitPoints++]; + + // Initialize the split point object + splitPoint.parent = masterThread.splitPoint; + splitPoint.master = master; + splitPoint.is_betaCutoff = false; + splitPoint.depth = depth; + splitPoint.threatMove = threatMove; + splitPoint.alpha = *alpha; + splitPoint.beta = beta; + splitPoint.pvNode = pvNode; + splitPoint.bestValue = *bestValue; + splitPoint.mp = mp; + splitPoint.moveCount = moveCount; + splitPoint.pos = &pos; + splitPoint.nodes = 0; + splitPoint.ss = ss; + for (i = 0; i < activeThreads; i++) + splitPoint.is_slave[i] = false; + + masterThread.splitPoint = &splitPoint; + + // If we are here it means we are not available + assert(masterThread.state != Thread::AVAILABLE); + + int workersCnt = 1; // At least the master is included + + // Allocate available threads setting state to THREAD_BOOKED + for (i = 0; !Fake && i < activeThreads && workersCnt < maxThreadsPerSplitPoint; i++) + if (i != master && threads[i].is_available_to(master)) + { + threads[i].state = Thread::BOOKED; + threads[i].splitPoint = &splitPoint; + splitPoint.is_slave[i] = true; + workersCnt++; + } + + assert(Fake || workersCnt > 1); + + // We can release the lock because slave threads are already booked and master is not available + lock_release(&mpLock); + + // Tell the threads that they have work to do. This will make them leave + // their idle loop. + for (i = 0; i < activeThreads; i++) + if (i == master || splitPoint.is_slave[i]) + { + assert(i == master || threads[i].state == Thread::BOOKED); + + threads[i].state = Thread::WORKISWAITING; // This makes the slave to exit from idle_loop() + + if (useSleepingThreads && i != master) + threads[i].wake_up(); + } + + // Everything is set up. The master thread enters the idle loop, from + // which it will instantly launch a search, because its state is + // THREAD_WORKISWAITING. We send the split point as a second parameter to the + // idle loop, which means that the main thread will return from the idle + // loop when all threads have finished their work at this split point. + idle_loop(master, &splitPoint); + + // We have returned from the idle loop, which means that all threads are + // finished. Update alpha and bestValue, and return. + lock_grab(&mpLock); + + *alpha = splitPoint.alpha; + *bestValue = splitPoint.bestValue; + masterThread.activeSplitPoints--; + masterThread.splitPoint = splitPoint.parent; + pos.set_nodes_searched(pos.nodes_searched() + splitPoint.nodes); + + lock_release(&mpLock); +} + +// Explicit template instantiations +template void ThreadsManager::split(Position&, SearchStack*, Value*, const Value, Value*, Depth, Move, int, MovePicker*, bool); +template void ThreadsManager::split(Position&, SearchStack*, Value*, const Value, Value*, Depth, Move, int, MovePicker*, bool); diff --git a/DroidFish/jni/stockfish/thread.h b/DroidFish/jni/stockfish/thread.h new file mode 100644 index 0000000..13121ca --- /dev/null +++ b/DroidFish/jni/stockfish/thread.h @@ -0,0 +1,132 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(THREAD_H_INCLUDED) +#define THREAD_H_INCLUDED + +#include + +#include "lock.h" +#include "material.h" +#include "movepick.h" +#include "pawns.h" +#include "position.h" + +const int MAX_THREADS = 32; +const int MAX_ACTIVE_SPLIT_POINTS = 8; + +struct SplitPoint { + + // Const data after splitPoint has been setup + SplitPoint* parent; + const Position* pos; + Depth depth; + bool pvNode; + Value beta; + int ply; + int master; + Move threatMove; + + // Const pointers to shared data + MovePicker* mp; + SearchStack* ss; + + // Shared data + Lock lock; + volatile int64_t nodes; + volatile Value alpha; + volatile Value bestValue; + volatile int moveCount; + volatile bool is_betaCutoff; + volatile bool is_slave[MAX_THREADS]; +}; + + +/// Thread struct is used to keep together all the thread related stuff like locks, +/// state and especially split points. We also use per-thread pawn and material hash +/// tables so that once we get a pointer to an entry its life time is unlimited and +/// we don't have to care about someone changing the entry under our feet. + +struct Thread { + + enum ThreadState + { + INITIALIZING, // Thread is initializing itself + SEARCHING, // Thread is performing work + AVAILABLE, // Thread is waiting for work + BOOKED, // Other thread (master) has booked us as a slave + WORKISWAITING, // Master has ordered us to start + TERMINATED // We are quitting and thread is terminated + }; + + void wake_up(); + bool cutoff_occurred() const; + bool is_available_to(int master) const; + + MaterialInfoTable materialTable; + PawnInfoTable pawnTable; + int maxPly; + Lock sleepLock; + WaitCondition sleepCond; + volatile ThreadState state; + SplitPoint* volatile splitPoint; + volatile int activeSplitPoints; + SplitPoint splitPoints[MAX_ACTIVE_SPLIT_POINTS]; +}; + + +/// ThreadsManager class is used to handle all the threads related stuff like init, +/// starting, parking and, the most important, launching a slave thread at a split +/// point. All the access to shared thread data is done through this class. + +class ThreadsManager { + /* As long as the single ThreadsManager object is defined as a global we don't + need to explicitly initialize to zero its data members because variables with + static storage duration are automatically set to zero before enter main() + */ +public: + Thread& operator[](int threadID) { return threads[threadID]; } + void init(); + void exit(); + void init_hash_tables(); + + int min_split_depth() const { return minimumSplitDepth; } + int size() const { return activeThreads; } + void set_size(int cnt) { activeThreads = cnt; } + + void read_uci_options(); + bool available_slave_exists(int master) const; + void idle_loop(int threadID, SplitPoint* sp); + + template + void split(Position& pos, SearchStack* ss, Value* alpha, const Value beta, Value* bestValue, + Depth depth, Move threatMove, int moveCount, MovePicker* mp, bool pvNode); +private: + Lock mpLock; + Depth minimumSplitDepth; + int maxThreadsPerSplitPoint; + bool useSleepingThreads; + int activeThreads; + volatile bool allThreadsShouldExit; + Thread threads[MAX_THREADS]; +}; + +extern ThreadsManager Threads; + +#endif // !defined(THREAD_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/timeman.cpp b/DroidFish/jni/stockfish/timeman.cpp new file mode 100644 index 0000000..f07f27e --- /dev/null +++ b/DroidFish/jni/stockfish/timeman.cpp @@ -0,0 +1,161 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "misc.h" +#include "search.h" +#include "timeman.h" +#include "ucioption.h" + +namespace { + + /// Constants + + const int MoveHorizon = 50; // Plan time management at most this many moves ahead + const float MaxRatio = 3.0f; // When in trouble, we can step over reserved time with this ratio + const float StealRatio = 0.33f; // However we must not steal time from remaining moves over this ratio + + + // MoveImportance[] is based on naive statistical analysis of "how many games are still undecided + // after n half-moves". Game is considered "undecided" as long as neither side has >275cp advantage. + // Data was extracted from CCRL game database with some simple filtering criteria. + const int MoveImportance[512] = { + 7780, 7780, 7780, 7780, 7780, 7780, 7780, 7780, 7780, 7780, 7780, 7780, 7780, 7780, 7780, 7780, + 7780, 7780, 7780, 7780, 7778, 7778, 7776, 7776, 7776, 7773, 7770, 7768, 7766, 7763, 7757, 7751, + 7743, 7735, 7724, 7713, 7696, 7689, 7670, 7656, 7627, 7605, 7571, 7549, 7522, 7493, 7462, 7425, + 7385, 7350, 7308, 7272, 7230, 7180, 7139, 7094, 7055, 7010, 6959, 6902, 6841, 6778, 6705, 6651, + 6569, 6508, 6435, 6378, 6323, 6253, 6152, 6085, 5995, 5931, 5859, 5794, 5717, 5646, 5544, 5462, + 5364, 5282, 5172, 5078, 4988, 4901, 4831, 4764, 4688, 4609, 4536, 4443, 4365, 4293, 4225, 4155, + 4085, 4005, 3927, 3844, 3765, 3693, 3634, 3560, 3479, 3404, 3331, 3268, 3207, 3146, 3077, 3011, + 2947, 2894, 2828, 2776, 2727, 2676, 2626, 2589, 2538, 2490, 2442, 2394, 2345, 2302, 2243, 2192, + 2156, 2115, 2078, 2043, 2004, 1967, 1922, 1893, 1845, 1809, 1772, 1736, 1702, 1674, 1640, 1605, + 1566, 1536, 1509, 1479, 1452, 1423, 1388, 1362, 1332, 1304, 1289, 1266, 1250, 1228, 1206, 1180, + 1160, 1134, 1118, 1100, 1080, 1068, 1051, 1034, 1012, 1001, 980, 960, 945, 934, 916, 900, 888, + 878, 865, 852, 828, 807, 787, 770, 753, 744, 731, 722, 706, 700, 683, 676, 671, 664, 652, 641, + 634, 627, 613, 604, 591, 582, 568, 560, 552, 540, 534, 529, 519, 509, 495, 484, 474, 467, 460, + 450, 438, 427, 419, 410, 406, 399, 394, 387, 382, 377, 372, 366, 359, 353, 348, 343, 337, 333, + 328, 321, 315, 309, 303, 298, 293, 287, 284, 281, 277, 273, 265, 261, 255, 251, 247, 241, 240, + 235, 229, 218, 217, 213, 212, 208, 206, 197, 193, 191, 189, 185, 184, 180, 177, 172, 170, 170, + 170, 166, 163, 159, 158, 156, 155, 151, 146, 141, 138, 136, 132, 130, 128, 125, 123, 122, 118, + 118, 118, 117, 115, 114, 108, 107, 105, 105, 105, 102, 97, 97, 95, 94, 93, 91, 88, 86, 83, 80, + 80, 79, 79, 79, 78, 76, 75, 72, 72, 71, 70, 68, 65, 63, 61, 61, 59, 59, 59, 58, 56, 55, 54, 54, + 52, 49, 48, 48, 48, 48, 45, 45, 45, 44, 43, 41, 41, 41, 41, 40, 40, 38, 37, 36, 34, 34, 34, 33, + 31, 29, 29, 29, 28, 28, 28, 28, 28, 28, 28, 27, 27, 27, 27, 27, 24, 24, 23, 23, 22, 21, 20, 20, + 19, 19, 19, 19, 19, 18, 18, 18, 18, 17, 17, 17, 17, 17, 16, 16, 15, 15, 14, 14, 14, 12, 12, 11, + 9, 9, 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 2, 2, 2, 2, + 2, 1, 1, 1, 1, 1, 1, 1 }; + + int move_importance(int ply) { return MoveImportance[Min(ply, 511)]; } + + + /// Function Prototypes + + enum TimeType { OptimumTime, MaxTime }; + + template + int remaining(int myTime, int movesToGo, int currentPly); +} + + +void TimeManager::pv_instability(int curChanges, int prevChanges) { + + unstablePVExtraTime = curChanges * (optimumSearchTime / 2) + + prevChanges * (optimumSearchTime / 3); +} + + +void TimeManager::init(const SearchLimits& limits, int currentPly) +{ + /* We support four different kind of time controls: + + increment == 0 && movesToGo == 0 means: x basetime [sudden death!] + increment == 0 && movesToGo != 0 means: x moves in y minutes + increment > 0 && movesToGo == 0 means: x basetime + z increment + increment > 0 && movesToGo != 0 means: x moves in y minutes + z increment + + Time management is adjusted by following UCI parameters: + + emergencyMoveHorizon: Be prepared to always play at least this many moves + emergencyBaseTime : Always attempt to keep at least this much time (in ms) at clock + emergencyMoveTime : Plus attempt to keep at least this much time for each remaining emergency move + minThinkingTime : No matter what, use at least this much thinking before doing the move + */ + + int hypMTG, hypMyTime, t1, t2; + + // Read uci parameters + int emergencyMoveHorizon = Options["Emergency Move Horizon"].value(); + int emergencyBaseTime = Options["Emergency Base Time"].value(); + int emergencyMoveTime = Options["Emergency Move Time"].value(); + int minThinkingTime = Options["Minimum Thinking Time"].value(); + + // Initialize to maximum values but unstablePVExtraTime that is reset + unstablePVExtraTime = 0; + optimumSearchTime = maximumSearchTime = limits.time; + + // We calculate optimum time usage for different hypothetic "moves to go"-values and choose the + // minimum of calculated search time values. Usually the greatest hypMTG gives the minimum values. + for (hypMTG = 1; hypMTG <= (limits.movesToGo ? Min(limits.movesToGo, MoveHorizon) : MoveHorizon); hypMTG++) + { + // Calculate thinking time for hypothetic "moves to go"-value + hypMyTime = limits.time + + limits.increment * (hypMTG - 1) + - emergencyBaseTime + - emergencyMoveTime * Min(hypMTG, emergencyMoveHorizon); + + hypMyTime = Max(hypMyTime, 0); + + t1 = minThinkingTime + remaining(hypMyTime, hypMTG, currentPly); + t2 = minThinkingTime + remaining(hypMyTime, hypMTG, currentPly); + + optimumSearchTime = Min(optimumSearchTime, t1); + maximumSearchTime = Min(maximumSearchTime, t2); + } + + if (Options["Ponder"].value()) + optimumSearchTime += optimumSearchTime / 4; + + // Make sure that maxSearchTime is not over absoluteMaxSearchTime + optimumSearchTime = Min(optimumSearchTime, maximumSearchTime); +} + + +namespace { + + template + int remaining(int myTime, int movesToGo, int currentPly) + { + const float TMaxRatio = (T == OptimumTime ? 1 : MaxRatio); + const float TStealRatio = (T == OptimumTime ? 0 : StealRatio); + + int thisMoveImportance = move_importance(currentPly); + int otherMovesImportance = 0; + + for (int i = 1; i < movesToGo; i++) + otherMovesImportance += move_importance(currentPly + 2 * i); + + float ratio1 = (TMaxRatio * thisMoveImportance) / float(TMaxRatio * thisMoveImportance + otherMovesImportance); + float ratio2 = (thisMoveImportance + TStealRatio * otherMovesImportance) / float(thisMoveImportance + otherMovesImportance); + + return int(floor(myTime * Min(ratio1, ratio2))); + } +} diff --git a/DroidFish/jni/stockfish/timeman.h b/DroidFish/jni/stockfish/timeman.h new file mode 100644 index 0000000..bf489b7 --- /dev/null +++ b/DroidFish/jni/stockfish/timeman.h @@ -0,0 +1,39 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(TIMEMAN_H_INCLUDED) +#define TIMEMAN_H_INCLUDED + +struct SearchLimits; + +class TimeManager { +public: + + void init(const SearchLimits& limits, int currentPly); + void pv_instability(int curChanges, int prevChanges); + int available_time() const { return optimumSearchTime + unstablePVExtraTime; } + int maximum_time() const { return maximumSearchTime; } + +private: + int optimumSearchTime; + int maximumSearchTime; + int unstablePVExtraTime; +}; + +#endif // !defined(TIMEMAN_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/tt.cpp b/DroidFish/jni/stockfish/tt.cpp new file mode 100644 index 0000000..86b16c8 --- /dev/null +++ b/DroidFish/jni/stockfish/tt.cpp @@ -0,0 +1,144 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +#include "tt.h" + +TranspositionTable TT; // Our global transposition table + +TranspositionTable::TranspositionTable() { + + size = generation = 0; + entries = NULL; +} + +TranspositionTable::~TranspositionTable() { + + delete [] entries; +} + + +/// TranspositionTable::set_size() sets the size of the transposition table, +/// measured in megabytes. + +void TranspositionTable::set_size(size_t mbSize) { + + size_t newSize = 1024; + + // Transposition table consists of clusters and each cluster consists + // of ClusterSize number of TTEntries. Each non-empty entry contains + // information of exactly one position and newSize is the number of + // clusters we are going to allocate. + while (2ULL * newSize * sizeof(TTCluster) <= (mbSize << 20)) + newSize *= 2; + + if (newSize == size) + return; + + size = newSize; + delete [] entries; + entries = new (std::nothrow) TTCluster[size]; + if (!entries) + { + std::cerr << "Failed to allocate " << mbSize + << " MB for transposition table." << std::endl; + exit(EXIT_FAILURE); + } + clear(); +} + + +/// TranspositionTable::clear() overwrites the entire transposition table +/// with zeroes. It is called whenever the table is resized, or when the +/// user asks the program to clear the table (from the UCI interface). + +void TranspositionTable::clear() { + + memset(entries, 0, size * sizeof(TTCluster)); +} + + +/// TranspositionTable::store() writes a new entry containing position key and +/// valuable information of current position. The lowest order bits of position +/// key are used to decide on which cluster the position will be placed. +/// When a new entry is written and there are no empty entries available in cluster, +/// it replaces the least valuable of entries. A TTEntry t1 is considered to be +/// more valuable than a TTEntry t2 if t1 is from the current search and t2 is from +/// a previous search, or if the depth of t1 is bigger than the depth of t2. + +void TranspositionTable::store(const Key posKey, Value v, ValueType t, Depth d, Move m, Value statV, Value kingD) { + + int c1, c2, c3; + TTEntry *tte, *replace; + uint32_t posKey32 = posKey >> 32; // Use the high 32 bits as key inside the cluster + + tte = replace = first_entry(posKey); + + for (int i = 0; i < ClusterSize; i++, tte++) + { + if (!tte->key() || tte->key() == posKey32) // Empty or overwrite old + { + // Preserve any existing ttMove + if (m == MOVE_NONE) + m = tte->move(); + + tte->save(posKey32, v, t, d, m, generation, statV, kingD); + return; + } + + // Implement replace strategy + c1 = (replace->generation() == generation ? 2 : 0); + c2 = (tte->generation() == generation || tte->type() == VALUE_TYPE_EXACT ? -2 : 0); + c3 = (tte->depth() < replace->depth() ? 1 : 0); + + if (c1 + c2 + c3 > 0) + replace = tte; + } + replace->save(posKey32, v, t, d, m, generation, statV, kingD); +} + + +/// TranspositionTable::probe() looks up the current position in the +/// transposition table. Returns a pointer to the TTEntry or NULL if +/// position is not found. + +TTEntry* TranspositionTable::probe(const Key posKey) const { + + uint32_t posKey32 = posKey >> 32; + TTEntry* tte = first_entry(posKey); + + for (int i = 0; i < ClusterSize; i++, tte++) + if (tte->key() == posKey32) + return tte; + + return NULL; +} + + +/// TranspositionTable::new_search() is called at the beginning of every new +/// search. It increments the "generation" variable, which is used to +/// distinguish transposition table entries from previous searches from +/// entries from the current search. + +void TranspositionTable::new_search() { + generation++; +} diff --git a/DroidFish/jni/stockfish/tt.h b/DroidFish/jni/stockfish/tt.h new file mode 100644 index 0000000..03147bf --- /dev/null +++ b/DroidFish/jni/stockfish/tt.h @@ -0,0 +1,173 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(TT_H_INCLUDED) +#define TT_H_INCLUDED + +#include + +#include "move.h" +#include "types.h" + + +/// The TTEntry is the class of transposition table entries +/// +/// A TTEntry needs 128 bits to be stored +/// +/// bit 0-31: key +/// bit 32-63: data +/// bit 64-79: value +/// bit 80-95: depth +/// bit 96-111: static value +/// bit 112-127: margin of static value +/// +/// the 32 bits of the data field are so defined +/// +/// bit 0-15: move +/// bit 16-20: not used +/// bit 21-22: value type +/// bit 23-31: generation + +class TTEntry { + +public: + void save(uint32_t k, Value v, ValueType t, Depth d, Move m, int g, Value statV, Value statM) { + + key32 = (uint32_t)k; + move16 = (uint16_t)m; + valueType = (uint8_t)t; + generation8 = (uint8_t)g; + value16 = (int16_t)v; + depth16 = (int16_t)d; + staticValue = (int16_t)statV; + staticMargin = (int16_t)statM; + } + void set_generation(int g) { generation8 = (uint8_t)g; } + + uint32_t key() const { return key32; } + Depth depth() const { return (Depth)depth16; } + Move move() const { return (Move)move16; } + Value value() const { return (Value)value16; } + ValueType type() const { return (ValueType)valueType; } + int generation() const { return (int)generation8; } + Value static_value() const { return (Value)staticValue; } + Value static_value_margin() const { return (Value)staticMargin; } + +private: + uint32_t key32; + uint16_t move16; + uint8_t valueType, generation8; + int16_t value16, depth16, staticValue, staticMargin; +}; + + +/// This is the number of TTEntry slots for each cluster +const int ClusterSize = 4; + + +/// TTCluster consists of ClusterSize number of TTEntries. Size of TTCluster +/// must not be bigger than a cache line size. In case it is less, it should +/// be padded to guarantee always aligned accesses. + +struct TTCluster { + TTEntry data[ClusterSize]; +}; + + +/// The transposition table class. This is basically just a huge array containing +/// TTCluster objects, and a few methods for writing and reading entries. + +class TranspositionTable { + + TranspositionTable(const TranspositionTable&); + TranspositionTable& operator=(const TranspositionTable&); + +public: + TranspositionTable(); + ~TranspositionTable(); + void set_size(size_t mbSize); + void clear(); + void store(const Key posKey, Value v, ValueType type, Depth d, Move m, Value statV, Value kingD); + TTEntry* probe(const Key posKey) const; + void new_search(); + TTEntry* first_entry(const Key posKey) const; + void refresh(const TTEntry* tte) const; + +private: + size_t size; + TTCluster* entries; + uint8_t generation; // Size must be not bigger then TTEntry::generation8 +}; + +extern TranspositionTable TT; + + +/// TranspositionTable::first_entry() returns a pointer to the first entry of +/// a cluster given a position. The lowest order bits of the key are used to +/// get the index of the cluster. + +inline TTEntry* TranspositionTable::first_entry(const Key posKey) const { + + return entries[((uint32_t)posKey) & (size - 1)].data; +} + + +/// TranspositionTable::refresh() updates the 'generation' value of the TTEntry +/// to avoid aging. Normally called after a TT hit. + +inline void TranspositionTable::refresh(const TTEntry* tte) const { + + const_cast(tte)->set_generation(generation); +} + + +/// A simple fixed size hash table used to store pawns and material +/// configurations. It is basically just an array of Entry objects. +/// Without cluster concept or overwrite policy. + +template +struct SimpleHash { + + typedef SimpleHash Base; + + void init() { + + if (entries) + return; + + entries = new (std::nothrow) Entry[HashSize]; + if (!entries) + { + std::cerr << "Failed to allocate " << HashSize * sizeof(Entry) + << " bytes for hash table." << std::endl; + exit(EXIT_FAILURE); + } + memset(entries, 0, HashSize * sizeof(Entry)); + } + + virtual ~SimpleHash() { delete [] entries; } + + Entry* probe(Key key) const { return entries + ((uint32_t)key & (HashSize - 1)); } + void prefetch(Key key) const { ::prefetch((char*)probe(key)); } + +protected: + Entry* entries; +}; + +#endif // !defined(TT_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/types.h b/DroidFish/jni/stockfish/types.h new file mode 100644 index 0000000..e2ed6f7 --- /dev/null +++ b/DroidFish/jni/stockfish/types.h @@ -0,0 +1,471 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(TYPES_H_INCLUDED) +#define TYPES_H_INCLUDED + +#include +#include +#include + +#if defined(_MSC_VER) + +// Disable some silly and noisy warning from MSVC compiler +#pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false' +#pragma warning(disable: 4127) // Conditional expression is constant +#pragma warning(disable: 4146) // Unary minus operator applied to unsigned type + +// MSVC does not support +typedef signed __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef signed __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef signed __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + +#else + +#include + +#endif + +#define Min(x, y) (((x) < (y)) ? (x) : (y)) +#define Max(x, y) (((x) < (y)) ? (y) : (x)) + +//// +//// Configuration +//// + +//// For Linux and OSX configuration is done automatically using Makefile. +//// To get started type "make help". +//// +//// For windows part of the configuration is detected automatically, but +//// some switches need to be set manually: +//// +//// -DNDEBUG | Disable debugging mode. Use always. +//// +//// -DNO_PREFETCH | Disable use of prefetch asm-instruction. A must if you want the +//// | executable to run on some very old machines. +//// +//// -DUSE_POPCNT | Add runtime support for use of popcnt asm-instruction. +//// | Works only in 64-bit mode. For compiling requires hardware +//// | with popcnt support. Around 4% speed-up. +//// +//// -DOLD_LOCKS | By default under Windows are used the fast Slim Reader/Writer (SRW) +//// | Locks and Condition Variables: these are not supported by Windows XP +//// | and older, to compile for those platforms you should enable OLD_LOCKS. + +// Automatic detection for 64-bit under Windows +#if defined(_WIN64) +#define IS_64BIT +#endif + +// Automatic detection for use of bsfq asm-instruction under Windows +#if defined(_WIN64) +#define USE_BSFQ +#endif + +// Intel header for _mm_popcnt_u64() intrinsic +#if defined(USE_POPCNT) && defined(_MSC_VER) && defined(__INTEL_COMPILER) +#include +#endif + +// Cache line alignment specification +#if defined(_MSC_VER) || defined(__INTEL_COMPILER) +#define CACHE_LINE_ALIGNMENT __declspec(align(64)) +#else +#define CACHE_LINE_ALIGNMENT __attribute__ ((aligned(64))) +#endif + +// Define a __cpuid() function for gcc compilers, for Intel and MSVC +// is already available as an intrinsic. +#if defined(_MSC_VER) +#include +#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +inline void __cpuid(int CPUInfo[4], int InfoType) +{ + int* eax = CPUInfo + 0; + int* ebx = CPUInfo + 1; + int* ecx = CPUInfo + 2; + int* edx = CPUInfo + 3; + + *eax = InfoType; + *ecx = 0; + __asm__("cpuid" : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx) + : "0" (*eax), "2" (*ecx)); +} +#else +inline void __cpuid(int CPUInfo[4], int) +{ + CPUInfo[0] = CPUInfo[1] = CPUInfo[2] = CPUInfo[3] = 0; +} +#endif + +// Define FORCE_INLINE macro to force inlining overriding compiler choice +#if defined(_MSC_VER) +#define FORCE_INLINE __forceinline +#elif defined(__GNUC__) +#define FORCE_INLINE inline __attribute__((always_inline)) +#else +#define FORCE_INLINE inline +#endif + +/// cpu_has_popcnt() detects support for popcnt instruction at runtime +inline bool cpu_has_popcnt() { + + int CPUInfo[4] = {-1}; + __cpuid(CPUInfo, 0x00000001); + return (CPUInfo[2] >> 23) & 1; +} + +/// CpuHasPOPCNT is a global constant initialized at startup that +/// is set to true if CPU on which application runs supports popcnt +/// hardware instruction. Unless USE_POPCNT is not defined. +#if defined(USE_POPCNT) +const bool CpuHasPOPCNT = cpu_has_popcnt(); +#else +const bool CpuHasPOPCNT = false; +#endif + + +/// CpuIs64Bit is a global constant initialized at compile time that +/// is set to true if CPU on which application runs is a 64 bits. +#if defined(IS_64BIT) +const bool CpuIs64Bit = true; +#else +const bool CpuIs64Bit = false; +#endif + +#include + +typedef uint64_t Key; +typedef uint64_t Bitboard; + +const int PLY_MAX = 100; +const int PLY_MAX_PLUS_2 = PLY_MAX + 2; + +enum ValueType { + VALUE_TYPE_NONE = 0, + VALUE_TYPE_UPPER = 1, + VALUE_TYPE_LOWER = 2, + VALUE_TYPE_EXACT = VALUE_TYPE_UPPER | VALUE_TYPE_LOWER +}; + +enum Value { + VALUE_ZERO = 0, + VALUE_DRAW = 0, + VALUE_KNOWN_WIN = 15000, + VALUE_MATE = 30000, + VALUE_INFINITE = 30001, + VALUE_NONE = 30002, + + VALUE_MATE_IN_PLY_MAX = VALUE_MATE - PLY_MAX, + VALUE_MATED_IN_PLY_MAX = -VALUE_MATE + PLY_MAX, + + VALUE_ENSURE_INTEGER_SIZE_P = INT_MAX, + VALUE_ENSURE_INTEGER_SIZE_N = INT_MIN +}; + +enum PieceType { + PIECE_TYPE_NONE = 0, + PAWN = 1, KNIGHT = 2, BISHOP = 3, ROOK = 4, QUEEN = 5, KING = 6 +}; + +enum Piece { + PIECE_NONE_DARK_SQ = 0, WP = 1, WN = 2, WB = 3, WR = 4, WQ = 5, WK = 6, + BP = 9, BN = 10, BB = 11, BR = 12, BQ = 13, BK = 14, PIECE_NONE = 16 +}; + +enum Color { + WHITE, BLACK, COLOR_NONE +}; + +enum Depth { + + ONE_PLY = 2, + + DEPTH_ZERO = 0 * ONE_PLY, + DEPTH_QS_CHECKS = -1 * ONE_PLY, + DEPTH_QS_NO_CHECKS = -2 * ONE_PLY, + + DEPTH_NONE = -127 * ONE_PLY +}; + +enum Square { + SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, + SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, + SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, + SQ_A4, SQ_B4, SQ_C4, SQ_D4, SQ_E4, SQ_F4, SQ_G4, SQ_H4, + SQ_A5, SQ_B5, SQ_C5, SQ_D5, SQ_E5, SQ_F5, SQ_G5, SQ_H5, + SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6, + SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7, + SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8, + SQ_NONE, + + DELTA_N = 8, + DELTA_E = 1, + DELTA_S = -8, + DELTA_W = -1, + + DELTA_NN = DELTA_N + DELTA_N, + DELTA_NE = DELTA_N + DELTA_E, + DELTA_SE = DELTA_S + DELTA_E, + DELTA_SS = DELTA_S + DELTA_S, + DELTA_SW = DELTA_S + DELTA_W, + DELTA_NW = DELTA_N + DELTA_W +}; + +enum File { + FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H +}; + +enum Rank { + RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8 +}; + +enum SquareColor { + DARK, LIGHT +}; + +enum ScaleFactor { + SCALE_FACTOR_ZERO = 0, + SCALE_FACTOR_NORMAL = 64, + SCALE_FACTOR_MAX = 128, + SCALE_FACTOR_NONE = 255 +}; + + +/// Score enum keeps a midgame and an endgame value in a single +/// integer (enum), first LSB 16 bits are used to store endgame +/// value, while upper bits are used for midgame value. Compiler +/// is free to choose the enum type as long as can keep its data, +/// so ensure Score to be an integer type. +enum Score { + SCORE_ZERO = 0, + SCORE_ENSURE_INTEGER_SIZE_P = INT_MAX, + SCORE_ENSURE_INTEGER_SIZE_N = INT_MIN +}; + +#define ENABLE_OPERATORS_ON(T) \ +inline T operator+ (const T d1, const T d2) { return T(int(d1) + int(d2)); } \ +inline T operator- (const T d1, const T d2) { return T(int(d1) - int(d2)); } \ +inline T operator* (int i, const T d) { return T(i * int(d)); } \ +inline T operator* (const T d, int i) { return T(int(d) * i); } \ +inline T operator/ (const T d, int i) { return T(int(d) / i); } \ +inline T operator- (const T d) { return T(-int(d)); } \ +inline T operator++ (T& d, int) {d = T(int(d) + 1); return d; } \ +inline T operator-- (T& d, int) { d = T(int(d) - 1); return d; } \ +inline void operator+= (T& d1, const T d2) { d1 = d1 + d2; } \ +inline void operator-= (T& d1, const T d2) { d1 = d1 - d2; } \ +inline void operator*= (T& d, int i) { d = T(int(d) * i); } \ +inline void operator/= (T& d, int i) { d = T(int(d) / i); } + +ENABLE_OPERATORS_ON(Value) +ENABLE_OPERATORS_ON(PieceType) +ENABLE_OPERATORS_ON(Piece) +ENABLE_OPERATORS_ON(Color) +ENABLE_OPERATORS_ON(Depth) +ENABLE_OPERATORS_ON(Square) +ENABLE_OPERATORS_ON(File) +ENABLE_OPERATORS_ON(Rank) + +#undef ENABLE_OPERATORS_ON + +// Extra operators for adding integers to a Value +inline Value operator+ (Value v, int i) { return Value(int(v) + i); } +inline Value operator- (Value v, int i) { return Value(int(v) - i); } + +// Extracting the _signed_ lower and upper 16 bits it not so trivial +// because according to the standard a simple cast to short is +// implementation defined and so is a right shift of a signed integer. +inline Value mg_value(Score s) { return Value(((int(s) + 32768) & ~0xffff) / 0x10000); } + +// Unfortunatly on Intel 64 bit we have a small speed regression, so use a faster code in +// this case, although not 100% standard compliant it seems to work for Intel and MSVC. +#if defined(IS_64BIT) && (!defined(__GNUC__) || defined(__INTEL_COMPILER)) +inline Value eg_value(Score s) { return Value(int16_t(s & 0xffff)); } +#else +inline Value eg_value(Score s) { return Value((int)(unsigned(s) & 0x7fffu) - (int)(unsigned(s) & 0x8000u)); } +#endif + +inline Score make_score(int mg, int eg) { return Score((mg << 16) + eg); } + +// Division must be handled separately for each term +inline Score operator/(Score s, int i) { return make_score(mg_value(s) / i, eg_value(s) / i); } + +// Only declared but not defined. We don't want to multiply two scores due to +// a very high risk of overflow. So user should explicitly convert to integer. +inline Score operator*(Score s1, Score s2); + +// Remaining Score operators are standard +inline Score operator+ (const Score d1, const Score d2) { return Score(int(d1) + int(d2)); } +inline Score operator- (const Score d1, const Score d2) { return Score(int(d1) - int(d2)); } +inline Score operator* (int i, const Score d) { return Score(i * int(d)); } +inline Score operator* (const Score d, int i) { return Score(int(d) * i); } +inline Score operator- (const Score d) { return Score(-int(d)); } +inline void operator+= (Score& d1, const Score d2) { d1 = d1 + d2; } +inline void operator-= (Score& d1, const Score d2) { d1 = d1 - d2; } +inline void operator*= (Score& d, int i) { d = Score(int(d) * i); } +inline void operator/= (Score& d, int i) { d = Score(int(d) / i); } + +const Value PawnValueMidgame = Value(0x0C6); +const Value PawnValueEndgame = Value(0x102); +const Value KnightValueMidgame = Value(0x331); +const Value KnightValueEndgame = Value(0x34E); +const Value BishopValueMidgame = Value(0x344); +const Value BishopValueEndgame = Value(0x359); +const Value RookValueMidgame = Value(0x4F6); +const Value RookValueEndgame = Value(0x4FE); +const Value QueenValueMidgame = Value(0x9D9); +const Value QueenValueEndgame = Value(0x9FE); + +inline Value value_mate_in(int ply) { + return VALUE_MATE - ply; +} + +inline Value value_mated_in(int ply) { + return -VALUE_MATE + ply; +} + +inline Piece make_piece(Color c, PieceType pt) { + return Piece((int(c) << 3) | int(pt)); +} + +inline PieceType type_of_piece(Piece p) { + return PieceType(int(p) & 7); +} + +inline Color color_of_piece(Piece p) { + return Color(int(p) >> 3); +} + +inline Color opposite_color(Color c) { + return Color(int(c) ^ 1); +} + +inline bool color_is_ok(Color c) { + return c == WHITE || c == BLACK; +} + +inline bool piece_type_is_ok(PieceType pt) { + return pt >= PAWN && pt <= KING; +} + +inline bool piece_is_ok(Piece p) { + return piece_type_is_ok(type_of_piece(p)) && color_is_ok(color_of_piece(p)); +} + +inline char piece_type_to_char(PieceType pt) { + static const char ch[] = " PNBRQK"; + return ch[pt]; +} + +inline Square make_square(File f, Rank r) { + return Square((int(r) << 3) | int(f)); +} + +inline File square_file(Square s) { + return File(int(s) & 7); +} + +inline Rank square_rank(Square s) { + return Rank(int(s) >> 3); +} + +inline Square flip_square(Square s) { + return Square(int(s) ^ 56); +} + +inline Square flop_square(Square s) { + return Square(int(s) ^ 7); +} + +inline Square relative_square(Color c, Square s) { + return Square(int(s) ^ (int(c) * 56)); +} + +inline Rank relative_rank(Color c, Rank r) { + return Rank(int(r) ^ (int(c) * 7)); +} + +inline Rank relative_rank(Color c, Square s) { + return relative_rank(c, square_rank(s)); +} + +inline SquareColor square_color(Square s) { + return SquareColor(int(square_rank(s) + s) & 1); +} + +inline bool opposite_color_squares(Square s1, Square s2) { + int s = int(s1) ^ int(s2); + return ((s >> 3) ^ s) & 1; +} + +inline int file_distance(Square s1, Square s2) { + return abs(square_file(s1) - square_file(s2)); +} + +inline int rank_distance(Square s1, Square s2) { + return abs(square_rank(s1) - square_rank(s2)); +} + +inline int square_distance(Square s1, Square s2) { + return Max(file_distance(s1, s2), rank_distance(s1, s2)); +} + +inline File file_from_char(char c) { + return File(c - 'a') + FILE_A; +} + +inline char file_to_char(File f) { + return char(f - FILE_A + int('a')); +} + +inline Rank rank_from_char(char c) { + return Rank(c - '1') + RANK_1; +} + +inline char rank_to_char(Rank r) { + return char(r - RANK_1 + int('1')); +} + +inline const std::string square_to_string(Square s) { + char ch[] = { file_to_char(square_file(s)), rank_to_char(square_rank(s)), 0 }; + return std::string(ch); +} + +inline bool file_is_ok(File f) { + return f >= FILE_A && f <= FILE_H; +} + +inline bool rank_is_ok(Rank r) { + return r >= RANK_1 && r <= RANK_8; +} + +inline bool square_is_ok(Square s) { + return s >= SQ_A1 && s <= SQ_H8; +} + +inline Square pawn_push(Color c) { + return c == WHITE ? DELTA_N : DELTA_S; +} + +#endif // !defined(TYPES_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/uci.cpp b/DroidFish/jni/stockfish/uci.cpp new file mode 100644 index 0000000..2dacd1c --- /dev/null +++ b/DroidFish/jni/stockfish/uci.cpp @@ -0,0 +1,247 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include + +#include "evaluate.h" +#include "misc.h" +#include "move.h" +#include "position.h" +#include "search.h" +#include "ucioption.h" + +using namespace std; + +namespace { + + // FEN string for the initial position + const string StartPositionFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + + // UCIParser is a class for parsing UCI input. The class + // is actually a string stream built on a given input string. + typedef istringstream UCIParser; + + void set_option(UCIParser& up); + void set_position(Position& pos, UCIParser& up); + bool go(Position& pos, UCIParser& up); + void perft(Position& pos, UCIParser& up); +} + + +/// execute_uci_command() takes a string as input, uses a UCIParser +/// object to parse this text string as a UCI command, and calls +/// the appropriate functions. In addition to the UCI commands, +/// the function also supports a few debug commands. + +bool execute_uci_command(const string& cmd) { + + static Position pos(StartPositionFEN, false, 0); // The root position + + UCIParser up(cmd); + string token; + + up >> token; // operator>>() skips any whitespace + + if (token == "quit") + return false; + + if (token == "go") + return go(pos, up); + + if (token == "ucinewgame") + pos.from_fen(StartPositionFEN, false); + + else if (token == "isready") + cout << "readyok" << endl; + + else if (token == "position") + set_position(pos, up); + + else if (token == "setoption") + set_option(up); + + else if (token == "perft") + perft(pos, up); + + else if (token == "d") + pos.print(); + + else if (token == "flip") + pos.flip(); + + else if (token == "eval") + { + read_evaluation_uci_options(pos.side_to_move()); + cout << trace_evaluate(pos) << endl; + } + + else if (token == "key") + cout << "key: " << hex << pos.get_key() + << "\nmaterial key: " << pos.get_material_key() + << "\npawn key: " << pos.get_pawn_key() << endl; + + else if (token == "uci") + cout << "id name " << engine_name() + << "\nid author " << engine_authors() + << "\n" << Options.print_all() + << "\nuciok" << endl; + else + cout << "Unknown command: " << cmd << endl; + + return true; +} + + +namespace { + + // set_position() is called when engine receives the "position" UCI + // command. The function sets up the position described in the given + // fen string ("fen") or the starting position ("startpos") and then + // makes the moves given in the following move list ("moves"). + + void set_position(Position& pos, UCIParser& up) { + + string token, fen; + + up >> token; // operator>>() skips any whitespace + + if (token == "startpos") + { + pos.from_fen(StartPositionFEN, false); + up >> token; // Consume "moves" token if any + } + else if (token == "fen") + { + while (up >> token && token != "moves") + fen += token + " "; + + pos.from_fen(fen, Options["UCI_Chess960"].value()); + } + else return; + + // Parse move list (if any) + while (up >> token) + pos.do_setup_move(move_from_uci(pos, token)); + } + + + // set_option() is called when engine receives the "setoption" UCI + // command. The function updates the corresponding UCI option ("name") + // to the given value ("value"). + + void set_option(UCIParser& up) { + + string token, name; + string value = "true"; // UCI buttons don't have a "value" field + + up >> token; // Consume "name" token + up >> name; // Read option name + + // Handle names with included spaces + while (up >> token && token != "value") + name += " " + token; + + up >> value; // Read option value + + // Handle values with included spaces + while (up >> token) + value += " " + token; + + if (Options.find(name) != Options.end()) + Options[name].set_value(value); + else + cout << "No such option: " << name << endl; + } + + + // go() is called when engine receives the "go" UCI command. The + // function sets the thinking time and other parameters from the input + // string, and then calls think(). Returns false if a quit command + // is received while thinking, true otherwise. + + bool go(Position& pos, UCIParser& up) { + + string token; + SearchLimits limits; + Move searchMoves[MAX_MOVES], *cur = searchMoves; + int time[] = { 0, 0 }, inc[] = { 0, 0 }; + + while (up >> token) + { + if (token == "infinite") + limits.infinite = true; + else if (token == "ponder") + limits.ponder = true; + else if (token == "wtime") + up >> time[WHITE]; + else if (token == "btime") + up >> time[BLACK]; + else if (token == "winc") + up >> inc[WHITE]; + else if (token == "binc") + up >> inc[BLACK]; + else if (token == "movestogo") + up >> limits.movesToGo; + else if (token == "depth") + up >> limits.maxDepth; + else if (token == "nodes") + up >> limits.maxNodes; + else if (token == "movetime") + up >> limits.maxTime; + else if (token == "searchmoves") + while (up >> token) + *cur++ = move_from_uci(pos, token); + } + + *cur = MOVE_NONE; + limits.time = time[pos.side_to_move()]; + limits.increment = inc[pos.side_to_move()]; + + assert(pos.is_ok()); + + return think(pos, limits, searchMoves); + } + + + // perft() is called when engine receives the "perft" command. + // The function calls perft() passing the required search depth + // then prints counted leaf nodes and elapsed time. + + void perft(Position& pos, UCIParser& up) { + + int depth, time; + int64_t n; + + if (!(up >> depth)) + return; + + time = get_system_time(); + + n = perft(pos, depth * ONE_PLY); + + time = get_system_time() - time; + + std::cout << "\nNodes " << n + << "\nTime (ms) " << time + << "\nNodes/second " << int(n / (time / 1000.0)) << std::endl; + } +} diff --git a/DroidFish/jni/stockfish/ucioption.cpp b/DroidFish/jni/stockfish/ucioption.cpp new file mode 100644 index 0000000..c388c5c --- /dev/null +++ b/DroidFish/jni/stockfish/ucioption.cpp @@ -0,0 +1,170 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +#include "misc.h" +#include "thread.h" +#include "ucioption.h" + +using std::string; +using std::cout; +using std::endl; + +OptionsMap Options; // Global object + + +// Our case insensitive less() function as required by UCI protocol +bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const { + + int c1, c2; + size_t i = 0; + + while (i < s1.size() && i < s2.size()) + { + c1 = tolower(s1[i]); + c2 = tolower(s2[i++]); + + if (c1 != c2) + return c1 < c2; + } + return s1.size() < s2.size(); +} + + +// stringify() converts a numeric value of type T to a std::string +template +static string stringify(const T& v) { + + std::ostringstream ss; + ss << v; + return ss.str(); +} + + +/// OptionsMap c'tor initializes the UCI options to their hard coded default +/// values and initializes the default value of "Threads" and "Minimum Split Depth" +/// parameters according to the number of CPU cores. + +OptionsMap::OptionsMap() { + + OptionsMap& o = *this; + + o["Use Search Log"] = UCIOption(false); + o["Search Log Filename"] = UCIOption("SearchLog.txt"); + o["Book File"] = UCIOption("book.bin"); + o["Best Book Move"] = UCIOption(false); + o["Mobility (Middle Game)"] = UCIOption(100, 0, 200); + o["Mobility (Endgame)"] = UCIOption(100, 0, 200); + o["Passed Pawns (Middle Game)"] = UCIOption(100, 0, 200); + o["Passed Pawns (Endgame)"] = UCIOption(100, 0, 200); + o["Space"] = UCIOption(100, 0, 200); + o["Aggressiveness"] = UCIOption(100, 0, 200); + o["Cowardice"] = UCIOption(100, 0, 200); + o["Minimum Split Depth"] = UCIOption(4, 4, 7); + o["Maximum Number of Threads per Split Point"] = UCIOption(5, 4, 8); + o["Threads"] = UCIOption(1, 1, MAX_THREADS); + o["Use Sleeping Threads"] = UCIOption(false); + o["Hash"] = UCIOption(32, 4, 8192); + o["Clear Hash"] = UCIOption(false, "button"); + o["Ponder"] = UCIOption(true); + o["OwnBook"] = UCIOption(true); + o["MultiPV"] = UCIOption(1, 1, 500); + o["Skill Level"] = UCIOption(20, 0, 20); + o["Emergency Move Horizon"] = UCIOption(40, 0, 50); + o["Emergency Base Time"] = UCIOption(200, 0, 30000); + o["Emergency Move Time"] = UCIOption(70, 0, 5000); + o["Minimum Thinking Time"] = UCIOption(20, 0, 5000); + o["UCI_Chess960"] = UCIOption(false); + o["UCI_AnalyseMode"] = UCIOption(false); + + // Set some SMP parameters accordingly to the detected CPU count + UCIOption& thr = o["Threads"]; + UCIOption& msd = o["Minimum Split Depth"]; + + thr.defaultValue = thr.currentValue = stringify(cpu_count()); + + if (cpu_count() >= 8) + msd.defaultValue = msd.currentValue = stringify(7); +} + + +/// OptionsMap::print_all() returns a string with all the UCI options in chronological +/// insertion order (the idx field) and in the format defined by the UCI protocol. + +string OptionsMap::print_all() const { + + std::stringstream s; + + for (size_t i = 0; i <= size(); i++) + for (OptionsMap::const_iterator it = begin(); it != end(); ++it) + if (it->second.idx == i) + { + const UCIOption& o = it->second; + s << "\noption name " << it->first << " type " << o.type; + + if (o.type != "button") + s << " default " << o.defaultValue; + + if (o.type == "spin") + s << " min " << o.minValue << " max " << o.maxValue; + + break; + } + return s.str(); +} + + +/// Option class c'tors + +UCIOption::UCIOption(const char* def) : type("string"), minValue(0), maxValue(0), idx(Options.size()) +{ defaultValue = currentValue = def; } + +UCIOption::UCIOption(bool def, string t) : type(t), minValue(0), maxValue(0), idx(Options.size()) +{ defaultValue = currentValue = (def ? "true" : "false"); } + +UCIOption::UCIOption(int def, int minv, int maxv) : type("spin"), minValue(minv), maxValue(maxv), idx(Options.size()) +{ defaultValue = currentValue = stringify(def); } + + +/// set_value() updates currentValue of the Option object. Normally it's up to +/// the GUI to check for option's limits, but we could receive the new value +/// directly from the user by teminal window. So let's check the bounds anyway. + +void UCIOption::set_value(const string& v) { + + assert(!type.empty()); + + if (v.empty()) + return; + + if ((type == "check" || type == "button") != (v == "true" || v == "false")) + return; + + if (type == "spin") + { + int val = atoi(v.c_str()); + if (val < minValue || val > maxValue) + return; + } + + currentValue = v; +} diff --git a/DroidFish/jni/stockfish/ucioption.h b/DroidFish/jni/stockfish/ucioption.h new file mode 100644 index 0000000..b6ad959 --- /dev/null +++ b/DroidFish/jni/stockfish/ucioption.h @@ -0,0 +1,85 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(UCIOPTION_H_INCLUDED) +#define UCIOPTION_H_INCLUDED + +#include +#include +#include +#include + +class UCIOption { +public: + UCIOption() {} // To be used in a std::map + UCIOption(const char* defaultValue); + UCIOption(bool defaultValue, std::string type = "check"); + UCIOption(int defaultValue, int minValue, int maxValue); + + void set_value(const std::string& v); + template T value() const; + +private: + friend class OptionsMap; + + std::string defaultValue, currentValue, type; + int minValue, maxValue; + size_t idx; +}; + + +/// Custom comparator because UCI options should not be case sensitive +struct CaseInsensitiveLess { + bool operator() (const std::string&, const std::string&) const; +}; + + +/// Our options container is actually a map with a customized c'tor +class OptionsMap : public std::map { +public: + OptionsMap(); + std::string print_all() const; +}; + +extern OptionsMap Options; + + +/// Option::value() definition and specializations +template +T UCIOption::value() const { + + assert(type == "spin"); + return T(atoi(currentValue.c_str())); +} + +template<> +inline std::string UCIOption::value() const { + + assert(type == "string"); + return currentValue; +} + +template<> +inline bool UCIOption::value() const { + + assert(type == "check" || type == "button"); + return currentValue == "true"; +} + +#endif // !defined(UCIOPTION_H_INCLUDED) diff --git a/DroidFish/project.properties b/DroidFish/project.properties new file mode 100644 index 0000000..ddd0fc4 --- /dev/null +++ b/DroidFish/project.properties @@ -0,0 +1,13 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +# Indicates whether an apk should be generated for each density. +split.density=false +# Project target. +target=android-10 diff --git a/DroidFish/res/drawable-hdpi/icon.png b/DroidFish/res/drawable-hdpi/icon.png new file mode 100644 index 0000000..a5bc0d2 Binary files /dev/null and b/DroidFish/res/drawable-hdpi/icon.png differ diff --git a/DroidFish/res/drawable-ldpi/icon.png b/DroidFish/res/drawable-ldpi/icon.png new file mode 100644 index 0000000..1c00884 Binary files /dev/null and b/DroidFish/res/drawable-ldpi/icon.png differ diff --git a/DroidFish/res/drawable-mdpi/icon.png b/DroidFish/res/drawable-mdpi/icon.png new file mode 100644 index 0000000..38616ad Binary files /dev/null and b/DroidFish/res/drawable-mdpi/icon.png differ diff --git a/DroidFish/res/drawable/edit.png b/DroidFish/res/drawable/edit.png new file mode 100644 index 0000000..6abfeaf Binary files /dev/null and b/DroidFish/res/drawable/edit.png differ diff --git a/DroidFish/res/drawable/icon.png b/DroidFish/res/drawable/icon.png new file mode 100644 index 0000000..38616ad Binary files /dev/null and b/DroidFish/res/drawable/icon.png differ diff --git a/DroidFish/res/drawable/left.png b/DroidFish/res/drawable/left.png new file mode 100644 index 0000000..bd4684e Binary files /dev/null and b/DroidFish/res/drawable/left.png differ diff --git a/DroidFish/res/drawable/left_large.png b/DroidFish/res/drawable/left_large.png new file mode 100644 index 0000000..aaa4a0c Binary files /dev/null and b/DroidFish/res/drawable/left_large.png differ diff --git a/DroidFish/res/drawable/mode.png b/DroidFish/res/drawable/mode.png new file mode 100644 index 0000000..14f7cc8 Binary files /dev/null and b/DroidFish/res/drawable/mode.png differ diff --git a/DroidFish/res/drawable/mode_large.png b/DroidFish/res/drawable/mode_large.png new file mode 100644 index 0000000..473a382 Binary files /dev/null and b/DroidFish/res/drawable/mode_large.png differ diff --git a/DroidFish/res/drawable/right.png b/DroidFish/res/drawable/right.png new file mode 100644 index 0000000..c969620 Binary files /dev/null and b/DroidFish/res/drawable/right.png differ diff --git a/DroidFish/res/drawable/right_large.png b/DroidFish/res/drawable/right_large.png new file mode 100644 index 0000000..0140644 Binary files /dev/null and b/DroidFish/res/drawable/right_large.png differ diff --git a/DroidFish/res/drawable/start.png b/DroidFish/res/drawable/start.png new file mode 100644 index 0000000..9a9f661 Binary files /dev/null and b/DroidFish/res/drawable/start.png differ diff --git a/DroidFish/res/layout-land/editboard.xml b/DroidFish/res/layout-land/editboard.xml new file mode 100644 index 0000000..1cdbcb6 --- /dev/null +++ b/DroidFish/res/layout-land/editboard.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/DroidFish/res/layout-land/main.xml b/DroidFish/res/layout-land/main.xml new file mode 100644 index 0000000..78bd575 --- /dev/null +++ b/DroidFish/res/layout-land/main.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DroidFish/res/layout/cpu_warning.xml b/DroidFish/res/layout/cpu_warning.xml new file mode 100644 index 0000000..f737e99 --- /dev/null +++ b/DroidFish/res/layout/cpu_warning.xml @@ -0,0 +1,6 @@ + + + diff --git a/DroidFish/res/layout/create_pgn_file.xml b/DroidFish/res/layout/create_pgn_file.xml new file mode 100644 index 0000000..0203c0f --- /dev/null +++ b/DroidFish/res/layout/create_pgn_file.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + diff --git a/DroidFish/res/layout/edit_comments.xml b/DroidFish/res/layout/edit_comments.xml new file mode 100644 index 0000000..26f26d2 --- /dev/null +++ b/DroidFish/res/layout/edit_comments.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + android:layout_weight="1" + + + + + + + + + + + + + + + + + + + + + diff --git a/DroidFish/res/layout/edit_headers.xml b/DroidFish/res/layout/edit_headers.xml new file mode 100644 index 0000000..1b53da6 --- /dev/null +++ b/DroidFish/res/layout/edit_headers.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DroidFish/res/layout/edit_move_counters.xml b/DroidFish/res/layout/edit_move_counters.xml new file mode 100644 index 0000000..85d181e --- /dev/null +++ b/DroidFish/res/layout/edit_move_counters.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + diff --git a/DroidFish/res/layout/editboard.xml b/DroidFish/res/layout/editboard.xml new file mode 100644 index 0000000..16eca88 --- /dev/null +++ b/DroidFish/res/layout/editboard.xml @@ -0,0 +1,40 @@ + + + + + + + + + diff --git a/DroidFish/res/layout/main.xml b/DroidFish/res/layout/main.xml new file mode 100644 index 0000000..13daa0c --- /dev/null +++ b/DroidFish/res/layout/main.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/DroidFish/res/layout/select_game.xml b/DroidFish/res/layout/select_game.xml new file mode 100644 index 0000000..2fc06f1 --- /dev/null +++ b/DroidFish/res/layout/select_game.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/DroidFish/res/layout/select_game_list_item.xml b/DroidFish/res/layout/select_game_list_item.xml new file mode 100644 index 0000000..98897b6 --- /dev/null +++ b/DroidFish/res/layout/select_game_list_item.xml @@ -0,0 +1,8 @@ + + + diff --git a/DroidFish/res/layout/select_move_number.xml b/DroidFish/res/layout/select_move_number.xml new file mode 100644 index 0000000..3a994de --- /dev/null +++ b/DroidFish/res/layout/select_move_number.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + diff --git a/DroidFish/res/layout/select_percentage.xml b/DroidFish/res/layout/select_percentage.xml new file mode 100644 index 0000000..9ec2c55 --- /dev/null +++ b/DroidFish/res/layout/select_percentage.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + diff --git a/DroidFish/res/layout/title.xml b/DroidFish/res/layout/title.xml new file mode 100644 index 0000000..e0a4809 --- /dev/null +++ b/DroidFish/res/layout/title.xml @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/DroidFish/res/menu/edit_file_options_menu.xml b/DroidFish/res/menu/edit_file_options_menu.xml new file mode 100644 index 0000000..bc66142 --- /dev/null +++ b/DroidFish/res/menu/edit_file_options_menu.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/DroidFish/res/menu/options_menu.xml b/DroidFish/res/menu/options_menu.xml new file mode 100644 index 0000000..92afba6 --- /dev/null +++ b/DroidFish/res/menu/options_menu.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DroidFish/res/raw/movesound.ogg b/DroidFish/res/raw/movesound.ogg new file mode 100644 index 0000000..b6cd57d Binary files /dev/null and b/DroidFish/res/raw/movesound.ogg differ diff --git a/DroidFish/res/values/strings.xml b/DroidFish/res/values/strings.xml new file mode 100644 index 0000000..ca88b45 --- /dev/null +++ b/DroidFish/res/values/strings.xml @@ -0,0 +1,321 @@ + + + DroidFish + + Stockfish + CuckooChess + + + stockfish + cuckoochess + + + Automatic + 1 + 2 + 3 + 4 + + + 0 + 1 + 2 + 3 + 4 + + + Whole Game + 1 move + 10 moves + 20 moves + 30 moves + 40 moves + 50 moves + 60 moves + + + 0 + 1 + 10 + 20 + 30 + 40 + 50 + 60 + + + 15 seconds + 30 seconds + 1 minute + 2 minutes + 3 minutes + 5 minutes + 10 minutes + 15 minutes + 30 minutes + 45 minutes + 60 minutes + 90 minutes + 120 minutes + + + 15000 + 30000 + 60000 + 120000 + 180000 + 300000 + 600000 + 900000 + 1800000 + 2700000 + 3600000 + 5400000 + 7200000 + + + 0s + 1s + 2s + 3s + 4s + 5s + 10s + 12s + 15s + 30s + 60s + + + 0 + 1000 + 2000 + 3000 + 4000 + 5000 + 10000 + 12000 + 15000 + 30000 + 60000 + + + Small + Medium + Large + Larger + + + 10 + 12 + 16 + 21 + + + No arrows + Max 1 Arrow + Max 2 Arrows + Max 3 Arrows + Max 4 Arrows + Max 5 Arrows + Max 6 Arrows + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + + + Off + Slowest + Slow + Normal + Fast + Fastest + + + 0 + 6 + 3 + 2 + 1 + 0.5 + + + 5 + 10 + 15 + 20 + 30 + 50 + 1000000 + + + 5 moves + 10 moves + 15 moves + 20 moves + 30 moves + 50 moves + Unlimited + + \ +About\n\ +DroidFish is an Android port of the famous stockfish 2.1 chess engine. \ +Stockfish is one of the strongest chess engines in the world. \ +The Android version can often search 15 ply or deeper on a 1GHz Snapdragon CPU.\n\ +\n\ +Features\n\ +* Opening book\n\ +* Unlimited undo/redo\n\ +* Copy/paste to/from clipboard\n\ +* Analysis mode\n\ +* Two player mode\n\ +* Computer vs computer mode\n\ +* Setup positions\n\ +* Extensive PGN support\n\ +* Adjustable playing strength\n\ +\n\ +Hints\n\ +* In game mode, long press chess board to activate copy/paste menu.\n\ +* In edit board mode, long press chess board to activate edit menu.\n\ +* Scroll left/right on the chess board to undo/redo moves.\n\ +* If the game contains multiple variations, scroll up/down on the chess board to \ +go to the previous/next variation.\n\ +* Long press left/right buttons for additional game navigation commands.\n\ +\n\ +Opening books\n\ +DroidFish contains a small internal opening book. If you want to use \ +a larger book, you can configure DroidFish to use external polyglot \ +or CTG book files:\n\ +1. Create a directory called DroidFish on the SD card.\n\ +2a. Copy one or more polyglot book files to the DroidFish directory. \ +Polyglot books must have the file extension .bin.\n\ +2b. Copy one more more CTG book files to the DroidFish directory. A CTG \ +book consists of three files with file extensions .ctg, .ctb and .cto. \ +You must copy all three files.\n\ +3. From the program menu, activate the Select opening book function.\n\ +4. Select the opening book file you want to use.\n\ +\n\ +PGN files\n\ +DroidFish can load games from PGN files. To use this feature:\n\ +1. Create a directory called DroidFish/pgn on the SD card.\n\ +2. Copy one or more PGN files to the DroidFish/pgn directory.\n\ +3. Long press the chess board and select Load game from PGN file.\n\ +4. Select the file, then the game within the file you want to load.\n\ +\n\ +To edit game headers such as player names, long press the move list and select \ +Edit Headers. To edit comments for a move, long press the move list and \ +select Edit Comments. To save your edited game, long press the chess board \ +and select Save game to PGN file.\n\ +\n\ +To add computer analysis to the game tree, long press the analysis information \ +when the computer is thinking and select Add Analysis. The current PV is \ +added as a variation in the game tree.\n\ +\n\ +In analysis mode, it is possible to add null moves to the game tree. Be aware though \ +that null moves are not included in the PGN standard and may not work in other PGN \ +reading chess programs.\n\ +\n\ +Scid files\n\ +If you have Scid on the go installed, you can read Scid database files \ +directly from DroidFish. Long press the chess board and select Load game from \ +Scid file.\n\ +\n\ +License\n\ +GNU GPL v3\n\ +\n\ +Developers\n\ +* Stockfish is developed by Tord Romstad, Marco Costalba and Joona Kiiski.\n\ +* Droidfish is developed by Peter Österlund.\n\ + + \ +CPU Usage\n\ +If you leave DroidFish running in the background and GameMode is set to \ +Analysis or Computer vs Computer, or if Thinking Time \ +is set to a large value, or if Pondering is enabled, DroidFish can use \ +a lot of CPU power.\n\ +\n\ +If you are running on battery power, it is recommended that you change settings to \ +conserve battery power, or that you exit DroidFish using the back button when \ +you are not actively using the program.\ + + Offer Draw + Queen + Rook + Bishop + Knight + Promote pawn to? + Copy Game to Clipboard + Copy Position to Clipboard + Paste from Clipboard + Load game from PGN file + Load game from Scid file + Save game to PGN file + Truncate Game Tree + Move Variation Up + Move Variation Down + Add Null Move + Tools + Load/Save Game + Goto move + Edit Strength + Edit Randomization + Invalid number format + Side to Move + Clear Board + Initial Position + Castling Flags + En Passant File + Move Counters + Paste Position + Edit Board + Select side to move first + White + Black + White king castle + White queen castle + Black king castle + Black queen castle + None + Select En Passant File + Edit Move Counters + <Internal Book> + Select opening book file + Select PGN file to open + Save to PGN file + Select Scid file to open + Select game to open + Reading PGN file + Reading Scid file + <New File> + Please wait... + No files found in directory DroidFish/pgn on the SD card + No files found in directory scid on the SD card + Select Color Theme + Edit/re-play Game + Analysis Mode + Play White + Play Black + Two Players + Computer vs Computer + Analysis + Edit Game + Edit Headers + Edit Comments + Go Back + Go Forward + Goto Start of Game + Goto Start of Variation + Goto Previous Variation + Load Previous Game + Goto End of Variation + Goto Next Variation + Load Next Game + No previous game + No next game + diff --git a/DroidFish/res/xml/preferences.xml b/DroidFish/res/xml/preferences.xml new file mode 100644 index 0000000..a2abb13 --- /dev/null +++ b/DroidFish/res/xml/preferences.xml @@ -0,0 +1,377 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DroidFish/src/org/petero/droidfish/BookOptions.java b/DroidFish/src/org/petero/droidfish/BookOptions.java new file mode 100644 index 0000000..49d6cd2 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/BookOptions.java @@ -0,0 +1,57 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish; + +/** Settings controlling opening book usage */ +public class BookOptions { + public String filename = ""; + + public int maxLength = 1000000; + public boolean preferMainLines = false; + public boolean tournamentMode = false; + public double random = 0; // Scale probabilities according to p^(exp(-random)) + + public BookOptions() { } + + public BookOptions(BookOptions other) { + filename = other.filename; + maxLength = other.maxLength; + preferMainLines = other.preferMainLines; + tournamentMode = other.tournamentMode; + random = other.random; + } + + @Override + public boolean equals(Object o) { + if ((o == null) || (o.getClass() != this.getClass())) + return false; + BookOptions other = (BookOptions)o; + + return ((filename.equals(other.filename)) && + (maxLength == other.maxLength) && + (preferMainLines == other.preferMainLines) && + (tournamentMode == other.tournamentMode) && + (random == other.random)); + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/DroidFish/src/org/petero/droidfish/ChessBoard.java b/DroidFish/src/org/petero/droidfish/ChessBoard.java new file mode 100644 index 0000000..759f195 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/ChessBoard.java @@ -0,0 +1,665 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish; + +import java.util.ArrayList; +import java.util.List; + +import org.petero.droidfish.gamelogic.Move; +import org.petero.droidfish.gamelogic.MoveGen; +import org.petero.droidfish.gamelogic.Piece; +import org.petero.droidfish.gamelogic.Position; +import org.petero.droidfish.gamelogic.UndoInfo; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.os.Handler; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +public class ChessBoard extends View { + public Position pos; + + public int selectedSquare; + public float cursorX, cursorY; + public boolean cursorVisible; + protected int x0, y0, sqSize; + int pieceXDelta, pieceYDelta; // top/left pixel draw position relative to square + public boolean flipped; + public boolean drawSquareLabels; + boolean oneTouchMoves; + + List moveHints; + + protected Paint darkPaint; + protected Paint brightPaint; + private Paint selectedSquarePaint; + private Paint cursorSquarePaint; + private Paint whitePiecePaint; + private Paint blackPiecePaint; + private Paint labelPaint; + private ArrayList moveMarkPaint; + + public ChessBoard(Context context, AttributeSet attrs) { + super(context, attrs); + pos = new Position(); + selectedSquare = -1; + cursorX = cursorY = 0; + cursorVisible = false; + x0 = y0 = sqSize = 0; + pieceXDelta = pieceYDelta = -1; + flipped = false; + drawSquareLabels = false; + oneTouchMoves = false; + + darkPaint = new Paint(); + brightPaint = new Paint(); + + selectedSquarePaint = new Paint(); + selectedSquarePaint.setStyle(Paint.Style.STROKE); + selectedSquarePaint.setAntiAlias(true); + + cursorSquarePaint = new Paint(); + cursorSquarePaint.setStyle(Paint.Style.STROKE); + cursorSquarePaint.setAntiAlias(true); + + whitePiecePaint = new Paint(); + whitePiecePaint.setAntiAlias(true); + + blackPiecePaint = new Paint(); + blackPiecePaint.setAntiAlias(true); + + labelPaint = new Paint(); + labelPaint.setAntiAlias(true); + + moveMarkPaint = new ArrayList(); + for (int i = 0; i < 6; i++) { + Paint p = new Paint(); + p.setStyle(Paint.Style.FILL); + p.setAntiAlias(true); + moveMarkPaint.add(p); + } + + Typeface chessFont = Typeface.createFromAsset(getContext().getAssets(), "ChessCases.ttf"); + whitePiecePaint.setTypeface(chessFont); + blackPiecePaint.setTypeface(chessFont); + + setColors(); + } + + /** Must be called for new color theme to take effect. */ + final void setColors() { + ColorTheme ct = ColorTheme.instance(); + darkPaint.setColor(ct.getColor(ColorTheme.DARK_SQUARE)); + brightPaint.setColor(ct.getColor(ColorTheme.BRIGHT_SQUARE)); + selectedSquarePaint.setColor(ct.getColor(ColorTheme.SELECTED_SQUARE)); + cursorSquarePaint.setColor(ct.getColor(ColorTheme.CURSOR_SQUARE)); + whitePiecePaint.setColor(ct.getColor(ColorTheme.BRIGHT_PIECE)); + blackPiecePaint.setColor(ct.getColor(ColorTheme.DARK_PIECE)); + labelPaint.setColor(ct.getColor(ColorTheme.SQUARE_LABEL)); + for (int i = 0; i < 6; i++) + moveMarkPaint.get(i).setColor(ct.getColor(ColorTheme.ARROW_0 + i)); + + invalidate(); + } + + private Handler handlerTimer = new Handler(); + + final class AnimInfo { + AnimInfo() { startTime = -1; } + boolean paused; + long posHash; // Position the animation is valid for + long startTime; // Time in milliseconds when animation was started + long stopTime; // Time in milliseconds when animation should stop + long now; // Current time in milliseconds + int piece1, from1, to1, hide1; + int piece2, from2, to2, hide2; + + public final boolean updateState() { + now = System.currentTimeMillis(); + return animActive(); + } + private final boolean animActive() { + if (paused || (startTime < 0) || (now >= stopTime) || (posHash != pos.zobristHash())) + return false; + return true; + } + public final boolean squareHidden(int sq) { + if (!animActive()) + return false; + return (sq == hide1) || (sq == hide2); + } + public final void draw(Canvas canvas) { + if (!animActive()) + return; + double animState = (now - startTime) / (double)(stopTime - startTime); + drawAnimPiece(canvas, piece2, from2, to2, animState); + drawAnimPiece(canvas, piece1, from1, to1, animState); + long now2 = System.currentTimeMillis(); + long delay = 20 - (now2 - now); +// System.out.printf("delay:%d\n", delay); + if (delay < 1) delay = 1; + handlerTimer.postDelayed(new Runnable() { + @Override + public void run() { + invalidate(); + } + }, delay); + } + private void drawAnimPiece(Canvas canvas, int piece, int from, int to, double animState) { + if (piece == Piece.EMPTY) + return; + final int xCrd1 = getXCrd(Position.getX(from)); + final int yCrd1 = getYCrd(Position.getY(from)); + final int xCrd2 = getXCrd(Position.getX(to)); + final int yCrd2 = getYCrd(Position.getY(to)); + final int xCrd = xCrd1 + (int)Math.round((xCrd2 - xCrd1) * animState); + final int yCrd = yCrd1 + (int)Math.round((yCrd2 - yCrd1) * animState); + drawPiece(canvas, xCrd, yCrd, piece); + } + } + AnimInfo anim = new AnimInfo(); + + /** + * Set up move animation. The animation will start the next time setPosition is called. + * @param sourcePos The source position for the animation. + * @param move The move leading to the target position. + * @param forward True if forward direction, false for undo move. + */ + public void setAnimMove(Position sourcePos, Move move, boolean forward) { + anim.startTime = -1; + anim.paused = true; // Animation starts at next position update + if (forward) { + // The animation will be played when pos == targetPos + Position targetPos = new Position(sourcePos); + UndoInfo ui = new UndoInfo(); + targetPos.makeMove(move, ui); + anim.posHash = targetPos.zobristHash(); + } else { + anim.posHash = sourcePos.zobristHash(); + } + int animTime; // Animation duration in milliseconds. + { + int dx = Position.getX(move.to) - Position.getX(move.from); + int dy = Position.getY(move.to) - Position.getY(move.from); + double dist = Math.sqrt(dx * dx + dy * dy); + double t = Math.sqrt(dist) * 100; + animTime = (int)Math.round(t); + } + if (animTime > 0) { + anim.startTime = System.currentTimeMillis(); + anim.stopTime = anim.startTime + animTime; + anim.piece2 = Piece.EMPTY; + anim.from2 = -1; + anim.to2 = -1; + anim.hide2 = -1; + if (forward) { + int p = sourcePos.getPiece(move.from); + anim.piece1 = p; + anim.from1 = move.from; + anim.to1 = move.to; + anim.hide1 = anim.to1; + int p2 = sourcePos.getPiece(move.to); + if (p2 != Piece.EMPTY) { // capture + anim.piece2 = p2; + anim.from2 = move.to; + anim.to2 = move.to; + } else if ((p == Piece.WKING) || (p == Piece.BKING)) { + boolean wtm = Piece.isWhite(p); + if (move.to == move.from + 2) { // O-O + anim.piece2 = wtm ? Piece.WROOK : Piece.BROOK; + anim.from2 = move.to + 1; + anim.to2 = move.to - 1; + anim.hide2 = anim.to2; + } else if (move.to == move.from - 2) { // O-O-O + anim.piece2 = wtm ? Piece.WROOK : Piece.BROOK; + anim.from2 = move.to - 2; + anim.to2 = move.to + 1; + anim.hide2 = anim.to2; + } + } + } else { + int p = sourcePos.getPiece(move.from); + anim.piece1 = p; + if (move.promoteTo != Piece.EMPTY) + anim.piece1 = Piece.isWhite(anim.piece1) ? Piece.WPAWN : Piece.BPAWN; + anim.from1 = move.to; + anim.to1 = move.from; + anim.hide1 = anim.to1; + if ((p == Piece.WKING) || (p == Piece.BKING)) { + boolean wtm = Piece.isWhite(p); + if (move.to == move.from + 2) { // O-O + anim.piece2 = wtm ? Piece.WROOK : Piece.BROOK; + anim.from2 = move.to - 1; + anim.to2 = move.to + 1; + anim.hide2 = anim.to2; + } else if (move.to == move.from - 2) { // O-O-O + anim.piece2 = wtm ? Piece.WROOK : Piece.BROOK; + anim.from2 = move.to + 1; + anim.to2 = move.to - 2; + anim.hide2 = anim.to2; + } + } + } + } + } + + /** + * Set the board to a given state. + * @param pos + */ + final public void setPosition(Position pos) { + boolean doInvalidate = false; + if (anim.paused = true) { + anim.paused = false; + doInvalidate = true; + } + if (!this.pos.equals(pos)) { + this.pos = new Position(pos); + doInvalidate = true; + } + if (doInvalidate) + invalidate(); + } + + /** + * Set/clear the board flipped status. + * @param flipped + */ + final public void setFlipped(boolean flipped) { + if (this.flipped != flipped) { + this.flipped = flipped; + invalidate(); + } + } + + /** + * Set/clear the board flipped status. + * @param flipped + */ + final public void setDrawSquareLabels(boolean drawSquareLabels) { + if (this.drawSquareLabels != drawSquareLabels) { + this.drawSquareLabels = drawSquareLabels; + invalidate(); + } + } + + /** + * Set/clear the selected square. + * @param square The square to select, or -1 to clear selection. + */ + final public void setSelection(int square) { + if (square != selectedSquare) { + selectedSquare = square; + invalidate(); + } + } + + protected int getWidth(int sqSize) { return sqSize * 8 + 4; } + protected int getHeight(int sqSize) { return sqSize * 8 + 4; } + protected int getSqSizeW(int width) { return (width - 4) / 8; } + protected int getSqSizeH(int height) { return (height - 4) / 8; } + + protected int getMaxHeightPercentage() { return 75; } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int width = getMeasuredWidth(); + int height = getMeasuredHeight(); + int sqSizeW = getSqSizeW(width); + int sqSizeH = getSqSizeH(height); + int sqSize = Math.min(sqSizeW, sqSizeH); + pieceXDelta = pieceYDelta = -1; + labelBounds = null; + if (height > width) { + int p = getMaxHeightPercentage(); + height = Math.min(getHeight(sqSize), height * p / 100); + } else { + width = Math.min(getWidth(sqSize), width * 65 / 100); + } + setMeasuredDimension(width, height); + } + + protected void computeOrigin(int width, int height) { + x0 = (width - sqSize * 8) / 2; + y0 = (height - sqSize * 8) / 2; + } + + protected int getXFromSq(int sq) { return Position.getX(sq); } + protected int getYFromSq(int sq) { return Position.getY(sq); } + + @Override + protected void onDraw(Canvas canvas) { +// long t0 = System.currentTimeMillis(); + boolean animActive = anim.updateState(); + final int width = getWidth(); + final int height = getHeight(); + sqSize = Math.min(getSqSizeW(width), getSqSizeH(height)); + blackPiecePaint.setTextSize(sqSize); + whitePiecePaint.setTextSize(sqSize); + labelPaint.setTextSize(sqSize/4.0f); + computeOrigin(width, height); + for (int x = 0; x < 8; x++) { + for (int y = 0; y < 8; y++) { + final int xCrd = getXCrd(x); + final int yCrd = getYCrd(y); + Paint paint = Position.darkSquare(x, y) ? darkPaint : brightPaint; + canvas.drawRect(xCrd, yCrd, xCrd+sqSize, yCrd+sqSize, paint); + + int sq = Position.getSquare(x, y); + if (!animActive || !anim.squareHidden(sq)) { + int p = pos.getPiece(sq); + drawPiece(canvas, xCrd, yCrd, p); + } + if (drawSquareLabels) { + if (x == (flipped ? 7 : 0)) { + drawLabel(canvas, xCrd, yCrd, false, false, "12345678".charAt(y)); + } + if (y == (flipped ? 7 : 0)) { + drawLabel(canvas, xCrd, yCrd, true, true, "abcdefgh".charAt(x)); + } + } + } + } + drawExtraSquares(canvas); + if (!animActive && (selectedSquare != -1)) { + int selX = getXFromSq(selectedSquare); + int selY = getYFromSq(selectedSquare); + selectedSquarePaint.setStrokeWidth(sqSize/(float)16); + int x0 = getXCrd(selX); + int y0 = getYCrd(selY); + canvas.drawRect(x0, y0, x0 + sqSize, y0 + sqSize, selectedSquarePaint); + } + if (cursorVisible) { + int x = Math.round(cursorX); + int y = Math.round(cursorY); + int x0 = getXCrd(x); + int y0 = getYCrd(y); + cursorSquarePaint.setStrokeWidth(sqSize/(float)16); + canvas.drawRect(x0, y0, x0 + sqSize, y0 + sqSize, cursorSquarePaint); + } + if (!animActive) + drawMoveHints(canvas); + + anim.draw(canvas); +// long t1 = System.currentTimeMillis(); +// System.out.printf("draw: %d\n", t1-t0); + } + + private final void drawMoveHints(Canvas canvas) { + if (moveHints == null) + return; + float h = (float)(sqSize / 2.0); + float d = (float)(sqSize / 8.0); + double v = 35 * Math.PI / 180; + double cosv = Math.cos(v); + double sinv = Math.sin(v); + double tanv = Math.tan(v); + int n = Math.min(moveMarkPaint.size(), moveHints.size()); + for (int i = 0; i < n; i++) { + Move m = moveHints.get(i); + if (m.from == m.to) + continue; + float x0 = getXCrd(Position.getX(m.from)) + h; + float y0 = getYCrd(Position.getY(m.from)) + h; + float x1 = getXCrd(Position.getX(m.to)) + h; + float y1 = getYCrd(Position.getY(m.to)) + h; + + float x2 = (float)(Math.hypot(x1 - x0, y1 - y0) + d); + float y2 = 0; + float x3 = (float)(x2 - h * cosv); + float y3 = (float)(y2 - h * sinv); + float x4 = (float)(x3 - d * sinv); + float y4 = (float)(y3 + d * cosv); + float x5 = (float)(x4 + (-d/2 - y4) / tanv); + float y5 = (float)(-d / 2); + float x6 = 0; + float y6 = y5 / 2; + Path path = new Path(); + path.moveTo(x2, y2); + path.lineTo(x3, y3); +// path.lineTo(x4, y4); + path.lineTo(x5, y5); + path.lineTo(x6, y6); + path.lineTo(x6, -y6); + path.lineTo(x5, -y5); +// path.lineTo(x4, -y4); + path.lineTo(x3, -y3); + path.close(); + Matrix mtx = new Matrix(); + mtx.postRotate((float)(Math.atan2(y1 - y0, x1 - x0) * 180 / Math.PI)); + mtx.postTranslate(x0, y0); + path.transform(mtx); + Paint p = moveMarkPaint.get(i); + canvas.drawPath(path, p); + } + } + + protected void drawExtraSquares(Canvas canvas) { + } + + protected final void drawPiece(Canvas canvas, int xCrd, int yCrd, int p) { + String psb, psw; + boolean rotate = false; + switch (p) { + default: + case Piece.EMPTY: psb = null; psw = null; break; + case Piece.WKING: psb = "H"; psw = "k"; break; + case Piece.WQUEEN: psb = "I"; psw = "l"; break; + case Piece.WROOK: psb = "J"; psw = "m"; break; + case Piece.WBISHOP: psb = "K"; psw = "n"; break; + case Piece.WKNIGHT: psb = "L"; psw = "o"; break; + case Piece.WPAWN: psb = "M"; psw = "p"; break; + case Piece.BKING: psb = "N"; psw = "q"; rotate = true; break; + case Piece.BQUEEN: psb = "O"; psw = "r"; rotate = true; break; + case Piece.BROOK: psb = "P"; psw = "s"; rotate = true; break; + case Piece.BBISHOP: psb = "Q"; psw = "t"; rotate = true; break; + case Piece.BKNIGHT: psb = "R"; psw = "u"; rotate = true; break; + case Piece.BPAWN: psb = "S"; psw = "v"; rotate = true; break; + } + if (psb != null) { + if (pieceXDelta < 0) { + Rect bounds = new Rect(); + blackPiecePaint.getTextBounds("H", 0, 1, bounds); + pieceXDelta = (sqSize - (bounds.left + bounds.right)) / 2; + pieceYDelta = (sqSize - (bounds.top + bounds.bottom)) / 2; + } + rotate ^= flipped; + rotate = false; // Disabled for now + if (rotate) { + canvas.save(); + canvas.rotate(180, xCrd + sqSize * 0.5f, yCrd + sqSize * 0.5f); + } + xCrd += pieceXDelta; + yCrd += pieceYDelta; + canvas.drawText(psw, xCrd, yCrd, whitePiecePaint); + canvas.drawText(psb, xCrd, yCrd, blackPiecePaint); + if (rotate) + canvas.restore(); + } + } + + private Rect labelBounds = null; + + private void drawLabel(Canvas canvas, int xCrd, int yCrd, boolean right, + boolean bottom, char c) { + String s = ""; + s += c; + if (labelBounds == null) { + labelBounds = new Rect(); + labelPaint.getTextBounds("f", 0, 1, labelBounds); + } + int margin = sqSize / 16; + if (right) { + xCrd += sqSize - labelBounds.right - margin; + } else { + xCrd += -labelBounds.left + margin; + } + if (bottom) { + yCrd += sqSize - labelBounds.bottom - margin; + } else { + yCrd += -labelBounds.top + margin; + } + canvas.drawText(s, xCrd, yCrd, labelPaint); + } + + protected int getXCrd(int x) { return x0 + sqSize * (flipped ? 7 - x : x); } + protected int getYCrd(int y) { return y0 + sqSize * (flipped ? y : 7 - y); } + protected int getXSq(int xCrd) { int t = (xCrd - x0) / sqSize; return flipped ? 7 - t : t; } + protected int getYSq(int yCrd) { int t = (yCrd - y0) / sqSize; return flipped ? t : 7 - t; } + + /** + * Compute the square corresponding to the coordinates of a mouse event. + * @param evt Details about the mouse event. + * @return The square corresponding to the mouse event, or -1 if outside board. + */ + public int eventToSquare(MotionEvent evt) { + int xCrd = (int)(evt.getX()); + int yCrd = (int)(evt.getY()); + + int sq = -1; + if (sqSize > 0) { + int x = getXSq(xCrd); + int y = getYSq(yCrd); + if ((x >= 0) && (x < 8) && (y >= 0) && (y < 8)) { + sq = Position.getSquare(x, y); + } + } + return sq; + } + + final private boolean myColor(int piece) { + return (piece != Piece.EMPTY) && (Piece.isWhite(piece) == pos.whiteMove); + } + + public Move mousePressed(int sq) { + if (sq < 0) + return null; + cursorVisible = false; + if (selectedSquare != -1) { + int p = pos.getPiece(selectedSquare); + if (!myColor(p)) { + setSelection(-1); // Remove selection of opponents last moving piece + } + } + + int p = pos.getPiece(sq); + if (selectedSquare != -1) { + if (sq != selectedSquare) { + if (!myColor(p)) { + Move m = new Move(selectedSquare, sq, Piece.EMPTY); + setSelection(sq); + return m; + } + } + setSelection(-1); + } else { + if (oneTouchMoves) { + ArrayList moves = new MoveGen().pseudoLegalMoves(pos); + moves = MoveGen.removeIllegal(pos, moves); + Move matchingMove = null; + int toSq = -1; + for (Move m : moves) { + if ((m.from == sq) || (m.to == sq)) { + if (matchingMove == null) { + matchingMove = m; + toSq = m.to; + } else { + matchingMove = null; + break; + } + } + } + if (matchingMove != null) { + setSelection(toSq); + return matchingMove; + } + } + if (myColor(p)) { + setSelection(sq); + } + } + return null; + } + + public static class OnTrackballListener { + public void onTrackballEvent(MotionEvent event) { } + } + private OnTrackballListener otbl = null; + public final void setOnTrackballListener(OnTrackballListener onTrackballListener) { + otbl = onTrackballListener; + } + @Override + public boolean onTrackballEvent(MotionEvent event) { + if (otbl != null) { + otbl.onTrackballEvent(event); + return true; + } + return false; + } + + protected int minValidY() { return 0; } + protected int getSquare(int x, int y) { return Position.getSquare(x, y); } + + public final Move handleTrackballEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + invalidate(); + if (cursorVisible) { + int x = Math.round(cursorX); + int y = Math.round(cursorY); + cursorX = x; + cursorY = y; + int sq = getSquare(x, y); + return mousePressed(sq); + } + return null; + } + cursorVisible = true; + int c = flipped ? -1 : 1; + cursorX += c * event.getX(); + cursorY -= c * event.getY(); + if (cursorX < 0) cursorX = 0; + if (cursorX > 7) cursorX = 7; + if (cursorY < minValidY()) cursorY = minValidY(); + if (cursorY > 7) cursorY = 7; + invalidate(); + return null; + } + + public final void setMoveHints(List moveHints) { + boolean equal = false; + if ((this.moveHints == null) || (moveHints == null)) { + equal = this.moveHints == moveHints; + } else { + equal = this.moveHints.equals(moveHints); + } + if (!equal) { + this.moveHints = moveHints; + invalidate(); + } + } +} diff --git a/DroidFish/src/org/petero/droidfish/ColorTheme.java b/DroidFish/src/org/petero/droidfish/ColorTheme.java new file mode 100644 index 0000000..823c696 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/ColorTheme.java @@ -0,0 +1,105 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish; + +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.graphics.Color; + +public class ColorTheme { + private static ColorTheme inst = null; + + /** Get singleton instance. */ + static final ColorTheme instance() { + if (inst == null) + inst = new ColorTheme(); + return inst; + } + + final static int DARK_SQUARE = 0; + final static int BRIGHT_SQUARE = 1; + final static int SELECTED_SQUARE = 2; + final static int CURSOR_SQUARE = 3; + final static int DARK_PIECE = 4; + final static int BRIGHT_PIECE = 5; + final static int CURRENT_MOVE = 6; + final static int ARROW_0 = 7; + final static int ARROW_1 = 8; + final static int ARROW_2 = 9; + final static int ARROW_3 = 10; + final static int ARROW_4 = 11; + final static int ARROW_5 = 12; + final static int SQUARE_LABEL = 13; + private final static int numColors = 14; + + private int colorTable[] = new int[numColors]; + + private static final String[] prefNames = { + "darkSquare", "brightSquare", "selectedSquare", "cursorSquare", "darkPiece", "brightPiece", "currentMove", + "arrow0", "arrow1", "arrow2", "arrow3", "arrow4", "arrow5", "squareLabel" + }; + private static final String prefPrefix = "color_"; + + private final static int defaultTheme = 2; + final static String[] themeNames = { "Original", "XBoard", "Blue", "Grey" }; + private final static String themeColors[][] = { + { + "#FF808080", "#FFBEBE5A", "#FFFF0000", "#FF00FF00", "#FF000000", "#FFFFFFFF", "#FF888888", + "#A01F1FFF", "#A0FF1F1F", "#501F1FFF", "#50FF1F1F", "#1E1F1FFF", "#28FF1F1F", "#FFFF0000" + }, + { + "#FF77A26D", "#FFC8C365", "#FFFFFF00", "#FF00FF00", "#FF202020", "#FFFFFFCC", "#FF6B9262", + "#A01F1FFF", "#A0FF1F1F", "#501F1FFF", "#50FF1F1F", "#1E1F1FFF", "#28FF1F1F", "#FFFF0000" + }, + { + "#FF83A5D2", "#FFFFFFFA", "#FF3232D1", "#FF5F5FFD", "#FF282828", "#FFF0F0F0", "#FF3333FF", + "#A01F1FFF", "#A01FFF1F", "#501F1FFF", "#501FFF1F", "#1E1F1FFF", "#281FFF1F", "#FFFF0000" + }, + { + "#FF666666", "#FFDDDDDD", "#FFFF0000", "#FF0000FF", "#FF000000", "#FFFFFFFF", "#FF888888", + "#A01F1FFF", "#A0FF1F1F", "#501F1FFF", "#50FF1F1F", "#1E1F1FFF", "#28FF1F1F", "#FFFF0000" + } + }; + + final void readColors(SharedPreferences settings) { + for (int i = 0; i < numColors; i++) { + String prefName = prefPrefix + prefNames[i]; + String defaultColor = themeColors[defaultTheme][i]; + String colorString = settings.getString(prefName, defaultColor); + colorTable[i] = 0; + try { + colorTable[i] = Color.parseColor(colorString); + } catch (IllegalArgumentException e) { + } catch (StringIndexOutOfBoundsException e) { + } + } + } + + final void setTheme(SharedPreferences settings, int themeType) { + Editor editor = settings.edit(); + for (int i = 0; i < numColors; i++) + editor.putString(prefPrefix + prefNames[i], themeColors[themeType][i]); + editor.commit(); + readColors(settings); + } + + final int getColor(int colorType) { + return colorTable[colorType]; + } +} diff --git a/DroidFish/src/org/petero/droidfish/DroidFish.java b/DroidFish/src/org/petero/droidfish/DroidFish.java new file mode 100644 index 0000000..ff588fc --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/DroidFish.java @@ -0,0 +1,1929 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +import org.petero.droidfish.activities.CPUWarning; +import org.petero.droidfish.activities.EditBoard; +import org.petero.droidfish.activities.EditComments; +import org.petero.droidfish.activities.EditHeaders; +import org.petero.droidfish.activities.EditPGNLoad; +import org.petero.droidfish.activities.EditPGNSave; +import org.petero.droidfish.activities.LoadScid; +import org.petero.droidfish.activities.Preferences; +import org.petero.droidfish.gamelogic.DroidChessController; +import org.petero.droidfish.gamelogic.ChessParseError; +import org.petero.droidfish.gamelogic.Move; +import org.petero.droidfish.gamelogic.Position; +import org.petero.droidfish.gamelogic.TextIO; +import org.petero.droidfish.gamelogic.PgnToken; +import org.petero.droidfish.gamelogic.GameTree.Node; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.content.pm.PackageInfo; +import android.content.pm.ProviderInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Typeface; +import android.media.MediaPlayer; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.preference.PreferenceManager; +import android.text.ClipboardManager; +import android.text.Html; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.style.BackgroundColorSpan; +import android.text.style.LeadingMarginSpan; +import android.text.style.StyleSpan; +import android.util.TypedValue; +import android.view.GestureDetector; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.view.View.OnClickListener; +import android.view.View.OnKeyListener; +import android.view.View.OnLongClickListener; +import android.view.View.OnTouchListener; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; +import android.widget.Toast; + +public class DroidFish extends Activity implements GUIInterface { + // FIXME!!! book.txt (and test classes) should not be included in apk + // FIXME!!! There should only be one Book.java file + // FIXME!!! Current position in game should be visible: moveListScroll.scrollTo(0, y); + + // FIXME!!! PGN view option: game continuation (for training) + // FIXME!!! Command to go to next/previous move in PGN export order. + // FIXME!!! Remove invalid playerActions in PGN import (should be done in verifyChildren) + // FIXME!!! Implement bookmark mechanism for positions in pgn files + + // FIXME!!! Computer clock should stop if phone turned off (computer stops thinking if unplugged) + // FIXME!!! Add support for all time controls defined by the PGN standard + // FIXME!!! How to handle hour-glass time control? + // FIXME!!! What should happen if you change time controls in the middle of a game? + + // FIXME!!! Online play on FICS + // FIXME!!! Add chess960 support + // FIXME!!! Make program translatable + // FIXME!!! Implement "hint" feature + + // FIXME!!! Don't send "stop" command when engine is already stopped + + // FIXME!!! Show extended book info. (Win percent, number of games, performance rating, etc.) + // FIXME!!! Green color for "main move". Red color for "don't play in tournaments" moves. + + // FIXME!!! Add anti-lamer test: 8/8/8/8/8/8/3R3r/k3K2R w K - 0 1 bm O-O + // FIXME!!! Add anti-lamer test: 4kr2/8/8/4PpN1/8/8/4Q3/3RK3 w - f6 0 2 bm exf6 + + // FIXME!!! Remember multi-PV analysis setting when program restarted. + + private ChessBoard cb; + private static DroidChessController ctrl = null; + private boolean mShowThinking; + private boolean mWhiteBasedScores; + private boolean mShowBookHints; + private int maxNumArrows; + private GameMode gameMode; + private boolean mPonderMode; + private int mEngineThreads; + private boolean boardFlipped; + private boolean autoSwapSides; + + private TextView status; + private ScrollView moveListScroll; + private TextView moveList; + private TextView thinking; + private ImageButton modeButton, undoButton, redoButton; + private TextView whiteClock, blackClock, titleText; + + SharedPreferences settings; + + private float scrollSensitivity; + private boolean invertScrollDirection; + private boolean soundEnabled; + private MediaPlayer moveSound; + private boolean animateMoves; + + private final static String bookDir = "DroidFish"; + private final static String pgnDir = "DroidFish" + File.separator + "pgn"; + private BookOptions bookOptions = new BookOptions(); + private PGNOptions pgnOptions = new PGNOptions(); + + private long lastVisibleMillis; // Time when GUI became invisible. 0 if currently visible. + private long lastComputationMillis; // Time when engine last showed that it was computing. + + PgnScreenText gameTextListener; + + private WakeLock wakeLock = null; + private boolean useWakeLock = false; + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + String pgn = getPgnIntent(); + + settings = PreferenceManager.getDefaultSharedPreferences(this); + settings.registerOnSharedPreferenceChangeListener(new OnSharedPreferenceChangeListener() { + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + readPrefs(); + ctrl.setGameMode(gameMode); + } + }); + + PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); + setWakeLock(false); + wakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "droidfish"); + wakeLock.setReferenceCounted(false); + + initUI(true); + + gameTextListener = new PgnScreenText(pgnOptions); + if (ctrl != null) + ctrl.shutdownEngine(); + ctrl = new DroidChessController(this, gameTextListener, pgnOptions); + readPrefs(); + ctrl.newGame(gameMode); + { + byte[] data = null; + if (savedInstanceState != null) { + data = savedInstanceState.getByteArray("gameState"); + } else { + String dataStr = settings.getString("gameState", null); + if (dataStr != null) + data = strToByteArr(dataStr); + } + if (data != null) + ctrl.fromByteArray(data); + } + ctrl.setGuiPaused(true); + ctrl.setGuiPaused(false); + ctrl.startGame(); + if (pgn != null) { + try { + ctrl.setFENOrPGN(pgn); + } catch (ChessParseError e) { + } + } + } + + private String getPgnIntent() { + String pgn = null; + try { + Intent intent = getIntent(); + if ((intent.getData() != null) && intent.getScheme().equals("content")) { + ContentResolver resolver = getContentResolver(); + InputStream in = resolver.openInputStream(intent.getData()); + String tmp = ""; + while (true) { + byte[] buffer = new byte[16384]; + int len = in.read(buffer); + if (len <= 0) + break; + tmp += new String(buffer, 0, len); + } + pgn = tmp; + } + } catch (IOException e) { + Toast.makeText(getApplicationContext(), "Failed to read pgn data", Toast.LENGTH_SHORT).show(); + } + return pgn; + } + + private final byte[] strToByteArr(String str) { + int nBytes = str.length() / 2; + byte[] ret = new byte[nBytes]; + for (int i = 0; i < nBytes; i++) { + int c1 = str.charAt(i * 2) - 'A'; + int c2 = str.charAt(i * 2 + 1) - 'A'; + ret[i] = (byte)(c1 * 16 + c2); + } + return ret; + } + + private final String byteArrToString(byte[] data) { + StringBuilder ret = new StringBuilder(32768); + int nBytes = data.length; + for (int i = 0; i < nBytes; i++) { + int b = data[i]; if (b < 0) b += 256; + char c1 = (char)('A' + (b / 16)); + char c2 = (char)('A' + (b & 15)); + ret.append(c1); + ret.append(c2); + } + return ret.toString(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + ChessBoard oldCB = cb; + String statusStr = status.getText().toString(); + initUI(false); + readPrefs(); + cb.cursorX = oldCB.cursorX; + cb.cursorY = oldCB.cursorY; + cb.cursorVisible = oldCB.cursorVisible; + cb.setPosition(oldCB.pos); + cb.setFlipped(oldCB.flipped); + cb.setDrawSquareLabels(oldCB.drawSquareLabels); + cb.oneTouchMoves = oldCB.oneTouchMoves; + setSelection(oldCB.selectedSquare); + setStatusString(statusStr); + moveListUpdated(); + updateThinkingInfo(); + } + + private final void initUI(boolean initTitle) { + if (initTitle) + requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); + setContentView(R.layout.main); + if (initTitle) { + getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.title); + whiteClock = (TextView)findViewById(R.id.white_clock); + blackClock = (TextView)findViewById(R.id.black_clock); + titleText = (TextView)findViewById(R.id.title_text); + } + status = (TextView)findViewById(R.id.status); + moveListScroll = (ScrollView)findViewById(R.id.scrollView); + moveList = (TextView)findViewById(R.id.moveList); + thinking = (TextView)findViewById(R.id.thinking); + status.setFocusable(false); + moveListScroll.setFocusable(false); + moveList.setFocusable(false); + thinking.setFocusable(false); + + cb = (ChessBoard)findViewById(R.id.chessboard); + cb.setFocusable(true); + cb.requestFocus(); + cb.setClickable(true); + + final GestureDetector gd = new GestureDetector(new GestureDetector.SimpleOnGestureListener() { + private float scrollX = 0; + private float scrollY = 0; + public boolean onDown(MotionEvent e) { + scrollX = 0; + scrollY = 0; + return false; + } + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + cb.cancelLongPress(); + if (invertScrollDirection) { + distanceX = -distanceX; + distanceY = -distanceY; + } + if (scrollSensitivity > 0) { + scrollX += distanceX; + scrollY += distanceY; + float scrollUnit = cb.sqSize * scrollSensitivity; + if (Math.abs(scrollX) >= Math.abs(scrollY)) { + // Undo/redo + int nRedo = 0, nUndo = 0; + while (scrollX > scrollUnit) { + nRedo++; + scrollX -= scrollUnit; + } + while (scrollX < -scrollUnit) { + nUndo++; + scrollX += scrollUnit; + } + if (nUndo + nRedo > 0) + scrollY = 0; + if (nRedo + nUndo > 1) { + boolean analysis = gameMode.analysisMode(); + boolean human = gameMode.playerWhite() || gameMode.playerBlack(); + if (analysis || !human) + ctrl.setGameMode(new GameMode(GameMode.TWO_PLAYERS)); + } + for (int i = 0; i < nRedo; i++) ctrl.redoMove(); + for (int i = 0; i < nUndo; i++) ctrl.undoMove(); + ctrl.setGameMode(gameMode); + } else { + // Next/previous variation + int varDelta = 0; + while (scrollY > scrollUnit) { + varDelta++; + scrollY -= scrollUnit; + } + while (scrollY < -scrollUnit) { + varDelta--; + scrollY += scrollUnit; + } + if (varDelta != 0) + scrollX = 0; + ctrl.changeVariation(varDelta); + } + } + return true; + } + public boolean onSingleTapUp(MotionEvent e) { + cb.cancelLongPress(); + handleClick(e); + return true; + } + public boolean onDoubleTapEvent(MotionEvent e) { + if (e.getAction() == MotionEvent.ACTION_UP) + handleClick(e); + return true; + } + private final void handleClick(MotionEvent e) { + if (ctrl.humansTurn()) { + int sq = cb.eventToSquare(e); + Move m = cb.mousePressed(sq); + if (m != null) + ctrl.makeHumanMove(m); + } + } + }); + cb.setOnTouchListener(new OnTouchListener() { + public boolean onTouch(View v, MotionEvent event) { + return gd.onTouchEvent(event); + } + }); + cb.setOnTrackballListener(new ChessBoard.OnTrackballListener() { + public void onTrackballEvent(MotionEvent event) { + if (ctrl.humansTurn()) { + Move m = cb.handleTrackballEvent(event); + if (m != null) { + ctrl.makeHumanMove(m); + } + } + } + }); + cb.setOnLongClickListener(new OnLongClickListener() { + public boolean onLongClick(View v) { + removeDialog(BOARD_MENU_DIALOG); + showDialog(BOARD_MENU_DIALOG); + return true; + } + }); + + moveList.setOnLongClickListener(new OnLongClickListener() { + public boolean onLongClick(View v) { + removeDialog(MOVELIST_MENU_DIALOG); + showDialog(MOVELIST_MENU_DIALOG); + return true; + } + }); + thinking.setOnLongClickListener(new OnLongClickListener() { + public boolean onLongClick(View v) { + removeDialog(THINKING_MENU_DIALOG); + showDialog(THINKING_MENU_DIALOG); + return true; + } + }); + + modeButton = (ImageButton)findViewById(R.id.modeButton); + modeButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + showDialog(GAME_MODE_DIALOG); + } + }); + undoButton = (ImageButton)findViewById(R.id.undoButton); + undoButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + ctrl.undoMove(); + } + }); + undoButton.setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + removeDialog(GO_BACK_MENU_DIALOG); + showDialog(GO_BACK_MENU_DIALOG); + return true; + } + }); + redoButton = (ImageButton)findViewById(R.id.redoButton); + redoButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + ctrl.redoMove(); + } + }); + redoButton.setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + removeDialog(GO_FORWARD_MENU_DIALOG); + showDialog(GO_FORWARD_MENU_DIALOG); + return true; + } + }); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + if (ctrl != null) { + byte[] data = ctrl.toByteArray(); + outState.putByteArray("gameState", data); + } + } + + @Override + protected void onResume() { + lastVisibleMillis = 0; + if (ctrl != null) { + ctrl.setGuiPaused(false); + } + updateNotification(); + setWakeLock(useWakeLock); + super.onResume(); + } + + @Override + protected void onPause() { + if (ctrl != null) { + ctrl.setGuiPaused(true); + byte[] data = ctrl.toByteArray(); + Editor editor = settings.edit(); + String dataStr = byteArrToString(data); + editor.putString("gameState", dataStr); + editor.commit(); + } + lastVisibleMillis = System.currentTimeMillis(); + updateNotification(); + setWakeLock(false); + super.onPause(); + } + + @Override + protected void onDestroy() { + if (ctrl != null) { + ctrl.shutdownEngine(); + } + setNotification(false); + super.onDestroy(); + } + + private final int getIntSetting(String settingName, int defaultValue) { + String tmp = settings.getString(settingName, String.format("%d", defaultValue)); + int value = Integer.parseInt(tmp); + return value; + } + + private final void readPrefs() { + int modeNr = getIntSetting("gameMode", 1); + gameMode = new GameMode(modeNr); + boardFlipped = settings.getBoolean("boardFlipped", false); + autoSwapSides = settings.getBoolean("autoSwapSides", false); + setBoardFlip(); + boolean drawSquareLabels = settings.getBoolean("drawSquareLabels", false); + cb.setDrawSquareLabels(drawSquareLabels); + cb.oneTouchMoves = settings.getBoolean("oneTouchMoves", false); + + mShowThinking = settings.getBoolean("showThinking", false); + mWhiteBasedScores = settings.getBoolean("whiteBasedScores", false); + maxNumArrows = getIntSetting("thinkingArrows", 2); + mShowBookHints = settings.getBoolean("bookHints", false); + + mEngineThreads = getIntSetting("threads", 0); + + String engine = settings.getString("engine", ""); + int strength = settings.getInt("strength", 1000); + setEngineStrength(engine, strength); + + mPonderMode = settings.getBoolean("ponderMode", false); + if (!mPonderMode) + ctrl.stopPonder(); + + int timeControl = getIntSetting("timeControl", 300000); + int movesPerSession = getIntSetting("movesPerSession", 60); + int timeIncrement = getIntSetting("timeIncrement", 0); + ctrl.setTimeLimit(timeControl, movesPerSession, timeIncrement); + + scrollSensitivity = Float.parseFloat(settings.getString("scrollSensitivity", "2")); + invertScrollDirection = settings.getBoolean("invertScrollDirection", false); + boolean fullScreenMode = settings.getBoolean("fullScreenMode", false); + setFullScreenMode(fullScreenMode); + useWakeLock = settings.getBoolean("wakeLock", false); + setWakeLock(useWakeLock); + + int fontSize = getIntSetting("fontSize", 12); + status.setTextSize(fontSize); + moveList.setTextSize(fontSize); + thinking.setTextSize(fontSize); + soundEnabled = settings.getBoolean("soundEnabled", false); + animateMoves = settings.getBoolean("animateMoves", true); + + boolean largeButtons = settings.getBoolean("largeButtons", false); + Resources r = getResources(); + int bWidth = (int)Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 36, r.getDisplayMetrics())); + int bHeight = (int)Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 32, r.getDisplayMetrics())); + if (largeButtons) { + bWidth = bWidth * 3 / 2; + bHeight = bHeight * 3 / 2; + modeButton.setImageResource(R.drawable.mode_large); + undoButton.setImageResource(R.drawable.left_large); + redoButton.setImageResource(R.drawable.right_large); + } else { + modeButton.setImageResource(R.drawable.mode); + undoButton.setImageResource(R.drawable.left); + redoButton.setImageResource(R.drawable.right); + } + modeButton.setLayoutParams(new LinearLayout.LayoutParams(bWidth, bHeight)); + undoButton.setLayoutParams(new LinearLayout.LayoutParams(bWidth, bHeight)); + redoButton.setLayoutParams(new LinearLayout.LayoutParams(bWidth, bHeight)); + + bookOptions.filename = settings.getString("bookFile", ""); + bookOptions.maxLength = getIntSetting("bookMaxLength", 1000000); + bookOptions.preferMainLines = settings.getBoolean("bookPreferMainLines", false); + bookOptions.tournamentMode = settings.getBoolean("bookTournamentMode", false); + bookOptions.random = (settings.getInt("bookRandom", 500) - 500) * (3.0 / 500); + setBookOptions(); + + updateThinkingInfo(); + + pgnOptions.view.variations = settings.getBoolean("viewVariations", true); + pgnOptions.view.comments = settings.getBoolean("viewComments", true); + pgnOptions.view.nag = settings.getBoolean("viewNAG", true); + pgnOptions.view.headers = settings.getBoolean("viewHeaders", false); + pgnOptions.imp.variations = settings.getBoolean("importVariations", true); + pgnOptions.imp.comments = settings.getBoolean("importComments", true); + pgnOptions.imp.nag = settings.getBoolean("importNAG", true); + pgnOptions.exp.variations = settings.getBoolean("exportVariations", true); + pgnOptions.exp.comments = settings.getBoolean("exportComments", true); + pgnOptions.exp.nag = settings.getBoolean("exportNAG", true); + pgnOptions.exp.playerAction = settings.getBoolean("exportPlayerAction", false); + pgnOptions.exp.clockInfo = settings.getBoolean("exportTime", false); + + ColorTheme.instance().readColors(settings); + cb.setColors(); + + gameTextListener.clear(); + ctrl.prefsChanged(); + } + + private synchronized final void setWakeLock(boolean enableLock) { + WakeLock wl = wakeLock; + if (wl != null) { + if (wl.isHeld()) + wl.release(); + if (enableLock) + wl.acquire(); + } + } + + private final void setEngineStrength(String engine, int strength) { + ctrl.setEngineStrength(engine, strength); + if (strength < 1000) { + titleText.setText(String.format("%s: %d%%", getString(R.string.app_name), strength / 10)); + } else { + titleText.setText(getString(R.string.app_name)); + } + } + + private final void setFullScreenMode(boolean fullScreenMode) { + WindowManager.LayoutParams attrs = getWindow().getAttributes(); + if (fullScreenMode) { + attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; + } else { + attrs.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + getWindow().setAttributes(attrs); + } + + private final void setBookOptions() { + BookOptions options = new BookOptions(bookOptions); + if (options.filename.length() > 0) { + File extDir = Environment.getExternalStorageDirectory(); + String sep = File.separator; + options.filename = extDir.getAbsolutePath() + sep + bookDir + sep + options.filename; + } + ctrl.setBookOptions(options); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.options_menu, menu); + return true; + } + + static private final int RESULT_EDITBOARD = 0; + static private final int RESULT_SETTINGS = 1; + static private final int RESULT_LOAD_PGN = 2; + static private final int RESULT_EDITHEADERS = 3; + static private final int RESULT_EDITCOMMENTS = 4; + static private final int RESULT_SELECT_SCID = 5; + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.item_new_game: + if (autoSwapSides && (gameMode.playerWhite() != gameMode.playerBlack())) { + int gameModeType; + if (gameMode.playerWhite()) { + gameModeType = GameMode.PLAYER_BLACK; + } else { + gameModeType = GameMode.PLAYER_WHITE; + } + Editor editor = settings.edit(); + String gameModeStr = String.format("%d", gameModeType); + editor.putString("gameMode", gameModeStr); + editor.commit(); + gameMode = new GameMode(gameModeType); + } +// savePGNToFile(ctrl.getPGN(), ".autosave.pgn", true); + ctrl.newGame(gameMode); + ctrl.startGame(); + return true; + case R.id.item_editboard: { + Intent i = new Intent(DroidFish.this, EditBoard.class); + i.setAction(ctrl.getFEN()); + startActivityForResult(i, RESULT_EDITBOARD); + return true; + } + case R.id.item_settings: { + Intent i = new Intent(DroidFish.this, Preferences.class); + startActivityForResult(i, RESULT_SETTINGS); + return true; + } + case R.id.item_file_menu: { + removeDialog(FILE_MENU_DIALOG); + showDialog(FILE_MENU_DIALOG); + return true; + } + case R.id.item_goto_move: { + showDialog(SELECT_MOVE_DIALOG); + return true; + } + case R.id.item_force_move: { + ctrl.stopSearch(); + return true; + } + case R.id.item_draw: { + if (ctrl.humansTurn()) { + if (ctrl.claimDrawIfPossible()) { + ctrl.stopPonder(); + } else { + Toast.makeText(getApplicationContext(), R.string.offer_draw, Toast.LENGTH_SHORT).show(); + } + } + return true; + } + case R.id.item_resign: { + if (ctrl.humansTurn()) { + ctrl.resignGame(); + } + return true; + } + case R.id.select_book: + removeDialog(SELECT_BOOK_DIALOG); + showDialog(SELECT_BOOK_DIALOG); + return true; + case R.id.set_color_theme: + showDialog(SET_COLOR_THEME_DIALOG); + return true; + case R.id.item_about: + showDialog(ABOUT_DIALOG); + return true; + } + return false; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case RESULT_SETTINGS: + readPrefs(); + ctrl.setGameMode(gameMode); + break; + case RESULT_EDITBOARD: + if (resultCode == RESULT_OK) { + try { + String fen = data.getAction(); + ctrl.setFENOrPGN(fen); + } catch (ChessParseError e) { + } + } + break; + case RESULT_LOAD_PGN: + if (resultCode == RESULT_OK) { + try { + String pgn = data.getAction(); + ctrl.setFENOrPGN(pgn); + } catch (ChessParseError e) { + Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_SHORT).show(); + } + } + break; + case RESULT_EDITHEADERS: + if (resultCode == RESULT_OK) { + Bundle bundle = data.getBundleExtra("org.petero.droidfish.headers"); + ArrayList tags = bundle.getStringArrayList("tags"); + ArrayList tagValues = bundle.getStringArrayList("tagValues"); + ctrl.setHeaders(tags, tagValues); + } + break; + case RESULT_EDITCOMMENTS: + if (resultCode == RESULT_OK) { + Bundle bundle = data.getBundleExtra("org.petero.droidfish.comments"); + DroidChessController.CommentInfo commInfo = new DroidChessController.CommentInfo(); + commInfo.preComment = bundle.getString("preComment"); + commInfo.postComment = bundle.getString("postComment"); + commInfo.nag = bundle.getInt("nag"); + ctrl.setComments(commInfo); + } + break; + case RESULT_SELECT_SCID: + if (resultCode == RESULT_OK) { + String pathName = data.getAction(); + if (pathName != null) { + Editor editor = settings.edit(); + editor.putString("currentScidFile", pathName); + editor.putInt("currFT", FT_SCID); + editor.commit(); + Intent i = new Intent(DroidFish.this, LoadScid.class); + i.setAction("org.petero.droidfish.loadScid"); + i.putExtra("org.petero.droidfish.pathname", pathName); + startActivityForResult(i, RESULT_LOAD_PGN); + } + } + break; + } + } + + private final void setBoardFlip() { + boolean flipped = boardFlipped; + if (autoSwapSides) { + if (gameMode.analysisMode()) { + flipped = !cb.pos.whiteMove; + } else if (gameMode.playerWhite() && gameMode.playerBlack()) { + flipped = !cb.pos.whiteMove; + } else if (gameMode.playerWhite()) { + flipped = false; + } else if (gameMode.playerBlack()) { + flipped = true; + } else { // two computers + flipped = !cb.pos.whiteMove; + } + } + cb.setFlipped(flipped); + } + + @Override + public void setSelection(int sq) { + cb.setSelection(sq); + } + + @Override + public void setStatusString(String str) { + status.setText(str); + } + + @Override + public void moveListUpdated() { + moveList.setText(gameTextListener.getSpannableData()); + if (gameTextListener.atEnd()) + moveListScroll.fullScroll(ScrollView.FOCUS_DOWN); + } + + @Override + public boolean whiteBasedScores() { + return mWhiteBasedScores; + } + + @Override + public boolean ponderMode() { + return mPonderMode; + } + + @Override + public int engineThreads() { + return mEngineThreads; + } + + /** Report a move made that is a candidate for GUI animation. */ + public void setAnimMove(Position sourcePos, Move move, boolean forward) { + if (animateMoves && (move != null)) + cb.setAnimMove(sourcePos, move, forward); + } + + @Override + public void setPosition(Position pos, String variantInfo, List variantMoves) { + variantStr = variantInfo; + this.variantMoves = variantMoves; + cb.setPosition(pos); + setBoardFlip(); + updateThinkingInfo(); + } + + private String thinkingStr = ""; + private String bookInfoStr = ""; + private String variantStr = ""; + private ArrayList> pvMoves = new ArrayList>(); + private List bookMoves = null; + private List variantMoves = null; + + @Override + public void setThinkingInfo(String pvStr, String bookInfo, + ArrayList> pvMoves, List bookMoves) { + thinkingStr = pvStr; + bookInfoStr = bookInfo; + this.pvMoves = pvMoves; + this.bookMoves = bookMoves; + updateThinkingInfo(); + + if (ctrl.computerBusy()) { + lastComputationMillis = System.currentTimeMillis(); + } else { + lastComputationMillis = 0; + } + updateNotification(); + } + + private final void updateThinkingInfo() { + boolean thinkingEmpty = true; + { + String s = ""; + if (mShowThinking || gameMode.analysisMode()) { + s = thinkingStr; + } + thinking.setText(s, TextView.BufferType.SPANNABLE); + if (s.length() > 0) thinkingEmpty = false; + } + if (mShowBookHints && (bookInfoStr.length() > 0)) { + String s = ""; + if (!thinkingEmpty) + s += "
"; + s += "Book:" + bookInfoStr; + thinking.append(Html.fromHtml(s)); + thinkingEmpty = false; + } + if (variantStr.indexOf(' ') >= 0) { + String s = ""; + if (!thinkingEmpty) + s += "
"; + s += "Var: " + variantStr; + thinking.append(Html.fromHtml(s)); + } + + List hints = null; + if (mShowThinking || gameMode.analysisMode()) { + ArrayList> pvMovesTmp = pvMoves; + if (pvMovesTmp.size() == 1) { + hints = pvMovesTmp.get(0); + } else if (pvMovesTmp.size() > 1) { + hints = new ArrayList(); + for (ArrayList pv : pvMovesTmp) + if (!pv.isEmpty()) + hints.add(pv.get(0)); + } + } + if ((hints == null) && mShowBookHints) + hints = bookMoves; + if ((variantMoves != null) && variantMoves.size() > 1) { + hints = variantMoves; + } + if ((hints != null) && (hints.size() > maxNumArrows)) { + hints = hints.subList(0, maxNumArrows); + } + cb.setMoveHints(hints); + } + + static private final int PROMOTE_DIALOG = 0; + static private final int BOARD_MENU_DIALOG = 1; + static private final int ABOUT_DIALOG = 2; + static private final int SELECT_MOVE_DIALOG = 3; + static private final int SELECT_BOOK_DIALOG = 4; + static private final int SELECT_PGN_FILE_DIALOG = 5; + static private final int SELECT_PGN_FILE_SAVE_DIALOG = 6; + static private final int SET_COLOR_THEME_DIALOG = 7; + static private final int GAME_MODE_DIALOG = 8; + static private final int SELECT_PGN_SAVE_NEWFILE_DIALOG = 9; + static private final int MOVELIST_MENU_DIALOG = 10; + static private final int THINKING_MENU_DIALOG = 11; + static private final int GO_BACK_MENU_DIALOG = 12; + static private final int GO_FORWARD_MENU_DIALOG = 13; + static private final int SELECT_SCID_FILE_DIALOG = 14; + static private final int FILE_MENU_DIALOG = 15; + + @Override + protected Dialog onCreateDialog(int id) { + switch (id) { + case PROMOTE_DIALOG: { + final CharSequence[] items = { + getString(R.string.queen), getString(R.string.rook), + getString(R.string.bishop), getString(R.string.knight) + }; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.promote_pawn_to); + builder.setItems(items, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + ctrl.reportPromotePiece(item); + } + }); + AlertDialog alert = builder.create(); + return alert; + } + case BOARD_MENU_DIALOG: { + final int COPY_GAME = 0; + final int COPY_POSITION = 1; + final int PASTE = 2; + final int LOAD_GAME = 3; + final int SAVE_GAME = 4; + final int LOAD_SCID_GAME = 5; + + List lst = new ArrayList(); + List actions = new ArrayList(); + lst.add(getString(R.string.copy_game)); actions.add(COPY_GAME); + lst.add(getString(R.string.copy_position)); actions.add(COPY_POSITION); + lst.add(getString(R.string.paste)); actions.add(PASTE); + lst.add(getString(R.string.load_game)); actions.add(LOAD_GAME); + lst.add(getString(R.string.save_game)); actions.add(SAVE_GAME); + if (hasScidProvider()) { + lst.add(getString(R.string.load_scid_game)); actions.add(LOAD_SCID_GAME); + } + final List finalActions = actions; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.tools_menu); + builder.setItems(lst.toArray(new CharSequence[lst.size()]), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + switch (finalActions.get(item)) { + case COPY_GAME: { + String pgn = ctrl.getPGN(); + ClipboardManager clipboard = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE); + clipboard.setText(pgn); + break; + } + case COPY_POSITION: { + String fen = ctrl.getFEN() + "\n"; + ClipboardManager clipboard = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE); + clipboard.setText(fen); + break; + } + case PASTE: { + ClipboardManager clipboard = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE); + if (clipboard.hasText()) { + String fenPgn = clipboard.getText().toString(); + try { + ctrl.setFENOrPGN(fenPgn); + } catch (ChessParseError e) { + Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_SHORT).show(); + } + } + break; + } + case LOAD_GAME: + removeDialog(SELECT_PGN_FILE_DIALOG); + showDialog(SELECT_PGN_FILE_DIALOG); + break; + case LOAD_SCID_GAME: + removeDialog(SELECT_SCID_FILE_DIALOG); + showDialog(SELECT_SCID_FILE_DIALOG); + break; + case SAVE_GAME: + removeDialog(SELECT_PGN_FILE_SAVE_DIALOG); + showDialog(SELECT_PGN_FILE_SAVE_DIALOG); + break; + } + } + }); + AlertDialog alert = builder.create(); + return alert; + } + case FILE_MENU_DIALOG: { + final int LOAD_GAME = 0; + final int SAVE_GAME = 1; + final int LOAD_SCID_GAME = 2; + + List lst = new ArrayList(); + List actions = new ArrayList(); + lst.add(getString(R.string.load_game)); actions.add(LOAD_GAME); + lst.add(getString(R.string.save_game)); actions.add(SAVE_GAME); + if (hasScidProvider()) { + lst.add(getString(R.string.load_scid_game)); actions.add(LOAD_SCID_GAME); + } + final List finalActions = actions; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.load_save_menu); + builder.setItems(lst.toArray(new CharSequence[lst.size()]), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + switch (finalActions.get(item)) { + case LOAD_GAME: + removeDialog(SELECT_PGN_FILE_DIALOG); + showDialog(SELECT_PGN_FILE_DIALOG); + break; + case SAVE_GAME: + removeDialog(SELECT_PGN_FILE_SAVE_DIALOG); + showDialog(SELECT_PGN_FILE_SAVE_DIALOG); + break; + case LOAD_SCID_GAME: + removeDialog(SELECT_SCID_FILE_DIALOG); + showDialog(SELECT_SCID_FILE_DIALOG); + break; + } + } + }); + AlertDialog alert = builder.create(); + return alert; + } + case ABOUT_DIALOG: { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + String title = getString(R.string.app_name); + try { + PackageInfo pi = getPackageManager().getPackageInfo("org.petero.droidfish", 0); + title += " " + pi.versionName; + } catch (NameNotFoundException e) { + } + builder.setTitle(title).setMessage(R.string.about_info); + AlertDialog alert = builder.create(); + return alert; + } + case SELECT_MOVE_DIALOG: { + final Dialog dialog = new Dialog(this); + dialog.setContentView(R.layout.select_move_number); + dialog.setTitle(R.string.goto_move); + final EditText moveNrView = (EditText)dialog.findViewById(R.id.selmove_number); + Button ok = (Button)dialog.findViewById(R.id.selmove_ok); + Button cancel = (Button)dialog.findViewById(R.id.selmove_cancel); + moveNrView.setText("1"); + final Runnable gotoMove = new Runnable() { + public void run() { + try { + int moveNr = Integer.parseInt(moveNrView.getText().toString()); + ctrl.gotoMove(moveNr); + dialog.cancel(); + } catch (NumberFormatException nfe) { + Toast.makeText(getApplicationContext(), R.string.invalid_number_format, Toast.LENGTH_SHORT).show(); + } + } + }; + moveNrView.setOnKeyListener(new OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { + gotoMove.run(); + return true; + } + return false; + } + }); + ok.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + gotoMove.run(); + } + }); + cancel.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + dialog.cancel(); + } + }); + return dialog; + } + case SELECT_BOOK_DIALOG: { + String[] fileNames = findFilesInDirectory(bookDir, new FileNameFilter() { + @Override + public boolean accept(String filename) { + int dotIdx = filename.lastIndexOf("."); + if (dotIdx < 0) + return false; + String ext = filename.substring(dotIdx+1); + return (ext.equals("ctg") || ext.equals("bin")); + } + }); + final int numFiles = fileNames.length; + CharSequence[] items = new CharSequence[numFiles + 1]; + for (int i = 0; i < numFiles; i++) + items[i] = fileNames[i]; + items[numFiles] = getString(R.string.internal_book); + final CharSequence[] finalItems = items; + int defaultItem = numFiles; + for (int i = 0; i < numFiles; i++) { + if (bookOptions.filename.equals(items[i])) { + defaultItem = i; + break; + } + } + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.select_opening_book_file); + builder.setSingleChoiceItems(items, defaultItem, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Editor editor = settings.edit(); + String bookFile = ""; + if (item < numFiles) + bookFile = finalItems[item].toString(); + editor.putString("bookFile", bookFile); + editor.commit(); + bookOptions.filename = bookFile; + setBookOptions(); + dialog.dismiss(); + } + }); + AlertDialog alert = builder.create(); + return alert; + } + case SELECT_PGN_FILE_DIALOG: { + final String[] fileNames = findFilesInDirectory(pgnDir, null); + final int numFiles = fileNames.length; + if (numFiles == 0) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.app_name).setMessage(R.string.no_pgn_files); + AlertDialog alert = builder.create(); + return alert; + } + int defaultItem = 0; + String currentPGNFile = settings.getString("currentPGNFile", ""); + for (int i = 0; i < numFiles; i++) { + if (currentPGNFile.equals(fileNames[i])) { + defaultItem = i; + break; + } + } + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.select_pgn_file); + builder.setSingleChoiceItems(fileNames, defaultItem, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Editor editor = settings.edit(); + String pgnFile = fileNames[item].toString(); + editor.putString("currentPGNFile", pgnFile); + editor.putInt("currFT", FT_PGN); + editor.commit(); + String sep = File.separator; + String pathName = Environment.getExternalStorageDirectory() + sep + pgnDir + sep + pgnFile; + Intent i = new Intent(DroidFish.this, EditPGNLoad.class); + i.setAction("org.petero.droidfish.loadFile"); + i.putExtra("org.petero.droidfish.pathname", pathName); + startActivityForResult(i, RESULT_LOAD_PGN); + dialog.dismiss(); + } + }); + AlertDialog alert = builder.create(); + return alert; + } + case SELECT_SCID_FILE_DIALOG: { + Intent intent = new Intent(); + intent.setComponent(new ComponentName("org.scid.android", + "org.scid.android.SelectFileActivity")); + intent.setAction(".si4"); + startActivityForResult(intent, RESULT_SELECT_SCID); + return null; + } + case SELECT_PGN_FILE_SAVE_DIALOG: { + final String[] fileNames = findFilesInDirectory(pgnDir, null); + final int numFiles = fileNames.length; + int defaultItem = 0; + String currentPGNFile = settings.getString("currentPGNFile", ""); + for (int i = 0; i < numFiles; i++) { + if (currentPGNFile.equals(fileNames[i])) { + defaultItem = i; + break; + } + } + CharSequence[] items = new CharSequence[numFiles + 1]; + for (int i = 0; i < numFiles; i++) + items[i] = fileNames[i]; + items[numFiles] = getString(R.string.new_file); + final CharSequence[] finalItems = items; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.select_pgn_file_save); + builder.setSingleChoiceItems(finalItems, defaultItem, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + String pgnFile; + if (item >= numFiles) { + dialog.dismiss(); + showDialog(SELECT_PGN_SAVE_NEWFILE_DIALOG); + } else { + Editor editor = settings.edit(); + pgnFile = fileNames[item].toString(); + editor.putString("currentPGNFile", pgnFile); + editor.commit(); + dialog.dismiss(); + savePGNToFile(ctrl.getPGN(), pgnFile, false); + } + } + }); + AlertDialog alert = builder.create(); + return alert; + } + case SELECT_PGN_SAVE_NEWFILE_DIALOG: { + final Dialog dialog = new Dialog(this); + dialog.setContentView(R.layout.create_pgn_file); + dialog.setTitle(R.string.select_pgn_file_save); + final EditText fileNameView = (EditText)dialog.findViewById(R.id.create_pgn_filename); + Button ok = (Button)dialog.findViewById(R.id.create_pgn_ok); + Button cancel = (Button)dialog.findViewById(R.id.create_pgn_cancel); + fileNameView.setText(""); + final Runnable savePGN = new Runnable() { + public void run() { + String pgnFile = fileNameView.getText().toString(); + if ((pgnFile.length() > 0) && !pgnFile.contains(".")) + pgnFile += ".pgn"; + savePGNToFile(ctrl.getPGN(), pgnFile, false); + dialog.cancel(); + } + }; + fileNameView.setOnKeyListener(new OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { + savePGN.run(); + return true; + } + return false; + } + }); + ok.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + savePGN.run(); + } + }); + cancel.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + dialog.cancel(); + } + }); + return dialog; + } + + case SET_COLOR_THEME_DIALOG: { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.select_color_theme); + builder.setSingleChoiceItems(ColorTheme.themeNames, -1, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + ColorTheme.instance().setTheme(settings, item); + cb.setColors(); + gameTextListener.setCurrent(gameTextListener.currNode); + moveListUpdated(); + dialog.dismiss(); + } + }); + return builder.create(); + } + case GAME_MODE_DIALOG: { + final CharSequence[] items = { + getString(R.string.analysis_mode), + getString(R.string.edit_replay_game), + getString(R.string.play_white), + getString(R.string.play_black), + getString(R.string.two_players), + getString(R.string.comp_vs_comp) + }; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setItems(items, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + int gameModeType = -1; + switch (item) { + case 0: gameModeType = GameMode.ANALYSIS; break; + case 1: gameModeType = GameMode.EDIT_GAME; break; + case 2: gameModeType = GameMode.PLAYER_WHITE; break; + case 3: gameModeType = GameMode.PLAYER_BLACK; break; + case 4: gameModeType = GameMode.TWO_PLAYERS; break; + case 5: gameModeType = GameMode.TWO_COMPUTERS; break; + default: break; + } + dialog.dismiss(); + if (gameModeType >= 0) { + Editor editor = settings.edit(); + String gameModeStr = String.format("%d", gameModeType); + editor.putString("gameMode", gameModeStr); + editor.commit(); + gameMode = new GameMode(gameModeType); + ctrl.setGameMode(gameMode); + } + } + }); + AlertDialog alert = builder.create(); + return alert; + } + case MOVELIST_MENU_DIALOG: { + final int EDIT_HEADERS = 0; + final int EDIT_COMMENTS = 1; + final int REMOVE_SUBTREE = 2; + final int MOVE_VAR_UP = 3; + final int MOVE_VAR_DOWN = 4; + final int ADD_NULL_MOVE = 5; + + List lst = new ArrayList(); + List actions = new ArrayList(); + lst.add(getString(R.string.edit_headers)); actions.add(EDIT_HEADERS); + if (ctrl.humansTurn()) { + lst.add(getString(R.string.edit_comments)); actions.add(EDIT_COMMENTS); + } + lst.add(getString(R.string.truncate_gametree)); actions.add(REMOVE_SUBTREE); + if (ctrl.numVariations() > 1) { + lst.add(getString(R.string.move_var_up)); actions.add(MOVE_VAR_UP); + lst.add(getString(R.string.move_var_down)); actions.add(MOVE_VAR_DOWN); + } + + boolean allowNullMove = + gameMode.analysisMode() || + (gameMode.playerWhite() && gameMode.playerBlack() && !gameMode.clocksActive()); + if (allowNullMove) { + lst.add(getString(R.string.add_null_move)); actions.add(ADD_NULL_MOVE); + } + final List finalActions = actions; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.edit_game); + builder.setItems(lst.toArray(new CharSequence[lst.size()]), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + switch (finalActions.get(item)) { + case EDIT_HEADERS: { + Intent i = new Intent(DroidFish.this, EditHeaders.class); + i.setAction(""); + Bundle bundle = new Bundle(); + ArrayList tags = new ArrayList(); + ArrayList tagValues = new ArrayList(); + ctrl.getHeaders(tags, tagValues); + bundle.putStringArrayList("tags", tags); + bundle.putStringArrayList("tagValues", tagValues); + i.putExtra("org.petero.droidfish.headers", bundle); + startActivityForResult(i, RESULT_EDITHEADERS); + break; + } + case EDIT_COMMENTS: { + Intent i = new Intent(DroidFish.this, EditComments.class); + i.setAction(""); + DroidChessController.CommentInfo commInfo = ctrl.getComments(); + Bundle bundle = new Bundle(); + bundle.putString("preComment", commInfo.preComment); + bundle.putString("postComment", commInfo.postComment); + bundle.putInt("nag", commInfo.nag); + bundle.putString("move", commInfo.move); + i.putExtra("org.petero.droidfish.comments", bundle); + startActivityForResult(i, RESULT_EDITCOMMENTS); + break; + } + case REMOVE_SUBTREE: + ctrl.removeSubTree(); + break; + case MOVE_VAR_UP: + ctrl.moveVariation(-1); + break; + case MOVE_VAR_DOWN: + ctrl.moveVariation(1); + break; + case ADD_NULL_MOVE: + ctrl.makeHumanNullMove(); + break; + } + } + }); + AlertDialog alert = builder.create(); + return alert; + } + case THINKING_MENU_DIALOG: { + if (pvMoves.isEmpty()) + return null; + final int ADD_ANALYSIS = 0; + final int MULTIPV_DEC = 1; + final int MULTIPV_INC = 2; + List lst = new ArrayList(); + List actions = new ArrayList(); + lst.add("Add Analysis"); actions.add(ADD_ANALYSIS); + final int numPV = ctrl.getNumPV(); + if (gameMode.analysisMode()) { + int maxPV = ctrl.maxPV(); + if (numPV > 1) { + lst.add("Fewer Variations"); actions.add(MULTIPV_DEC); + } + if (numPV < maxPV) { + lst.add("More Variations"); actions.add(MULTIPV_INC); + } + } + final List finalActions = actions; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.analysis); + builder.setItems(lst.toArray(new CharSequence[lst.size()]), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + switch (finalActions.get(item)) { + case ADD_ANALYSIS: { + ArrayList> pvMovesTmp = pvMoves; + String[] pvStrs = thinkingStr.split("\n"); + for (int i = 0; i < pvMovesTmp.size(); i++) { + ArrayList pv = pvMovesTmp.get(i); + StringBuilder preComment = new StringBuilder(); + if (pvStrs.length > i) { + String[] tmp = pvStrs[i].split(" "); + for (int j = 0; j < 2; j++) { + if (j < tmp.length) { + if (j > 0) preComment.append(' '); + preComment.append(tmp[j]); + } + } + if (preComment.length() > 0) preComment.append(':'); + } + boolean updateDefault = (i == 0); + ctrl.addVariation(preComment.toString(), pv, updateDefault); + } + break; + } + case MULTIPV_DEC: + ctrl.setMultiPVMode(numPV - 1); + break; + case MULTIPV_INC: + ctrl.setMultiPVMode(numPV + 1); + break; + } + } + }); + AlertDialog alert = builder.create(); + return alert; + } + case GO_BACK_MENU_DIALOG: { + final int GOTO_START_GAME = 0; + final int GOTO_START_VAR = 1; + final int GOTO_PREV_VAR = 2; + final int LOAD_PREV_GAME = 3; + + List lst = new ArrayList(); + List actions = new ArrayList(); + lst.add(getString(R.string.goto_start_game)); actions.add(GOTO_START_GAME); + lst.add(getString(R.string.goto_start_variation)); actions.add(GOTO_START_VAR); + if (ctrl.currVariation() > 0) { + lst.add(getString(R.string.goto_prev_variation)); actions.add(GOTO_PREV_VAR); + } + final int currFT = currFileType(); + final String currFileName = currFileName(); + if (currFT != FT_NONE) { + lst.add(getString(R.string.load_prev_game)); actions.add(LOAD_PREV_GAME); + } + final List finalActions = actions; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.go_back); + builder.setItems(lst.toArray(new CharSequence[lst.size()]), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + switch (finalActions.get(item)) { + case GOTO_START_GAME: ctrl.gotoMove(0); break; + case GOTO_START_VAR: ctrl.gotoStartOfVariation(); break; + case GOTO_PREV_VAR: ctrl.changeVariation(-1); break; + case LOAD_PREV_GAME: + String sep = File.separator; + String pathName = Environment.getExternalStorageDirectory() + sep; + Intent i; + if (currFT == FT_PGN) { + i = new Intent(DroidFish.this, EditPGNLoad.class); + i.setAction("org.petero.droidfish.loadFilePrevGame"); + i.putExtra("org.petero.droidfish.pathname", pathName + pgnDir + sep + currFileName); + } else { + i = new Intent(DroidFish.this, LoadScid.class); + i.setAction("org.petero.droidfish.loadScidPrevGame"); + i.putExtra("org.petero.droidfish.pathname", currFileName); + } + startActivityForResult(i, RESULT_LOAD_PGN); + break; + } + } + }); + AlertDialog alert = builder.create(); + return alert; + } + case GO_FORWARD_MENU_DIALOG: { + final int GOTO_END_VAR = 0; + final int GOTO_NEXT_VAR = 1; + final int LOAD_NEXT_GAME = 2; + + List lst = new ArrayList(); + List actions = new ArrayList(); + lst.add(getString(R.string.goto_end_variation)); actions.add(GOTO_END_VAR); + if (ctrl.currVariation() < ctrl.numVariations() - 1) { + lst.add(getString(R.string.goto_next_variation)); actions.add(GOTO_NEXT_VAR); + } + final int currFT = currFileType(); + final String currFileName = currFileName(); + if (currFT != FT_NONE) { + lst.add(getString(R.string.load_next_game)); actions.add(LOAD_NEXT_GAME); + } + final List finalActions = actions; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.go_forward); + builder.setItems(lst.toArray(new CharSequence[lst.size()]), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + switch (finalActions.get(item)) { + case GOTO_END_VAR: ctrl.gotoMove(Integer.MAX_VALUE); break; + case GOTO_NEXT_VAR: ctrl.changeVariation(1); break; + case LOAD_NEXT_GAME: + String sep = File.separator; + String pathName = Environment.getExternalStorageDirectory() + sep; + Intent i; + if (currFT == FT_PGN) { + i = new Intent(DroidFish.this, EditPGNLoad.class); + i.setAction("org.petero.droidfish.loadFileNextGame"); + i.putExtra("org.petero.droidfish.pathname", pathName + pgnDir + sep + currFileName); + } else { + i = new Intent(DroidFish.this, LoadScid.class); + i.setAction("org.petero.droidfish.loadScidNextGame"); + i.putExtra("org.petero.droidfish.pathname", currFileName); + } + startActivityForResult(i, RESULT_LOAD_PGN); + break; + } + } + }); + AlertDialog alert = builder.create(); + return alert; + } + } + return null; + } + + final static int FT_NONE = 0; + final static int FT_PGN = 1; + final static int FT_SCID = 2; + + private final int currFileType() { + if (gameMode.clocksActive()) + return FT_NONE; + int ft = settings.getInt("currFT", FT_NONE); + return ft; + } + + private final String currFileName() { + int ft = settings.getInt("currFT", FT_NONE); + switch (ft) { + case FT_PGN: return settings.getString("currentPGNFile", ""); + case FT_SCID: return settings.getString("currentScidFile", ""); + default: return ""; + } + } + + private static interface FileNameFilter { + boolean accept(String filename); + } + + private final String[] findFilesInDirectory(String dirName, final FileNameFilter filter) { + File extDir = Environment.getExternalStorageDirectory(); + String sep = File.separator; + File dir = new File(extDir.getAbsolutePath() + sep + dirName); + File[] files = dir.listFiles(new FileFilter() { + public boolean accept(File pathname) { + if (!pathname.isFile()) + return false; + return (filter == null) || filter.accept(pathname.getAbsolutePath()); + } + }); + if (files == null) + files = new File[0]; + final int numFiles = files.length; + String[] fileNames = new String[numFiles]; + for (int i = 0; i < files.length; i++) + fileNames[i] = files[i].getName(); + Arrays.sort(fileNames, String.CASE_INSENSITIVE_ORDER); + return fileNames; + } + + private final void savePGNToFile(String pgn, String filename, boolean silent) { + String sep = File.separator; + String pathName = Environment.getExternalStorageDirectory() + sep + pgnDir + sep + filename; + Intent i = new Intent(DroidFish.this, EditPGNSave.class); + i.setAction("org.petero.droidfish.saveFile"); + i.putExtra("org.petero.droidfish.pathname", pathName); + i.putExtra("org.petero.droidfish.pgn", pgn); + i.putExtra("org.petero.droidfish.silent", silent); + startActivity(i); + } + + @Override + public void requestPromotePiece() { + runOnUIThread(new Runnable() { + public void run() { + showDialog(PROMOTE_DIALOG); + } + }); + } + + @Override + public void reportInvalidMove(Move m) { + String msg = String.format("Invalid move %s-%s", TextIO.squareToString(m.from), TextIO.squareToString(m.to)); + Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show(); + } + + @Override + public void computerMoveMade() { + if (soundEnabled) { + if (moveSound != null) + moveSound.release(); + moveSound = MediaPlayer.create(this, R.raw.movesound); + moveSound.start(); + } + } + + @Override + public void runOnUIThread(Runnable runnable) { + runOnUiThread(runnable); + } + + /** Decide if user should be warned about heavy CPU usage. */ + private final void updateNotification() { + boolean warn = false; + if (lastVisibleMillis != 0) { // GUI not visible + warn = lastComputationMillis >= lastVisibleMillis + 90000; + } + setNotification(warn); + } + + private boolean notificationActive = false; + + /** Set/clear the "heavy CPU usage" notification. */ + private final void setNotification(boolean show) { + if (notificationActive == show) + return; + notificationActive = show; + final int cpuUsage = 1; + String ns = Context.NOTIFICATION_SERVICE; + NotificationManager mNotificationManager = (NotificationManager)getSystemService(ns); + if (show) { + int icon = R.drawable.icon; + CharSequence tickerText = "Heavy CPU usage"; + long when = System.currentTimeMillis(); + Notification notification = new Notification(icon, tickerText, when); + notification.flags |= Notification.FLAG_ONGOING_EVENT; + + Context context = getApplicationContext(); + CharSequence contentTitle = "Background processing"; + CharSequence contentText = "DroidFish is using a lot of CPU power"; + Intent notificationIntent = new Intent(this, CPUWarning.class); + + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); + notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent); + + mNotificationManager.notify(cpuUsage, notification); + } else { + mNotificationManager.cancel(cpuUsage); + } + } + + private final String timeToString(long time) { + int secs = (int)Math.floor((time + 999) / 1000.0); + boolean neg = false; + if (secs < 0) { + neg = true; + secs = -secs; + } + int mins = secs / 60; + secs -= mins * 60; + StringBuilder ret = new StringBuilder(); + if (neg) ret.append('-'); + ret.append(mins); + ret.append(':'); + if (secs < 10) ret.append('0'); + ret.append(secs); + return ret.toString(); + } + + private Handler handlerTimer = new Handler(); + private Runnable r = new Runnable() { + public void run() { + ctrl.updateRemainingTime(); + } + }; + + @Override + public void setRemainingTime(long wTime, long bTime, long nextUpdate) { + whiteClock.setText("White: " + timeToString(wTime)); + blackClock.setText("Black: " + timeToString(bTime)); + handlerTimer.removeCallbacks(r); + if (nextUpdate > 0) { + handlerTimer.postDelayed(r, nextUpdate); + } + } + + /** PngTokenReceiver implementation that renders PGN data for screen display. */ + static class PgnScreenText implements PgnToken.PgnTokenReceiver { + private SpannableStringBuilder sb = new SpannableStringBuilder(); + private int prevType = PgnToken.EOF; + int nestLevel = 0; + boolean col0 = true; + Node currNode = null; + final static int indentStep = 15; + int currPos = 0, endPos = 0; + boolean upToDate = false; + PGNOptions options; + + private static class NodeInfo { + int l0, l1; + NodeInfo(int ls, int le) { + l0 = ls; + l1 = le; + } + } + HashMap nodeToCharPos; + + PgnScreenText(PGNOptions options) { + nodeToCharPos = new HashMap(); + this.options = options; + } + + public final SpannableStringBuilder getSpannableData() { + return sb; + } + public final boolean atEnd() { + return currPos >= endPos - 10; + } + + public boolean isUpToDate() { + return upToDate; + } + + int paraStart = 0; + int paraIndent = 0; + boolean paraBold = false; + private final void newLine() { newLine(false); } + private final void newLine(boolean eof) { + if (!col0) { + if (paraIndent > 0) { + int paraEnd = sb.length(); + int indent = paraIndent * indentStep; + sb.setSpan(new LeadingMarginSpan.Standard(indent), paraStart, paraEnd, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (paraBold) { + int paraEnd = sb.length(); + sb.setSpan(new StyleSpan(Typeface.BOLD), paraStart, paraEnd, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (!eof) + sb.append('\n'); + paraStart = sb.length(); + paraIndent = nestLevel; + paraBold = false; + } + col0 = true; + } + + boolean pendingNewLine = false; + + public void processToken(Node node, int type, String token) { + if ( (prevType == PgnToken.RIGHT_BRACKET) && + (type != PgnToken.LEFT_BRACKET)) { + if (options.view.headers) { + col0 = false; + newLine(); + } else { + sb.clear(); + paraBold = false; + } + } + if (pendingNewLine) { + if (type != PgnToken.RIGHT_PAREN) { + newLine(); + pendingNewLine = false; + } + } + switch (type) { + case PgnToken.STRING: + sb.append(" \""); + sb.append(token); + sb.append('"'); + break; + case PgnToken.INTEGER: + if ( (prevType != PgnToken.LEFT_PAREN) && + (prevType != PgnToken.RIGHT_BRACKET) && !col0) + sb.append(' '); + sb.append(token); + col0 = false; + break; + case PgnToken.PERIOD: + sb.append('.'); + col0 = false; + break; + case PgnToken.ASTERISK: sb.append(" *"); col0 = false; break; + case PgnToken.LEFT_BRACKET: sb.append('['); col0 = false; break; + case PgnToken.RIGHT_BRACKET: sb.append("]\n"); col0 = false; break; + case PgnToken.LEFT_PAREN: + nestLevel++; + if (col0) + paraIndent++; + newLine(); + sb.append('('); + col0 = false; + break; + case PgnToken.RIGHT_PAREN: + sb.append(')'); + nestLevel--; + pendingNewLine = true; + break; + case PgnToken.NAG: + sb.append(Node.nagStr(Integer.parseInt(token))); + col0 = false; + break; + case PgnToken.SYMBOL: { + if ((prevType != PgnToken.RIGHT_BRACKET) && (prevType != PgnToken.LEFT_BRACKET) && !col0) + sb.append(' '); + int l0 = sb.length(); + sb.append(token); + int l1 = sb.length(); + nodeToCharPos.put(node, new NodeInfo(l0, l1)); + if (endPos < l0) endPos = l0; + col0 = false; + if (nestLevel == 0) paraBold = true; + break; + } + case PgnToken.COMMENT: + if (prevType == PgnToken.RIGHT_BRACKET) { + } else if (nestLevel == 0) { + nestLevel++; + newLine(); + nestLevel--; + } else { + if ((prevType != PgnToken.LEFT_PAREN) && !col0) { + sb.append(' '); + } + } + sb.append(token.replaceAll("[ \t\r\n]+", " ").trim()); + col0 = false; + if (nestLevel == 0) + newLine(); + break; + case PgnToken.EOF: + newLine(true); + upToDate = true; + break; + } + prevType = type; + } + + @Override + public void clear() { + sb.clear(); + prevType = PgnToken.EOF; + nestLevel = 0; + col0 = true; + currNode = null; + currPos = 0; + endPos = 0; + nodeToCharPos.clear(); + paraStart = 0; + paraIndent = 0; + paraBold = false; + pendingNewLine = false; + + upToDate = false; + } + + BackgroundColorSpan bgSpan = new BackgroundColorSpan(0xff888888); + + @Override + public void setCurrent(Node node) { + sb.removeSpan(bgSpan); + NodeInfo ni = nodeToCharPos.get(node); + if (ni != null) { + int color = ColorTheme.instance().getColor(ColorTheme.CURRENT_MOVE); + bgSpan = new BackgroundColorSpan(color); + sb.setSpan(bgSpan, ni.l0, ni.l1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + currPos = ni.l0; + } + currNode = node; + } + } + + private final boolean hasScidProvider() { + List providers = getPackageManager().queryContentProviders(null, 0, 0); + for (ProviderInfo info : providers) + if (info.authority.equals("org.scid.database.scidprovider")) + return true; + return false; + } +} diff --git a/DroidFish/src/org/petero/droidfish/GUIInterface.java b/DroidFish/src/org/petero/droidfish/GUIInterface.java new file mode 100644 index 0000000..8333e29 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/GUIInterface.java @@ -0,0 +1,72 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish; + +import java.util.ArrayList; +import java.util.List; + +import org.petero.droidfish.gamelogic.Move; +import org.petero.droidfish.gamelogic.Position; + + +/** Interface between the GUI and the ChessController. */ +public interface GUIInterface { + + /** Update the displayed board position. */ + public void setPosition(Position pos, String variantInfo, List variantMoves); + + /** Mark square i as selected. Set to -1 to clear selection. */ + public void setSelection(int sq); + + /** Set the status text. */ + public void setStatusString(String str); + + /** Update the list of moves. */ + public void moveListUpdated(); + + /** Update the computer thinking information. */ + public void setThinkingInfo(String pvStr, String bookInfo, ArrayList> pvMoves, List bookMoves); + + /** Ask what to promote a pawn to. Should call reportPromotePiece() when done. */ + public void requestPromotePiece(); + + /** Run code on the GUI thread. */ + public void runOnUIThread(Runnable runnable); + + /** Report that user attempted to make an invalid move. */ + public void reportInvalidMove(Move m); + + /** Called when computer made a move. GUI can notify user, for example by playing a sound. */ + public void computerMoveMade(); + + /** Report remaining thinking time to GUI. */ + public void setRemainingTime(long wTime, long bTime, long nextUpdate); + + /** Report a move made that is a candidate for GUI animation. */ + public void setAnimMove(Position sourcePos, Move move, boolean forward); + + /** Return true if positive analysis scores means good for white. */ + public boolean whiteBasedScores(); + + /** Return true if pondering (permanent brain) is enabled. */ + public boolean ponderMode(); + + /** Return the number of engine threads to use. */ + int engineThreads(); +} diff --git a/DroidFish/src/org/petero/droidfish/GameMode.java b/DroidFish/src/org/petero/droidfish/GameMode.java new file mode 100644 index 0000000..19bed07 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/GameMode.java @@ -0,0 +1,111 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish; + +public class GameMode { + private final boolean playerWhite; + private final boolean playerBlack; + private final boolean analysisMode; + private final boolean clocksActive; + + public static final int PLAYER_WHITE = 1; + public static final int PLAYER_BLACK = 2; + public static final int TWO_PLAYERS = 3; + public static final int ANALYSIS = 4; + public static final int TWO_COMPUTERS = 5; + public static final int EDIT_GAME = 6; + + public GameMode(int modeNr) { + switch (modeNr) { + case PLAYER_WHITE: default: + playerWhite = true; + playerBlack = false; + analysisMode = false; + clocksActive = true; + break; + case PLAYER_BLACK: + playerWhite = false; + playerBlack = true; + analysisMode = false; + clocksActive = true; + break; + case TWO_PLAYERS: + playerWhite = true; + playerBlack = true; + analysisMode = false; + clocksActive = true; + break; + case ANALYSIS: + playerWhite = true; + playerBlack = true; + analysisMode = true; + clocksActive = false; + break; + case TWO_COMPUTERS: + playerWhite = false; + playerBlack = false; + analysisMode = false; + clocksActive = true; + break; + case EDIT_GAME: + playerWhite = true; + playerBlack = true; + analysisMode = false; + clocksActive = false; + break; + } + } + + public final boolean playerWhite() { + return playerWhite; + } + public final boolean playerBlack() { + return playerBlack; + } + public final boolean analysisMode() { + return analysisMode; + } + public final boolean humansTurn(boolean whiteMove) { + return (whiteMove ? playerWhite : playerBlack) || analysisMode; + } + public final boolean clocksActive() { + return clocksActive; + } + + @Override + public boolean equals(Object o) { + if ((o == null) || (o.getClass() != this.getClass())) + return false; + GameMode other = (GameMode)o; + if (playerWhite != other.playerWhite) + return false; + if (playerBlack != other.playerBlack) + return false; + if (analysisMode != other.analysisMode) + return false; + if (clocksActive != other.clocksActive) + return false; + return true; + } + + @Override + public int hashCode() { + return 0; + } +} diff --git a/DroidFish/src/org/petero/droidfish/MyScrollView.java b/DroidFish/src/org/petero/droidfish/MyScrollView.java new file mode 100644 index 0000000..99ed54f --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/MyScrollView.java @@ -0,0 +1,27 @@ +package org.petero.droidfish; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ScrollView; + +/** A ScrollView that uses at most 75% of the parent height. */ +public class MyScrollView extends ScrollView { + + public MyScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int width = getMeasuredWidth(); + int height = getMeasuredHeight(); + if (getParent() instanceof View) { + int parentHeight = ((View)getParent()).getHeight(); + if (parentHeight > 0) + height = Math.min(height, parentHeight * 3 / 4); + } + setMeasuredDimension(width, height); + } +} diff --git a/DroidFish/src/org/petero/droidfish/PGNOptions.java b/DroidFish/src/org/petero/droidfish/PGNOptions.java new file mode 100644 index 0000000..8cd02d9 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/PGNOptions.java @@ -0,0 +1,54 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish; + +/** Settings controlling PGN import/export */ +public class PGNOptions { + public static class Viewer { + public boolean variations; + public boolean comments; + public boolean nag; + public boolean headers; + } + public static class Import { + public boolean variations; + public boolean comments; + public boolean nag; + } + public static class Export { + public boolean variations; + public boolean comments; + public boolean nag; + public boolean playerAction; + public boolean clockInfo; + public boolean pgnPromotions; + public boolean moveNrAfterNag; + } + + public Viewer view; + public Import imp; + public Export exp; + + public PGNOptions() { + view = new Viewer(); + imp = new Import(); + exp = new Export(); + exp.moveNrAfterNag = true; + } +} diff --git a/DroidFish/src/org/petero/droidfish/SeekBarPreference.java b/DroidFish/src/org/petero/droidfish/SeekBarPreference.java new file mode 100644 index 0000000..8f61fa3 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/SeekBarPreference.java @@ -0,0 +1,183 @@ +package org.petero.droidfish; + +import android.app.Dialog; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.TypedArray; +import android.graphics.Typeface; +import android.preference.Preference; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.View.OnClickListener; +import android.view.View.OnKeyListener; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.SeekBar; +import android.widget.TextView; +import android.widget.SeekBar.OnSeekBarChangeListener; + +/** Lets user enter a percentage value using a seek bar. */ +public class SeekBarPreference extends Preference + implements OnSeekBarChangeListener { + private final static int maxValue = 1000; + private final static int DEFAULT_VALUE = 1000; + private int currVal = DEFAULT_VALUE; + private TextView currValBox; + + public SeekBarPreference(Context context) { + super(context); + } + public SeekBarPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + public SeekBarPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected View onCreateView(ViewGroup parent) { + TextView name = new TextView(getContext()); + name.setText(getTitle()); + name.setTextSize(20); + name.setTypeface(Typeface.SANS_SERIF, Typeface.BOLD); + name.setGravity(Gravity.LEFT); + LinearLayout.LayoutParams lp = + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + lp.gravity = Gravity.LEFT; + lp.weight = 1.0f; + name.setLayoutParams(lp); + + currValBox = new TextView(getContext()); + currValBox.setTextSize(12); + currValBox.setTypeface(Typeface.MONOSPACE, Typeface.ITALIC); + currValBox.setPadding(2, 5, 0, 0); + currValBox.setText(valToString()); + lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + lp.gravity = Gravity.CENTER; + currValBox.setLayoutParams(lp); + + LinearLayout row1 = new LinearLayout(getContext()); + row1.setOrientation(LinearLayout.HORIZONTAL); + row1.addView(name); + row1.addView(currValBox); + + final SeekBar bar = new SeekBar(getContext()); + bar.setMax(maxValue); + bar.setProgress(currVal); + bar.setOnSeekBarChangeListener(this); + lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + lp.gravity = Gravity.RIGHT; + bar.setLayoutParams(lp); + + LinearLayout layout = new LinearLayout(getContext()); + layout.setPadding(25, 5, 25, 5); + layout.setOrientation(LinearLayout.VERTICAL); + layout.addView(row1); + layout.addView(bar); + layout.setId(android.R.id.widget_frame); + + currValBox.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + final Dialog dialog = new Dialog(getContext()); + dialog.setContentView(R.layout.select_percentage); + String title = ""; + String key = getKey(); + if (key.equals("strength")) { + title = getContext().getString(R.string.edit_strength); + } else if (key.equals("bookRandom")) { + title = getContext().getString(R.string.edit_randomization); + } + dialog.setTitle(title); + final EditText valueView = (EditText)dialog.findViewById(R.id.selpercentage_number); + Button ok = (Button)dialog.findViewById(R.id.selpercentage_ok); + Button cancel = (Button)dialog.findViewById(R.id.selpercentage_cancel); + valueView.setText(currValBox.getText().toString().replaceAll("%", "")); + final Runnable selectValue = new Runnable() { + public void run() { + try { + String txt = valueView.getText().toString(); + int value = (int)(Double.parseDouble(txt) * 10 + 0.5); + if (value < 0) value = 0; + if (value > maxValue) value = maxValue; + dialog.cancel(); + onProgressChanged(bar, value, false); + } catch (NumberFormatException nfe) { + } + } + }; + valueView.setOnKeyListener(new OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { + selectValue.run(); + return true; + } + return false; + } + }); + ok.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + selectValue.run(); + } + }); + cancel.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + dialog.cancel(); + } + }); + + dialog.show(); + } + }); + + return layout; + } + + private final String valToString() { + return String.format("%.1f%%", currVal*0.1); + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) { + if (!callChangeListener(progress)) { + seekBar.setProgress(currVal); + return; + } + seekBar.setProgress(progress); + currVal = progress; + currValBox.setText(valToString()); + SharedPreferences.Editor editor = getEditor(); + editor.putInt(getKey(), progress); + editor.commit(); + } + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + notifyChanged(); + } + + @Override + protected Object onGetDefaultValue(TypedArray a, int index) { + int defVal = a.getInt(index, DEFAULT_VALUE); + if (defVal > maxValue) defVal = maxValue; + if (defVal < 0) defVal = 0; + return defVal; + } + + @Override + protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) { + int val = restorePersistedValue ? getPersistedInt(DEFAULT_VALUE) : (Integer)defaultValue; + if (!restorePersistedValue) + persistInt(val); + currVal = val; + } +} diff --git a/DroidFish/src/org/petero/droidfish/activities/CPUWarning.java b/DroidFish/src/org/petero/droidfish/activities/CPUWarning.java new file mode 100644 index 0000000..2778485 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/activities/CPUWarning.java @@ -0,0 +1,56 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.activities; + +import org.petero.droidfish.R; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.DialogInterface.OnDismissListener; +import android.os.Bundle; + +public class CPUWarning extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.cpu_warning); + showDialog(CPU_WARNING_DIALOG); + } + + static final int CPU_WARNING_DIALOG = 1; + + @Override + protected Dialog onCreateDialog(int id) { + switch (id) { + case CPU_WARNING_DIALOG: + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.app_name).setMessage(R.string.cpu_warning); + AlertDialog alert = builder.create(); + alert.setOnDismissListener(new OnDismissListener() { + public void onDismiss(DialogInterface dialog) { + finish(); + } + }); + return alert; + } + return null; + } +} diff --git a/DroidFish/src/org/petero/droidfish/activities/ChessBoardEdit.java b/DroidFish/src/org/petero/droidfish/activities/ChessBoardEdit.java new file mode 100644 index 0000000..8b8ebd0 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/activities/ChessBoardEdit.java @@ -0,0 +1,210 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.activities; + +import org.petero.droidfish.ChessBoard; +import org.petero.droidfish.gamelogic.Move; +import org.petero.droidfish.gamelogic.Piece; +import org.petero.droidfish.gamelogic.Position; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.MotionEvent; + +/** + * Chess board widget suitable for edit mode. + * @author petero + */ +public class ChessBoardEdit extends ChessBoard { + public ChessBoardEdit(Context context, AttributeSet attrs) { + super(context, attrs); + drawSquareLabels = true; + } + + private final static int gap = 2; + + @Override + protected int getWidth(int sqSize) { return sqSize * 8 + 4; } + @Override + protected int getHeight(int sqSize) { return sqSize * 10 + 4 + gap; } + @Override + protected int getSqSizeW(int width) { return (width - 4) / 8; } + @Override + protected int getSqSizeH(int height) { return (height - 4 - gap) / 10; } + @Override + protected int getMaxHeightPercentage() { return 85; } + + @Override + protected void computeOrigin(int width, int height) { + x0 = (width - sqSize * 8) / 2; + y0 = (height - (sqSize * 10 + gap)) / 2; + } + + int extraPieces(int x, int y) { + if (y == -1) { // White pieces + switch (x) { + case 0: return Piece.WKING; + case 1: return Piece.WQUEEN; + case 2: return Piece.WROOK; + case 3: return Piece.WBISHOP; + case 4: return Piece.WKNIGHT; + case 5: return Piece.WPAWN; + } + } else if (y == -2) { + switch (x) { + case 0: return Piece.BKING; + case 1: return Piece.BQUEEN; + case 2: return Piece.BROOK; + case 3: return Piece.BBISHOP; + case 4: return Piece.BKNIGHT; + case 5: return Piece.BPAWN; + } + } + return Piece.EMPTY; + } + + @Override + protected int getXFromSq(int sq) { + if (sq >= 0) { + return Position.getX(sq); + } else { + int p = -2 - sq; + switch (p) { + case Piece.WKING: case Piece.BKING: return 0; + case Piece.WQUEEN: case Piece.BQUEEN: return 1; + case Piece.WROOK: case Piece.BROOK: return 2; + case Piece.WBISHOP: case Piece.BBISHOP: return 3; + case Piece.WKNIGHT: case Piece.BKNIGHT: return 4; + case Piece.WPAWN: case Piece.BPAWN: return 5; + default: return 6; + } + } + } + + @Override + protected int getYFromSq(int sq) { + if (sq >= 0) { + return Position.getY(sq); + } else { + int p = -2 - sq; + return Piece.isWhite(p) ? -1 : -2; + } + } + + @Override + protected int getSquare(int x, int y) { + if (y >= 0) { + return Position.getSquare(x, y); + } else { + int p = extraPieces(x, y); + return -p - 2; + } + } + + @Override + protected void drawExtraSquares(Canvas canvas) { + for (int x = 0; x < 8; x++) { + for (int y = -2; y < 0; y++) { + final int xCrd = getXCrd(x); + final int yCrd = getYCrd(y); + Paint paint = Position.darkSquare(x, y) ? darkPaint : brightPaint; + canvas.drawRect(xCrd, yCrd, xCrd+sqSize, yCrd+sqSize, paint); + int p = extraPieces(x, y); + drawPiece(canvas, xCrd, yCrd, p); + } + } + } + + @Override + public + Move mousePressed(int sq) { + if (sq == -1) + return null; + cursorVisible = false; + if (selectedSquare != -1) { + if (sq != selectedSquare) { + Move m = new Move(selectedSquare, sq, Piece.EMPTY); + setSelection(sq); + return m; + } + setSelection(-1); + } else { + setSelection(sq); + } + return null; + } + + @Override + protected int minValidY() { return -2; } + + @Override + protected int getXCrd(int x) { + return x0 + sqSize * (flipped ? 7 - x : x); + } + + @Override + protected int getYCrd(int y) { + if (y >= 0) { + return y0 + sqSize * (flipped ? y : 7 - y); + } else { + return y0 + gap + sqSize * (7 - y); + } + } + + @Override + protected int getXSq(int xCrd) { + int t = (xCrd - x0) / sqSize; return flipped ? 7 - t : t; + } + + @Override + protected int getYSq(int yCrd) { + int t = (yCrd - y0) / sqSize; + t = flipped ? t : 7 - t; + if ((t >= 0) && (t < 8)) + return t; + return 7 - (yCrd - y0 - gap) / sqSize; + } + + /** + * Compute the square corresponding to the coordinates of a mouse event. + * @param evt Details about the mouse event. + * @return The square corresponding to the mouse event, or -1 if outside board. + */ + @Override + public int eventToSquare(MotionEvent evt) { + int sq = super.eventToSquare(evt); + if (sq != -1) + return sq; + + int xCrd = (int)(evt.getX()); + int yCrd = (int)(evt.getY()); + + if (sqSize > 0) { + int x = getXSq(xCrd); + int y = getYSq(yCrd); + if ((x >= 0) && (x < 8) && (y >= -2) && (y < 0)) { + int p = extraPieces(x, y); + sq = -p - 2; + } + } + return sq; + } +} diff --git a/DroidFish/src/org/petero/droidfish/activities/EditBoard.java b/DroidFish/src/org/petero/droidfish/activities/EditBoard.java new file mode 100644 index 0000000..db712c2 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/activities/EditBoard.java @@ -0,0 +1,427 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.activities; + +import org.petero.droidfish.ChessBoard; +import org.petero.droidfish.R; +import org.petero.droidfish.gamelogic.ChessParseError; +import org.petero.droidfish.gamelogic.Move; +import org.petero.droidfish.gamelogic.Piece; +import org.petero.droidfish.gamelogic.Position; +import org.petero.droidfish.gamelogic.TextIO; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Configuration; +import android.os.Bundle; +import android.text.ClipboardManager; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnKeyListener; +import android.view.View.OnLongClickListener; +import android.view.View.OnTouchListener; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +public class EditBoard extends Activity { + private ChessBoardEdit cb; + private TextView status; + private Button okButton; + private Button cancelButton; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + initUI(); + + Intent i = getIntent(); + Position pos; + try { + pos = TextIO.readFEN(i.getAction()); + cb.setPosition(pos); + } catch (ChessParseError e) { + } + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + ChessBoardEdit oldCB = cb; + String statusStr = status.getText().toString(); + initUI(); + cb.cursorX = oldCB.cursorX; + cb.cursorY = oldCB.cursorY; + cb.cursorVisible = oldCB.cursorVisible; + cb.setPosition(oldCB.pos); + cb.setSelection(oldCB.selectedSquare); + status.setText(statusStr); + } + + private final void initUI() { + setContentView(R.layout.editboard); + cb = (ChessBoardEdit)findViewById(R.id.eb_chessboard); + status = (TextView)findViewById(R.id.eb_status); + okButton = (Button)findViewById(R.id.eb_ok); + cancelButton = (Button)findViewById(R.id.eb_cancel); + + okButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + sendBackResult(); + } + }); + cancelButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + setResult(RESULT_CANCELED); + finish(); + } + }); + + status.setFocusable(false); + cb.setFocusable(true); + cb.requestFocus(); + cb.setClickable(true); + cb.setOnTouchListener(new OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP) { + int sq = cb.eventToSquare(event); + Move m = cb.mousePressed(sq); + if (m != null) { + doMove(m); + } + return false; + } + return false; + } + }); + cb.setOnTrackballListener(new ChessBoard.OnTrackballListener() { + public void onTrackballEvent(MotionEvent event) { + Move m = cb.handleTrackballEvent(event); + if (m != null) { + doMove(m); + } + } + }); + cb.setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + showDialog(EDIT_DIALOG); + return true; + } + }); + } + + private void doMove(Move m) { + if (m.to < 0) { + if ((m.from < 0) || (cb.pos.getPiece(m.from) == Piece.EMPTY)) { + cb.setSelection(m.to); + return; + } + } + Position pos = new Position(cb.pos); + int piece = Piece.EMPTY; + if (m.from >= 0) { + piece = pos.getPiece(m.from); + } else { + piece = -(m.from + 2); + } + if (m.to >= 0) + pos.setPiece(m.to, piece); + if (m.from >= 0) + pos.setPiece(m.from, Piece.EMPTY); + cb.setPosition(pos); + cb.setSelection(-1); + checkValid(); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + sendBackResult(); + return true; + } + return super.onKeyDown(keyCode, event); + } + + private final void sendBackResult() { + if (checkValid()) { + setPosFields(); + String fen = TextIO.toFEN(cb.pos); + setResult(RESULT_OK, (new Intent()).setAction(fen)); + } else { + setResult(RESULT_CANCELED); + } + finish(); + } + + private final void setPosFields() { + setEPFile(getEPFile()); // To handle sideToMove change + TextIO.fixupEPSquare(cb.pos); + TextIO.removeBogusCastleFlags(cb.pos); + } + + private final int getEPFile() { + int epSquare = cb.pos.getEpSquare(); + if (epSquare < 0) return 8; + return Position.getX(epSquare); + } + + private final void setEPFile(int epFile) { + int epSquare = -1; + if ((epFile >= 0) && (epFile < 8)) { + int epRank = cb.pos.whiteMove ? 5 : 2; + epSquare = Position.getSquare(epFile, epRank); + } + cb.pos.setEpSquare(epSquare); + } + + /** Test if a position is valid. */ + private final boolean checkValid() { + try { + String fen = TextIO.toFEN(cb.pos); + TextIO.readFEN(fen); + status.setText(""); + return true; + } catch (ChessParseError e) { + status.setText(e.getMessage()); + } + return false; + } + + static final int EDIT_DIALOG = 0; + static final int SIDE_DIALOG = 1; + static final int CASTLE_DIALOG = 2; + static final int EP_DIALOG = 3; + static final int MOVCNT_DIALOG = 4; + + @Override + protected Dialog onCreateDialog(int id) { + switch (id) { + case EDIT_DIALOG: { + final CharSequence[] items = { + getString(R.string.side_to_move), + getString(R.string.clear_board), getString(R.string.initial_position), + getString(R.string.castling_flags), getString(R.string.en_passant_file), + getString(R.string.move_counters), + getString(R.string.copy_position), getString(R.string.paste_position) + }; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.edit_board); + builder.setItems(items, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + switch (item) { + case 0: // Edit side to move + showDialog(SIDE_DIALOG); + cb.setSelection(-1); + checkValid(); + break; + case 1: { // Clear board + Position pos = new Position(); + cb.setPosition(pos); + cb.setSelection(-1); + checkValid(); + break; + } + case 2: { // Set initial position + try { + Position pos = TextIO.readFEN(TextIO.startPosFEN); + cb.setPosition(pos); + cb.setSelection(-1); + checkValid(); + } catch (ChessParseError e) { + } + break; + } + case 3: // Edit castling flags + removeDialog(CASTLE_DIALOG); + showDialog(CASTLE_DIALOG); + cb.setSelection(-1); + checkValid(); + break; + case 4: // Edit en passant file + removeDialog(EP_DIALOG); + showDialog(EP_DIALOG); + cb.setSelection(-1); + checkValid(); + break; + case 5: // Edit move counters + removeDialog(MOVCNT_DIALOG); + showDialog(MOVCNT_DIALOG); + cb.setSelection(-1); + checkValid(); + break; + case 6: { // Copy position + setPosFields(); + String fen = TextIO.toFEN(cb.pos) + "\n"; + ClipboardManager clipboard = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE); + clipboard.setText(fen); + cb.setSelection(-1); + break; + } + case 7: { // Paste position + ClipboardManager clipboard = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE); + if (clipboard.hasText()) { + String fen = clipboard.getText().toString(); + try { + Position pos = TextIO.readFEN(fen); + cb.setPosition(pos); + } catch (ChessParseError e) { + if (e.pos != null) + cb.setPosition(e.pos); + Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_SHORT).show(); + } + cb.setSelection(-1); + checkValid(); + } + break; + } + } + } + }); + AlertDialog alert = builder.create(); + return alert; + } + case SIDE_DIALOG: { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage(R.string.select_side_to_move_first) + .setPositiveButton(R.string.white, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + cb.pos.setWhiteMove(true); + checkValid(); + dialog.cancel(); + } + }) + .setNegativeButton(R.string.black, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + cb.pos.setWhiteMove(false); + checkValid(); + dialog.cancel(); + } + }); + AlertDialog alert = builder.create(); + return alert; + } + case CASTLE_DIALOG: { + final CharSequence[] items = { + getString(R.string.white_king_castle), getString(R.string.white_queen_castle), + getString(R.string.black_king_castle), getString(R.string.black_queen_castle) + }; + boolean[] checkedItems = { + cb.pos.h1Castle(), cb.pos.a1Castle(), + cb.pos.h8Castle(), cb.pos.a8Castle() + }; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.castling_flags); + builder.setMultiChoiceItems(items, checkedItems, new DialogInterface.OnMultiChoiceClickListener() { + @Override + public void onClick(DialogInterface dialog, int which, boolean isChecked) { + Position pos = new Position(cb.pos); + boolean a1Castle = pos.a1Castle(); + boolean h1Castle = pos.h1Castle(); + boolean a8Castle = pos.a8Castle(); + boolean h8Castle = pos.h8Castle(); + switch (which) { + case 0: h1Castle = isChecked; break; + case 1: a1Castle = isChecked; break; + case 2: h8Castle = isChecked; break; + case 3: a8Castle = isChecked; break; + } + int castleMask = 0; + if (a1Castle) castleMask |= 1 << Position.A1_CASTLE; + if (h1Castle) castleMask |= 1 << Position.H1_CASTLE; + if (a8Castle) castleMask |= 1 << Position.A8_CASTLE; + if (h8Castle) castleMask |= 1 << Position.H8_CASTLE; + pos.setCastleMask(castleMask); + cb.setPosition(pos); + checkValid(); + } + }); + AlertDialog alert = builder.create(); + return alert; + } + case EP_DIALOG: { + final CharSequence[] items = { + "A", "B", "C", "D", "E", "F", "G", "H", getString(R.string.none) + }; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.select_en_passant_file); + builder.setSingleChoiceItems(items, getEPFile(), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + setEPFile(item); + } + }); + AlertDialog alert = builder.create(); + return alert; + } + case MOVCNT_DIALOG: { + final Dialog dialog = new Dialog(this); + dialog.setContentView(R.layout.edit_move_counters); + dialog.setTitle(R.string.edit_move_counters); + final EditText halfMoveClock = (EditText)dialog.findViewById(R.id.ed_cnt_halfmove); + final EditText fullMoveCounter = (EditText)dialog.findViewById(R.id.ed_cnt_fullmove); + Button ok = (Button)dialog.findViewById(R.id.ed_cnt_ok); + Button cancel = (Button)dialog.findViewById(R.id.ed_cnt_cancel); + halfMoveClock.setText(String.format("%d", cb.pos.halfMoveClock)); + fullMoveCounter.setText(String.format("%d", cb.pos.fullMoveCounter)); + final Runnable setCounters = new Runnable() { + public void run() { + try { + int halfClock = Integer.parseInt(halfMoveClock.getText().toString()); + int fullCount = Integer.parseInt(fullMoveCounter.getText().toString()); + cb.pos.halfMoveClock = halfClock; + cb.pos.fullMoveCounter = fullCount; + dialog.cancel(); + } catch (NumberFormatException nfe) { + Toast.makeText(getApplicationContext(), R.string.invalid_number_format, Toast.LENGTH_SHORT).show(); + } + } + }; + fullMoveCounter.setOnKeyListener(new OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { + setCounters.run(); + return true; + } + return false; + } + }); + ok.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + setCounters.run(); + } + }); + cancel.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + dialog.cancel(); + } + }); + return dialog; + } + } + return null; + } +} diff --git a/DroidFish/src/org/petero/droidfish/activities/EditComments.java b/DroidFish/src/org/petero/droidfish/activities/EditComments.java new file mode 100644 index 0000000..d321077 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/activities/EditComments.java @@ -0,0 +1,93 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.activities; + +import org.petero.droidfish.R; +import org.petero.droidfish.gamelogic.GameTree.Node; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.TextView; + +/** Activity to edit PGN comments. */ +public class EditComments extends Activity { + TextView preComment, postComment, nag; + private Button okButton; + private Button cancelButton; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.edit_comments); + preComment = (TextView)findViewById(R.id.ed_comments_pre); + TextView moveView = (TextView)findViewById(R.id.ed_comments_move); + nag = (TextView)findViewById(R.id.ed_comments_nag); + postComment = (TextView)findViewById(R.id.ed_comments_post); + okButton = (Button)findViewById(R.id.ed_comments_ok); + cancelButton = (Button)findViewById(R.id.ed_comments_cancel); + + postComment.requestFocus(); + + Intent data = getIntent(); + Bundle bundle = data.getBundleExtra("org.petero.droidfish.comments"); + String pre = bundle.getString("preComment"); + String post = bundle.getString("postComment"); + String move = bundle.getString("move"); + int nagVal = bundle.getInt("nag"); + preComment.setText(pre); + postComment.setText(post); + moveView.setText(move); + String nagStr = Node.nagStr(nagVal).trim(); + if ((nagStr.length() == 0) && (nagVal > 0)) + nagStr = String.format("%d", nagVal); + nag.setText(nagStr); + + okButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + sendBackResult(); + } + }); + cancelButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + setResult(RESULT_CANCELED); + finish(); + } + }); + } + + private final void sendBackResult() { + String pre = preComment.getText().toString().trim(); + String post = postComment.getText().toString().trim(); + int nagVal = Node.strToNag(nag.getText().toString()); + + Bundle bundle = new Bundle(); + bundle.putString("preComment", pre); + bundle.putString("postComment", post); + bundle.putInt("nag", nagVal); + Intent data = new Intent(); + data.putExtra("org.petero.droidfish.comments", bundle); + setResult(RESULT_OK, data); + finish(); + } +} diff --git a/DroidFish/src/org/petero/droidfish/activities/EditHeaders.java b/DroidFish/src/org/petero/droidfish/activities/EditHeaders.java new file mode 100644 index 0000000..cea446d --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/activities/EditHeaders.java @@ -0,0 +1,121 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.activities; + +import java.util.ArrayList; +import java.util.TreeMap; + +import org.petero.droidfish.R; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.TextView; + +/** Activity to edit PGN headers. */ +public class EditHeaders extends Activity { + + private TextView event; + private TextView site; + private TextView date; + private TextView round; + private TextView white; + private TextView black; + private Button okButton; + private Button cancelButton; + + private TreeMap headers = new TreeMap(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.edit_headers); + event = (TextView)findViewById(R.id.ed_header_event); + site = (TextView)findViewById(R.id.ed_header_site); + date = (TextView)findViewById(R.id.ed_header_date); + round = (TextView)findViewById(R.id.ed_header_round); + white = (TextView)findViewById(R.id.ed_header_white); + black = (TextView)findViewById(R.id.ed_header_black); + + okButton = (Button)findViewById(R.id.ed_header_ok); + cancelButton = (Button)findViewById(R.id.ed_header_cancel); + + Intent data = getIntent(); + Bundle bundle = data.getBundleExtra("org.petero.droidfish.headers"); + ArrayList tags = bundle.getStringArrayList("tags"); + ArrayList tagValues = bundle.getStringArrayList("tagValues"); + for (int i = 0; i < tags.size(); i++) + headers.put(tags.get(i), tagValues.get(i)); + event.setText(headers.get("Event")); + site .setText(headers.get("Site")); + date .setText(headers.get("Date")); + round.setText(headers.get("Round")); + white.setText(headers.get("White")); + black.setText(headers.get("Black")); + + okButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + sendBackResult(); + } + }); + cancelButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + setResult(RESULT_CANCELED); + finish(); + } + }); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + sendBackResult(); + return true; + } + return super.onKeyDown(keyCode, event); + } + + private final void sendBackResult() { + headers.put("Event", event.getText().toString().trim()); + headers.put("Site", site .getText().toString().trim()); + headers.put("Date", date .getText().toString().trim()); + headers.put("Round", round.getText().toString().trim()); + headers.put("White", white.getText().toString().trim()); + headers.put("Black", black.getText().toString().trim()); + + Bundle bundle = new Bundle(); + ArrayList tags = new ArrayList(); + ArrayList values = new ArrayList(); + for (String k : headers.keySet()) { + tags.add(k); + values.add(headers.get(k)); + } + bundle.putStringArrayList("tags", tags); + bundle.putStringArrayList("tagValues", values); + Intent data = new Intent(); + data.putExtra("org.petero.droidfish.headers", bundle); + setResult(RESULT_OK, data); + finish(); + } +} diff --git a/DroidFish/src/org/petero/droidfish/activities/EditPGN.java b/DroidFish/src/org/petero/droidfish/activities/EditPGN.java new file mode 100644 index 0000000..2900dd2 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/activities/EditPGN.java @@ -0,0 +1,433 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.activities; + +import java.io.File; +import java.util.ArrayList; + +import org.petero.droidfish.R; +import org.petero.droidfish.activities.PGNFile.GameInfo; +import org.petero.droidfish.activities.PGNFile.GameInfoResult; +import org.petero.droidfish.gamelogic.Pair; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ListActivity; +import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.DialogInterface.OnCancelListener; +import android.content.SharedPreferences.Editor; +import android.content.res.Configuration; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.Toast; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemLongClickListener; + +public class EditPGN extends ListActivity { + static ArrayList gamesInFile = new ArrayList(); + static boolean cacheValid = false; + PGNFile pgnFile; + ProgressDialog progress; + GameInfo selectedGi = null; + ArrayAdapter aa = null; + EditText filterText = null; + + SharedPreferences settings; + int defaultItem = 0; + String lastSearchString = ""; + String lastFileName = ""; + long lastModTime = -1; + + Thread workThread = null; + + boolean loadGame; // True when loading game, false when saving + String pgnToSave; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + settings = PreferenceManager.getDefaultSharedPreferences(this); + + if (savedInstanceState != null) { + defaultItem = savedInstanceState.getInt("defaultItem"); + lastSearchString = savedInstanceState.getString("lastSearchString"); + if (lastSearchString == null) lastSearchString = ""; + lastFileName = savedInstanceState.getString("lastFileName"); + if (lastFileName == null) lastFileName = ""; + lastModTime = savedInstanceState.getLong("lastModTime"); + } else { + defaultItem = settings.getInt("defaultItem", 0); + lastSearchString = settings.getString("lastSearchString", ""); + lastFileName = settings.getString("lastFileName", ""); + lastModTime = settings.getLong("lastModTime", 0); + } + + Intent i = getIntent(); + String action = i.getAction(); + String fileName = i.getStringExtra("org.petero.droidfish.pathname"); + if (action.equals("org.petero.droidfish.loadFile")) { + pgnFile = new PGNFile(fileName); + loadGame = true; + showDialog(PROGRESS_DIALOG); + final EditPGN lpgn = this; + workThread = new Thread(new Runnable() { + public void run() { + if (!readFile()) + return; + runOnUiThread(new Runnable() { + public void run() { + lpgn.showList(); + } + }); + } + }); + workThread.start(); + } else if (action.equals("org.petero.droidfish.loadFileNextGame") || + action.equals("org.petero.droidfish.loadFilePrevGame")) { + pgnFile = new PGNFile(fileName); + loadGame = true; + boolean next = action.equals("org.petero.droidfish.loadFileNextGame"); + final int loadItem = defaultItem + (next ? 1 : -1); + if (loadItem < 0) { + Toast.makeText(getApplicationContext(), R.string.no_prev_game, + Toast.LENGTH_SHORT).show(); + setResult(RESULT_CANCELED); + finish(); + } else { + workThread = new Thread(new Runnable() { + public void run() { + if (!readFile()) + return; + runOnUiThread(new Runnable() { + public void run() { + if (loadItem >= gamesInFile.size()) { + Toast.makeText(getApplicationContext(), R.string.no_next_game, + Toast.LENGTH_SHORT).show(); + setResult(RESULT_CANCELED); + finish(); + } else { + defaultItem = loadItem; + sendBackResult(gamesInFile.get(loadItem)); + } + } + }); + } + }); + workThread.start(); + } + } else if (action.equals("org.petero.droidfish.saveFile")) { + loadGame = false; + pgnToSave = i.getStringExtra("org.petero.droidfish.pgn"); + boolean silent = i.getBooleanExtra("org.petero.droidfish.silent", false); + if (silent) { // Silently append to file + PGNFile pgnFile2 = new PGNFile(fileName); + pgnFile2.appendPGN(pgnToSave, null); + } else { + pgnFile = new PGNFile(fileName); + showDialog(PROGRESS_DIALOG); + final EditPGN lpgn = this; + workThread = new Thread(new Runnable() { + public void run() { + if (!readFile()) + return; + runOnUiThread(new Runnable() { + public void run() { + if (gamesInFile.size() == 0) { + pgnFile.appendPGN(pgnToSave, getApplicationContext()); + finish(); + } else { + lpgn.showList(); + } + } + }); + } + }); + workThread.start(); + } + } else { // Unsupported action + setResult(RESULT_CANCELED); + finish(); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt("defaultItem", defaultItem); + outState.putString("lastSearchString", lastSearchString); + outState.putString("lastFileName", lastFileName); + outState.putLong("lastModTime", lastModTime); + } + + @Override + protected void onPause() { + Editor editor = settings.edit(); + editor.putInt("defaultItem", defaultItem); + editor.putString("lastSearchString", lastSearchString); + editor.putString("lastFileName", lastFileName); + editor.putLong("lastModTime", lastModTime); + editor.commit(); + super.onPause(); + } + + @Override + protected void onDestroy() { + if (workThread != null) { + workThread.interrupt(); + try { + workThread.join(); + } catch (InterruptedException e) { + } + workThread = null; + } + super.onDestroy(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.edit_file_options_menu, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.item_delete_file: + removeDialog(DELETE_PGN_FILE_DIALOG); + showDialog(DELETE_PGN_FILE_DIALOG); + break; + } + return false; + } + + private final void showList() { + progress.dismiss(); + setContentView(R.layout.select_game); + aa = new ArrayAdapter(this, R.layout.select_game_list_item, gamesInFile); + setListAdapter(aa); + ListView lv = getListView(); + lv.setSelectionFromTop(defaultItem, 0); + lv.setFastScrollEnabled(true); + lv.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int pos, long id) { + selectedGi = aa.getItem(pos); + if (loadGame) { + defaultItem = pos; + sendBackResult(selectedGi); + } else { + removeDialog(SAVE_GAME_DIALOG); + showDialog(SAVE_GAME_DIALOG); + } + } + }); + lv.setOnItemLongClickListener(new OnItemLongClickListener() { + @Override + public boolean onItemLongClick(AdapterView parent, View view, int pos, long id) { + selectedGi = aa.getItem(pos); + if (!selectedGi.isNull()) { + removeDialog(DELETE_GAME_DIALOG); + showDialog(DELETE_GAME_DIALOG); + } + return true; + } + }); + +// lv.setTextFilterEnabled(true); + filterText = (EditText)findViewById(R.id.select_game_filter); + filterText.addTextChangedListener(new TextWatcher() { + @Override + public void afterTextChanged(Editable s) { } + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { } + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + aa.getFilter().filter(s); + lastSearchString = s.toString(); + } + }); + filterText.setText(lastSearchString); + lv.requestFocus(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + } + + final static int PROGRESS_DIALOG = 0; + final static int DELETE_GAME_DIALOG = 1; + final static int SAVE_GAME_DIALOG = 2; + final static int DELETE_PGN_FILE_DIALOG = 3; + + @Override + protected Dialog onCreateDialog(int id) { + switch (id) { + case PROGRESS_DIALOG: + progress = new ProgressDialog(this); + progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + progress.setTitle(R.string.reading_pgn_file); + progress.setMessage(getString(R.string.please_wait)); + progress.setOnCancelListener(new OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + Thread thr = workThread; + if (thr != null) + thr.interrupt(); + } + }); + return progress; + case DELETE_GAME_DIALOG: { + final GameInfo gi = selectedGi; + selectedGi = null; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Delete game?"); + String msg = gi.toString(); + builder.setMessage(msg); + builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + deleteGame(gi); + dialog.cancel(); + } + }); + builder.setNegativeButton("No", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + AlertDialog alert = builder.create(); + return alert; + } + case SAVE_GAME_DIALOG: { + final GameInfo gi = selectedGi; + selectedGi = null; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Save game?"); + final CharSequence[] items = { "Before Selected", "After Selected", "Replace Selected" }; + builder.setItems(items, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + GameInfo giToReplace; + switch (item) { + case 0: giToReplace = new GameInfo().setNull(gi.startPos); break; + case 1: giToReplace = new GameInfo().setNull(gi.endPos); break; + case 2: giToReplace = gi; break; + default: + finish(); return; + } + pgnFile.replacePGN(pgnToSave, giToReplace, getApplicationContext()); + finish(); + } + }); + AlertDialog alert = builder.create(); + return alert; + } + case DELETE_PGN_FILE_DIALOG: { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Delete file?"); + String name = new File(pgnFile.getName()).getName(); + String msg = String.format("Delete file %s?", name); + builder.setMessage(msg); + builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + pgnFile.delete(); + finish(); + } + }); + builder.setNegativeButton("No", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + AlertDialog alert = builder.create(); + return alert; + } + default: + return null; + } + } + + private final boolean readFile() { + String fileName = pgnFile.getName(); + if (!fileName.equals(lastFileName)) + defaultItem = 0; + long modTime = new File(fileName).lastModified(); + if (cacheValid && (modTime == lastModTime) && fileName.equals(lastFileName)) + return true; + pgnFile = new PGNFile(fileName); + Pair> p = pgnFile.getGameInfo(this, progress); + if (p.first != GameInfoResult.OK) { + gamesInFile = new ArrayList(); + if (p.first == GameInfoResult.OUT_OF_MEMORY) { + runOnUiThread(new Runnable() { + public void run() { + Toast.makeText(getApplicationContext(), "File too large", Toast.LENGTH_SHORT).show(); + } + }); + } + setResult(RESULT_CANCELED); + finish(); + return false; + } + gamesInFile = p.second; + cacheValid = true; + lastModTime = modTime; + lastFileName = fileName; + return true; + } + + private final void sendBackResult(GameInfo gi) { + String pgn = pgnFile.readOneGame(gi); + if (pgn != null) { + setResult(RESULT_OK, (new Intent()).setAction(pgn)); + finish(); + } else { + setResult(RESULT_CANCELED); + finish(); + } + } + + private final void deleteGame(GameInfo gi) { + if (pgnFile.deleteGame(gi, getApplicationContext(), gamesInFile)) { + ListView lv = getListView(); + int pos = lv.pointToPosition(0,0); + aa = new ArrayAdapter(this, R.layout.select_game_list_item, gamesInFile); + setListAdapter(aa); + String s = filterText.getText().toString(); + aa.getFilter().filter(s); + lv.setSelection(pos); + // Update lastModTime, since current change has already been handled + String fileName = pgnFile.getName(); + long modTime = new File(fileName).lastModified(); + lastModTime = modTime; + } + } +} diff --git a/DroidFish/src/org/petero/droidfish/activities/EditPGNLoad.java b/DroidFish/src/org/petero/droidfish/activities/EditPGNLoad.java new file mode 100644 index 0000000..87f7c2d --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/activities/EditPGNLoad.java @@ -0,0 +1,24 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.activities; + +/** Load version of EditPGN class. */ +public class EditPGNLoad extends EditPGN { + +} diff --git a/DroidFish/src/org/petero/droidfish/activities/EditPGNSave.java b/DroidFish/src/org/petero/droidfish/activities/EditPGNSave.java new file mode 100644 index 0000000..957ee14 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/activities/EditPGNSave.java @@ -0,0 +1,24 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.activities; + +/** Save version of EditPGN class. */ +public class EditPGNSave extends EditPGN { + +} diff --git a/DroidFish/src/org/petero/droidfish/activities/LoadScid.java b/DroidFish/src/org/petero/droidfish/activities/LoadScid.java new file mode 100644 index 0000000..ea38957 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/activities/LoadScid.java @@ -0,0 +1,262 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.activities; + +import java.io.File; +import java.util.Vector; + +import org.petero.droidfish.R; + +import android.app.Dialog; +import android.app.ListActivity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.Toast; +import android.widget.AdapterView.OnItemClickListener; + +public class LoadScid extends ListActivity { + private static final class GameInfo { + String summary = ""; + int gameId = -1; + public String toString() { + return summary; + } + } + + private static Vector gamesInFile = new Vector(); + private static boolean cacheValid = false; + private String fileName; + private ProgressDialog progress; + + private SharedPreferences settings; + private int defaultItem = 0; + private String lastFileName = ""; + private long lastModTime = -1; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + settings = PreferenceManager.getDefaultSharedPreferences(this); + if (savedInstanceState != null) { + defaultItem = savedInstanceState.getInt("defaultScidItem"); + lastFileName = savedInstanceState.getString("lastScidFileName"); + if (lastFileName == null) lastFileName = ""; + lastModTime = savedInstanceState.getLong("lastScidModTime"); + } else { + defaultItem = settings.getInt("defaultScidItem", 0); + lastFileName = settings.getString("lastScidFileName", ""); + lastModTime = settings.getLong("lastScidModTime", 0); + } + + Intent i = getIntent(); + String action = i.getAction(); + fileName = i.getStringExtra("org.petero.droidfish.pathname"); + if (action.equals("org.petero.droidfish.loadScid")) { + showDialog(PROGRESS_DIALOG); + final LoadScid lpgn = this; + new Thread(new Runnable() { + public void run() { + readFile(); + runOnUiThread(new Runnable() { + public void run() { + lpgn.showList(); + } + }); + } + }).start(); + } else if (action.equals("org.petero.droidfish.loadScidNextGame") || + action.equals("org.petero.droidfish.loadScidPrevGame")) { + boolean next = action.equals("org.petero.droidfish.loadScidNextGame"); + final int loadItem = defaultItem + (next ? 1 : -1); + if (loadItem < 0) { + Toast.makeText(getApplicationContext(), R.string.no_prev_game, + Toast.LENGTH_SHORT).show(); + setResult(RESULT_CANCELED); + finish(); + } else { + new Thread(new Runnable() { + public void run() { + readFile(); + runOnUiThread(new Runnable() { + public void run() { + if (loadItem >= gamesInFile.size()) { + Toast.makeText(getApplicationContext(), R.string.no_next_game, + Toast.LENGTH_SHORT).show(); + setResult(RESULT_CANCELED); + finish(); + } else { + defaultItem = loadItem; + sendBackResult(gamesInFile.get(loadItem)); + } + } + }); + } + }).start(); + } + } else { // Unsupported action + setResult(RESULT_CANCELED); + finish(); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt("defaultScidItem", defaultItem); + outState.putString("lastScidFileName", lastFileName); + outState.putLong("lastScidModTime", lastModTime); + } + + @Override + protected void onPause() { + Editor editor = settings.edit(); + editor.putInt("defaultScidItem", defaultItem); + editor.putString("lastScidFileName", lastFileName); + editor.putLong("lastScidModTime", lastModTime); + editor.commit(); + super.onPause(); + } + + private final void showList() { + progress.dismiss(); + final ArrayAdapter aa = new ArrayAdapter(this, R.layout.select_game_list_item, gamesInFile); + setListAdapter(aa); + ListView lv = getListView(); + lv.setSelectionFromTop(defaultItem, 0); + lv.setFastScrollEnabled(true); + lv.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int pos, long id) { + defaultItem = pos; + sendBackResult(aa.getItem(pos)); + } + }); + } + + final static int PROGRESS_DIALOG = 0; + + @Override + protected Dialog onCreateDialog(int id) { + switch (id) { + case PROGRESS_DIALOG: + progress = new ProgressDialog(this); + progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + progress.setTitle(R.string.reading_scid_file); + progress.setMessage(getString(R.string.please_wait)); + progress.setCancelable(false); + return progress; + default: + return null; + } + } + + private final void readFile() { + if (!fileName.equals(lastFileName)) + defaultItem = 0; + long modTime = new File(fileName).lastModified(); + if (cacheValid && (modTime == lastModTime) && fileName.equals(lastFileName)) + return; + lastModTime = modTime; + lastFileName = fileName; + + gamesInFile.clear(); + Cursor cursor = getListCursor(); + if (cursor != null) { + int noGames = cursor.getCount(); + gamesInFile.ensureCapacity(noGames); + int percent = -1; + if (cursor.moveToFirst()) { + addGameInfo(cursor); + int gameNo = 1; + while (cursor.moveToNext()) { + addGameInfo(cursor); + gameNo++; + final int newPercent = (int)(gameNo * 100 / noGames); + if (newPercent > percent) { + percent = newPercent; + if (progress != null) { + runOnUiThread(new Runnable() { + public void run() { + progress.setProgress(newPercent); + } + }); + } + } + } + } + } + cacheValid = true; + } + + private int idIdx; + private int summaryIdx; + + private Cursor getListCursor() { + String scidFileName = fileName.substring(0, fileName.indexOf(".")); + String[] proj = new String[]{"_id", "summary"}; + Cursor cursor = managedQuery(Uri.parse("content://org.scid.database.scidprovider/games"), + proj, scidFileName, null, null); + idIdx = cursor.getColumnIndex("_id"); + summaryIdx = cursor.getColumnIndex("summary"); + return cursor; + } + + private Cursor getOneGameCursor(int gameId) { + String scidFileName = fileName.substring(0, fileName.indexOf(".")); + String[] proj = new String[]{"pgn"}; + String uri = String.format("content://org.scid.database.scidprovider/games/%d", gameId); + Cursor cursor = managedQuery(Uri.parse(uri), + proj, scidFileName, null, null); + return cursor; + } + + private void addGameInfo(Cursor cursor) { + GameInfo gi = new GameInfo(); + gi.gameId = cursor.getInt(idIdx); + gi.summary = cursor.getString(summaryIdx); + gamesInFile.add(gi); + } + + private final void sendBackResult(GameInfo gi) { + if (gi.gameId >= 0) { + Cursor cursor = getOneGameCursor(gi.gameId); + if (cursor != null && cursor.moveToFirst()) { + String pgn = cursor.getString(cursor.getColumnIndex("pgn")); + if (pgn != null && pgn.length() > 0) { + setResult(RESULT_OK, (new Intent()).setAction(pgn)); + finish(); + return; + } + } + } + setResult(RESULT_CANCELED); + finish(); + } +} diff --git a/DroidFish/src/org/petero/droidfish/activities/PGNFile.java b/DroidFish/src/org/petero/droidfish/activities/PGNFile.java new file mode 100644 index 0000000..0895a94 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/activities/PGNFile.java @@ -0,0 +1,392 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.activities; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.ArrayList; + +import org.petero.droidfish.gamelogic.Pair; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Context; +import android.widget.Toast; + +public class PGNFile { + private final File fileName; + + public PGNFile(String fileName) { + this.fileName = new File(fileName); + } + + public final String getName() { + return fileName.getAbsolutePath(); + } + + static final class GameInfo { + String info = ""; + long startPos; + long endPos; + + final GameInfo setNull(long currPos) { + info = null; + startPos = currPos; + endPos = currPos; + return this; + } + + final boolean isNull() { return info == null; } + + public String toString() { + if (info == null) + return "--"; + return info; + } + } + + + static private final class BufferedRandomAccessFileReader { + RandomAccessFile f; + byte[] buffer = new byte[8192]; + long bufStartFilePos = 0; + int bufLen = 0; + int bufPos = 0; + + BufferedRandomAccessFileReader(String fileName) throws FileNotFoundException { + f = new RandomAccessFile(fileName, "r"); + } + final long length() throws IOException { + return f.length(); + } + final long getFilePointer() throws IOException { + return bufStartFilePos + bufPos; + } + final void close() throws IOException { + f.close(); + } + + private final static int EOF = -1024; + + final String readLine() throws IOException { + // First handle the common case where the next line is entirely + // contained in the buffer + for (int i = bufPos; i < bufLen; i++) { + byte b = buffer[i]; + if ((b == '\n') || (b == '\r')) { + String line = new String(buffer, bufPos, i - bufPos); + for ( ; i < bufLen; i++) { + b = buffer[i]; + if ((b != '\n') && (b != '\r')) { + bufPos = i; + return line; + } + } + break; + } + } + + // Generic case + byte[] lineBuf = new byte[8192]; + int lineLen = 0; + int b; + while (true) { + b = getByte(); + if (b == '\n' || b == '\r' || b == EOF) + break; + lineBuf[lineLen++] = (byte)b; + if (lineLen >= lineBuf.length) + break; + } + while (true) { + b = getByte(); + if ((b != '\n') && (b != '\r')) { + if (b != EOF) + bufPos--; + break; + } + } + if ((b == EOF) && (lineLen == 0)) + return null; + else + return new String(lineBuf, 0, lineLen); + } + + private final int getByte() throws IOException { + if (bufPos >= bufLen) { + bufStartFilePos = f.getFilePointer(); + bufLen = f.read(buffer); + bufPos = 0; + if (bufLen <= 0) + return EOF; + } + return buffer[bufPos++]; + } + } + + private final static class HeaderInfo { + String event = ""; + String site = ""; + String date = ""; + String round = ""; + String white = ""; + String black = ""; + String result = ""; + + public String toString() { + StringBuilder info = new StringBuilder(128); + info.append(white); + info.append(" - "); + info.append(black); + if (date.length() > 0) { + info.append(' '); + info.append(date); + } + if (round.length() > 0) { + info.append(' '); + info.append(round); + } + if (event.length() > 0) { + info.append(' '); + info.append(event); + } + if (site.length() > 0) { + info.append(' '); + info.append(site); + } + info.append(' '); + info.append(result); + return info.toString(); + } + } + + public static enum GameInfoResult { + OK, + CANCEL, + OUT_OF_MEMORY; + } + + /** Return info about all PGN games in a file. */ + public final Pair> getGameInfo(Activity activity, + final ProgressDialog progress) { + ArrayList gamesInFile = new ArrayList(); + try { + int percent = -1; + gamesInFile.clear(); + BufferedRandomAccessFileReader f = new BufferedRandomAccessFileReader(fileName.getAbsolutePath()); + long fileLen = f.length(); + GameInfo gi = null; + HeaderInfo hi = null; + boolean inHeader = false; + long filePos = 0; + while (true) { + filePos = f.getFilePointer(); + String line = f.readLine(); + if (line == null) + break; // EOF + int len = line.length(); + if (len == 0) + continue; + boolean isHeader = line.charAt(0) == '['; + if (isHeader) { + if (!line.contains("\"")) // Try to avoid some false positives + isHeader = false; + } + if (isHeader) { + if (!inHeader) { // Start of game + inHeader = true; + if (gi != null) { + gi.endPos = filePos; + gi.info = hi.toString(); + gamesInFile.add(gi); + final int newPercent = (int)(filePos * 100 / fileLen); + if (newPercent > percent) { + percent = newPercent; + if (progress != null) { + activity.runOnUiThread(new Runnable() { + public void run() { + progress.setProgress(newPercent); + } + }); + } + } + if (Thread.currentThread().isInterrupted()) + return new Pair>(GameInfoResult.CANCEL, null); + } + gi = new GameInfo(); + gi.startPos = filePos; + gi.endPos = -1; + hi = new HeaderInfo(); + } + if (line.startsWith("[Event ")) { + hi.event = line.substring(8, len - 2); + if (hi.event.equals("?")) hi.event = ""; + } else if (line.startsWith("[Site ")) { + hi.site = line.substring(7, len - 2); + if (hi.site.equals("?")) hi.site = ""; + } else if (line.startsWith("[Date ")) { + hi.date = line.substring(7, len - 2); + if (hi.date.equals("?")) hi.date = ""; + } else if (line.startsWith("[Round ")) { + hi.round = line.substring(8, len - 2); + if (hi.round.equals("?")) hi.round = ""; + } else if (line.startsWith("[White ")) { + hi.white = line.substring(8, len - 2); + } else if (line.startsWith("[Black ")) { + hi.black = line.substring(8, len - 2); + } else if (line.startsWith("[Result ")) { + hi.result = line.substring(9, len - 2); + if (hi.result.equals("1-0")) hi.result = "1-0"; + else if (hi.result.equals("0-1")) hi.result = "0-1"; + else if ((hi.result.equals("1/2-1/2")) || (hi.result.equals("1/2"))) hi.result = "1/2-1/2"; + else hi.result = "*"; + } + } else { + inHeader = false; + } + } + if (gi != null) { + gi.endPos = filePos; + gi.info = hi.toString(); + gamesInFile.add(gi); + } + f.close(); + } catch (IOException e) { + } catch (OutOfMemoryError e) { + gamesInFile.clear(); + gamesInFile = null; + return new Pair>(GameInfoResult.OUT_OF_MEMORY, null); + } + + return new Pair>(GameInfoResult.OK, gamesInFile); + } + + private final void mkDirs() { + File dirFile = fileName.getParentFile(); + dirFile.mkdirs(); + } + + /** Read one game defined by gi. Return null on failure. */ + final String readOneGame(GameInfo gi) { + try { + RandomAccessFile f = new RandomAccessFile(fileName, "r"); + byte[] pgnData = new byte[(int) (gi.endPos - gi.startPos)]; + f.seek(gi.startPos); + f.readFully(pgnData); + f.close(); + return new String(pgnData); + } catch (IOException e) { + } + return null; + } + + /** Append PGN to the end of this PGN file. */ + public final void appendPGN(String pgn, Context context) { + try { + mkDirs(); + FileWriter fw = new FileWriter(fileName, true); + fw.write(pgn); + fw.close(); + Toast.makeText(context, "Game saved", Toast.LENGTH_SHORT).show(); + } catch (IOException e) { + if (context != null) { + String msg = "Failed to save game"; + Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); + } + } + } + + final boolean deleteGame(GameInfo gi, Context context, ArrayList gamesInFile) { + try { + File tmpFile = new File(fileName + ".tmp_delete"); + RandomAccessFile fileReader = new RandomAccessFile(fileName, "r"); + RandomAccessFile fileWriter = new RandomAccessFile(tmpFile, "rw"); + copyData(fileReader, fileWriter, gi.startPos); + fileReader.seek(gi.endPos); + copyData(fileReader, fileWriter, fileReader.length() - gi.endPos); + fileReader.close(); + fileWriter.close(); + tmpFile.renameTo(fileName); + + // Update gamesInFile + if (gamesInFile != null) { + gamesInFile.remove(gi); + final int nGames = gamesInFile.size(); + final long delta = gi.endPos - gi.startPos; + for (int i = 0; i < nGames; i++) { + GameInfo tmpGi = gamesInFile.get(i); + if (tmpGi.startPos > gi.startPos) { + tmpGi.startPos -= delta; + tmpGi.endPos -= delta; + } + } + } + return true; + } catch (IOException e) { + if (context != null) { + String msg = "Failed to delete game"; + Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); + } + } + return false; + } + + final boolean replacePGN(String pgnToSave, GameInfo gi, Context context) { + try { + File tmpFile = new File(fileName + ".tmp_delete"); + RandomAccessFile fileReader = new RandomAccessFile(fileName, "r"); + RandomAccessFile fileWriter = new RandomAccessFile(tmpFile, "rw"); + copyData(fileReader, fileWriter, gi.startPos); + fileWriter.write(pgnToSave.getBytes()); + fileReader.seek(gi.endPos); + copyData(fileReader, fileWriter, fileReader.length() - gi.endPos); + fileReader.close(); + fileWriter.close(); + tmpFile.renameTo(fileName); + Toast.makeText(context, "Game saved", Toast.LENGTH_SHORT).show(); + return true; + } catch (IOException e) { + if (context != null) { + String msg = "Failed to save game"; + Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); + } + } + return false; + } + + private final static void copyData(RandomAccessFile fileReader, + RandomAccessFile fileWriter, + long nBytes) throws IOException { + byte[] buffer = new byte[8192]; + while (nBytes > 0) { + int nRead = fileReader.read(buffer, 0, Math.min(buffer.length, (int)nBytes)); + if (nRead > 0) { + fileWriter.write(buffer, 0, nRead); + nBytes -= nRead; + } + } + } + + final boolean delete() { + return fileName.delete(); + } +} diff --git a/DroidFish/src/org/petero/droidfish/activities/Preferences.java b/DroidFish/src/org/petero/droidfish/activities/Preferences.java new file mode 100644 index 0000000..22f5155 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/activities/Preferences.java @@ -0,0 +1,33 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.activities; + +import org.petero.droidfish.R; + +import android.os.Bundle; +import android.preference.PreferenceActivity; + +public class Preferences extends PreferenceActivity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.preferences); + } +} diff --git a/DroidFish/src/org/petero/droidfish/engine/CtgBook.java b/DroidFish/src/org/petero/droidfish/engine/CtgBook.java new file mode 100644 index 0000000..58fac85 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/engine/CtgBook.java @@ -0,0 +1,740 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.engine; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.ArrayList; +import java.util.List; + +import org.petero.droidfish.BookOptions; +import org.petero.droidfish.engine.DroidBook.BookEntry; +import org.petero.droidfish.gamelogic.Move; +import org.petero.droidfish.gamelogic.Piece; +import org.petero.droidfish.gamelogic.Position; +import org.petero.droidfish.gamelogic.TextIO; +import org.petero.droidfish.gamelogic.UndoInfo; + +public class CtgBook implements IOpeningBook { + private BookOptions options = new BookOptions(); + private File ctgFile; + private File ctbFile; + private File ctoFile; + + static boolean canHandle(BookOptions options) { + String filename = options.filename; + return (filename.endsWith(".ctg") || + filename.endsWith(".ctb") || + filename.endsWith(".cto")); + } + + @Override + public boolean enabled() { + return ctgFile.canRead() && + ctbFile.canRead() && + ctoFile.canRead(); + } + + @Override + public void setOptions(BookOptions options) { + this.options = new BookOptions(options); + String fileName = options.filename; + int len = fileName.length(); + ctgFile = new File(fileName.substring(0, len-1) + "g"); + ctbFile = new File(fileName.substring(0, len-1) + "b"); + ctoFile = new File(fileName.substring(0, len-1) + "o"); + } + + @Override + public List getBookEntries(Position pos) { + RandomAccessFile ctgF = null; + RandomAccessFile ctbF = null; + RandomAccessFile ctoF = null; + try { + ctgF = new RandomAccessFile(ctgFile, "r"); + ctbF = new RandomAccessFile(ctbFile, "r"); + ctoF = new RandomAccessFile(ctoFile, "r"); + + CtbFile ctb = new CtbFile(ctbF); + CtoFile cto = new CtoFile(ctoF); + CtgFile ctg = new CtgFile(ctgF, ctb, cto); + + List ret = null; + PositionData pd = ctg.getPositionData(pos); + if (pd != null) { + boolean mirrorColor = pd.mirrorColor; + boolean mirrorLeftRight = pd.mirrorLeftRight; + ret = pd.getBookMoves(); + UndoInfo ui = new UndoInfo(); + for (BookEntry be : ret) { + pd.pos.makeMove(be.move, ui); + PositionData movePd = ctg.getPositionData(pd.pos); + pd.pos.unMakeMove(be.move, ui); + double weight = be.weight; + if (movePd == null) { +// System.out.printf("%s : no pos\n", TextIO.moveToUCIString(be.move)); + weight = 0; + } else { + int recom = movePd.getRecommendation(); + if ((recom >= 64) && (recom < 128)) { + if (options.tournamentMode) + weight = 0; + } else if (recom >= 128) { + if (options.preferMainLines) + weight *= 10; + } + int score = movePd.getOpponentScore(); +// double w0 = weight; + weight = weight * score; +// System.out.printf("%s : w0:%.3f rec:%d score:%d %.3f\n", TextIO.moveToUCIString(be.move), +// w0, recom, score, weight); + } + be.weight = weight; + } + if (mirrorLeftRight) { + for (int i = 0; i < ret.size(); i++) + ret.get(i).move = mirrorMoveLeftRight(ret.get(i).move); + } + if (mirrorColor) { + for (int i = 0; i < ret.size(); i++) + ret.get(i).move = mirrorMoveColor(ret.get(i).move); + } + } + return ret; + } catch (IOException e) { + return null; + } finally { + if (ctgF != null) try { ctgF.close(); } catch (IOException e) { } + if (ctbF != null) try { ctbF.close(); } catch (IOException e) { } + if (ctoF != null) try { ctoF.close(); } catch (IOException e) { } + } + } + + /** Read len bytes from offs in file f. */ + private final static byte[] readBytes(RandomAccessFile f, int offs, int len) throws IOException { + byte[] ret = new byte[len]; + f.seek(offs); + f.readFully(ret); + return ret; + } + + /** Convert len bytes starting at offs in buf to an integer. */ + private final static int extractInt(byte[] buf, int offs, int len) { + int ret = 0; + for (int i = 0; i < len; i++) { + int b = buf[offs + i]; + if (b < 0) b += 256; + ret = (ret << 8) + b; + } + return ret; + } + + private final static class CtbFile { + int lowerPageBound; + int upperPageBound; + CtbFile(RandomAccessFile f) throws IOException { + byte[] buf = readBytes(f, 4, 8); + lowerPageBound = extractInt(buf, 0, 4); + upperPageBound = extractInt(buf, 4, 4); + } + } + + private final static class BitVector { + private List buf = new ArrayList(); + private int length = 0; + + void addBit(boolean value) { + int byteIdx = length / 8; + int bitIdx = 7 - (length & 7); + while (buf.size() <= byteIdx) + buf.add(Byte.valueOf((byte)0)); + if (value) + buf.set(byteIdx, (byte)(buf.get(byteIdx) | (1 << bitIdx))); + length++; + } + + void addBits(int mask, int numBits) { + for (int i = 0; i < numBits; i++) { + int b = numBits - 1 - i; + addBit((mask & (1 << b)) != 0); + } + } + + /** Number of bits left in current byte. */ + int padBits() { + int bitIdx = length & 7; + return (bitIdx == 0) ? 0 : 8 - bitIdx; + } + + final byte[] toByteArray() { + byte[] ret = new byte[buf.size()]; + for (int i = 0; i < buf.size(); i++) + ret[i] = buf.get(i); + return ret; + } + } + + /** Converts a position to a byte array. */ + private final static byte[] positionToByteArray(Position pos) { + BitVector bits = new BitVector(); + bits.addBits(0, 8); // Header byte + for (int x = 0; x < 8; x++) { + for (int y = 0; y < 8; y++) { + int p = pos.getPiece(Position.getSquare(x, y)); + switch (p) { + case Piece.EMPTY: bits.addBits(0x00, 1); break; + case Piece.WKING: bits.addBits(0x20, 6); break; + case Piece.WQUEEN: bits.addBits(0x22, 6); break; + case Piece.WROOK: bits.addBits(0x16, 5); break; + case Piece.WBISHOP: bits.addBits(0x14, 5); break; + case Piece.WKNIGHT: bits.addBits(0x12, 5); break; + case Piece.WPAWN: bits.addBits(0x06, 3); break; + case Piece.BKING: bits.addBits(0x21, 6); break; + case Piece.BQUEEN: bits.addBits(0x23, 6); break; + case Piece.BROOK: bits.addBits(0x17, 5); break; + case Piece.BBISHOP: bits.addBits(0x15, 5); break; + case Piece.BKNIGHT: bits.addBits(0x13, 5); break; + case Piece.BPAWN: bits.addBits(0x07, 3); break; + } + } + } + + TextIO.fixupEPSquare(pos); + boolean ep = pos.getEpSquare() != -1; + boolean cs = pos.getCastleMask() != 0; + if (!ep && !cs) + bits.addBit(false); // At least one pad bit + + int specialBits = (ep ? 3 : 0) + (cs ? 4 : 0); + while (bits.padBits() != specialBits) + bits.addBit(false); + + if (ep) + bits.addBits(Position.getX(pos.getEpSquare()), 3); + if (cs) { + bits.addBit(pos.h8Castle()); + bits.addBit(pos.a8Castle()); + bits.addBit(pos.h1Castle()); + bits.addBit(pos.a1Castle()); + } + + if ((bits.length & 7) != 0) throw new RuntimeException(); + int header = bits.length / 8; + if (ep) header |= 0x20; + if (cs) header |= 0x40; + + byte[] buf = bits.toByteArray(); + buf[0] = (byte)header; + return buf; + } + + private final static class CtoFile { + RandomAccessFile f; + CtoFile(RandomAccessFile f) { + this.f = f; + } + + final static ArrayList getHashIndices(byte[] encodedPos, CtbFile ctb) throws IOException { + ArrayList ret = new ArrayList(); + int hash = getHashValue(encodedPos); + for (int n = 0; n < 0x7fffffff; n = 2*n + 1) { + int c = (hash & n) + n; + if (c < ctb.lowerPageBound) + continue; + ret.add(c); + if (c >= ctb.upperPageBound) + break; + } + return ret; + } + + final int getPage(int hashIndex) throws IOException { + byte[] buf = readBytes(f, 16 + 4 * hashIndex, 4); + int page = extractInt(buf, 0, 4); + return page; + } + + final private static int tbl[] = { + 0x3100d2bf, 0x3118e3de, 0x34ab1372, 0x2807a847, + 0x1633f566, 0x2143b359, 0x26d56488, 0x3b9e6f59, + 0x37755656, 0x3089ca7b, 0x18e92d85, 0x0cd0e9d8, + 0x1a9e3b54, 0x3eaa902f, 0x0d9bfaae, 0x2f32b45b, + 0x31ed6102, 0x3d3c8398, 0x146660e3, 0x0f8d4b76, + 0x02c77a5f, 0x146c8799, 0x1c47f51f, 0x249f8f36, + 0x24772043, 0x1fbc1e4d, 0x1e86b3fa, 0x37df36a6, + 0x16ed30e4, 0x02c3148e, 0x216e5929, 0x0636b34e, + 0x317f9f56, 0x15f09d70, 0x131026fb, 0x38c784b1, + 0x29ac3305, 0x2b485dc5, 0x3c049ddc, 0x35a9fbcd, + 0x31d5373b, 0x2b246799, 0x0a2923d3, 0x08a96e9d, + 0x30031a9f, 0x08f525b5, 0x33611c06, 0x2409db98, + 0x0ca4feb2, 0x1000b71e, 0x30566e32, 0x39447d31, + 0x194e3752, 0x08233a95, 0x0f38fe36, 0x29c7cd57, + 0x0f7b3a39, 0x328e8a16, 0x1e7d1388, 0x0fba78f5, + 0x274c7e7c, 0x1e8be65c, 0x2fa0b0bb, 0x1eb6c371 + }; + + final private static int getHashValue(byte[] encodedPos) { + int hash = 0; + int tmp = 0; + for (int i = 0; i < encodedPos.length; i++) { + int ch = encodedPos[i]; + tmp += ((0x0f - (ch & 0x0f)) << 2) + 1; + hash += tbl[tmp & 0x3f]; + tmp += ((0xf0 - (ch & 0xf0)) >> 2) + 1; + hash += tbl[tmp & 0x3f]; + } + return hash; + } + } + + private final static class CtgFile { + private RandomAccessFile f; + private CtbFile ctb; + private CtoFile cto; + CtgFile(RandomAccessFile f, CtbFile ctb, CtoFile cto) { + this.f = f; + this.ctb = ctb; + this.cto = cto; + } + + final PositionData getPositionData(Position pos) throws IOException { + boolean mirrorColor = !pos.whiteMove; + boolean needCopy = true; + if (mirrorColor) { + pos = mirrorPosColor(pos); + needCopy = false; + } + + boolean mirrorLeftRight = false; + if ((pos.getCastleMask() == 0) && (Position.getX(pos.getKingSq(true)) < 4)) { + pos = mirrorPosLeftRight(pos); + mirrorLeftRight = true; + needCopy = false; + } + if (needCopy) + pos = new Position(pos); + + byte[] encodedPos = positionToByteArray(pos); + ArrayList hashIdxList = CtoFile.getHashIndices(encodedPos, ctb); + + PositionData pd = null; + for (int i = 0; i < hashIdxList.size(); i++) { + int page = cto.getPage(hashIdxList.get(i)); + if (page < 0) + continue; + pd = findInPage(page, encodedPos); + if (pd != null) { + pd.pos = pos; + pd.mirrorColor = mirrorColor; + pd.mirrorLeftRight = mirrorLeftRight; + break; + } + } + return pd; + } + + private final PositionData findInPage(int page, byte[] encodedPos) throws IOException { + byte[] pageBuf = readBytes(f, (page+1)*4096, 4096); + try { + int nPos = extractInt(pageBuf, 0, 2); + int nBytes = extractInt(pageBuf, 2, 2); + for (int i = nBytes; i < 4096; i++) + pageBuf[i] = 0; // Don't depend on trailing garbage + int offs = 4; + for (int p = 0; p < nPos; p++) { + boolean match = true; + for (int i = 0; i < encodedPos.length; i++) + if (encodedPos[i] != pageBuf[offs+i]) { + match = false; + break; + } + if (match) + return new PositionData(pageBuf, offs); + + int posLen = pageBuf[offs] & 0x1f; + offs += posLen; + int moveBytes = extractInt(pageBuf, offs, 1); + offs += moveBytes; + offs += PositionData.posInfoBytes; + } + return null; + } catch (ArrayIndexOutOfBoundsException ex) { + return null; // Ignore corrupt book file entries + } + } + } + + private final static class PositionData { + private byte[] buf; + private int posLen; + private int moveBytes; + final static int posInfoBytes = 3*4 + 4 + (3+4)*2 + 1 + 1 + 1; + + Position pos; + boolean mirrorColor = false; + boolean mirrorLeftRight = false; + + PositionData(byte[] pageBuf, int offs) { + posLen = pageBuf[offs] & 0x1f; + moveBytes = extractInt(pageBuf, offs + posLen, 1); + int bufLen = posLen + moveBytes + posInfoBytes; + buf = new byte[bufLen]; + for (int i = 0; i < bufLen; i++) + buf[i] = pageBuf[offs + i]; + } + + final ArrayList getBookMoves() { + ArrayList entries = new ArrayList(); + int nMoves = (moveBytes - 1) / 2; + for (int mi = 0; mi < nMoves; mi++) { + int move = extractInt(buf, posLen + 1 + mi * 2, 1); + int flags = extractInt(buf, posLen + 1 + mi * 2 + 1, 1); + Move m = decodeMove(pos, move); + if (m == null) + continue; +// System.out.printf("mi:%d m:%s flags:%d\n", mi, TextIO.moveToUCIString(m), flags); + BookEntry ent = new BookEntry(m); + switch (flags) { + default: + case 0x00: ent.weight = 1; break; // No annotation + case 0x01: ent.weight = 8; break; // ! + case 0x02: ent.weight = 0; break; // ? + case 0x03: ent.weight = 32; break; // !! + case 0x04: ent.weight = 0; break; // ?? + case 0x05: ent.weight = 0.5; break; // !? + case 0x06: ent.weight = 0.125; break; // ?! + case 0x08: ent.weight = 1000000; break; // Only move + } + entries.add(ent); + } + return entries; + } + + /** Return (wins + draws/2) / games. */ + final int getOpponentScore() { + int statStart = posLen + moveBytes; +// int wins = extractInt(buf, statStart + 3, 3); + int loss = extractInt(buf, statStart + 6, 3); + int draws = extractInt(buf, statStart + 9, 3); + return loss * 2 + draws; + } + + final int getRecommendation() { + int statStart = posLen + moveBytes; + int recom = extractInt(buf, statStart + 30, 1); + return recom; + } + + private static final class MoveInfo { + int piece; + int pieceNo; + int dx; + int dy; + } + + private final static MoveInfo MI(int piece, int pieceNo, int dx, int dy) { + MoveInfo mi = new MoveInfo(); + mi.piece = piece; + mi.pieceNo = pieceNo; + mi.dx = dx; + mi.dy = dy; + return mi; + } + + private final static MoveInfo[] moveInfo = new MoveInfo[256]; + static { + moveInfo[0x00] = MI(Piece.WPAWN , 4, +1, +1); + moveInfo[0x01] = MI(Piece.WKNIGHT, 1, -2, -1); + moveInfo[0x03] = MI(Piece.WQUEEN , 1, +2, +0); + moveInfo[0x04] = MI(Piece.WPAWN , 1, +0, +1); + moveInfo[0x05] = MI(Piece.WQUEEN , 0, +0, +1); + moveInfo[0x06] = MI(Piece.WPAWN , 3, -1, +1); + moveInfo[0x08] = MI(Piece.WQUEEN , 1, +4, +0); + moveInfo[0x09] = MI(Piece.WBISHOP, 1, +6, +6); + moveInfo[0x0a] = MI(Piece.WKING , 0, +0, -1); + moveInfo[0x0c] = MI(Piece.WPAWN , 0, -1, +1); + moveInfo[0x0d] = MI(Piece.WBISHOP, 0, +3, +3); + moveInfo[0x0e] = MI(Piece.WROOK , 1, +3, +0); + moveInfo[0x0f] = MI(Piece.WKNIGHT, 0, -2, -1); + moveInfo[0x12] = MI(Piece.WBISHOP, 0, +7, +7); + moveInfo[0x13] = MI(Piece.WKING , 0, +0, +1); + moveInfo[0x14] = MI(Piece.WPAWN , 7, +1, +1); + moveInfo[0x15] = MI(Piece.WBISHOP, 0, +5, +5); + moveInfo[0x18] = MI(Piece.WPAWN , 6, +0, +1); + moveInfo[0x1a] = MI(Piece.WQUEEN , 1, +0, +6); + moveInfo[0x1b] = MI(Piece.WBISHOP, 0, -1, +1); + moveInfo[0x1d] = MI(Piece.WBISHOP, 1, +7, +7); + moveInfo[0x21] = MI(Piece.WROOK , 1, +7, +0); + moveInfo[0x22] = MI(Piece.WBISHOP, 1, -2, +2); + moveInfo[0x23] = MI(Piece.WQUEEN , 1, +6, +6); + moveInfo[0x24] = MI(Piece.WPAWN , 7, -1, +1); + moveInfo[0x26] = MI(Piece.WBISHOP, 0, -7, +7); + moveInfo[0x27] = MI(Piece.WPAWN , 2, -1, +1); + moveInfo[0x28] = MI(Piece.WQUEEN , 0, +5, +5); + moveInfo[0x29] = MI(Piece.WQUEEN , 0, +6, +0); + moveInfo[0x2a] = MI(Piece.WKNIGHT, 1, +1, -2); + moveInfo[0x2d] = MI(Piece.WPAWN , 5, +1, +1); + moveInfo[0x2e] = MI(Piece.WBISHOP, 0, +1, +1); + moveInfo[0x2f] = MI(Piece.WQUEEN , 0, +1, +0); + moveInfo[0x30] = MI(Piece.WKNIGHT, 1, -1, -2); + moveInfo[0x31] = MI(Piece.WQUEEN , 0, +3, +0); + moveInfo[0x32] = MI(Piece.WBISHOP, 1, +5, +5); + moveInfo[0x34] = MI(Piece.WKNIGHT, 0, +1, +2); + moveInfo[0x36] = MI(Piece.WKNIGHT, 0, +2, +1); + moveInfo[0x37] = MI(Piece.WQUEEN , 0, +0, +4); + moveInfo[0x38] = MI(Piece.WQUEEN , 1, -4, +4); + moveInfo[0x39] = MI(Piece.WQUEEN , 0, +5, +0); + moveInfo[0x3a] = MI(Piece.WBISHOP, 0, +6, +6); + moveInfo[0x3b] = MI(Piece.WQUEEN , 1, -5, +5); + moveInfo[0x3c] = MI(Piece.WBISHOP, 0, -5, +5); + moveInfo[0x41] = MI(Piece.WQUEEN , 1, +5, +5); + moveInfo[0x42] = MI(Piece.WQUEEN , 0, -7, +7); + moveInfo[0x44] = MI(Piece.WKING , 0, +1, -1); + moveInfo[0x45] = MI(Piece.WQUEEN , 0, +3, +3); + moveInfo[0x4a] = MI(Piece.WPAWN , 7, +0, +2); + moveInfo[0x4b] = MI(Piece.WQUEEN , 0, -5, +5); + moveInfo[0x4c] = MI(Piece.WKNIGHT, 1, +1, +2); + moveInfo[0x4d] = MI(Piece.WQUEEN , 1, +0, +1); + moveInfo[0x50] = MI(Piece.WROOK , 0, +0, +6); + moveInfo[0x52] = MI(Piece.WROOK , 0, +6, +0); + moveInfo[0x54] = MI(Piece.WBISHOP, 1, -1, +1); + moveInfo[0x55] = MI(Piece.WPAWN , 2, +0, +1); + moveInfo[0x5c] = MI(Piece.WPAWN , 6, +1, +1); + moveInfo[0x5f] = MI(Piece.WPAWN , 4, +0, +2); + moveInfo[0x61] = MI(Piece.WQUEEN , 0, +6, +6); + moveInfo[0x62] = MI(Piece.WPAWN , 1, +0, +2); + moveInfo[0x63] = MI(Piece.WQUEEN , 1, -7, +7); + moveInfo[0x66] = MI(Piece.WBISHOP, 0, -3, +3); + moveInfo[0x67] = MI(Piece.WKING , 0, +1, +1); + moveInfo[0x69] = MI(Piece.WROOK , 1, +0, +7); + moveInfo[0x6a] = MI(Piece.WBISHOP, 0, +4, +4); + moveInfo[0x6b] = MI(Piece.WKING , 0, +2, +0); + moveInfo[0x6e] = MI(Piece.WROOK , 0, +5, +0); + moveInfo[0x6f] = MI(Piece.WQUEEN , 1, +7, +7); + moveInfo[0x72] = MI(Piece.WBISHOP, 1, -7, +7); + moveInfo[0x74] = MI(Piece.WQUEEN , 0, +2, +0); + moveInfo[0x79] = MI(Piece.WBISHOP, 1, -6, +6); + moveInfo[0x7a] = MI(Piece.WROOK , 0, +0, +3); + moveInfo[0x7b] = MI(Piece.WROOK , 1, +0, +6); + moveInfo[0x7c] = MI(Piece.WPAWN , 2, +1, +1); + moveInfo[0x7d] = MI(Piece.WROOK , 1, +0, +1); + moveInfo[0x7e] = MI(Piece.WQUEEN , 0, -3, +3); + moveInfo[0x7f] = MI(Piece.WROOK , 0, +1, +0); + moveInfo[0x80] = MI(Piece.WQUEEN , 0, -6, +6); + moveInfo[0x81] = MI(Piece.WROOK , 0, +0, +1); + moveInfo[0x82] = MI(Piece.WPAWN , 5, -1, +1); + moveInfo[0x85] = MI(Piece.WKNIGHT, 0, -1, +2); + moveInfo[0x86] = MI(Piece.WROOK , 0, +7, +0); + moveInfo[0x87] = MI(Piece.WROOK , 0, +0, +5); + moveInfo[0x8a] = MI(Piece.WKNIGHT, 0, +1, -2); + moveInfo[0x8b] = MI(Piece.WPAWN , 0, +1, +1); + moveInfo[0x8c] = MI(Piece.WKING , 0, -1, -1); + moveInfo[0x8e] = MI(Piece.WQUEEN , 1, -2, +2); + moveInfo[0x8f] = MI(Piece.WQUEEN , 0, +7, +0); + moveInfo[0x92] = MI(Piece.WQUEEN , 1, +1, +1); + moveInfo[0x94] = MI(Piece.WQUEEN , 0, +0, +3); + moveInfo[0x96] = MI(Piece.WPAWN , 1, +1, +1); + moveInfo[0x97] = MI(Piece.WKING , 0, -1, +0); + moveInfo[0x98] = MI(Piece.WROOK , 0, +3, +0); + moveInfo[0x99] = MI(Piece.WROOK , 0, +0, +4); + moveInfo[0x9a] = MI(Piece.WQUEEN , 0, +0, +6); + moveInfo[0x9b] = MI(Piece.WPAWN , 2, +0, +2); + moveInfo[0x9d] = MI(Piece.WQUEEN , 0, +0, +2); + moveInfo[0x9f] = MI(Piece.WBISHOP, 1, -4, +4); + moveInfo[0xa0] = MI(Piece.WQUEEN , 1, +0, +3); + moveInfo[0xa2] = MI(Piece.WQUEEN , 0, +2, +2); + moveInfo[0xa3] = MI(Piece.WPAWN , 7, +0, +1); + moveInfo[0xa5] = MI(Piece.WROOK , 1, +0, +5); + moveInfo[0xa9] = MI(Piece.WROOK , 1, +2, +0); + moveInfo[0xab] = MI(Piece.WQUEEN , 1, -6, +6); + moveInfo[0xad] = MI(Piece.WROOK , 1, +4, +0); + moveInfo[0xae] = MI(Piece.WQUEEN , 1, +3, +3); + moveInfo[0xb0] = MI(Piece.WQUEEN , 1, +0, +4); + moveInfo[0xb1] = MI(Piece.WPAWN , 5, +0, +2); + moveInfo[0xb2] = MI(Piece.WBISHOP, 0, -6, +6); + moveInfo[0xb5] = MI(Piece.WROOK , 1, +5, +0); + moveInfo[0xb7] = MI(Piece.WQUEEN , 0, +0, +5); + moveInfo[0xb9] = MI(Piece.WBISHOP, 1, +3, +3); + moveInfo[0xbb] = MI(Piece.WPAWN , 4, +0, +1); + moveInfo[0xbc] = MI(Piece.WQUEEN , 1, +5, +0); + moveInfo[0xbd] = MI(Piece.WQUEEN , 1, +0, +2); + moveInfo[0xbe] = MI(Piece.WKING , 0, +1, +0); + moveInfo[0xc1] = MI(Piece.WBISHOP, 0, +2, +2); + moveInfo[0xc2] = MI(Piece.WBISHOP, 1, +2, +2); + moveInfo[0xc3] = MI(Piece.WBISHOP, 0, -2, +2); + moveInfo[0xc4] = MI(Piece.WROOK , 1, +1, +0); + moveInfo[0xc5] = MI(Piece.WROOK , 1, +0, +4); + moveInfo[0xc6] = MI(Piece.WQUEEN , 1, +0, +5); + moveInfo[0xc7] = MI(Piece.WPAWN , 6, -1, +1); + moveInfo[0xc8] = MI(Piece.WPAWN , 6, +0, +2); + moveInfo[0xc9] = MI(Piece.WQUEEN , 1, +0, +7); + moveInfo[0xca] = MI(Piece.WBISHOP, 1, -3, +3); + moveInfo[0xcb] = MI(Piece.WPAWN , 5, +0, +1); + moveInfo[0xcc] = MI(Piece.WBISHOP, 1, -5, +5); + moveInfo[0xcd] = MI(Piece.WROOK , 0, +2, +0); + moveInfo[0xcf] = MI(Piece.WPAWN , 3, +0, +1); + moveInfo[0xd1] = MI(Piece.WPAWN , 1, -1, +1); + moveInfo[0xd2] = MI(Piece.WKNIGHT, 1, +2, +1); + moveInfo[0xd3] = MI(Piece.WKNIGHT, 1, -2, +1); + moveInfo[0xd7] = MI(Piece.WQUEEN , 0, -1, +1); + moveInfo[0xd8] = MI(Piece.WROOK , 1, +6, +0); + moveInfo[0xd9] = MI(Piece.WQUEEN , 0, -2, +2); + moveInfo[0xda] = MI(Piece.WKNIGHT, 0, -1, -2); + moveInfo[0xdb] = MI(Piece.WPAWN , 0, +0, +2); + moveInfo[0xde] = MI(Piece.WPAWN , 4, -1, +1); + moveInfo[0xdf] = MI(Piece.WKING , 0, -1, +1); + moveInfo[0xe0] = MI(Piece.WKNIGHT, 1, +2, -1); + moveInfo[0xe1] = MI(Piece.WROOK , 0, +0, +7); + moveInfo[0xe3] = MI(Piece.WROOK , 1, +0, +3); + moveInfo[0xe5] = MI(Piece.WQUEEN , 0, +4, +0); + moveInfo[0xe6] = MI(Piece.WPAWN , 3, +0, +2); + moveInfo[0xe7] = MI(Piece.WQUEEN , 0, +4, +4); + moveInfo[0xe8] = MI(Piece.WROOK , 0, +0, +2); + moveInfo[0xe9] = MI(Piece.WKNIGHT, 0, +2, -1); + moveInfo[0xeb] = MI(Piece.WPAWN , 3, +1, +1); + moveInfo[0xec] = MI(Piece.WPAWN , 0, +0, +1); + moveInfo[0xed] = MI(Piece.WQUEEN , 0, +7, +7); + moveInfo[0xee] = MI(Piece.WQUEEN , 1, -1, +1); + moveInfo[0xef] = MI(Piece.WROOK , 0, +4, +0); + moveInfo[0xf0] = MI(Piece.WQUEEN , 1, +7, +0); + moveInfo[0xf1] = MI(Piece.WQUEEN , 0, +1, +1); + moveInfo[0xf3] = MI(Piece.WKNIGHT, 1, -1, +2); + moveInfo[0xf4] = MI(Piece.WROOK , 1, +0, +2); + moveInfo[0xf5] = MI(Piece.WBISHOP, 1, +1, +1); + moveInfo[0xf6] = MI(Piece.WKING , 0, -2, +0); + moveInfo[0xf7] = MI(Piece.WKNIGHT, 0, -2, +1); + moveInfo[0xf8] = MI(Piece.WQUEEN , 1, +1, +0); + moveInfo[0xf9] = MI(Piece.WQUEEN , 1, +0, +6); + moveInfo[0xfa] = MI(Piece.WQUEEN , 1, +3, +0); + moveInfo[0xfb] = MI(Piece.WQUEEN , 1, +2, +2); + moveInfo[0xfd] = MI(Piece.WQUEEN , 0, +0, +7); + moveInfo[0xfe] = MI(Piece.WQUEEN , 1, -3, +3); + } + + private final static int findPiece(Position pos, int piece, int pieceNo) { + for (int x = 0; x < 8; x++) + for (int y = 0; y < 8; y++) { + int sq = Position.getSquare(x, y); + if (pos.getPiece(sq) == piece) + if (pieceNo-- == 0) + return sq; + } + return -1; + } + + private final Move decodeMove(Position pos, int moveCode) { + MoveInfo mi = moveInfo[moveCode]; + if (mi == null) + return null; + int from = findPiece(pos, mi.piece, mi.pieceNo); + if (from < 0) + return null; + int toX = (Position.getX(from) + mi.dx) & 7; + int toY = (Position.getY(from) + mi.dy) & 7; + int to = Position.getSquare(toX, toY); + int promoteTo = Piece.EMPTY; + if ((pos.getPiece(from) == Piece.WPAWN) && (toY == 7)) + promoteTo = Piece.WQUEEN; + Move m = new Move(from, to, promoteTo); + return m; + } + } + + private final static int mirrorSquareColor(int sq) { + int x = Position.getX(sq); + int y = 7 - Position.getY(sq); + return Position.getSquare(x, y); + } + + private final static int mirrorPieceColor(int piece) { + if (Piece.isWhite(piece)) { + piece = Piece.makeBlack(piece); + } else { + piece = Piece.makeWhite(piece); + } + return piece; + } + + private final static Position mirrorPosColor(Position pos) { + Position ret = new Position(pos); + for (int sq = 0; sq < 64; sq++) { + int mSq = mirrorSquareColor(sq); + int piece = pos.getPiece(sq); + int mPiece = mirrorPieceColor(piece); + ret.setPiece(mSq, mPiece); + } + ret.setWhiteMove(!pos.whiteMove); + int castleMask = 0; + if (pos.a1Castle()) castleMask |= (1 << Position.A8_CASTLE); + if (pos.h1Castle()) castleMask |= (1 << Position.H8_CASTLE); + if (pos.a8Castle()) castleMask |= (1 << Position.A1_CASTLE); + if (pos.h8Castle()) castleMask |= (1 << Position.H1_CASTLE); + ret.setCastleMask(castleMask); + int epSquare = pos.getEpSquare(); + if (epSquare >= 0) { + int mEpSquare = mirrorSquareColor(epSquare); + ret.setEpSquare(mEpSquare); + } + ret.halfMoveClock = pos.halfMoveClock; + ret.fullMoveCounter = pos.fullMoveCounter; + return ret; + } + + private final static Move mirrorMoveColor(Move m) { + if (m == null) return null; + Move ret = new Move(m); + ret.from = mirrorSquareColor(m.from); + ret.to = mirrorSquareColor(m.to); + ret.promoteTo = mirrorPieceColor(m.promoteTo); + return ret; + } + + private final static int mirrorSquareLeftRight(int sq) { + int x = 7 - Position.getX(sq); + int y = Position.getY(sq); + return Position.getSquare(x, y); + } + + private final static Position mirrorPosLeftRight(Position pos) { + Position ret = new Position(pos); + for (int sq = 0; sq < 64; sq++) { + int mSq = mirrorSquareLeftRight(sq); + int piece = pos.getPiece(sq); + ret.setPiece(mSq, piece); + } + int epSquare = pos.getEpSquare(); + if (epSquare >= 0) { + int mEpSquare = mirrorSquareLeftRight(epSquare); + ret.setEpSquare(mEpSquare); + } + ret.halfMoveClock = pos.halfMoveClock; + ret.fullMoveCounter = pos.fullMoveCounter; + return ret; + } + + private final static Move mirrorMoveLeftRight(Move m) { + if (m == null) return null; + Move ret = new Move(m); + ret.from = mirrorSquareLeftRight(m.from); + ret.to = mirrorSquareLeftRight(m.to); + ret.promoteTo = m.promoteTo; + return ret; + } +} diff --git a/DroidFish/src/org/petero/droidfish/engine/DroidBook.java b/DroidFish/src/org/petero/droidfish/engine/DroidBook.java new file mode 100644 index 0000000..334f542 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/engine/DroidBook.java @@ -0,0 +1,267 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.engine; + +import java.io.BufferedReader; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Random; + +import org.petero.droidfish.BookOptions; +import org.petero.droidfish.gamelogic.ChessParseError; +import org.petero.droidfish.gamelogic.Move; +import org.petero.droidfish.gamelogic.MoveGen; +import org.petero.droidfish.gamelogic.Position; +import org.petero.droidfish.gamelogic.TextIO; +import org.petero.droidfish.gamelogic.Pair; +import org.petero.droidfish.gamelogic.UndoInfo; + +import chess.Piece; + +/** + * Implements an opening book. + * @author petero + */ +public final class DroidBook { + static class BookEntry { + Move move; + double weight; + BookEntry(Move move) { + this.move = move; + weight = 1; + } + @Override + public String toString() { + return TextIO.moveToUCIString(move) + " (" + weight + ")"; + } + } + private Random rndGen = new SecureRandom(); + + private IOpeningBook externalBook = new NullBook(); + private IOpeningBook internalBook = new InternalBook(); + private BookOptions options = null; + + private static final DroidBook INSTANCE = new DroidBook(); + public static DroidBook getInstance() { + return INSTANCE; + } + + private DroidBook() { + rndGen.setSeed(System.currentTimeMillis()); + } + + public final void setOptions(BookOptions options) { + this.options = options; + if (CtgBook.canHandle(options)) + externalBook = new CtgBook(); + else if (PolyglotBook.canHandle(options)) + externalBook = new PolyglotBook(); + else + externalBook = new NullBook(); + externalBook.setOptions(options); + internalBook.setOptions(options); + } + + /** Return a random book move for a position, or null if out of book. */ + public final Move getBookMove(Position pos) { + if ((options != null) && (pos.fullMoveCounter > options.maxLength)) + return null; + List bookMoves = getBook().getBookEntries(pos); + if (bookMoves == null) + return null; + + ArrayList legalMoves = new MoveGen().pseudoLegalMoves(pos); + legalMoves = MoveGen.removeIllegal(pos, legalMoves); + double sum = 0; + final int nMoves = bookMoves.size(); + for (int i = 0; i < nMoves; i++) { + BookEntry be = bookMoves.get(i); + if (!legalMoves.contains(be.move)) { + // If an illegal move was found, it means there was a hash collision, + // or a corrupt external book file. + return null; + } + sum += scaleWeight(bookMoves.get(i).weight); + } + if (sum <= 0) { + return null; + } + double rnd = rndGen.nextDouble() * sum; + sum = 0; + for (int i = 0; i < nMoves; i++) { + sum += scaleWeight(bookMoves.get(i).weight); + if (rnd < sum) + return bookMoves.get(i).move; + } + return bookMoves.get(nMoves-1).move; + } + + private final double scaleWeight(double w) { + if (w <= 0) + return 0; + if (options == null) + return w; + return Math.pow(w, Math.exp(-options.random)); + } + + final private IOpeningBook getBook() { + if (externalBook.enabled()) { + return externalBook; + } else { + return internalBook; + } + } + + /** Return a string describing all book moves. */ + public final Pair> getAllBookMoves(Position pos) { + StringBuilder ret = new StringBuilder(); + ArrayList bookMoveList = new ArrayList(); + List bookMoves = getBook().getBookEntries(pos); + + // Check legality + if (bookMoves != null) { + ArrayList legalMoves = new MoveGen().pseudoLegalMoves(pos); + legalMoves = MoveGen.removeIllegal(pos, legalMoves); + for (int i = 0; i < bookMoves.size(); i++) { + BookEntry be = bookMoves.get(i); + if (!legalMoves.contains(be.move)) { + bookMoves = null; + break; + } + } + } + + if (bookMoves != null) { + Collections.sort(bookMoves, new Comparator() { + public int compare(BookEntry arg0, BookEntry arg1) { + double wd = arg1.weight - arg0.weight; + if (wd != 0) + return (wd > 0) ? 1 : -1; + String str0 = TextIO.moveToUCIString(arg0.move); + String str1 = TextIO.moveToUCIString(arg1.move); + return str0.compareTo(str1); + }}); + double totalWeight = 0; + for (BookEntry be : bookMoves) + totalWeight += scaleWeight(be.weight); + if (totalWeight <= 0) totalWeight = 1; + for (BookEntry be : bookMoves) { + Move m = be.move; + bookMoveList.add(m); + String moveStr = TextIO.moveToString(pos, m, false); + ret.append(moveStr); + ret.append(':'); + int percent = (int)Math.round(scaleWeight(be.weight) * 100 / totalWeight); + ret.append(percent); + ret.append(' '); + } + } + return new Pair>(ret.toString(), bookMoveList); + } + + /** Creates the book.bin file. */ + public static void main(String[] args) throws IOException { + List binBook = createBinBook(); + FileOutputStream out = new FileOutputStream("../src/book.bin"); + int bookLen = binBook.size(); + byte[] binBookA = new byte[bookLen]; + for (int i = 0; i < bookLen; i++) + binBookA[i] = binBook.get(i); + out.write(binBookA); + out.close(); + } + + public static List createBinBook() { + List binBook = new ArrayList(0); + try { + InputStream inStream = new Object().getClass().getResourceAsStream("/book.txt"); + InputStreamReader inFile = new InputStreamReader(inStream); + BufferedReader inBuf = new BufferedReader(inFile, 8192); + LineNumberReader lnr = new LineNumberReader(inBuf); + String line; + while ((line = lnr.readLine()) != null) { + if (line.startsWith("#") || (line.length() == 0)) { + continue; + } + if (!addBookLine(line, binBook)) { + System.out.printf("Book parse error, line:%d\n", lnr.getLineNumber()); + throw new RuntimeException(); + } +// System.out.printf("no:%d line:%s%n", lnr.getLineNumber(), line); + } + lnr.close(); + } catch (ChessParseError ex) { + throw new RuntimeException(); + } catch (IOException ex) { + System.out.println("Can't read opening book resource"); + throw new RuntimeException(); + } + return binBook; + } + + /** Add a sequence of moves, starting from the initial position, to the binary opening book. */ + private static boolean addBookLine(String line, List binBook) throws ChessParseError { + Position pos = TextIO.readFEN(TextIO.startPosFEN); + UndoInfo ui = new UndoInfo(); + String[] strMoves = line.split(" "); + for (String strMove : strMoves) { +// System.out.printf("Adding move:%s\n", strMove); + int bad = 0; + if (strMove.endsWith("?")) { + strMove = strMove.substring(0, strMove.length() - 1); + bad = 1; + } + Move m = TextIO.stringToMove(pos, strMove); + if (m == null) { + return false; + } + int prom = pieceToProm(m.promoteTo); + int val = m.from + (m.to << 6) + (prom << 12) + (bad << 15); + binBook.add((byte)(val >> 8)); + binBook.add((byte)(val & 255)); + pos.makeMove(m, ui); + } + binBook.add((byte)0); + binBook.add((byte)0); + return true; + } + + private static int pieceToProm(int p) { + switch (p) { + case Piece.WQUEEN: case Piece.BQUEEN: + return 1; + case Piece.WROOK: case Piece.BROOK: + return 2; + case Piece.WBISHOP: case Piece.BBISHOP: + return 3; + case Piece.WKNIGHT: case Piece.BKNIGHT: + return 4; + default: + return 0; + } + } +} diff --git a/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java b/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java new file mode 100644 index 0000000..0ec8c45 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java @@ -0,0 +1,597 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.engine; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; + +import org.petero.droidfish.BookOptions; +import org.petero.droidfish.engine.cuckoochess.CuckooChessEngine; +import org.petero.droidfish.gamelogic.Move; +import org.petero.droidfish.gamelogic.MoveGen; +import org.petero.droidfish.gamelogic.Pair; +import org.petero.droidfish.gamelogic.Position; +import org.petero.droidfish.gamelogic.SearchListener; +import org.petero.droidfish.gamelogic.TextIO; +import org.petero.droidfish.gamelogic.UndoInfo; +import org.petero.droidfish.gamelogic.SearchListener.PvInfo; + +/** + * A computer algorithm player. + * @author petero + */ +public class DroidComputerPlayer { + private static String engineName = ""; + + private static UCIEngine uciEngine = null; + private SearchListener listener; + private DroidBook book; + private boolean newGame = false; + private String engine = ""; + private int maxPV = 1; // >1 if multiPV mode is supported + private static int numCPUs = 1; + + private boolean havePonderHit = false; + + public DroidComputerPlayer(String engine) { + this.engine = engine; + if (uciEngine != null) { + stopSearch(); + } else { + startEngine(); + } + listener = null; + book = DroidBook.getInstance(); + } + + private final synchronized void startEngine() { + boolean useCuckoo = engine.equals("cuckoochess"); + if (uciEngine == null) { + if (useCuckoo) { + uciEngine = new CuckooChessEngine(); + } else { + uciEngine = new NativePipedProcess(); + } + uciEngine.initialize(); + uciEngine.writeLineToEngine("uci"); + readUCIOptions(); + int nThreads = getNumCPUs(); + if (nThreads > 8) nThreads = 8; + numCPUs = nThreads; + if (!useCuckoo) + uciEngine.setOption("Hash", 16); + uciEngine.setOption("Ponder", false); + uciEngine.writeLineToEngine("ucinewgame"); + syncReady(); + } + } + + public final synchronized void setEngineStrength(String engine, int strength) { + if (!engine.equals(this.engine)) { + shutdownEngine(); + this.engine = engine; + startEngine(); + } + if (uciEngine != null) + uciEngine.setStrength(strength); + } + + public final synchronized int getMaxPV() { + return maxPV; + } + + public final synchronized void setNumPV(int numPV) { + if ((uciEngine != null) && (maxPV > 1)) { + int num = Math.min(maxPV, numPV); + uciEngine.setOption("MultiPV", num); + } + } + + private static int getNumCPUs() { + int nCPUsFromProc = 1; + try { + FileReader fr = new FileReader("/proc/stat"); + BufferedReader inBuf = new BufferedReader(fr, 8192); + String line; + int nCPUs = 0; + while ((line = inBuf.readLine()) != null) { + if ((line.length() >= 4) && line.startsWith("cpu") && Character.isDigit(line.charAt(3))) + nCPUs++; + } + inBuf.close(); + if (nCPUs < 1) nCPUs = 1; + nCPUsFromProc = nCPUs; + } catch (IOException e) { + } + int nCPUsFromOS = NativePipedProcess.getNPhysicalProcessors(); + return Math.max(nCPUsFromProc, nCPUsFromOS); + } + + public final void setListener(SearchListener listener) { + this.listener = listener; + } + + public final void setBookOptions(BookOptions options) { + book.setOptions(options); + } + + private void readUCIOptions() { + int timeout = 1000; + maxPV = 1; + while (true) { + String s = uciEngine.readLineFromEngine(timeout); + String[] tokens = tokenize(s); + if (tokens[0].equals("uciok")) + break; + else if (tokens[0].equals("id")) { + if (tokens[1].equals("name")) { + engineName = ""; + for (int i = 2; i < tokens.length; i++) { + if (engineName.length() > 0) + engineName += " "; + engineName += tokens[i]; + } + } + } else if ((tokens.length > 2) && tokens[2].toLowerCase().equals("multipv")) { + try { + for (int i = 3; i < tokens.length; i++) { + if (tokens[i].equals("max") && (i+1 < tokens.length)) { + maxPV = Math.max(maxPV, Integer.parseInt(tokens[i+1])); + break; + } + } + } catch (NumberFormatException nfe) { } + } + } + } + + public synchronized String getEngineName() { + if (uciEngine != null) + return engineName + uciEngine.addStrengthToName(); + else + return engineName; + } + + /** Convert a string to tokens by splitting at whitespace characters. */ + private final String[] tokenize(String cmdLine) { + cmdLine = cmdLine.trim(); + return cmdLine.split("\\s+"); + } + + private final void syncReady() { + uciEngine.writeLineToEngine("isready"); + while (true) { + String s = uciEngine.readLineFromEngine(1000); + if (s.equals("readyok")) + break; + } + } + + /** Clear transposition table. */ + public final void clearTT() { + newGame = true; + } + + public final void maybeNewGame() { + if (newGame) { + newGame = false; + if (uciEngine != null) { + uciEngine.writeLineToEngine("ucinewgame"); + syncReady(); + } + } + } + + /** Stop the engine process. */ + public final synchronized void shutdownEngine() { + if (uciEngine != null) { + uciEngine.shutDown(); + uciEngine = null; + } + } + + public final synchronized void ponderHit(Position pos, Move ponderMove) { + havePonderHit = true; + uciEngine.writeLineToEngine("ponderhit"); + pvModified = true; + notifyGUI(pos, ponderMove); + } + + /** + * Do a search and return a command from the computer player. + * The command can be a valid move string, in which case the move is played + * and the turn goes over to the other player. The command can also be a special + * command, such as "draw" and "resign". + * @param pos An earlier position from the game + * @param mList List of moves to go from the earlier position to the current position. + * This list makes it possible for the computer to correctly handle draw + * by repetition/50 moves. + * @param ponderEnabled True if pondering is enabled in the GUI. Can affect time management. + * @param ponderMove Move to ponder, or null for non-ponder search. + * @param engineThreads Number of engine threads to use, if supported by engine. + * @return The computer player command, and the next ponder move. + */ + public final Pair doSearch(Position prevPos, ArrayList mList, + Position currPos, boolean drawOffer, + int wTime, int bTime, int inc, int movesToGo, + boolean ponderEnabled, Move ponderMove, + int engineThreads) { + if (listener != null) + listener.notifyBookInfo("", null); + + if (ponderMove != null) + mList.add(ponderMove); + + havePonderHit = false; + + // Set up for draw detection + long[] posHashList = new long[mList.size()+1]; + int posHashListSize = 0; + Position p = new Position(prevPos); + UndoInfo ui = new UndoInfo(); + for (int i = 0; i < mList.size(); i++) { + posHashList[posHashListSize++] = p.zobristHash(); + p.makeMove(mList.get(i), ui); + } + + if (ponderMove == null) { + // If we have a book move, play it. + Move bookMove = book.getBookMove(currPos); + if (bookMove != null) { + if (canClaimDraw(currPos, posHashList, posHashListSize, bookMove) == "") { + return new Pair(TextIO.moveToString(currPos, bookMove, false), null); + } + } + + // If only one legal move, play it without searching + ArrayList moves = new MoveGen().pseudoLegalMoves(currPos); + moves = MoveGen.removeIllegal(currPos, moves); + if (moves.size() == 0) { + return new Pair("", null); // User set up a position where computer has no valid moves. + } + if (moves.size() == 1) { + Move bestMove = moves.get(0); + if (canClaimDraw(currPos, posHashList, posHashListSize, bestMove) == "") { + return new Pair(TextIO.moveToUCIString(bestMove), null); + } + } + } + + StringBuilder posStr = new StringBuilder(); + posStr.append("position fen "); + posStr.append(TextIO.toFEN(prevPos)); + int nMoves = mList.size(); + if (nMoves > 0) { + posStr.append(" moves"); + for (int i = 0; i < nMoves; i++) { + posStr.append(" "); + posStr.append(TextIO.moveToUCIString(mList.get(i))); + } + } + maybeNewGame(); + uciEngine.setOption("Ponder", ponderEnabled); + uciEngine.setOption("UCI_AnalyseMode", false); + uciEngine.setOption("Threads", engineThreads > 0 ? engineThreads : numCPUs); + uciEngine.writeLineToEngine(posStr.toString()); + if (wTime < 1) wTime = 1; + if (bTime < 1) bTime = 1; + StringBuilder goStr = new StringBuilder(96); + goStr.append(String.format("go wtime %d btime %d", wTime, bTime)); + if (inc > 0) + goStr.append(String.format(" winc %d binc %d", inc, inc)); + if (movesToGo > 0) + goStr.append(String.format(" movestogo %d", movesToGo)); + if (ponderMove != null) + goStr.append(" ponder"); + uciEngine.writeLineToEngine(goStr.toString()); + + Pair pair = monitorEngine(currPos, ponderMove); + String bestMove = pair.first; + Move nextPonderMove = TextIO.UCIstringToMove(pair.second); + + // Claim draw if appropriate + if (statScore <= 0) { + String drawClaim = canClaimDraw(currPos, posHashList, posHashListSize, TextIO.UCIstringToMove(bestMove)); + if (drawClaim != "") + bestMove = drawClaim; + } + // Accept draw offer if engine is losing + if (drawOffer && !statIsMate && (statScore <= -300)) { + bestMove = "draw accept"; + } + return new Pair(bestMove, nextPonderMove); + } + + /** Wait for engine to respond with bestMove and ponderMove. + * While waiting, monitor and report search info. */ + private final Pair monitorEngine(Position pos, Move ponderMove) { + // Monitor engine response + clearInfo(); + boolean stopSent = false; + while (true) { + int timeout = 2000; + while (true) { + UCIEngine uci = uciEngine; + if (uci == null) + break; + if (shouldStop && !stopSent) { + uci.writeLineToEngine("stop"); + stopSent = true; + } + String s = uci.readLineFromEngine(timeout); + if (s.length() == 0) + break; + String[] tokens = tokenize(s); + if (tokens[0].equals("info")) { + parseInfoCmd(tokens, ponderMove); + } else if (tokens[0].equals("bestmove")) { + String bestMove = tokens[1]; + String nextPonderMove = ""; + if ((tokens.length >= 4) && (tokens[2].equals("ponder"))) + nextPonderMove = tokens[3]; + return new Pair(bestMove, nextPonderMove); + } + timeout = 0; + } + notifyGUI(pos, ponderMove); + try { + Thread.sleep(100); // 10 GUI updates per second is enough + } catch (InterruptedException e) { + } + } + } + + public final Pair> getBookHints(Position pos) { + Pair> bi = book.getAllBookMoves(pos); + return new Pair>(bi.first, bi.second); + } + + public boolean shouldStop = false; + + public final void analyze(Position prevPos, ArrayList mList, Position currPos, + boolean drawOffer, int engineThreads) { + if (shouldStop) + return; + if (listener != null) { + Pair> bi = getBookHints(currPos); + listener.notifyBookInfo(bi.first, bi.second); + } + + // If no legal moves, there is nothing to analyze + ArrayList moves = new MoveGen().pseudoLegalMoves(currPos); + moves = MoveGen.removeIllegal(currPos, moves); + if (moves.size() == 0) + return; + + StringBuilder posStr = new StringBuilder(); + posStr.append("position fen "); + posStr.append(TextIO.toFEN(prevPos)); + int nMoves = mList.size(); + if (nMoves > 0) { + posStr.append(" moves"); + for (int i = 0; i < nMoves; i++) { + posStr.append(" "); + posStr.append(TextIO.moveToUCIString(mList.get(i))); + } + } + maybeNewGame(); + uciEngine.writeLineToEngine(posStr.toString()); + uciEngine.setOption("UCI_AnalyseMode", true); + uciEngine.setOption("Threads", engineThreads > 0 ? engineThreads : numCPUs); + String goStr = String.format("go infinite"); + uciEngine.writeLineToEngine(goStr); + + monitorEngine(currPos, null); + } + + /** Check if a draw claim is allowed, possibly after playing "move". + * @param move The move that may have to be made before claiming draw. + * @return The draw string that claims the draw, or empty string if draw claim not valid. + */ + private String canClaimDraw(Position pos, long[] posHashList, int posHashListSize, Move move) { + String drawStr = ""; + if (canClaimDraw50(pos)) { + drawStr = "draw 50"; + } else if (canClaimDrawRep(pos, posHashList, posHashListSize, posHashListSize)) { + drawStr = "draw rep"; + } else if (move != null) { + String strMove = TextIO.moveToString(pos, move, false); + posHashList[posHashListSize++] = pos.zobristHash(); + UndoInfo ui = new UndoInfo(); + pos.makeMove(move, ui); + if (canClaimDraw50(pos)) { + drawStr = "draw 50 " + strMove; + } else if (canClaimDrawRep(pos, posHashList, posHashListSize, posHashListSize)) { + drawStr = "draw rep " + strMove; + } + pos.unMakeMove(move, ui); + } + return drawStr; + } + + private final static boolean canClaimDraw50(Position pos) { + return (pos.halfMoveClock >= 100); + } + + private final static boolean canClaimDrawRep(Position pos, long[] posHashList, int posHashListSize, int posHashFirstNew) { + int reps = 0; + for (int i = posHashListSize - 4; i >= 0; i -= 2) { + if (pos.zobristHash() == posHashList[i]) { + reps++; + if (i >= posHashFirstNew) { + reps++; + break; + } + } + } + return (reps >= 2); + } + + + private int statCurrDepth = 0; + private int statPVDepth = 0; + private int statScore = 0; + private boolean statIsMate = false; + private boolean statUpperBound = false; + private boolean statLowerBound = false; + private int statTime = 0; + private int statNodes = 0; + private int statNps = 0; + private int pvNum = 0; + private ArrayList statPV = new ArrayList(); + private String statCurrMove = ""; + private int statCurrMoveNr = 0; + + private ArrayList statPvInfo = new ArrayList(); + + private boolean depthModified = false; + private boolean currMoveModified = false; + private boolean pvModified = false; + private boolean statsModified = false; + + private final void clearInfo() { + depthModified = false; + currMoveModified = false; + pvModified = false; + statsModified = false; + statPvInfo.clear(); + } + + private final void parseInfoCmd(String[] tokens, Move ponderMove) { + try { + boolean havePvData = false; + int nTokens = tokens.length; + int i = 1; + while (i < nTokens - 1) { + String is = tokens[i++]; + if (is.equals("depth")) { + statCurrDepth = Integer.parseInt(tokens[i++]); + depthModified = true; + } else if (is.equals("currmove")) { + statCurrMove = tokens[i++]; + currMoveModified = true; + } else if (is.equals("currmovenumber")) { + statCurrMoveNr = Integer.parseInt(tokens[i++]); + currMoveModified = true; + } else if (is.equals("time")) { + statTime = Integer.parseInt(tokens[i++]); + statsModified = true; + } else if (is.equals("nodes")) { + statNodes = Integer.parseInt(tokens[i++]); + statsModified = true; + } else if (is.equals("nps")) { + statNps = Integer.parseInt(tokens[i++]); + statsModified = true; + } else if (is.equals("multipv")) { + pvNum = Integer.parseInt(tokens[i++]) - 1; + if (pvNum < 0) pvNum = 0; + if (pvNum > 255) pvNum = 255; + pvModified = true; + } else if (is.equals("pv")) { + statPV.clear(); + while (i < nTokens) + statPV.add(tokens[i++]); + pvModified = true; + havePvData = true; + statPVDepth = statCurrDepth; + } else if (is.equals("score")) { + statIsMate = tokens[i++].equals("mate"); + statScore = Integer.parseInt(tokens[i++]); + statUpperBound = false; + statLowerBound = false; + if (tokens[i].equals("upperbound")) { + statUpperBound = true; + i++; + } else if (tokens[i].equals("lowerbound")) { + statLowerBound = true; + i++; + } + pvModified = true; + } + } + if (havePvData) { + while (statPvInfo.size() < pvNum) + statPvInfo.add(new PvInfo(0, 0, 0, 0, 0, false, false, false, new ArrayList())); + while (statPvInfo.size() <= pvNum) + statPvInfo.add(null); + ArrayList moves = new ArrayList(); + if (ponderMove != null) + moves.add(ponderMove); + int nMoves = statPV.size(); + for (i = 0; i < nMoves; i++) + moves.add(TextIO.UCIstringToMove(statPV.get(i))); + statPvInfo.set(pvNum, new PvInfo(statPVDepth, statScore, statTime, statNodes, statNps, statIsMate, + statUpperBound, statLowerBound, moves)); + } + } catch (NumberFormatException nfe) { + // Ignore + } catch (ArrayIndexOutOfBoundsException aioob) { + // Ignore + } + } + + /** Notify GUI about search statistics. */ + private final synchronized void notifyGUI(Position pos, Move ponderMove) { + if (listener == null) + return; + if (depthModified) { + listener.notifyDepth(statCurrDepth); + depthModified = false; + } + if (currMoveModified) { + Move m = TextIO.UCIstringToMove(statCurrMove); + listener.notifyCurrMove(pos, m, statCurrMoveNr); + currMoveModified = false; + } + if (pvModified) { + Position notifyPos = pos; + ArrayList pvInfo = statPvInfo; + boolean isPonder = ponderMove != null; + if (isPonder && havePonderHit) { + isPonder = false; + + UndoInfo ui = new UndoInfo(); + notifyPos = new Position(pos); + notifyPos.makeMove(ponderMove, ui); + + pvInfo = new ArrayList(statPvInfo.size()); + for (int i = 0; i < statPvInfo.size(); i++) { + PvInfo pvi = new PvInfo(statPvInfo.get(i)); + pvi.removeFirstMove(); + pvInfo.add(pvi); + } + } + listener.notifyPV(notifyPos, pvInfo, isPonder); + pvModified = false; + } + if (statsModified) { + listener.notifyStats(statNodes, statNps, statTime); + statsModified = false; + } + } + + public final synchronized void stopSearch() { + shouldStop = true; + if (uciEngine != null) + uciEngine.writeLineToEngine("stop"); + havePonderHit = false; + } +} diff --git a/DroidFish/src/org/petero/droidfish/engine/IOpeningBook.java b/DroidFish/src/org/petero/droidfish/engine/IOpeningBook.java new file mode 100644 index 0000000..a871d1e --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/engine/IOpeningBook.java @@ -0,0 +1,36 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.engine; + +import java.util.List; + +import org.petero.droidfish.BookOptions; +import org.petero.droidfish.engine.DroidBook.BookEntry; +import org.petero.droidfish.gamelogic.Position; + +interface IOpeningBook { + /** Return true if book is currently enabled. */ + boolean enabled(); + + /** Set book options, including filename. */ + void setOptions(BookOptions options); + + /** Get all book entries for a position. */ + List getBookEntries(Position pos); +} diff --git a/DroidFish/src/org/petero/droidfish/engine/InternalBook.java b/DroidFish/src/org/petero/droidfish/engine/InternalBook.java new file mode 100644 index 0000000..edff909 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/engine/InternalBook.java @@ -0,0 +1,159 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.engine; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.petero.droidfish.BookOptions; +import org.petero.droidfish.engine.DroidBook.BookEntry; +import org.petero.droidfish.gamelogic.ChessParseError; +import org.petero.droidfish.gamelogic.Move; +import org.petero.droidfish.gamelogic.Piece; +import org.petero.droidfish.gamelogic.Position; +import org.petero.droidfish.gamelogic.TextIO; +import org.petero.droidfish.gamelogic.UndoInfo; + +public final class InternalBook implements IOpeningBook { + private static Map> bookMap; + private static int numBookMoves = -1; + + InternalBook() { + Thread t = new Thread(new Runnable() { + @Override + public void run() { + initInternalBook(); + } + }); + t.setPriority(Thread.MIN_PRIORITY); + t.start(); + } + + static boolean canHandle(String filename) { + return filename.length() == 0; + } + + @Override + public boolean enabled() { + return true; + } + + @Override + public List getBookEntries(Position pos) { + initInternalBook(); + List ents = bookMap.get(pos.zobristHash()); + if (ents == null) + return null; + List ret = new ArrayList(); + for (BookEntry be : ents) { + BookEntry be2 = new BookEntry(be.move); + be2.weight = Math.sqrt(be.weight) * 100 + 1; + ret.add(be2); + } + return ret; + } + + @Override + public void setOptions(BookOptions options) { + } + + private synchronized final void initInternalBook() { + if (numBookMoves >= 0) + return; +// long t0 = System.currentTimeMillis(); + bookMap = new HashMap>(); + numBookMoves = 0; + try { + InputStream inStream = getClass().getResourceAsStream("/book.bin"); + List buf = new ArrayList(8192); + byte[] tmpBuf = new byte[1024]; + while (true) { + int len = inStream.read(tmpBuf); + if (len <= 0) break; + for (int i = 0; i < len; i++) + buf.add(tmpBuf[i]); + } + inStream.close(); + Position startPos = TextIO.readFEN(TextIO.startPosFEN); + Position pos = new Position(startPos); + UndoInfo ui = new UndoInfo(); + int len = buf.size(); + for (int i = 0; i < len; i += 2) { + int b0 = buf.get(i); if (b0 < 0) b0 += 256; + int b1 = buf.get(i+1); if (b1 < 0) b1 += 256; + int move = (b0 << 8) + b1; + if (move == 0) { + pos = new Position(startPos); + } else { + boolean bad = ((move >> 15) & 1) != 0; + int prom = (move >> 12) & 7; + Move m = new Move(move & 63, (move >> 6) & 63, + promToPiece(prom, pos.whiteMove)); + if (!bad) + addToBook(pos, m); + pos.makeMove(m, ui); + } + } + } catch (ChessParseError ex) { + throw new RuntimeException(); + } catch (IOException ex) { + System.out.println("Can't read opening book resource"); + throw new RuntimeException(); + } +/* { + long t1 = System.currentTimeMillis(); + System.out.printf("Book moves:%d (parse time:%.3f)%n", numBookMoves, + (t1 - t0) / 1000.0); + } */ + } + + + /** Add a move to a position in the opening book. */ + private final void addToBook(Position pos, Move moveToAdd) { + List ent = bookMap.get(pos.zobristHash()); + if (ent == null) { + ent = new ArrayList(); + bookMap.put(pos.zobristHash(), ent); + } + for (int i = 0; i < ent.size(); i++) { + BookEntry be = ent.get(i); + if (be.move.equals(moveToAdd)) { + be.weight++; + return; + } + } + BookEntry be = new BookEntry(moveToAdd); + ent.add(be); + numBookMoves++; + } + + private static int promToPiece(int prom, boolean whiteMove) { + switch (prom) { + case 1: return whiteMove ? Piece.WQUEEN : Piece.BQUEEN; + case 2: return whiteMove ? Piece.WROOK : Piece.BROOK; + case 3: return whiteMove ? Piece.WBISHOP : Piece.BBISHOP; + case 4: return whiteMove ? Piece.WKNIGHT : Piece.BKNIGHT; + default: return Piece.EMPTY; + } + } +} diff --git a/DroidFish/src/org/petero/droidfish/engine/NativePipedProcess.java b/DroidFish/src/org/petero/droidfish/engine/NativePipedProcess.java new file mode 100644 index 0000000..9c09af1 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/engine/NativePipedProcess.java @@ -0,0 +1,72 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.engine; + +public class NativePipedProcess extends UCIEngineBase { + static { + System.loadLibrary("jni"); + } + + @Override + public void setStrength(int strength) { + this.strength = strength; + setOption("Skill Level", strength/50); + } + + /** + * Read a line from the process. + * @param timeoutMillis Maximum time to wait for data + * @return The line, without terminating newline characters, + * or empty string if no data available, + * or null if I/O error. + */ + @Override + public final String readLineFromEngine(int timeoutMillis) { + String ret = readFromProcess(timeoutMillis); + if (ret == null) + return null; + if (ret.length() > 0) { +// System.out.printf("Engine -> GUI: %s\n", ret); + } + return ret; + } + + /** Write a line to the process. \n will be added automatically. */ + @Override + public final synchronized void writeLineToEngine(String data) { +// System.out.printf("GUI -> Engine: %s\n", data); + writeToProcess(data + "\n"); + } + + /** Start the child process. */ + protected final native void startProcess(); + + /** + * Read a line of data from the process. + * Return as soon as there is a full line of data to return, + * or when timeoutMillis milliseconds have passed. + */ + private final native String readFromProcess(int timeoutMillis); + + /** Write data to the process. */ + private final native void writeToProcess(String data); + + /** Return number of physical processors, i.e. hyper-threading ignored. */ + final static native int getNPhysicalProcessors(); +} diff --git a/DroidFish/src/org/petero/droidfish/engine/NullBook.java b/DroidFish/src/org/petero/droidfish/engine/NullBook.java new file mode 100644 index 0000000..7af42a0 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/engine/NullBook.java @@ -0,0 +1,42 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.engine; + +import java.util.List; + +import org.petero.droidfish.BookOptions; +import org.petero.droidfish.engine.DroidBook.BookEntry; +import org.petero.droidfish.gamelogic.Position; + +public class NullBook implements IOpeningBook { + + @Override + public boolean enabled() { + return false; + } + + @Override + public List getBookEntries(Position pos) { + return null; + } + + @Override + public void setOptions(BookOptions options) { + } +} diff --git a/DroidFish/src/org/petero/droidfish/engine/PolyglotBook.java b/DroidFish/src/org/petero/droidfish/engine/PolyglotBook.java new file mode 100644 index 0000000..38fc0ff --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/engine/PolyglotBook.java @@ -0,0 +1,420 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.engine; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.ArrayList; +import java.util.List; + +import org.petero.droidfish.BookOptions; +import org.petero.droidfish.engine.DroidBook.BookEntry; +import org.petero.droidfish.gamelogic.Move; +import org.petero.droidfish.gamelogic.Piece; +import org.petero.droidfish.gamelogic.Position; + +public class PolyglotBook implements IOpeningBook { + private File bookFile; + + PolyglotBook() { + bookFile = new File(""); + } + + @Override + public final void setOptions(BookOptions options) { + bookFile = new File(options.filename); + } + + /** Compute a polyglot hash key corresponding to a position. */ + static long getHashKey(Position pos) { + // Pieces + long key = 0; + for (int sq = 0; sq < 64; sq++) { + int pVal = -1; + switch (pos.getPiece(sq)) { + case Piece.BPAWN: pVal = 0; break; + case Piece.WPAWN: pVal = 1; break; + case Piece.BKNIGHT: pVal = 2; break; + case Piece.WKNIGHT: pVal = 3; break; + case Piece.BBISHOP: pVal = 4; break; + case Piece.WBISHOP: pVal = 5; break; + case Piece.BROOK: pVal = 6; break; + case Piece.WROOK: pVal = 7; break; + case Piece.BQUEEN: pVal = 8; break; + case Piece.WQUEEN: pVal = 9; break; + case Piece.BKING: pVal = 10; break; + case Piece.WKING: pVal = 11; break; + } + if (pVal >= 0) + key ^= hashRandoms[64 * pVal + sq]; + } + + // Castle flags + if (pos.h1Castle()) key ^= hashRandoms[768 + 0]; + if (pos.a1Castle()) key ^= hashRandoms[768 + 1]; + if (pos.h8Castle()) key ^= hashRandoms[768 + 2]; + if (pos.a8Castle()) key ^= hashRandoms[768 + 3]; + + // EP file + if (pos.getEpSquare() >= 0) { + int epFile = Position.getX(pos.getEpSquare()); + key ^= hashRandoms[772 + epFile]; + } + + // Side to move + if (pos.whiteMove) + key ^= hashRandoms[780]; + + return key; + } + + static private long hashRandoms[] = { + 0x9D39247E33776D41L, 0x2AF7398005AAA5C7L, 0x44DB015024623547L, 0x9C15F73E62A76AE2L, + 0x75834465489C0C89L, 0x3290AC3A203001BFL, 0x0FBBAD1F61042279L, 0xE83A908FF2FB60CAL, + 0x0D7E765D58755C10L, 0x1A083822CEAFE02DL, 0x9605D5F0E25EC3B0L, 0xD021FF5CD13A2ED5L, + 0x40BDF15D4A672E32L, 0x011355146FD56395L, 0x5DB4832046F3D9E5L, 0x239F8B2D7FF719CCL, + 0x05D1A1AE85B49AA1L, 0x679F848F6E8FC971L, 0x7449BBFF801FED0BL, 0x7D11CDB1C3B7ADF0L, + 0x82C7709E781EB7CCL, 0xF3218F1C9510786CL, 0x331478F3AF51BBE6L, 0x4BB38DE5E7219443L, + 0xAA649C6EBCFD50FCL, 0x8DBD98A352AFD40BL, 0x87D2074B81D79217L, 0x19F3C751D3E92AE1L, + 0xB4AB30F062B19ABFL, 0x7B0500AC42047AC4L, 0xC9452CA81A09D85DL, 0x24AA6C514DA27500L, + 0x4C9F34427501B447L, 0x14A68FD73C910841L, 0xA71B9B83461CBD93L, 0x03488B95B0F1850FL, + 0x637B2B34FF93C040L, 0x09D1BC9A3DD90A94L, 0x3575668334A1DD3BL, 0x735E2B97A4C45A23L, + 0x18727070F1BD400BL, 0x1FCBACD259BF02E7L, 0xD310A7C2CE9B6555L, 0xBF983FE0FE5D8244L, + 0x9F74D14F7454A824L, 0x51EBDC4AB9BA3035L, 0x5C82C505DB9AB0FAL, 0xFCF7FE8A3430B241L, + 0x3253A729B9BA3DDEL, 0x8C74C368081B3075L, 0xB9BC6C87167C33E7L, 0x7EF48F2B83024E20L, + 0x11D505D4C351BD7FL, 0x6568FCA92C76A243L, 0x4DE0B0F40F32A7B8L, 0x96D693460CC37E5DL, + 0x42E240CB63689F2FL, 0x6D2BDCDAE2919661L, 0x42880B0236E4D951L, 0x5F0F4A5898171BB6L, + 0x39F890F579F92F88L, 0x93C5B5F47356388BL, 0x63DC359D8D231B78L, 0xEC16CA8AEA98AD76L, + 0x5355F900C2A82DC7L, 0x07FB9F855A997142L, 0x5093417AA8A7ED5EL, 0x7BCBC38DA25A7F3CL, + 0x19FC8A768CF4B6D4L, 0x637A7780DECFC0D9L, 0x8249A47AEE0E41F7L, 0x79AD695501E7D1E8L, + 0x14ACBAF4777D5776L, 0xF145B6BECCDEA195L, 0xDABF2AC8201752FCL, 0x24C3C94DF9C8D3F6L, + 0xBB6E2924F03912EAL, 0x0CE26C0B95C980D9L, 0xA49CD132BFBF7CC4L, 0xE99D662AF4243939L, + 0x27E6AD7891165C3FL, 0x8535F040B9744FF1L, 0x54B3F4FA5F40D873L, 0x72B12C32127FED2BL, + 0xEE954D3C7B411F47L, 0x9A85AC909A24EAA1L, 0x70AC4CD9F04F21F5L, 0xF9B89D3E99A075C2L, + 0x87B3E2B2B5C907B1L, 0xA366E5B8C54F48B8L, 0xAE4A9346CC3F7CF2L, 0x1920C04D47267BBDL, + 0x87BF02C6B49E2AE9L, 0x092237AC237F3859L, 0xFF07F64EF8ED14D0L, 0x8DE8DCA9F03CC54EL, + 0x9C1633264DB49C89L, 0xB3F22C3D0B0B38EDL, 0x390E5FB44D01144BL, 0x5BFEA5B4712768E9L, + 0x1E1032911FA78984L, 0x9A74ACB964E78CB3L, 0x4F80F7A035DAFB04L, 0x6304D09A0B3738C4L, + 0x2171E64683023A08L, 0x5B9B63EB9CEFF80CL, 0x506AACF489889342L, 0x1881AFC9A3A701D6L, + 0x6503080440750644L, 0xDFD395339CDBF4A7L, 0xEF927DBCF00C20F2L, 0x7B32F7D1E03680ECL, + 0xB9FD7620E7316243L, 0x05A7E8A57DB91B77L, 0xB5889C6E15630A75L, 0x4A750A09CE9573F7L, + 0xCF464CEC899A2F8AL, 0xF538639CE705B824L, 0x3C79A0FF5580EF7FL, 0xEDE6C87F8477609DL, + 0x799E81F05BC93F31L, 0x86536B8CF3428A8CL, 0x97D7374C60087B73L, 0xA246637CFF328532L, + 0x043FCAE60CC0EBA0L, 0x920E449535DD359EL, 0x70EB093B15B290CCL, 0x73A1921916591CBDL, + 0x56436C9FE1A1AA8DL, 0xEFAC4B70633B8F81L, 0xBB215798D45DF7AFL, 0x45F20042F24F1768L, + 0x930F80F4E8EB7462L, 0xFF6712FFCFD75EA1L, 0xAE623FD67468AA70L, 0xDD2C5BC84BC8D8FCL, + 0x7EED120D54CF2DD9L, 0x22FE545401165F1CL, 0xC91800E98FB99929L, 0x808BD68E6AC10365L, + 0xDEC468145B7605F6L, 0x1BEDE3A3AEF53302L, 0x43539603D6C55602L, 0xAA969B5C691CCB7AL, + 0xA87832D392EFEE56L, 0x65942C7B3C7E11AEL, 0xDED2D633CAD004F6L, 0x21F08570F420E565L, + 0xB415938D7DA94E3CL, 0x91B859E59ECB6350L, 0x10CFF333E0ED804AL, 0x28AED140BE0BB7DDL, + 0xC5CC1D89724FA456L, 0x5648F680F11A2741L, 0x2D255069F0B7DAB3L, 0x9BC5A38EF729ABD4L, + 0xEF2F054308F6A2BCL, 0xAF2042F5CC5C2858L, 0x480412BAB7F5BE2AL, 0xAEF3AF4A563DFE43L, + 0x19AFE59AE451497FL, 0x52593803DFF1E840L, 0xF4F076E65F2CE6F0L, 0x11379625747D5AF3L, + 0xBCE5D2248682C115L, 0x9DA4243DE836994FL, 0x066F70B33FE09017L, 0x4DC4DE189B671A1CL, + 0x51039AB7712457C3L, 0xC07A3F80C31FB4B4L, 0xB46EE9C5E64A6E7CL, 0xB3819A42ABE61C87L, + 0x21A007933A522A20L, 0x2DF16F761598AA4FL, 0x763C4A1371B368FDL, 0xF793C46702E086A0L, + 0xD7288E012AEB8D31L, 0xDE336A2A4BC1C44BL, 0x0BF692B38D079F23L, 0x2C604A7A177326B3L, + 0x4850E73E03EB6064L, 0xCFC447F1E53C8E1BL, 0xB05CA3F564268D99L, 0x9AE182C8BC9474E8L, + 0xA4FC4BD4FC5558CAL, 0xE755178D58FC4E76L, 0x69B97DB1A4C03DFEL, 0xF9B5B7C4ACC67C96L, + 0xFC6A82D64B8655FBL, 0x9C684CB6C4D24417L, 0x8EC97D2917456ED0L, 0x6703DF9D2924E97EL, + 0xC547F57E42A7444EL, 0x78E37644E7CAD29EL, 0xFE9A44E9362F05FAL, 0x08BD35CC38336615L, + 0x9315E5EB3A129ACEL, 0x94061B871E04DF75L, 0xDF1D9F9D784BA010L, 0x3BBA57B68871B59DL, + 0xD2B7ADEEDED1F73FL, 0xF7A255D83BC373F8L, 0xD7F4F2448C0CEB81L, 0xD95BE88CD210FFA7L, + 0x336F52F8FF4728E7L, 0xA74049DAC312AC71L, 0xA2F61BB6E437FDB5L, 0x4F2A5CB07F6A35B3L, + 0x87D380BDA5BF7859L, 0x16B9F7E06C453A21L, 0x7BA2484C8A0FD54EL, 0xF3A678CAD9A2E38CL, + 0x39B0BF7DDE437BA2L, 0xFCAF55C1BF8A4424L, 0x18FCF680573FA594L, 0x4C0563B89F495AC3L, + 0x40E087931A00930DL, 0x8CFFA9412EB642C1L, 0x68CA39053261169FL, 0x7A1EE967D27579E2L, + 0x9D1D60E5076F5B6FL, 0x3810E399B6F65BA2L, 0x32095B6D4AB5F9B1L, 0x35CAB62109DD038AL, + 0xA90B24499FCFAFB1L, 0x77A225A07CC2C6BDL, 0x513E5E634C70E331L, 0x4361C0CA3F692F12L, + 0xD941ACA44B20A45BL, 0x528F7C8602C5807BL, 0x52AB92BEB9613989L, 0x9D1DFA2EFC557F73L, + 0x722FF175F572C348L, 0x1D1260A51107FE97L, 0x7A249A57EC0C9BA2L, 0x04208FE9E8F7F2D6L, + 0x5A110C6058B920A0L, 0x0CD9A497658A5698L, 0x56FD23C8F9715A4CL, 0x284C847B9D887AAEL, + 0x04FEABFBBDB619CBL, 0x742E1E651C60BA83L, 0x9A9632E65904AD3CL, 0x881B82A13B51B9E2L, + 0x506E6744CD974924L, 0xB0183DB56FFC6A79L, 0x0ED9B915C66ED37EL, 0x5E11E86D5873D484L, + 0xF678647E3519AC6EL, 0x1B85D488D0F20CC5L, 0xDAB9FE6525D89021L, 0x0D151D86ADB73615L, + 0xA865A54EDCC0F019L, 0x93C42566AEF98FFBL, 0x99E7AFEABE000731L, 0x48CBFF086DDF285AL, + 0x7F9B6AF1EBF78BAFL, 0x58627E1A149BBA21L, 0x2CD16E2ABD791E33L, 0xD363EFF5F0977996L, + 0x0CE2A38C344A6EEDL, 0x1A804AADB9CFA741L, 0x907F30421D78C5DEL, 0x501F65EDB3034D07L, + 0x37624AE5A48FA6E9L, 0x957BAF61700CFF4EL, 0x3A6C27934E31188AL, 0xD49503536ABCA345L, + 0x088E049589C432E0L, 0xF943AEE7FEBF21B8L, 0x6C3B8E3E336139D3L, 0x364F6FFA464EE52EL, + 0xD60F6DCEDC314222L, 0x56963B0DCA418FC0L, 0x16F50EDF91E513AFL, 0xEF1955914B609F93L, + 0x565601C0364E3228L, 0xECB53939887E8175L, 0xBAC7A9A18531294BL, 0xB344C470397BBA52L, + 0x65D34954DAF3CEBDL, 0xB4B81B3FA97511E2L, 0xB422061193D6F6A7L, 0x071582401C38434DL, + 0x7A13F18BBEDC4FF5L, 0xBC4097B116C524D2L, 0x59B97885E2F2EA28L, 0x99170A5DC3115544L, + 0x6F423357E7C6A9F9L, 0x325928EE6E6F8794L, 0xD0E4366228B03343L, 0x565C31F7DE89EA27L, + 0x30F5611484119414L, 0xD873DB391292ED4FL, 0x7BD94E1D8E17DEBCL, 0xC7D9F16864A76E94L, + 0x947AE053EE56E63CL, 0xC8C93882F9475F5FL, 0x3A9BF55BA91F81CAL, 0xD9A11FBB3D9808E4L, + 0x0FD22063EDC29FCAL, 0xB3F256D8ACA0B0B9L, 0xB03031A8B4516E84L, 0x35DD37D5871448AFL, + 0xE9F6082B05542E4EL, 0xEBFAFA33D7254B59L, 0x9255ABB50D532280L, 0xB9AB4CE57F2D34F3L, + 0x693501D628297551L, 0xC62C58F97DD949BFL, 0xCD454F8F19C5126AL, 0xBBE83F4ECC2BDECBL, + 0xDC842B7E2819E230L, 0xBA89142E007503B8L, 0xA3BC941D0A5061CBL, 0xE9F6760E32CD8021L, + 0x09C7E552BC76492FL, 0x852F54934DA55CC9L, 0x8107FCCF064FCF56L, 0x098954D51FFF6580L, + 0x23B70EDB1955C4BFL, 0xC330DE426430F69DL, 0x4715ED43E8A45C0AL, 0xA8D7E4DAB780A08DL, + 0x0572B974F03CE0BBL, 0xB57D2E985E1419C7L, 0xE8D9ECBE2CF3D73FL, 0x2FE4B17170E59750L, + 0x11317BA87905E790L, 0x7FBF21EC8A1F45ECL, 0x1725CABFCB045B00L, 0x964E915CD5E2B207L, + 0x3E2B8BCBF016D66DL, 0xBE7444E39328A0ACL, 0xF85B2B4FBCDE44B7L, 0x49353FEA39BA63B1L, + 0x1DD01AAFCD53486AL, 0x1FCA8A92FD719F85L, 0xFC7C95D827357AFAL, 0x18A6A990C8B35EBDL, + 0xCCCB7005C6B9C28DL, 0x3BDBB92C43B17F26L, 0xAA70B5B4F89695A2L, 0xE94C39A54A98307FL, + 0xB7A0B174CFF6F36EL, 0xD4DBA84729AF48ADL, 0x2E18BC1AD9704A68L, 0x2DE0966DAF2F8B1CL, + 0xB9C11D5B1E43A07EL, 0x64972D68DEE33360L, 0x94628D38D0C20584L, 0xDBC0D2B6AB90A559L, + 0xD2733C4335C6A72FL, 0x7E75D99D94A70F4DL, 0x6CED1983376FA72BL, 0x97FCAACBF030BC24L, + 0x7B77497B32503B12L, 0x8547EDDFB81CCB94L, 0x79999CDFF70902CBL, 0xCFFE1939438E9B24L, + 0x829626E3892D95D7L, 0x92FAE24291F2B3F1L, 0x63E22C147B9C3403L, 0xC678B6D860284A1CL, + 0x5873888850659AE7L, 0x0981DCD296A8736DL, 0x9F65789A6509A440L, 0x9FF38FED72E9052FL, + 0xE479EE5B9930578CL, 0xE7F28ECD2D49EECDL, 0x56C074A581EA17FEL, 0x5544F7D774B14AEFL, + 0x7B3F0195FC6F290FL, 0x12153635B2C0CF57L, 0x7F5126DBBA5E0CA7L, 0x7A76956C3EAFB413L, + 0x3D5774A11D31AB39L, 0x8A1B083821F40CB4L, 0x7B4A38E32537DF62L, 0x950113646D1D6E03L, + 0x4DA8979A0041E8A9L, 0x3BC36E078F7515D7L, 0x5D0A12F27AD310D1L, 0x7F9D1A2E1EBE1327L, + 0xDA3A361B1C5157B1L, 0xDCDD7D20903D0C25L, 0x36833336D068F707L, 0xCE68341F79893389L, + 0xAB9090168DD05F34L, 0x43954B3252DC25E5L, 0xB438C2B67F98E5E9L, 0x10DCD78E3851A492L, + 0xDBC27AB5447822BFL, 0x9B3CDB65F82CA382L, 0xB67B7896167B4C84L, 0xBFCED1B0048EAC50L, + 0xA9119B60369FFEBDL, 0x1FFF7AC80904BF45L, 0xAC12FB171817EEE7L, 0xAF08DA9177DDA93DL, + 0x1B0CAB936E65C744L, 0xB559EB1D04E5E932L, 0xC37B45B3F8D6F2BAL, 0xC3A9DC228CAAC9E9L, + 0xF3B8B6675A6507FFL, 0x9FC477DE4ED681DAL, 0x67378D8ECCEF96CBL, 0x6DD856D94D259236L, + 0xA319CE15B0B4DB31L, 0x073973751F12DD5EL, 0x8A8E849EB32781A5L, 0xE1925C71285279F5L, + 0x74C04BF1790C0EFEL, 0x4DDA48153C94938AL, 0x9D266D6A1CC0542CL, 0x7440FB816508C4FEL, + 0x13328503DF48229FL, 0xD6BF7BAEE43CAC40L, 0x4838D65F6EF6748FL, 0x1E152328F3318DEAL, + 0x8F8419A348F296BFL, 0x72C8834A5957B511L, 0xD7A023A73260B45CL, 0x94EBC8ABCFB56DAEL, + 0x9FC10D0F989993E0L, 0xDE68A2355B93CAE6L, 0xA44CFE79AE538BBEL, 0x9D1D84FCCE371425L, + 0x51D2B1AB2DDFB636L, 0x2FD7E4B9E72CD38CL, 0x65CA5B96B7552210L, 0xDD69A0D8AB3B546DL, + 0x604D51B25FBF70E2L, 0x73AA8A564FB7AC9EL, 0x1A8C1E992B941148L, 0xAAC40A2703D9BEA0L, + 0x764DBEAE7FA4F3A6L, 0x1E99B96E70A9BE8BL, 0x2C5E9DEB57EF4743L, 0x3A938FEE32D29981L, + 0x26E6DB8FFDF5ADFEL, 0x469356C504EC9F9DL, 0xC8763C5B08D1908CL, 0x3F6C6AF859D80055L, + 0x7F7CC39420A3A545L, 0x9BFB227EBDF4C5CEL, 0x89039D79D6FC5C5CL, 0x8FE88B57305E2AB6L, + 0xA09E8C8C35AB96DEL, 0xFA7E393983325753L, 0xD6B6D0ECC617C699L, 0xDFEA21EA9E7557E3L, + 0xB67C1FA481680AF8L, 0xCA1E3785A9E724E5L, 0x1CFC8BED0D681639L, 0xD18D8549D140CAEAL, + 0x4ED0FE7E9DC91335L, 0xE4DBF0634473F5D2L, 0x1761F93A44D5AEFEL, 0x53898E4C3910DA55L, + 0x734DE8181F6EC39AL, 0x2680B122BAA28D97L, 0x298AF231C85BAFABL, 0x7983EED3740847D5L, + 0x66C1A2A1A60CD889L, 0x9E17E49642A3E4C1L, 0xEDB454E7BADC0805L, 0x50B704CAB602C329L, + 0x4CC317FB9CDDD023L, 0x66B4835D9EAFEA22L, 0x219B97E26FFC81BDL, 0x261E4E4C0A333A9DL, + 0x1FE2CCA76517DB90L, 0xD7504DFA8816EDBBL, 0xB9571FA04DC089C8L, 0x1DDC0325259B27DEL, + 0xCF3F4688801EB9AAL, 0xF4F5D05C10CAB243L, 0x38B6525C21A42B0EL, 0x36F60E2BA4FA6800L, + 0xEB3593803173E0CEL, 0x9C4CD6257C5A3603L, 0xAF0C317D32ADAA8AL, 0x258E5A80C7204C4BL, + 0x8B889D624D44885DL, 0xF4D14597E660F855L, 0xD4347F66EC8941C3L, 0xE699ED85B0DFB40DL, + 0x2472F6207C2D0484L, 0xC2A1E7B5B459AEB5L, 0xAB4F6451CC1D45ECL, 0x63767572AE3D6174L, + 0xA59E0BD101731A28L, 0x116D0016CB948F09L, 0x2CF9C8CA052F6E9FL, 0x0B090A7560A968E3L, + 0xABEEDDB2DDE06FF1L, 0x58EFC10B06A2068DL, 0xC6E57A78FBD986E0L, 0x2EAB8CA63CE802D7L, + 0x14A195640116F336L, 0x7C0828DD624EC390L, 0xD74BBE77E6116AC7L, 0x804456AF10F5FB53L, + 0xEBE9EA2ADF4321C7L, 0x03219A39EE587A30L, 0x49787FEF17AF9924L, 0xA1E9300CD8520548L, + 0x5B45E522E4B1B4EFL, 0xB49C3B3995091A36L, 0xD4490AD526F14431L, 0x12A8F216AF9418C2L, + 0x001F837CC7350524L, 0x1877B51E57A764D5L, 0xA2853B80F17F58EEL, 0x993E1DE72D36D310L, + 0xB3598080CE64A656L, 0x252F59CF0D9F04BBL, 0xD23C8E176D113600L, 0x1BDA0492E7E4586EL, + 0x21E0BD5026C619BFL, 0x3B097ADAF088F94EL, 0x8D14DEDB30BE846EL, 0xF95CFFA23AF5F6F4L, + 0x3871700761B3F743L, 0xCA672B91E9E4FA16L, 0x64C8E531BFF53B55L, 0x241260ED4AD1E87DL, + 0x106C09B972D2E822L, 0x7FBA195410E5CA30L, 0x7884D9BC6CB569D8L, 0x0647DFEDCD894A29L, + 0x63573FF03E224774L, 0x4FC8E9560F91B123L, 0x1DB956E450275779L, 0xB8D91274B9E9D4FBL, + 0xA2EBEE47E2FBFCE1L, 0xD9F1F30CCD97FB09L, 0xEFED53D75FD64E6BL, 0x2E6D02C36017F67FL, + 0xA9AA4D20DB084E9BL, 0xB64BE8D8B25396C1L, 0x70CB6AF7C2D5BCF0L, 0x98F076A4F7A2322EL, + 0xBF84470805E69B5FL, 0x94C3251F06F90CF3L, 0x3E003E616A6591E9L, 0xB925A6CD0421AFF3L, + 0x61BDD1307C66E300L, 0xBF8D5108E27E0D48L, 0x240AB57A8B888B20L, 0xFC87614BAF287E07L, + 0xEF02CDD06FFDB432L, 0xA1082C0466DF6C0AL, 0x8215E577001332C8L, 0xD39BB9C3A48DB6CFL, + 0x2738259634305C14L, 0x61CF4F94C97DF93DL, 0x1B6BACA2AE4E125BL, 0x758F450C88572E0BL, + 0x959F587D507A8359L, 0xB063E962E045F54DL, 0x60E8ED72C0DFF5D1L, 0x7B64978555326F9FL, + 0xFD080D236DA814BAL, 0x8C90FD9B083F4558L, 0x106F72FE81E2C590L, 0x7976033A39F7D952L, + 0xA4EC0132764CA04BL, 0x733EA705FAE4FA77L, 0xB4D8F77BC3E56167L, 0x9E21F4F903B33FD9L, + 0x9D765E419FB69F6DL, 0xD30C088BA61EA5EFL, 0x5D94337FBFAF7F5BL, 0x1A4E4822EB4D7A59L, + 0x6FFE73E81B637FB3L, 0xDDF957BC36D8B9CAL, 0x64D0E29EEA8838B3L, 0x08DD9BDFD96B9F63L, + 0x087E79E5A57D1D13L, 0xE328E230E3E2B3FBL, 0x1C2559E30F0946BEL, 0x720BF5F26F4D2EAAL, + 0xB0774D261CC609DBL, 0x443F64EC5A371195L, 0x4112CF68649A260EL, 0xD813F2FAB7F5C5CAL, + 0x660D3257380841EEL, 0x59AC2C7873F910A3L, 0xE846963877671A17L, 0x93B633ABFA3469F8L, + 0xC0C0F5A60EF4CDCFL, 0xCAF21ECD4377B28CL, 0x57277707199B8175L, 0x506C11B9D90E8B1DL, + 0xD83CC2687A19255FL, 0x4A29C6465A314CD1L, 0xED2DF21216235097L, 0xB5635C95FF7296E2L, + 0x22AF003AB672E811L, 0x52E762596BF68235L, 0x9AEBA33AC6ECC6B0L, 0x944F6DE09134DFB6L, + 0x6C47BEC883A7DE39L, 0x6AD047C430A12104L, 0xA5B1CFDBA0AB4067L, 0x7C45D833AFF07862L, + 0x5092EF950A16DA0BL, 0x9338E69C052B8E7BL, 0x455A4B4CFE30E3F5L, 0x6B02E63195AD0CF8L, + 0x6B17B224BAD6BF27L, 0xD1E0CCD25BB9C169L, 0xDE0C89A556B9AE70L, 0x50065E535A213CF6L, + 0x9C1169FA2777B874L, 0x78EDEFD694AF1EEDL, 0x6DC93D9526A50E68L, 0xEE97F453F06791EDL, + 0x32AB0EDB696703D3L, 0x3A6853C7E70757A7L, 0x31865CED6120F37DL, 0x67FEF95D92607890L, + 0x1F2B1D1F15F6DC9CL, 0xB69E38A8965C6B65L, 0xAA9119FF184CCCF4L, 0xF43C732873F24C13L, + 0xFB4A3D794A9A80D2L, 0x3550C2321FD6109CL, 0x371F77E76BB8417EL, 0x6BFA9AAE5EC05779L, + 0xCD04F3FF001A4778L, 0xE3273522064480CAL, 0x9F91508BFFCFC14AL, 0x049A7F41061A9E60L, + 0xFCB6BE43A9F2FE9BL, 0x08DE8A1C7797DA9BL, 0x8F9887E6078735A1L, 0xB5B4071DBFC73A66L, + 0x230E343DFBA08D33L, 0x43ED7F5A0FAE657DL, 0x3A88A0FBBCB05C63L, 0x21874B8B4D2DBC4FL, + 0x1BDEA12E35F6A8C9L, 0x53C065C6C8E63528L, 0xE34A1D250E7A8D6BL, 0xD6B04D3B7651DD7EL, + 0x5E90277E7CB39E2DL, 0x2C046F22062DC67DL, 0xB10BB459132D0A26L, 0x3FA9DDFB67E2F199L, + 0x0E09B88E1914F7AFL, 0x10E8B35AF3EEAB37L, 0x9EEDECA8E272B933L, 0xD4C718BC4AE8AE5FL, + 0x81536D601170FC20L, 0x91B534F885818A06L, 0xEC8177F83F900978L, 0x190E714FADA5156EL, + 0xB592BF39B0364963L, 0x89C350C893AE7DC1L, 0xAC042E70F8B383F2L, 0xB49B52E587A1EE60L, + 0xFB152FE3FF26DA89L, 0x3E666E6F69AE2C15L, 0x3B544EBE544C19F9L, 0xE805A1E290CF2456L, + 0x24B33C9D7ED25117L, 0xE74733427B72F0C1L, 0x0A804D18B7097475L, 0x57E3306D881EDB4FL, + 0x4AE7D6A36EB5DBCBL, 0x2D8D5432157064C8L, 0xD1E649DE1E7F268BL, 0x8A328A1CEDFE552CL, + 0x07A3AEC79624C7DAL, 0x84547DDC3E203C94L, 0x990A98FD5071D263L, 0x1A4FF12616EEFC89L, + 0xF6F7FD1431714200L, 0x30C05B1BA332F41CL, 0x8D2636B81555A786L, 0x46C9FEB55D120902L, + 0xCCEC0A73B49C9921L, 0x4E9D2827355FC492L, 0x19EBB029435DCB0FL, 0x4659D2B743848A2CL, + 0x963EF2C96B33BE31L, 0x74F85198B05A2E7DL, 0x5A0F544DD2B1FB18L, 0x03727073C2E134B1L, + 0xC7F6AA2DE59AEA61L, 0x352787BAA0D7C22FL, 0x9853EAB63B5E0B35L, 0xABBDCDD7ED5C0860L, + 0xCF05DAF5AC8D77B0L, 0x49CAD48CEBF4A71EL, 0x7A4C10EC2158C4A6L, 0xD9E92AA246BF719EL, + 0x13AE978D09FE5557L, 0x730499AF921549FFL, 0x4E4B705B92903BA4L, 0xFF577222C14F0A3AL, + 0x55B6344CF97AAFAEL, 0xB862225B055B6960L, 0xCAC09AFBDDD2CDB4L, 0xDAF8E9829FE96B5FL, + 0xB5FDFC5D3132C498L, 0x310CB380DB6F7503L, 0xE87FBB46217A360EL, 0x2102AE466EBB1148L, + 0xF8549E1A3AA5E00DL, 0x07A69AFDCC42261AL, 0xC4C118BFE78FEAAEL, 0xF9F4892ED96BD438L, + 0x1AF3DBE25D8F45DAL, 0xF5B4B0B0D2DEEEB4L, 0x962ACEEFA82E1C84L, 0x046E3ECAAF453CE9L, + 0xF05D129681949A4CL, 0x964781CE734B3C84L, 0x9C2ED44081CE5FBDL, 0x522E23F3925E319EL, + 0x177E00F9FC32F791L, 0x2BC60A63A6F3B3F2L, 0x222BBFAE61725606L, 0x486289DDCC3D6780L, + 0x7DC7785B8EFDFC80L, 0x8AF38731C02BA980L, 0x1FAB64EA29A2DDF7L, 0xE4D9429322CD065AL, + 0x9DA058C67844F20CL, 0x24C0E332B70019B0L, 0x233003B5A6CFE6ADL, 0xD586BD01C5C217F6L, + 0x5E5637885F29BC2BL, 0x7EBA726D8C94094BL, 0x0A56A5F0BFE39272L, 0xD79476A84EE20D06L, + 0x9E4C1269BAA4BF37L, 0x17EFEE45B0DEE640L, 0x1D95B0A5FCF90BC6L, 0x93CBE0B699C2585DL, + 0x65FA4F227A2B6D79L, 0xD5F9E858292504D5L, 0xC2B5A03F71471A6FL, 0x59300222B4561E00L, + 0xCE2F8642CA0712DCL, 0x7CA9723FBB2E8988L, 0x2785338347F2BA08L, 0xC61BB3A141E50E8CL, + 0x150F361DAB9DEC26L, 0x9F6A419D382595F4L, 0x64A53DC924FE7AC9L, 0x142DE49FFF7A7C3DL, + 0x0C335248857FA9E7L, 0x0A9C32D5EAE45305L, 0xE6C42178C4BBB92EL, 0x71F1CE2490D20B07L, + 0xF1BCC3D275AFE51AL, 0xE728E8C83C334074L, 0x96FBF83A12884624L, 0x81A1549FD6573DA5L, + 0x5FA7867CAF35E149L, 0x56986E2EF3ED091BL, 0x917F1DD5F8886C61L, 0xD20D8C88C8FFE65FL, + 0x31D71DCE64B2C310L, 0xF165B587DF898190L, 0xA57E6339DD2CF3A0L, 0x1EF6E6DBB1961EC9L, + 0x70CC73D90BC26E24L, 0xE21A6B35DF0C3AD7L, 0x003A93D8B2806962L, 0x1C99DED33CB890A1L, + 0xCF3145DE0ADD4289L, 0xD0E4427A5514FB72L, 0x77C621CC9FB3A483L, 0x67A34DAC4356550BL, + 0xF8D626AAAF278509L, + }; + + static boolean canHandle(BookOptions options) { + return options.filename.endsWith(".bin"); + } + + /** Return true if the external book is available. */ + @Override + public final boolean enabled() { + return bookFile.canRead(); + } + + private static class PGBookEntry { + private byte[] data; + + PGBookEntry() { + data = new byte[16]; + } + + private final long getBytes(int start, int len) { + long ret = 0; + int stop = start + len; + for (int i = start; i < stop; i++) { + int val = data[i]; + if (val < 0) val += 256; + ret = (ret << 8) + val; + } + return ret; + } + + final long getKey() { + return getBytes(0, 8); + } + + final Move getMove(Position pos) { + short move = (short)getBytes(8, 2); + boolean wtm = pos.whiteMove; + int toFile = move & 7; + int toRow = (move >> 3) & 7; + int fromFile = (move >> 6) & 7; + int fromRow = (move >> 9) & 7; + int prom = (move >> 12) & 7; + + int from = Position.getSquare(fromFile, fromRow); + int to = Position.getSquare(toFile, toRow); + int promoteTo = Piece.EMPTY; + switch (prom) { + case 1: promoteTo = wtm ? Piece.WKNIGHT : Piece.BKNIGHT; break; + case 2: promoteTo = wtm ? Piece.WBISHOP : Piece.BBISHOP; break; + case 3: promoteTo = wtm ? Piece.WROOK : Piece.BROOK; break; + case 4: promoteTo = wtm ? Piece.WQUEEN : Piece.BQUEEN; break; + default: promoteTo = Piece.EMPTY; break; + } + + // Convert castling moves + if ((from == 4) && (pos.getPiece(from) == Piece.WKING)) { + if (to == 7) + to = 6; + else if (to == 0) + to = 2; + } + if ((from == 60) && (pos.getPiece(from) == Piece.BKING)) { + if (to == 56+7) + to = 56+6; + else if (to == 56+0) + to = 56+2; + } + + Move m = new Move(from, to, promoteTo); + return m; + } + final int getWeight() { return (int)getBytes(10, 2); } + } + + private final void readEntry(RandomAccessFile f, long entNo, PGBookEntry ent) throws IOException { + f.seek(entNo * 16); + if (f.read(ent.data) != 16) { + for (int i = 0; i < 16; i++) ent.data[i] = 0; + } + } + + /** Return true if key1 < key2, when compared as unsigned longs. */ + private final boolean keyLess(long key1, long key2) { + if ((key1 < 0) == (key2 < 0)) { // Same sign, normal compare + return key1 < key2; + } else { // The negative number is largest + return key2 < 0; + } + } + + @Override + public final List getBookEntries(Position pos) { + try { + RandomAccessFile f = new RandomAccessFile(bookFile, "r"); + long numEntries = f.length() / 16; + long key = getHashKey(pos); + PGBookEntry ent = new PGBookEntry(); + + // Find first entry with hash key >= wantedKey + long lo = -1; + long hi = numEntries; + // ent[lo] < key <= ent[hi] + while (hi - lo > 1) { + long mid = (lo + hi) / 2; + readEntry(f, mid, ent); + long midKey = ent.getKey(); + if (keyLess(midKey, key)) { + lo = mid; + } else { + hi = mid; + } + } + + // Read all entries with matching hash key + List ret = new ArrayList(); + long entNo = hi; + while (entNo < numEntries) { + readEntry(f, entNo, ent); + if (ent.getKey() != key) + break; + Move m = ent.getMove(pos); + BookEntry be = new BookEntry(m); + be.weight = ent.getWeight(); + ret.add(be); + entNo++; + } + f.close(); + return ret; + } catch (FileNotFoundException e) { + return null; + } catch (IOException e) { + return null; + } + } +} diff --git a/DroidFish/src/org/petero/droidfish/engine/UCIEngine.java b/DroidFish/src/org/petero/droidfish/engine/UCIEngine.java new file mode 100644 index 0000000..ce15901 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/engine/UCIEngine.java @@ -0,0 +1,51 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.engine; + +public interface UCIEngine { + + /** Start engine. */ + public void initialize(); + + /** Shut down engine. */ + public void shutDown(); + + /** + * Read a line from the engine. + * @param timeoutMillis Maximum time to wait for data + * @return The line, without terminating newline characters, + * or empty string if no data available, + * or null if I/O error. + */ + public String readLineFromEngine(int timeoutMillis); + + /** Write a line to the engine. \n will be added automatically. */ + public void writeLineToEngine(String data); + + /** Set the engine strength, allowed values 0 - 1000. */ + public void setStrength(int strength); + + /** Add strength information to the engine name. */ + public String addStrengthToName(); + + /** Set an engine option. */ + public void setOption(String name, int value); + public void setOption(String name, boolean value); + public void setOption(String name, String value); +} diff --git a/DroidFish/src/org/petero/droidfish/engine/UCIEngineBase.java b/DroidFish/src/org/petero/droidfish/engine/UCIEngineBase.java new file mode 100644 index 0000000..9a4606f --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/engine/UCIEngineBase.java @@ -0,0 +1,57 @@ +package org.petero.droidfish.engine; + +import java.util.HashMap; + +public abstract class UCIEngineBase implements UCIEngine { + + private boolean processAlive; + protected int strength = 1000; + + protected UCIEngineBase() { + processAlive = false; + } + + protected abstract void startProcess(); + + @Override + public final void initialize() { + if (!processAlive) { + startProcess(); + processAlive = true; + } + } + + @Override + public final void shutDown() { + if (processAlive) { + writeLineToEngine("quit"); + processAlive = false; + } + } + + @Override + public String addStrengthToName() { + return strength < 1000 ? String.format(" (%.1f%%)", strength * 0.1) : ""; + } + + @Override + public void setOption(String name, int value) { + setOption(name, String.format("%d", value)); + } + + @Override + public void setOption(String name, boolean value) { + setOption(name, value ? "true" : "false"); + } + + private HashMap options = new HashMap(); + + @Override + public void setOption(String name, String value) { + String currVal = options.get(name.toLowerCase()); + if (value.equals(currVal)) + return; + writeLineToEngine(String.format("setoption name %s value %s", name, value)); + options.put(name.toLowerCase(), value); + } +} diff --git a/DroidFish/src/org/petero/droidfish/engine/cuckoochess/CuckooChessEngine.java b/DroidFish/src/org/petero/droidfish/engine/cuckoochess/CuckooChessEngine.java new file mode 100644 index 0000000..550b47e --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/engine/cuckoochess/CuckooChessEngine.java @@ -0,0 +1,252 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.engine.cuckoochess; + +import chess.ChessParseError; +import chess.ComputerPlayer; +import chess.Move; +import chess.Position; +import chess.TextIO; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.Pipe; +import java.util.ArrayList; + +import org.petero.droidfish.engine.UCIEngineBase; + +/** + * UCI interface to cuckoochess engine. + * @author petero + */ +public class CuckooChessEngine extends UCIEngineBase { + + // Data set by the "position" command. + private Position pos; + private ArrayList moves; + + // Engine data + private DroidEngineControl engine; + + // Set to true to break out of main loop + private boolean quit; + + private Pipe guiToEngine; + private Pipe engineToGui; + private NioInputStream inFromEngine; + + public CuckooChessEngine() { + try { + pos = TextIO.readFEN(TextIO.startPosFEN); + } catch (ChessParseError ex) { + throw new RuntimeException(); + } + moves = new ArrayList(); + quit = false; + try { + guiToEngine = Pipe.open(); + engineToGui = Pipe.open(); + inFromEngine = new NioInputStream(engineToGui); + } catch (IOException e) { + } + } + + @Override + public void setStrength(int strength) { + this.strength = strength; + setOption("strength", strength); + } + + protected final void startProcess() { + new Thread(new Runnable() { + public void run() { + NioInputStream in = new NioInputStream(guiToEngine); + NioPrintStream out = new NioPrintStream(engineToGui); + mainLoop(in, out); + } + }).start(); + } + + private final void mainLoop(NioInputStream is, NioPrintStream os) { + String line; + while ((line = is.readLine()) != null) { + handleCommand(line, os); + if (quit) { + break; + } + } + } + + @Override + public final String readLineFromEngine(int timeoutMillis) { + String ret = inFromEngine.readLine(timeoutMillis); + if (ret == null) + return null; + if (ret.length() > 0) { +// System.out.printf("Engine -> GUI: %s\n", ret); + } + return ret; + } + + @Override + public final synchronized void writeLineToEngine(String data) { +// System.out.printf("GUI -> Engine: %s\n", data); + try { + String s = data + "\n"; + guiToEngine.sink().write(ByteBuffer.wrap(s.getBytes())); + } catch (IOException e) { + } + } + + private final void handleCommand(String cmdLine, NioPrintStream os) { + String[] tokens = tokenize(cmdLine); + try { + String cmd = tokens[0]; + if (cmd.equals("uci")) { + os.printf("id name %s%n", ComputerPlayer.engineName); + os.printf("id author Peter Osterlund%n"); + DroidEngineControl.printOptions(os); + os.printf("uciok%n"); + } else if (cmd.equals("isready")) { + initEngine(os); + os.printf("readyok%n"); + } else if (cmd.equals("setoption")) { + initEngine(os); + StringBuilder optionName = new StringBuilder(); + StringBuilder optionValue = new StringBuilder(); + if (tokens[1].endsWith("name")) { + int idx = 2; + while ((idx < tokens.length) && !tokens[idx].equals("value")) { + optionName.append(tokens[idx++].toLowerCase()); + optionName.append(' '); + } + if ((idx < tokens.length) && tokens[idx++].equals("value")) { + while ((idx < tokens.length)) { + optionValue.append(tokens[idx++].toLowerCase()); + optionValue.append(' '); + } + } + engine.setOption(optionName.toString().trim(), optionValue.toString().trim()); + } + } else if (cmd.equals("ucinewgame")) { + if (engine != null) { + engine.newGame(); + } + } else if (cmd.equals("position")) { + String fen = null; + int idx = 1; + if (tokens[idx].equals("startpos")) { + idx++; + fen = TextIO.startPosFEN; + } else if (tokens[idx].equals("fen")) { + idx++; + StringBuilder sb = new StringBuilder(); + while ((idx < tokens.length) && !tokens[idx].equals("moves")) { + sb.append(tokens[idx++]); + sb.append(' '); + } + fen = sb.toString().trim(); + } + if (fen != null) { + pos = TextIO.readFEN(fen); + moves.clear(); + if ((idx < tokens.length) && tokens[idx++].equals("moves")) { + for (int i = idx; i < tokens.length; i++) { + Move m = TextIO.uciStringToMove(tokens[i]); + if (m != null) { + moves.add(m); + } else { + break; + } + } + } + } + } else if (cmd.equals("go")) { + initEngine(os); + int idx = 1; + SearchParams sPar = new SearchParams(); + boolean ponder = false; + while (idx < tokens.length) { + String subCmd = tokens[idx++]; + if (subCmd.equals("searchmoves")) { + while (idx < tokens.length) { + Move m = TextIO.uciStringToMove(tokens[idx]); + if (m != null) { + sPar.searchMoves.add(m); + idx++; + } else { + break; + } + } + } else if (subCmd.equals("ponder")) { + ponder = true; + } else if (subCmd.equals("wtime")) { + sPar.wTime = Integer.parseInt(tokens[idx++]); + } else if (subCmd.equals("btime")) { + sPar.bTime = Integer.parseInt(tokens[idx++]); + } else if (subCmd.equals("winc")) { + sPar.wInc = Integer.parseInt(tokens[idx++]); + } else if (subCmd.equals("binc")) { + sPar.bInc = Integer.parseInt(tokens[idx++]); + } else if (subCmd.equals("movestogo")) { + sPar.movesToGo = Integer.parseInt(tokens[idx++]); + } else if (subCmd.equals("depth")) { + sPar.depth = Integer.parseInt(tokens[idx++]); + } else if (subCmd.equals("nodes")) { + sPar.nodes = Integer.parseInt(tokens[idx++]); + } else if (subCmd.equals("mate")) { + sPar.mate = Integer.parseInt(tokens[idx++]); + } else if (subCmd.equals("movetime")) { + sPar.moveTime = Integer.parseInt(tokens[idx++]); + } else if (subCmd.equals("infinite")) { + sPar.infinite = true; + } + } + if (ponder) { + engine.startPonder(pos, moves, sPar); + } else { + engine.startSearch(pos, moves, sPar); + } + } else if (cmd.equals("stop")) { + engine.stopSearch(); + } else if (cmd.equals("ponderhit")) { + engine.ponderHit(); + } else if (cmd.equals("quit")) { + if (engine != null) { + engine.stopSearch(); + } + quit = true; + } + } catch (ChessParseError ex) { + } catch (ArrayIndexOutOfBoundsException e) { + } catch (NumberFormatException nfe) { + } + } + + private final void initEngine(NioPrintStream os) { + if (engine == null) { + engine = new DroidEngineControl(os); + } + } + + /** Convert a string to tokens by splitting at whitespace characters. */ + private final String[] tokenize(String cmdLine) { + cmdLine = cmdLine.trim(); + return cmdLine.split("\\s+"); + } +} diff --git a/DroidFish/src/org/petero/droidfish/engine/cuckoochess/DroidEngineControl.java b/DroidFish/src/org/petero/droidfish/engine/cuckoochess/DroidEngineControl.java new file mode 100644 index 0000000..f3b2e45 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/engine/cuckoochess/DroidEngineControl.java @@ -0,0 +1,388 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.engine.cuckoochess; + +import chess.Book; +import chess.ComputerPlayer; +import chess.Move; +import chess.MoveGen; +import chess.Piece; +import chess.Position; +import chess.Search; +import chess.TextIO; +import chess.TranspositionTable; +import chess.TranspositionTable.TTEntry; +import chess.UndoInfo; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +/** + * Control the search thread. + * @author petero + */ +public class DroidEngineControl { + NioPrintStream os; + + Thread engineThread; + private final Object threadMutex; + Search sc; + TranspositionTable tt; + MoveGen moveGen; + + Position pos; + long[] posHashList; + int posHashListSize; + boolean ponder; // True if currently doing pondering + boolean onePossibleMove; + boolean infinite; + + int minTimeLimit; + int maxTimeLimit; + int maxDepth; + int maxNodes; + List searchMoves; + + // Options + int hashSizeMB = 2; + boolean ownBook = false; + boolean analyseMode = false; + boolean ponderMode = true; + + // Reduced strength variables + private int strength = 1000; + private long randomSeed = 0; + private Random rndGen = new Random(); + + /** + * This class is responsible for sending "info" strings during search. + */ + static class SearchListener implements Search.Listener { + NioPrintStream os; + + SearchListener(NioPrintStream os) { + this.os = os; + } + + public void notifyDepth(int depth) { + os.printf("info depth %d%n", depth); + } + + public void notifyCurrMove(Move m, int moveNr) { + os.printf("info currmove %s currmovenumber %d%n", moveToString(m), moveNr); + } + + public void notifyPV(int depth, int score, int time, int nodes, int nps, boolean isMate, + boolean upperBound, boolean lowerBound, ArrayList pv) { + StringBuilder pvBuf = new StringBuilder(); + for (Move m : pv) { + pvBuf.append(" "); + pvBuf.append(moveToString(m)); + } + String bound = ""; + if (upperBound) { + bound = " upperbound"; + } else if (lowerBound) { + bound = " lowerbound"; + } + os.printf("info depth %d score %s %d%s time %d nodes %d nps %d pv%s%n", + depth, isMate ? "mate" : "cp", score, bound, time, nodes, nps, pvBuf.toString()); + } + + public void notifyStats(int nodes, int nps, int time) { + os.printf("info nodes %d nps %d time %d%n", nodes, nps, time); + } + } + + public DroidEngineControl(NioPrintStream os) { + this.os = os; + threadMutex = new Object(); + setupTT(); + moveGen = new MoveGen(); + } + + final public void startSearch(Position pos, ArrayList moves, SearchParams sPar) { + setupPosition(new Position(pos), moves); + computeTimeLimit(sPar); + ponder = false; + infinite = (maxTimeLimit < 0) && (maxDepth < 0) && (maxNodes < 0); + startThread(minTimeLimit, maxTimeLimit, maxDepth, maxNodes); + searchMoves = sPar.searchMoves; + } + + final public void startPonder(Position pos, List moves, SearchParams sPar) { + setupPosition(new Position(pos), moves); + computeTimeLimit(sPar); + ponder = true; + infinite = false; + startThread(-1, -1, -1, -1); + } + + final public void ponderHit() { + Search mySearch; + synchronized (threadMutex) { + mySearch = sc; + } + if (mySearch != null) { + if (onePossibleMove) { + if (minTimeLimit > 1) minTimeLimit = 1; + if (maxTimeLimit > 1) maxTimeLimit = 1; + } + mySearch.timeLimit(minTimeLimit, maxTimeLimit); + } + infinite = (maxTimeLimit < 0) && (maxDepth < 0) && (maxNodes < 0); + ponder = false; + } + + final public void stopSearch() { + stopThread(); + } + + final public void newGame() { + randomSeed = rndGen.nextLong(); + tt.clear(); + } + + /** + * Compute thinking time for current search. + */ + final public void computeTimeLimit(SearchParams sPar) { + minTimeLimit = -1; + maxTimeLimit = -1; + maxDepth = -1; + maxNodes = -1; + if (sPar.infinite) { + minTimeLimit = -1; + maxTimeLimit = -1; + maxDepth = -1; + } else if (sPar.depth > 0) { + maxDepth = sPar.depth; + } else if (sPar.mate > 0) { + maxDepth = sPar.mate * 2 - 1; + } else if (sPar.moveTime > 0) { + minTimeLimit = maxTimeLimit = sPar.moveTime; + } else if (sPar.nodes > 0) { + maxNodes = sPar.nodes; + } else { + int moves = sPar.movesToGo; + if (moves == 0) { + moves = 999; + } + moves = Math.min(moves, 45); // Assume 45 more moves until end of game + if (ponderMode) { + final double ponderHitRate = 0.35; + moves = (int)Math.ceil(moves * (1 - ponderHitRate)); + } + boolean white = pos.whiteMove; + int time = white ? sPar.wTime : sPar.bTime; + int inc = white ? sPar.wInc : sPar.bInc; + final int margin = Math.min(1000, time * 9 / 10); + int timeLimit = (time + inc * (moves - 1) - margin) / moves; + minTimeLimit = (int)(timeLimit * 0.85); + maxTimeLimit = (int)(minTimeLimit * (Math.max(2.5, Math.min(4.0, moves / 2.0)))); + + // Leave at least 1s on the clock, but can't use negative time + minTimeLimit = clamp(minTimeLimit, 1, time - margin); + maxTimeLimit = clamp(maxTimeLimit, 1, time - margin); + } + } + + static final int clamp(int val, int min, int max) { + if (val < min) { + return min; + } else if (val > max) { + return max; + } else { + return val; + } + } + + final private void startThread(final int minTimeLimit, final int maxTimeLimit, + int maxDepth, final int maxNodes) { + synchronized (threadMutex) {} // Must not start new search until old search is finished + sc = new Search(pos, posHashList, posHashListSize, tt); + sc.timeLimit(minTimeLimit, maxTimeLimit); + sc.setListener(new SearchListener(os)); + sc.setStrength(strength, randomSeed); + sc.nodesBetweenTimeCheck = 500; + MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos); + MoveGen.removeIllegal(pos, moves); + if ((searchMoves != null) && (searchMoves.size() > 0)) { + Arrays.asList(moves.m).retainAll(searchMoves); + } + final MoveGen.MoveList srchMoves = moves; + onePossibleMove = false; + if ((srchMoves.size < 2) && !infinite) { + onePossibleMove = true; + if (!ponder) { + if ((maxDepth < 0) || (maxDepth > 2)) maxDepth = 2; + } + } + tt.nextGeneration(); + final int srchmaxDepth = maxDepth; + Runnable run = new Runnable() { + public void run() { + Move m = null; + if (ownBook && !analyseMode) { + Book book = new Book(false); + m = book.getBookMove(pos); + } + if (m == null) { + m = sc.iterativeDeepening(srchMoves, srchmaxDepth, maxNodes, false); + } + while (ponder || infinite) { + // We should not respond until told to do so. Just wait until + // we are allowed to respond. + try { + Thread.sleep(10); + } catch (InterruptedException ex) { + break; + } + } + Move ponderMove = getPonderMove(pos, m); + synchronized (threadMutex) { + if (ponderMove != null) { + os.printf("bestmove %s ponder %s%n", moveToString(m), moveToString(ponderMove)); + } else { + os.printf("bestmove %s%n", moveToString(m)); + } + engineThread = null; + sc = null; + } + } + }; + ThreadGroup tg = new ThreadGroup("searcher"); + engineThread = new Thread(tg, run, "searcher", 32768); + engineThread.start(); + } + + final public void stopThread() { + Thread myThread; + Search mySearch; + synchronized (threadMutex) { + myThread = engineThread; + mySearch = sc; + } + if (myThread != null) { + mySearch.timeLimit(0, 0); + infinite = false; + ponder = false; + try { + myThread.join(); + } catch (InterruptedException ex) { + throw new RuntimeException(); + } + } + } + + + private final void setupTT() { + int nEntries = hashSizeMB > 0 ? hashSizeMB * (1 << 20) / 24 : 1024; + int logSize = (int) Math.floor(Math.log(nEntries) / Math.log(2)); + tt = new TranspositionTable(logSize); + } + + final void setupPosition(Position pos, List moves) { + UndoInfo ui = new UndoInfo(); + posHashList = new long[200 + moves.size()]; + posHashListSize = 0; + for (Move m : moves) { + posHashList[posHashListSize++] = pos.zobristHash(); + pos.makeMove(m, ui); + } + this.pos = pos; + } + + /** + * Try to find a move to ponder from the transposition table. + */ + final Move getPonderMove(Position pos, Move m) { + Move ret = null; + UndoInfo ui = new UndoInfo(); + pos.makeMove(m, ui); + TTEntry ent = tt.probe(pos.historyHash()); + if (ent.type != TTEntry.T_EMPTY) { + ret = new Move(0, 0, 0); + ent.getMove(ret); + MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos); + MoveGen.removeIllegal(pos, moves); + if (!Arrays.asList(moves.m).contains(ret)) { + ret = null; + } + } + pos.unMakeMove(m, ui); + return ret; + } + + static final String moveToString(Move m) { + String ret = TextIO.squareToString(m.from); + ret += TextIO.squareToString(m.to); + switch (m.promoteTo) { + case Piece.WQUEEN: + case Piece.BQUEEN: + ret += "q"; + break; + case Piece.WROOK: + case Piece.BROOK: + ret += "r"; + break; + case Piece.WBISHOP: + case Piece.BBISHOP: + ret += "b"; + break; + case Piece.WKNIGHT: + case Piece.BKNIGHT: + ret += "n"; + break; + default: + break; + } + return ret; + } + + static void printOptions(NioPrintStream os) { + os.printf("option name Hash type spin default 2 min 1 max 2048%n"); + os.printf("option name OwnBook type check default false%n"); + os.printf("option name Ponder type check default true%n"); + os.printf("option name UCI_AnalyseMode type check default false%n"); + os.printf("option name UCI_EngineAbout type string default %s by Peter Osterlund, see http://web.comhem.se/petero2home/javachess/index.html%n", + ComputerPlayer.engineName); + os.printf("option name Strength type spin default 1000 min 0 max 1000\n"); + } + + final void setOption(String optionName, String optionValue) { + try { + if (optionName.equals("hash")) { + hashSizeMB = Integer.parseInt(optionValue); + setupTT(); + } else if (optionName.equals("ownbook")) { + ownBook = Boolean.parseBoolean(optionValue); + } else if (optionName.equals("ponder")) { + ponderMode = Boolean.parseBoolean(optionValue); + } else if (optionName.equals("uci_analysemode")) { + analyseMode = Boolean.parseBoolean(optionValue); + } else if (optionName.equals("strength")) { + strength = Integer.parseInt(optionValue); + } + } catch (NumberFormatException nfe) { + } + } +} diff --git a/DroidFish/src/org/petero/droidfish/engine/cuckoochess/NioInputStream.java b/DroidFish/src/org/petero/droidfish/engine/cuckoochess/NioInputStream.java new file mode 100644 index 0000000..5bb60b8 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/engine/cuckoochess/NioInputStream.java @@ -0,0 +1,109 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.engine.cuckoochess; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.Pipe; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.util.ArrayList; + +/** Simple InputStream look-alike on top of nio. */ +class NioInputStream { + Pipe.SourceChannel in; + ByteBuffer buffer; + Selector selector; + + ArrayList inBuf; + StringBuilder lineBuf; + + public NioInputStream(Pipe pipe) { + in = pipe.source(); + try { + in.configureBlocking(false); + selector = Selector.open(); + in.register(selector, SelectionKey.OP_READ); + + buffer = ByteBuffer.allocate(1024); + inBuf = new ArrayList(); + lineBuf = new StringBuilder(128); + } catch (IOException e) { + } + } + + public String readLine() { + while (true) { + String s = readLine(1000); + if (s != null) + return s; + } + } + + public String readLine(int timeoutMillis) { + try { + boolean haveNewLine = false; + for (int i = 0; i < inBuf.size(); i++) { + if (inBuf.get(i) == '\n') { + haveNewLine = true; + break; + } + } + if (!haveNewLine) { + // Refill inBuf + if (timeoutMillis < 1) + timeoutMillis = 1; + selector.select(timeoutMillis); + buffer.clear(); + for (SelectionKey sk : selector.selectedKeys()) + if (sk.isValid() && sk.isReadable()) + in.read(buffer); + buffer.flip(); + while (buffer.position() < buffer.limit()) { + byte b = buffer.get(); + inBuf.add((char)b); + } + } + + // Extract line + String ret = ""; + int i; + for (i = 0; i < inBuf.size(); i++) { + char c = inBuf.get(i); + if (c == '\n') { + int newSize = inBuf.size() - i - 1; + for (int j = 0; j < newSize; j++) + inBuf.set(j, inBuf.get(j+i+1)); + while (inBuf.size() > newSize) + inBuf.remove(inBuf.size() - 1); + ret = lineBuf.toString(); + lineBuf = new StringBuilder(128); + break; + } else { + lineBuf.append(c); + } + } + if (i == inBuf.size()) + inBuf.clear(); + return ret; + } catch (IOException e) { + } + return null; + } +} diff --git a/DroidFish/src/org/petero/droidfish/engine/cuckoochess/NioPrintStream.java b/DroidFish/src/org/petero/droidfish/engine/cuckoochess/NioPrintStream.java new file mode 100644 index 0000000..f367777 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/engine/cuckoochess/NioPrintStream.java @@ -0,0 +1,48 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.engine.cuckoochess; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.Pipe; + +/** Simple PrintStream look-alike on top of nio. */ +class NioPrintStream { + Pipe.SinkChannel out; + + public NioPrintStream(Pipe pipe) { + out = pipe.sink(); + } + + public void printf(String format) { + try { + String s = String.format(format, new Object[]{}); + out.write(ByteBuffer.wrap(s.getBytes())); + } catch (IOException e) { + } + } + + public void printf(String format, Object ... args) { + try { + String s = String.format(format, args); + out.write(ByteBuffer.wrap(s.getBytes())); + } catch (IOException e) { + } + } +} diff --git a/DroidFish/src/org/petero/droidfish/engine/cuckoochess/SearchParams.java b/DroidFish/src/org/petero/droidfish/engine/cuckoochess/SearchParams.java new file mode 100644 index 0000000..644eac2 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/engine/cuckoochess/SearchParams.java @@ -0,0 +1,45 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.engine.cuckoochess; + +import chess.Move; +import java.util.ArrayList; +import java.util.List; + +/** + * Store search parameters (times, increments, max depth, etc). + * @author petero + */ +public class SearchParams { + List searchMoves; // If non-empty, search only these moves + int wTime; // White remaining time, ms + int bTime; // Black remaining time, ms + int wInc; // White increment per move, ms + int bInc; // Black increment per move, ms + int movesToGo; // Moves to next time control + int depth; // If >0, don't search deeper than this + int nodes; // If >0, don't search more nodes than this + int mate; // If >0, search for mate-in-x + int moveTime; // If >0, search for exactly this amount of time, ms + boolean infinite; + + public SearchParams() { + searchMoves = new ArrayList(); + } +} diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/ChessParseError.java b/DroidFish/src/org/petero/droidfish/gamelogic/ChessParseError.java new file mode 100644 index 0000000..856e952 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/gamelogic/ChessParseError.java @@ -0,0 +1,40 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.gamelogic; + +/** + * Exception class to represent parse errors in FEN or algebraic notation. + * @author petero + */ +public class ChessParseError extends Exception { + private static final long serialVersionUID = -6051856171275301175L; + + public Position pos; + + public ChessParseError() { + } + public ChessParseError(String msg) { + super(msg); + pos = null; + } + public ChessParseError(String msg, Position pos) { + super(msg); + this.pos = pos; + } +} diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java b/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java new file mode 100644 index 0000000..2d3320d --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java @@ -0,0 +1,937 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.gamelogic; + +import java.util.ArrayList; +import java.util.List; + +import org.petero.droidfish.BookOptions; +import org.petero.droidfish.GUIInterface; +import org.petero.droidfish.GameMode; +import org.petero.droidfish.PGNOptions; +import org.petero.droidfish.engine.DroidComputerPlayer; +import org.petero.droidfish.gamelogic.Game.GameState; +import org.petero.droidfish.gamelogic.GameTree.Node; + +/** + * The glue between the chess engine and the GUI. + * @author petero + */ +public class DroidChessController { + private DroidComputerPlayer computerPlayer = null; + private PgnToken.PgnTokenReceiver gameTextListener = null; + private BookOptions bookOptions = new BookOptions(); + private Game game = null; + private Move ponderMove = null; + private GUIInterface gui; + private GameMode gameMode; + private PGNOptions pgnOptions; + private Thread computerThread; + private Thread analysisThread; + + private String engine = ""; + private int strength = 1000; + private int numPV = 1; + + private int timeControl; + private int movesPerSession; + private int timeIncrement; + + class SearchListener implements org.petero.droidfish.gamelogic.SearchListener { + private int currDepth = 0; + private int currMoveNr = 0; + private String currMove = ""; + private int currNodes = 0; + private int currNps = 0; + private int currTime = 0; + + private boolean whiteMove = true; + private String bookInfo = ""; + private List bookMoves = null; + + private ArrayList pvInfoV = new ArrayList(); + + public final void clearSearchInfo() { + pvInfoV.clear(); + currDepth = 0; + bookInfo = ""; + bookMoves = null; + setSearchInfo(); + } + + private final void setSearchInfo() { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < pvInfoV.size(); i++) { + PvInfo pvi = pvInfoV.get(i); + if (pvi.depth <= 0) + continue; + buf.append(String.format("[%d] ", pvi.depth)); + boolean negateScore = !whiteMove && gui.whiteBasedScores(); + if (pvi.upperBound || pvi.lowerBound) { + boolean upper = pvi.upperBound ^ negateScore; + buf.append(upper ? "<=" : ">="); + } + int score = negateScore ? -pvi.score : pvi.score; + if (pvi.isMate) { + buf.append(String.format("m%d", score)); + } else { + buf.append(String.format("%.2f", score / 100.0)); + } + + buf.append(pvi.pvStr); + buf.append("\n"); + } + if (currDepth > 0) { + buf.append(String.format("d:%d %d:%s t:%.2f n:%d nps:%d", currDepth, + currMoveNr, currMove, currTime / 1000.0, currNodes, currNps)); + } + final String newPV = buf.toString(); + final String newBookInfo = bookInfo; + final SearchStatus localSS = ss; + gui.runOnUIThread(new Runnable() { + public void run() { + if (!localSS.searchResultWanted && (bookMoves != null)) + return; + ArrayList> pvMoves = new ArrayList>(); + for (int i = 0; i < pvInfoV.size(); i++) + pvMoves.add(pvInfoV.get(i).pv); + gui.setThinkingInfo(newPV, newBookInfo, pvMoves, bookMoves); + } + }); + } + + @Override + public void notifyDepth(int depth) { + currDepth = depth; + setSearchInfo(); + } + + @Override + public void notifyCurrMove(Position pos, Move m, int moveNr) { + currMove = TextIO.moveToString(pos, m, false); + currMoveNr = moveNr; + setSearchInfo(); + } + + @SuppressWarnings("unchecked") + @Override + public void notifyPV(Position pos, ArrayList pvInfo, boolean isPonder) { + pvInfoV = (ArrayList) pvInfo.clone(); + for (PvInfo pv : pvInfo) { + currTime = pv.time; + currNodes = pv.nodes; + currNps = pv.nps; + + StringBuilder buf = new StringBuilder(); + Position tmpPos = new Position(pos); + UndoInfo ui = new UndoInfo(); + boolean first = true; + for (Move m : pv.pv) { + String moveStr = TextIO.moveToString(tmpPos, m, false); + if (first && isPonder) { + buf.append(String.format(" [%s]", moveStr)); + first = false; + } else { + buf.append(String.format(" %s", moveStr)); + } + tmpPos.makeMove(m, ui); + } + pv.pvStr = buf.toString(); + } + whiteMove = pos.whiteMove ^ isPonder; + + setSearchInfo(); + } + + @Override + public void notifyStats(int nodes, int nps, int time) { + currNodes = nodes; + currNps = nps; + currTime = time; + setSearchInfo(); + } + + @Override + public void notifyBookInfo(String bookInfo, List moveList) { + this.bookInfo = bookInfo; + bookMoves = moveList; + setSearchInfo(); + } + + public void prefsChanged() { + setSearchInfo(); + } + } + SearchListener listener; + + + public DroidChessController(GUIInterface gui, PgnToken.PgnTokenReceiver gameTextListener, PGNOptions options) { + this.gui = gui; + this.gameTextListener = gameTextListener; + pgnOptions = options; + listener = new SearchListener(); + } + + public final void setBookOptions(BookOptions options) { + if (!bookOptions.equals(options)) { + bookOptions = options; + if (computerPlayer != null) { + computerPlayer.setBookOptions(bookOptions); + if (analysisThread != null) { + stopAnalysis(); + startAnalysis(); + } + updateBookHints(); + } + } + } + + private final void updateBookHints() { + if (gameMode != null) { + boolean analysis = gameMode.analysisMode(); + if (!analysis && humansTurn()) { + ss = new SearchStatus(); + Pair> bi = computerPlayer.getBookHints(game.currPos()); + listener.notifyBookInfo(bi.first, bi.second); + } + } + } + + private final static class SearchStatus { + boolean searchResultWanted = true; + } + SearchStatus ss = new SearchStatus(); + + public final void newGame(GameMode gameMode) { + ss.searchResultWanted = false; + stopComputerThinking(); + stopAnalysis(); + this.gameMode = gameMode; + ponderMove = null; + if (computerPlayer == null) { + computerPlayer = new DroidComputerPlayer(engine); + computerPlayer.setListener(listener); + computerPlayer.setBookOptions(bookOptions); + } + computerPlayer.setEngineStrength(engine, strength); + computerPlayer.setNumPV(numPV); + game = new Game(computerPlayer, gameTextListener, timeControl, movesPerSession, timeIncrement); + setPlayerNames(game); + updateGameMode(); + } + + public final void startGame() { + updateComputeThreads(true); + setSelection(); + updateGUI(); + updateGameMode(); + } + + private boolean guiPaused = false; + public final void setGuiPaused(boolean paused) { + guiPaused = paused; + updateGameMode(); + } + + private final void updateGameMode() { + if (game != null) { + boolean gamePaused = !gameMode.clocksActive() || (humansTurn() && guiPaused); + game.setGamePaused(gamePaused); + updateRemainingTime(); + boolean addFirst = gameMode.clocksActive(); + game.setAddFirst(addFirst); + } + } + + private final void updateComputeThreads(boolean clearPV) { + boolean analysis = gameMode.analysisMode(); + boolean computersTurn = !humansTurn(); + boolean ponder = gui.ponderMode() && !analysis && !computersTurn && (ponderMove != null); + if (!analysis) + stopAnalysis(); + if (!(computersTurn || ponder)) + stopComputerThinking(); + if (clearPV) { + listener.clearSearchInfo(); + updateBookHints(); + } + if (analysis) + startAnalysis(); + if (computersTurn || ponder) + startComputerThinking(ponder); + } + + /** Set game mode. */ + public final void setGameMode(GameMode newMode) { + if (!gameMode.equals(newMode)) { + if (newMode.humansTurn(game.currPos().whiteMove)) + ss.searchResultWanted = false; + gameMode = newMode; + if (!gameMode.playerWhite() || !gameMode.playerBlack()) + setPlayerNames(game); // If computer player involved, set player names + updateGameMode(); + ponderMove = null; + ss.searchResultWanted = false; + stopComputerThinking(); + updateComputeThreads(true); + updateGUI(); + } + } + + public final void prefsChanged() { + updateBookHints(); + updateMoveList(); + listener.prefsChanged(); + } + + private final void setPlayerNames(Game game) { + if (game != null) { + String engine = (computerPlayer != null) ? computerPlayer.getEngineName() : + "Computer"; + String white = gameMode.playerWhite() ? "Player" : engine; + String black = gameMode.playerBlack() ? "Player" : engine; + game.tree.setPlayerNames(white, black); + } + } + + public final void fromByteArray(byte[] data) { + game.fromByteArray(data); + } + + public final byte[] toByteArray() { + return game.tree.toByteArray(); + } + + public final String getFEN() { + return TextIO.toFEN(game.tree.currentPos); + } + + /** Convert current game to PGN format. */ + public final String getPGN() { + return game.tree.toPGN(pgnOptions); + } + + public final void setFENOrPGN(String fenPgn) throws ChessParseError { + Game newGame = new Game(null, gameTextListener, timeControl, movesPerSession, timeIncrement); + try { + Position pos = TextIO.readFEN(fenPgn); + newGame.setPos(pos); + setPlayerNames(newGame); + } catch (ChessParseError e) { + // Try read as PGN instead + if (!newGame.readPGN(fenPgn, pgnOptions)) { + throw e; + } + } + ss.searchResultWanted = false; + game = newGame; + game.setComputerPlayer(computerPlayer); + gameTextListener.clear(); + updateGameMode(); + stopAnalysis(); + stopComputerThinking(); + computerPlayer.clearTT(); + ponderMove = null; + updateComputeThreads(true); + gui.setSelection(-1); + updateGUI(); + } + + /** True if human's turn to make a move. (True in analysis mode.) */ + public final boolean humansTurn() { + return gameMode.humansTurn(game.currPos().whiteMove); + } + + /** Return true if computer player is using CPU power. */ + public final boolean computerBusy() { + return (computerThread != null) || (analysisThread != null); + } + + private final boolean undoMoveNoUpdate() { + if (game.getLastMove() == null) + return false; + ss.searchResultWanted = false; + game.undoMove(); + ponderMove = null; + if (!humansTurn()) { + if (game.getLastMove() != null) { + game.undoMove(); + if (!humansTurn()) { + game.redoMove(); + } + } else { + // Don't undo first white move if playing black vs computer, + // because that would cause computer to immediately make + // a new move and the whole redo history will be lost. + if (gameMode.playerWhite() || gameMode.playerBlack()) { + game.redoMove(); + return false; + } + } + } + return true; + } + + public final void undoMove() { + if (game.getLastMove() != null) { + ss.searchResultWanted = false; + stopAnalysis(); + stopComputerThinking(); + boolean didUndo = undoMoveNoUpdate(); + updateComputeThreads(true); + setSelection(); + if (didUndo) + setAnimMove(game.currPos(), game.getNextMove(), false); + updateGUI(); + } + } + + private final void redoMoveNoUpdate() { + if (game.canRedoMove()) { + ss.searchResultWanted = false; + game.redoMove(); + ponderMove = null; + if (!humansTurn() && game.canRedoMove()) { + game.redoMove(); + if (!humansTurn()) + game.undoMove(); + } + } + } + + public final void redoMove() { + if (game.canRedoMove()) { + ss.searchResultWanted = false; + stopAnalysis(); + stopComputerThinking(); + redoMoveNoUpdate(); + updateComputeThreads(true); + setSelection(); + setAnimMove(game.prevPos(), game.getLastMove(), true); + updateGUI(); + } + } + + public final int numVariations() { + return game.numVariations(); + } + + public final int currVariation() { + return game.currVariation(); + } + + public final void changeVariation(int delta) { + if (game.numVariations() > 1) { + ss.searchResultWanted = false; + stopAnalysis(); + stopComputerThinking(); + game.changeVariation(delta); + ponderMove = null; + updateComputeThreads(true); + setSelection(); + updateGUI(); + } + } + + public final void removeSubTree() { + ss.searchResultWanted = false; + stopAnalysis(); + stopComputerThinking(); + game.removeSubTree(); + ponderMove = null; + updateComputeThreads(true); + setSelection(); + updateGUI(); + } + + public final void moveVariation(int delta) { + if (game.numVariations() > 1) { + game.moveVariation(delta); + updateGUI(); + } + } + + public final void addVariation(String preComment, List pvMoves, boolean updateDefault) { + for (int i = 0; i < pvMoves.size(); i++) { + Move m = pvMoves.get(i); + String moveStr = TextIO.moveToUCIString(m); + String pre = (i == 0) ? preComment : ""; + int varNo = game.tree.addMove(moveStr, "", 0, pre, ""); + game.tree.goForward(varNo, updateDefault); + } + for (int i = 0; i < pvMoves.size(); i++) + game.tree.goBack(); + gameTextListener.clear(); + updateGUI(); + } + + public final void gotoMove(int moveNr) { + boolean needUpdate = false; + while (game.currPos().fullMoveCounter > moveNr) { // Go backward + int before = game.currPos().fullMoveCounter * 2 + (game.currPos().whiteMove ? 0 : 1); + undoMoveNoUpdate(); + int after = game.currPos().fullMoveCounter * 2 + (game.currPos().whiteMove ? 0 : 1); + if (after >= before) + break; + needUpdate = true; + } + while (game.currPos().fullMoveCounter < moveNr) { // Go forward + int before = game.currPos().fullMoveCounter * 2 + (game.currPos().whiteMove ? 0 : 1); + redoMoveNoUpdate(); + int after = game.currPos().fullMoveCounter * 2 + (game.currPos().whiteMove ? 0 : 1); + if (after <= before) + break; + needUpdate = true; + } + if (needUpdate) { + ss.searchResultWanted = false; + stopAnalysis(); + stopComputerThinking(); + ponderMove = null; + updateComputeThreads(true); + setSelection(); + updateGUI(); + } + } + + public final void gotoStartOfVariation() { + ss.searchResultWanted = false; + stopAnalysis(); + stopComputerThinking(); + while (true) { + if (!undoMoveNoUpdate()) + break; + if (game.numVariations() > 1) + break; + } + ponderMove = null; + updateComputeThreads(true); + setSelection(); + updateGUI(); + } + + public final void makeHumanMove(Move m) { + if (humansTurn()) { + Position oldPos = new Position(game.currPos()); + if (doMove(m)) { + if (m.equals(ponderMove) && !gameMode.analysisMode() && + (analysisThread == null) && (computerThread != null)) { + computerPlayer.ponderHit(oldPos, ponderMove); + } else { + ss.searchResultWanted = false; + stopAnalysis(); + stopComputerThinking(); + updateComputeThreads(true); + } + setAnimMove(oldPos, m, true); + updateGUI(); + } else { + gui.setSelection(-1); + } + } + } + + public final void makeHumanNullMove() { + if (humansTurn()) { + int varNo = game.tree.addMove("--", "", 0, "", ""); + game.tree.goForward(varNo); + ss.searchResultWanted = false; + stopAnalysis(); + stopComputerThinking(); + ponderMove = null; + updateComputeThreads(true); + updateGUI(); + gui.setSelection(-1); + } + } + + Move promoteMove; + public final void reportPromotePiece(int choice) { + final boolean white = game.currPos().whiteMove; + int promoteTo; + switch (choice) { + case 1: + promoteTo = white ? Piece.WROOK : Piece.BROOK; + break; + case 2: + promoteTo = white ? Piece.WBISHOP : Piece.BBISHOP; + break; + case 3: + promoteTo = white ? Piece.WKNIGHT : Piece.BKNIGHT; + break; + default: + promoteTo = white ? Piece.WQUEEN : Piece.BQUEEN; + break; + } + promoteMove.promoteTo = promoteTo; + Move m = promoteMove; + promoteMove = null; + makeHumanMove(m); + } + + /** + * Move a piece from one square to another. + * @return True if the move was legal, false otherwise. + */ + final private boolean doMove(Move move) { + Position pos = game.currPos(); + ArrayList moves = new MoveGen().pseudoLegalMoves(pos); + moves = MoveGen.removeIllegal(pos, moves); + int promoteTo = move.promoteTo; + for (Move m : moves) { + if ((m.from == move.from) && (m.to == move.to)) { + if ((m.promoteTo != Piece.EMPTY) && (promoteTo == Piece.EMPTY)) { + promoteMove = m; + gui.requestPromotePiece(); + return false; + } + if (m.promoteTo == promoteTo) { + String strMove = TextIO.moveToString(pos, m, false); + game.processString(strMove); + return true; + } + } + } + gui.reportInvalidMove(move); + return false; + } + + final private void updateGUI() { + String str; + if (game.getGameState() == Game.GameState.ALIVE) { + str = Integer.valueOf(game.currPos().fullMoveCounter).toString(); + str += game.currPos().whiteMove ? ". White's move" : "... Black's move"; + if (computerThread != null) + str += humansTurn() ? " (ponder)" : " (thinking)"; + if (analysisThread != null) str += " (analyzing)"; + } else { + str = game.getGameStateString(); + } + gui.setStatusString(str); + updateMoveList(); + + StringBuilder sb = new StringBuilder(); + if (game.tree.currentNode != game.tree.rootNode) { + game.tree.goBack(); + Position pos = game.currPos(); + List prevVarList = game.tree.variations(); + for (int i = 0; i < prevVarList.size(); i++) { + if (i > 0) sb.append(' '); + if (i == game.tree.currentNode.defaultChild) + sb.append(""); + sb.append(TextIO.moveToString(pos, prevVarList.get(i), false)); + if (i == game.tree.currentNode.defaultChild) + sb.append(""); + } + game.tree.goForward(-1); + } + gui.setPosition(game.currPos(), sb.toString(), game.tree.variations()); + + updateRemainingTime(); + } + + final private void updateMoveList() { + if (game == null) + return; + if (!gameTextListener.isUpToDate()) { + PGNOptions tmpOptions = new PGNOptions(); + tmpOptions.exp.variations = pgnOptions.view.variations; + tmpOptions.exp.comments = pgnOptions.view.comments; + tmpOptions.exp.nag = pgnOptions.view.nag; + tmpOptions.exp.playerAction = false; + tmpOptions.exp.clockInfo = false; + tmpOptions.exp.moveNrAfterNag = false; + gameTextListener.clear(); + game.tree.pgnTreeWalker(tmpOptions, gameTextListener); + } + gameTextListener.setCurrent(game.tree.currentNode); + gui.moveListUpdated(); + } + + final public void updateRemainingTime() { + // Update remaining time + long now = System.currentTimeMillis(); + long wTime = game.timeController.getRemainingTime(true, now); + long bTime = game.timeController.getRemainingTime(false, now); + long nextUpdate = 0; + if (game.timeController.clockRunning()) { + long t = game.currPos().whiteMove ? wTime : bTime; + nextUpdate = (t % 1000); + if (nextUpdate < 0) nextUpdate += 1000; + nextUpdate += 1; + } + gui.setRemainingTime(wTime, bTime, nextUpdate); + } + + final private void setSelection() { + Move m = game.getLastMove(); + int sq = ((m != null) && (m.from != m.to)) ? m.to : -1; + gui.setSelection(sq); + } + + private void setAnimMove(Position sourcePos, Move move, boolean forward) { + gui.setAnimMove(sourcePos, move, forward); + } + + private final synchronized void startComputerThinking(boolean ponder) { + if (analysisThread != null) return; + if (game.getGameState() != GameState.ALIVE) return; + if (computerThread == null) { + ss = new SearchStatus(); + final Pair> ph = game.getUCIHistory(); + final Game g = game; + final boolean haveDrawOffer = g.haveDrawOffer(); + final Position currPos = new Position(g.currPos()); + long now = System.currentTimeMillis(); + final int wTime = game.timeController.getRemainingTime(true, now); + final int bTime = game.timeController.getRemainingTime(false, now); + final int inc = game.timeController.getIncrement(); + int movesToGo = game.timeController.getMovesToTC(); + if (ponder && !currPos.whiteMove && (movesToGo > 0)) { + movesToGo--; + if (movesToGo <= 0) + movesToGo += game.timeController.getMovesPerSession(); + } + final int fMovesToGo = movesToGo; + final Move fPonderMove = ponder ? ponderMove : null; + computerThread = new Thread(new Runnable() { + public void run() { + computerPlayer.setEngineStrength(engine, strength); + computerPlayer.setNumPV(1); + final Pair pair = + computerPlayer.doSearch(ph.first, ph.second, currPos, haveDrawOffer, + wTime, bTime, inc, fMovesToGo, + gui.ponderMode(), fPonderMove, + gui.engineThreads()); + final String cmd = pair.first; + final Move ponder = pair.second; + final SearchStatus localSS = ss; + gui.runOnUIThread(new Runnable() { + public void run() { + synchronized (shutdownEngineLock) { + if (!localSS.searchResultWanted) + return; + Position oldPos = new Position(g.currPos()); + g.processString(cmd); + ponderMove = ponder; + updateGameMode(); + gui.computerMoveMade(); + listener.clearSearchInfo(); + stopComputerThinking(); + stopAnalysis(); // To force analysis to restart for new position + updateComputeThreads(true); + setSelection(); + setAnimMove(oldPos, g.getLastMove(), true); + updateGUI(); + } + } + }); + } + }); + listener.clearSearchInfo(); + computerPlayer.shouldStop = false; + computerThread.start(); + updateGUI(); + } + } + + private final synchronized void stopComputerThinking() { + if (computerThread != null) { + computerPlayer.stopSearch(); + try { + computerThread.join(); + } catch (InterruptedException ex) { + System.out.printf("Could not stop computer thread%n"); + } + computerThread = null; + updateGUI(); + } + } + + private final synchronized void startAnalysis() { + if (gameMode.analysisMode()) { + if (computerThread != null) return; + if (analysisThread == null) { + ss = new SearchStatus(); + final Pair> ph = game.getUCIHistory(); + final boolean haveDrawOffer = game.haveDrawOffer(); + final Position currPos = new Position(game.currPos()); + final boolean alive = game.tree.getGameState() == GameState.ALIVE; + analysisThread = new Thread(new Runnable() { + public void run() { + if (alive) { + computerPlayer.setEngineStrength(engine, 1000); + computerPlayer.setNumPV(numPV); + computerPlayer.analyze(ph.first, ph.second, currPos, haveDrawOffer, + gui.engineThreads()); + } + } + }); + listener.clearSearchInfo(); + computerPlayer.shouldStop = false; + analysisThread.start(); + updateGUI(); + } + } + } + + private final synchronized void stopAnalysis() { + if (analysisThread != null) { + computerPlayer.stopSearch(); + try { + analysisThread.join(); + } catch (InterruptedException ex) { + System.out.printf("Could not stop analysis thread%n"); + } + analysisThread = null; + listener.clearSearchInfo(); + updateGUI(); + } + } + + public final synchronized void setEngineStrength(String engine, int strength) { + boolean newEngine = !engine.equals(this.engine); + if (newEngine) + numPV = 1; + if (newEngine || (strength != this.strength)) { + this.engine = engine; + this.strength = strength; + if (newEngine && ((analysisThread != null) || (computerThread != null))) { + stopAnalysis(); + updateComputeThreads(true); + updateGUI(); + } + } + } + + public final synchronized int maxPV() { + if (computerPlayer == null) + return 1; + return computerPlayer.getMaxPV(); + } + + public final int getNumPV() { + return this.numPV; + } + + public final synchronized void setMultiPVMode(int numPV) { + if (numPV < 1) numPV = 1; + if (numPV > maxPV()) numPV = maxPV(); + if (numPV != this.numPV) { + this.numPV = numPV; + if (analysisThread != null) { + stopAnalysis(); + ponderMove = null; + updateComputeThreads(true); + updateGUI(); + } + } + } + + public final synchronized void setTimeLimit(int time, int moves, int inc) { + timeControl = time; + movesPerSession = moves; + timeIncrement = inc; + if (game != null) + game.timeController.setTimeControl(timeControl, movesPerSession, timeIncrement); + } + + public final void stopSearch() { + if (computerThread != null) { + computerPlayer.stopSearch(); + } + } + + public final void stopPonder() { + if ((computerThread != null) && humansTurn()) + stopComputerThinking(); + } + + private Object shutdownEngineLock = new Object(); + public final synchronized void shutdownEngine() { + synchronized (shutdownEngineLock) { + gameMode = new GameMode(GameMode.TWO_PLAYERS); + ss.searchResultWanted = false; + stopComputerThinking(); + stopAnalysis(); + ponderMove = null; + computerPlayer.shutdownEngine(); + } + } + + /** Help human to claim a draw by trying to find and execute a valid draw claim. */ + public final boolean claimDrawIfPossible() { + if (!findValidDrawClaim()) + return false; + updateGUI(); + return true; + } + + private final boolean findValidDrawClaim() { + if (game.getGameState() != GameState.ALIVE) return true; + game.processString("draw accept"); + if (game.getGameState() != GameState.ALIVE) return true; + game.processString("draw rep"); + if (game.getGameState() != GameState.ALIVE) return true; + game.processString("draw 50"); + if (game.getGameState() != GameState.ALIVE) return true; + return false; + } + + public final void resignGame() { + if (game.getGameState() == GameState.ALIVE) { + game.processString("resign"); + updateGUI(); + } + } + + public final void setHeaders(ArrayList tags, ArrayList tagValues) { + game.tree.setHeaders(tags, tagValues); + gameTextListener.clear(); + updateGUI(); + } + + public final void getHeaders(ArrayList tags, ArrayList tagValues) { + game.tree.getHeaders(tags, tagValues); + } + + public static final class CommentInfo { + public String move; + public String preComment, postComment; + public int nag; + } + public final CommentInfo getComments() { + Node cur = game.tree.currentNode; + CommentInfo ret = new CommentInfo(); + ret.move = cur.moveStr; + ret.preComment = cur.preComment; + ret.postComment = cur.postComment; + ret.nag = cur.nag; + return ret; + } + + public final void setComments(CommentInfo commInfo) { + Node cur = game.tree.currentNode; + cur.preComment = commInfo.preComment; + cur.postComment = commInfo.postComment; + cur.nag = commInfo.nag; + gameTextListener.clear(); + updateGUI(); + } +} diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/Game.java b/DroidFish/src/org/petero/droidfish/gamelogic/Game.java new file mode 100644 index 0000000..d7b2feb --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/gamelogic/Game.java @@ -0,0 +1,466 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.gamelogic; + +import java.util.ArrayList; +import java.util.List; + +import org.petero.droidfish.PGNOptions; +import org.petero.droidfish.engine.DroidComputerPlayer; +import org.petero.droidfish.gamelogic.GameTree.Node; + +/** + * + * @author petero + */ +public class Game { + boolean pendingDrawOffer; + GameTree tree; + private DroidComputerPlayer computerPlayer; + TimeControl timeController; + private boolean gamePaused; + private boolean addFirst; + + PgnToken.PgnTokenReceiver gameTextListener; + + public Game(DroidComputerPlayer computerPlayer, PgnToken.PgnTokenReceiver gameTextListener, + int timeControl, int movesPerSession, int timeIncrement) { + this.computerPlayer = computerPlayer; + this.gameTextListener = gameTextListener; + tree = new GameTree(gameTextListener); + timeController = new TimeControl(); + timeController.setTimeControl(timeControl, movesPerSession, timeIncrement); + gamePaused = false; + newGame(); + } + + final void fromByteArray(byte[] data) { + tree.fromByteArray(data); + updateTimeControl(true); + } + + public final void setComputerPlayer(DroidComputerPlayer computerPlayer) { + this.computerPlayer = computerPlayer; + } + + public final void setGamePaused(boolean gamePaused) { + if (gamePaused != this.gamePaused) { + this.gamePaused = gamePaused; + updateTimeControl(false); + } + } + + public final void setAddFirst(boolean addFirst) { + this.addFirst = addFirst; + } + + final void setPos(Position pos) { + tree.setStartPos(new Position(pos)); + updateTimeControl(false); + } + + final boolean readPGN(String pgn, PGNOptions options) throws ChessParseError { + boolean ret = tree.readPGN(pgn, options); + if (ret) + updateTimeControl(false); + return ret; + } + + final Position currPos() { + return tree.currentPos; + } + + final Position prevPos() { + Move m = tree.currentNode.move; + if (m != null) { + tree.goBack(); + Position ret = new Position(currPos()); + tree.goForward(-1); + return ret; + } else { + return currPos(); + } + } + + public final Move getNextMove() { + if (canRedoMove()) { + tree.goForward(-1); + Move ret = tree.currentNode.move; + tree.goBack(); + return ret; + } else { + return null; + } + } + + /** + * Update the game state according to move/command string from a player. + * @param str The move or command to process. + * @return True if str was understood, false otherwise. + */ + public final boolean processString(String str) { + if (getGameState() != GameState.ALIVE) + return false; + if (str.startsWith("draw ")) { + String drawCmd = str.substring(str.indexOf(" ") + 1); + handleDrawCmd(drawCmd); + return true; + } else if (str.equals("resign")) { + addToGameTree(new Move(0, 0, 0), "resign"); + return true; + } + + Move m = TextIO.UCIstringToMove(str); + if (m != null) { + ArrayList moves = new MoveGen().pseudoLegalMoves(currPos()); + moves = MoveGen.removeIllegal(currPos(), moves); + boolean legal = false; + for (int i = 0; i < moves.size(); i++) { + if (m.equals(moves.get(i))) { + legal = true; + break; + } + } + if (!legal) + m = null; + } + if (m == null) { + m = TextIO.stringToMove(currPos(), str); + } + if (m == null) { + return false; + } + + addToGameTree(m, pendingDrawOffer ? "draw offer" : ""); + return true; + } + + private final void addToGameTree(Move m, String playerAction) { + if (m.equals(new Move(0, 0, 0))) { // Don't create more than one null move at a node + List varMoves = tree.variations(); + for (int i = varMoves.size() - 1; i >= 0; i--) { + if (varMoves.get(i).equals(m)) { + tree.deleteVariation(i); + } + } + } + + List varMoves = tree.variations(); + boolean movePresent = false; + int varNo; + for (varNo = 0; varNo < varMoves.size(); varNo++) { + if (varMoves.get(varNo).equals(m)) { + movePresent = true; + break; + } + } + if (!movePresent) { + String moveStr = TextIO.moveToUCIString(m); + varNo = tree.addMove(moveStr, playerAction, 0, "", ""); + } + int newPos = addFirst ? 0 : varNo; + tree.reorderVariation(varNo, newPos); + tree.goForward(newPos); + int remaining = timeController.moveMade(System.currentTimeMillis(), !gamePaused); + tree.setRemainingTime(remaining); + updateTimeControl(true); + pendingDrawOffer = false; + } + + private final void updateTimeControl(boolean discardElapsed) { + int move = currPos().fullMoveCounter; + boolean wtm = currPos().whiteMove; + if (discardElapsed || (move != timeController.currentMove) || (wtm != timeController.whiteToMove)) { + int initialTime = timeController.getInitialTime(); + int whiteBaseTime = tree.getRemainingTime(true, initialTime); + int blackBaseTime = tree.getRemainingTime(false, initialTime); + timeController.setCurrentMove(move, wtm, whiteBaseTime, blackBaseTime); + } + long now = System.currentTimeMillis(); + if (gamePaused || (getGameState() != GameState.ALIVE)) { + timeController.stopTimer(now); + } else { + timeController.startTimer(now); + } + } + + public final String getGameStateString() { + switch (getGameState()) { + case ALIVE: + return ""; + case WHITE_MATE: + return "Game over, white mates!"; + case BLACK_MATE: + return "Game over, black mates!"; + case WHITE_STALEMATE: + case BLACK_STALEMATE: + return "Game over, draw by stalemate!"; + case DRAW_REP: + { + String ret = "Game over, draw by repetition!"; + String drawInfo = tree.getGameStateInfo(); + if (drawInfo.length() > 0) { + ret = ret + " [" + drawInfo+ "]"; + } + return ret; + } + case DRAW_50: + { + String ret = "Game over, draw by 50 move rule!"; + String drawInfo = tree.getGameStateInfo(); + if (drawInfo.length() > 0) { + ret = ret + " [" + drawInfo + "]"; + } + return ret; + } + case DRAW_NO_MATE: + return "Game over, draw by impossibility of mate!"; + case DRAW_AGREE: + return "Game over, draw by agreement!"; + case RESIGN_WHITE: + return "Game over, white resigns!"; + case RESIGN_BLACK: + return "Game over, black resigns!"; + default: + throw new RuntimeException(); + } + } + + /** + * Get the last played move, or null if no moves played yet. + */ + public final Move getLastMove() { + return tree.currentNode.move; + } + + /** Return true if there is a move to redo. */ + public final boolean canRedoMove() { + int nVar = tree.variations().size(); + return nVar > 0; + } + + public final int numVariations() { + if (tree.currentNode == tree.rootNode) + return 1; + tree.goBack(); + int nChildren = tree.variations().size(); + tree.goForward(-1); + return nChildren; + } + + public final int currVariation() { + if (tree.currentNode == tree.rootNode) + return 0; + tree.goBack(); + int defChild = tree.currentNode.defaultChild; + tree.goForward(-1); + return defChild; + } + + public final void changeVariation(int delta) { + if (tree.currentNode == tree.rootNode) + return; + tree.goBack(); + int defChild = tree.currentNode.defaultChild; + int nChildren = tree.variations().size(); + int newChild = defChild + delta; + newChild = Math.max(newChild, 0); + newChild = Math.min(newChild, nChildren - 1); + tree.goForward(newChild); + pendingDrawOffer = false; + updateTimeControl(true); + } + + public final void moveVariation(int delta) { + if (tree.currentNode == tree.rootNode) + return; + tree.goBack(); + int varNo = tree.currentNode.defaultChild; + int nChildren = tree.variations().size(); + int newPos = varNo + delta; + newPos = Math.max(newPos, 0); + newPos = Math.min(newPos, nChildren - 1); + tree.reorderVariation(varNo, newPos); + tree.goForward(newPos); + pendingDrawOffer = false; + updateTimeControl(true); + } + + public final void removeSubTree() { + if (getLastMove() != null) { + tree.goBack(); + int defChild = tree.currentNode.defaultChild; + tree.deleteVariation(defChild); + } else { + while (canRedoMove()) + tree.deleteVariation(0); + } + pendingDrawOffer = false; + updateTimeControl(true); + } + + public static enum GameState { + ALIVE, + WHITE_MATE, // White mates + BLACK_MATE, // Black mates + WHITE_STALEMATE, // White is stalemated + BLACK_STALEMATE, // Black is stalemated + DRAW_REP, // Draw by 3-fold repetition + DRAW_50, // Draw by 50 move rule + DRAW_NO_MATE, // Draw by impossibility of check mate + DRAW_AGREE, // Draw by agreement + RESIGN_WHITE, // White resigns + RESIGN_BLACK // Black resigns + } + + /** + * Get the current state (draw, mate, ongoing, etc) of the game. + */ + public final GameState getGameState() { + return tree.getGameState(); + } + + /** + * Check if a draw offer is available. + * @return True if the current player has the option to accept a draw offer. + */ + public final boolean haveDrawOffer() { + return tree.currentNode.playerAction.equals("draw offer"); + } + + public final void undoMove() { + Move m = tree.currentNode.move; + if (m != null) { + tree.goBack(); + pendingDrawOffer = false; + updateTimeControl(true); + } + } + + public final void redoMove() { + if (canRedoMove()) { + tree.goForward(-1); + pendingDrawOffer = false; + updateTimeControl(true); + } + } + + public final void newGame() { + tree = new GameTree(gameTextListener); + if (computerPlayer != null) + computerPlayer.clearTT(); + timeController.reset(); + pendingDrawOffer = false; + updateTimeControl(true); + } + + + /** + * Return the last zeroing position and a list of moves + * to go from that position to the current position. + */ + public final Pair> getUCIHistory() { + Pair, Integer> ml = tree.getMoveList(); + List moveList = ml.first; + Position pos = new Position(tree.startPos); + ArrayList mList = new ArrayList(); + Position currPos = new Position(pos); + UndoInfo ui = new UndoInfo(); + int nMoves = ml.second; + for (int i = 0; i < nMoves; i++) { + Node n = moveList.get(i); + mList.add(n.move); + currPos.makeMove(n.move, ui); + if (currPos.halfMoveClock == 0) { + pos = new Position(currPos); + mList.clear(); + } + } + return new Pair>(pos, mList); + } + + private final void handleDrawCmd(String drawCmd) { + Position pos = tree.currentPos; + if (drawCmd.startsWith("rep") || drawCmd.startsWith("50")) { + boolean rep = drawCmd.startsWith("rep"); + Move m = null; + String ms = null; + int firstSpace = drawCmd.indexOf(" "); + if (firstSpace >= 0) { + ms = drawCmd.substring(firstSpace + 1); + if (ms.length() > 0) { + m = TextIO.stringToMove(pos, ms); + } + } + boolean valid; + if (rep) { + valid = false; + UndoInfo ui = new UndoInfo(); + int repetitions = 0; + Position posToCompare = new Position(tree.currentPos); + if (m != null) { + posToCompare.makeMove(m, ui); + repetitions = 1; + } + Pair, Integer> ml = tree.getMoveList(); + List moveList = ml.first; + Position tmpPos = new Position(tree.startPos); + if (tmpPos.drawRuleEquals(posToCompare)) + repetitions++; + int nMoves = ml.second; + for (int i = 0; i < nMoves; i++) { + Node n = moveList.get(i); + tmpPos.makeMove(n.move, ui); + TextIO.fixupEPSquare(tmpPos); + if (tmpPos.drawRuleEquals(posToCompare)) + repetitions++; + } + if (repetitions >= 3) + valid = true; + } else { + Position tmpPos = new Position(pos); + if (m != null) { + UndoInfo ui = new UndoInfo(); + tmpPos.makeMove(m, ui); + } + valid = tmpPos.halfMoveClock >= 100; + } + if (valid) { + String playerAction = rep ? "draw rep" : "draw 50"; + if (m != null) + playerAction += " " + TextIO.moveToString(pos, m, false); + addToGameTree(new Move(0, 0, 0), playerAction); + } else { + pendingDrawOffer = true; + if (m != null) { + processString(ms); + } + } + } else if (drawCmd.startsWith("offer ")) { + pendingDrawOffer = true; + String ms = drawCmd.substring(drawCmd.indexOf(" ") + 1); + if (TextIO.stringToMove(pos, ms) != null) { + processString(ms); + } + } else if (drawCmd.equals("accept")) { + if (haveDrawOffer()) + addToGameTree(new Move(0, 0, 0), "draw accept"); + } + } +} diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/GameTree.java b/DroidFish/src/org/petero/droidfish/gamelogic/GameTree.java new file mode 100644 index 0000000..3e3e47f --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/gamelogic/GameTree.java @@ -0,0 +1,1396 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.gamelogic; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.GregorianCalendar; +import java.util.List; + +import org.petero.droidfish.PGNOptions; +import org.petero.droidfish.gamelogic.Game.GameState; + +public class GameTree { + // Data from the seven tag roster (STR) part of the PGN standard + String event, site, date, round, white, black; + // Result is the last tag pair in the STR, but it is computed on demand from the game tree. + + Position startPos; + String timeControl; + + // Non-standard tags + static private class TagPair { + String tagName; + String tagValue; + } + List tagPairs; + + Node rootNode; + Node currentNode; + Position currentPos; // Cached value. Computable from "currentNode". + + PgnToken.PgnTokenReceiver gameStateListener; + + public GameTree(PgnToken.PgnTokenReceiver gameStateListener) { + this.gameStateListener = gameStateListener; + try { + setStartPos(TextIO.readFEN(TextIO.startPosFEN)); + } catch (ChessParseError e) { + } + } + + final void setPlayerNames(String white, String black) { + this.white = white; + this.black = black; + updateListener(); + } + + /** Set start position. Drops the whole game tree. */ + final void setStartPos(Position pos) { + event = "?"; + site = "?"; + { + Calendar now = GregorianCalendar.getInstance(); + int year = now.get(Calendar.YEAR); + int month = now.get(Calendar.MONTH) + 1; + int day = now.get(Calendar.DAY_OF_MONTH); + date = String.format("%04d.%02d.%02d", year, month, day); + } + round = "?"; + white = "?"; + black = "?"; + startPos = pos; + timeControl = "?"; + tagPairs = new ArrayList(); + rootNode = new Node(); + currentNode = rootNode; + currentPos = new Position(startPos); + updateListener(); + } + + private final void updateListener() { + if (gameStateListener != null) { + gameStateListener.clear(); + } + } + + /** PngTokenReceiver implementation that generates plain text PGN data. */ + private static class PgnText implements PgnToken.PgnTokenReceiver { + private StringBuilder sb = new StringBuilder(256); + private String header = ""; + private int prevType = PgnToken.EOF; + + final String getPgnString() { + StringBuilder ret = new StringBuilder(4096); + ret.append(header); + ret.append('\n'); + + String[] words = sb.toString().split(" "); + int currLineLength = 0; + final int arrLen = words.length; + for (int i = 0; i < arrLen; i++) { + String word = words[i].trim(); + int wordLen = word.length(); + if (wordLen > 0) { + if (currLineLength == 0) { + ret.append(word); + currLineLength = wordLen; + } else if (currLineLength + 1 + wordLen >= 80) { + ret.append('\n'); + ret.append(word); + currLineLength = wordLen; + } else { + ret.append(' '); + currLineLength++; + ret.append(word); + currLineLength += wordLen; + } + } + } + ret.append("\n\n"); + return ret.toString(); + } + + @Override + public void processToken(Node node, int type, String token) { + if ( (prevType == PgnToken.RIGHT_BRACKET) && + (type != PgnToken.LEFT_BRACKET)) { + header = sb.toString(); + sb = new StringBuilder(4096); + } + switch (type) { + case PgnToken.STRING: { + sb.append(" \""); + int len = token.length(); + for (int i = 0; i < len; i++) { + char c = token.charAt(i); + if ((c == '\\') || (c == '"')) { + sb.append('\\'); + } + sb.append(c); + } + sb.append("\""); + break; + } + case PgnToken.INTEGER: + if ( (prevType != PgnToken.LEFT_PAREN) && + (prevType != PgnToken.RIGHT_BRACKET)) + sb.append(' '); + sb.append(token); + break; + case PgnToken.PERIOD: + sb.append('.'); + break; + case PgnToken.ASTERISK: + sb.append(" *"); + break; + case PgnToken.LEFT_BRACKET: + sb.append('['); + break; + case PgnToken.RIGHT_BRACKET: + sb.append("]\n"); + break; + case PgnToken.LEFT_PAREN: + sb.append(" ("); + break; + case PgnToken.RIGHT_PAREN: + sb.append(')'); + break; + case PgnToken.NAG: + sb.append(" $"); + sb.append(token); + break; + case PgnToken.SYMBOL: + if ((prevType != PgnToken.RIGHT_BRACKET) && (prevType != PgnToken.LEFT_BRACKET)) + sb.append(' '); + sb.append(token); + break; + case PgnToken.COMMENT: + if ( (prevType != PgnToken.LEFT_PAREN) && + (prevType != PgnToken.RIGHT_BRACKET)) + sb.append(' '); + sb.append('{'); + sb.append(token); + sb.append('}'); + break; + case PgnToken.EOF: + break; + } + prevType = type; + } + + @Override + public boolean isUpToDate() { + return true; + } + @Override + public void clear() { + } + @Override + public void setCurrent(Node node) { + } + } + + /** Export game tree in PGN format. */ + public final String toPGN(PGNOptions options) { + PgnText pgnText = new PgnText(); + options.exp.pgnPromotions = true; + pgnTreeWalker(options, pgnText); + return pgnText.getPgnString(); + } + + /** Walks the game tree in PGN export order. */ + public final void pgnTreeWalker(PGNOptions options, PgnToken.PgnTokenReceiver out) { + // Go to end of mainline to evaluate PGN result string. + String pgnResultString; + { + List currPath = new ArrayList(); + while (currentNode != rootNode) { + Node child = currentNode; + goBack(); + int childNum = variations().indexOf(child); + currPath.add(childNum); + } + while (variations().size() > 0) + goForward(0, false); + pgnResultString = getPGNResultString(); + while (currentNode != rootNode) + goBack(); + for (int i = currPath.size() - 1; i >= 0; i--) + goForward(currPath.get(i), false); + } + + // Write seven tag roster + addTagPair(out, "Event", event); + addTagPair(out, "Site", site); + addTagPair(out, "Date", date); + addTagPair(out, "Round", round); + addTagPair(out, "White", white); + addTagPair(out, "Black", black); + addTagPair(out, "Result", pgnResultString); + + // Write special tag pairs + String fen = TextIO.toFEN(startPos); + if (!fen.equals(TextIO.startPosFEN)) { + addTagPair(out, "FEN", fen); + addTagPair(out, "Setup", "1"); + } + if (!timeControl.equals("?")) + addTagPair(out, "TimeControl", timeControl); + + // Write other non-standard tag pairs + for (int i = 0; i < tagPairs.size(); i++) + addTagPair(out, tagPairs.get(i).tagName, tagPairs.get(i).tagValue); + + // Write moveText section + MoveNumber mn = new MoveNumber(startPos.fullMoveCounter, startPos.whiteMove); + Node.addPgnData(out, rootNode, mn.prev(), options); + out.processToken(null, PgnToken.SYMBOL, pgnResultString); + out.processToken(null, PgnToken.EOF, null); + } + + private final void addTagPair(PgnToken.PgnTokenReceiver out, String tagName, String tagValue) { + out.processToken(null, PgnToken.LEFT_BRACKET, null); + out.processToken(null, PgnToken.SYMBOL, tagName); + out.processToken(null, PgnToken.STRING, tagValue); + out.processToken(null, PgnToken.RIGHT_BRACKET, null); + } + + final static class PgnScanner { + String data; + int idx; + List savedTokens; + + PgnScanner(String pgn) { + savedTokens = new ArrayList(); + // Skip "escape" lines, ie lines starting with a '%' character + StringBuilder sb = new StringBuilder(); + int len = pgn.length(); + boolean col0 = true; + for (int i = 0; i < len; i++) { + char c = pgn.charAt(i); + if (c == '%' && col0) { + while (i + 1 < len) { + char nextChar = pgn.charAt(i + 1); + if ((nextChar == '\n') || (nextChar == '\r')) + break; + i++; + } + col0 = true; + } else { + sb.append(c); + col0 = ((c == '\n') || (c == '\r')); + } + } + sb.append('\n'); // Terminating whitespace simplifies the tokenizer + data = sb.toString(); + idx = 0; + } + + final void putBack(PgnToken tok) { + savedTokens.add(tok); + } + + final PgnToken nextToken() { + if (savedTokens.size() > 0) { + int len = savedTokens.size(); + PgnToken ret = savedTokens.get(len - 1); + savedTokens.remove(len - 1); + return ret; + } + + PgnToken ret = new PgnToken(PgnToken.EOF, null); + try { + while (true) { + char c = data.charAt(idx++); + if (Character.isWhitespace(c)) { + // Skip + } else if (c == '.') { + ret.type = PgnToken.PERIOD; + break; + } else if (c == '*') { + ret.type = PgnToken.ASTERISK; + break; + } else if (c == '[') { + ret.type = PgnToken.LEFT_BRACKET; + break; + } else if (c == ']') { + ret.type = PgnToken.RIGHT_BRACKET; + break; + } else if (c == '(') { + ret.type = PgnToken.LEFT_PAREN; + break; + } else if (c == ')') { + ret.type = PgnToken.RIGHT_PAREN; + break; + } else if (c == '{') { + ret.type = PgnToken.COMMENT; + StringBuilder sb = new StringBuilder();; + while ((c = data.charAt(idx++)) != '}') { + sb.append(c); + } + ret.token = sb.toString(); + break; + } else if (c == ';') { + ret.type = PgnToken.COMMENT; + StringBuilder sb = new StringBuilder();; + while (true) { + c = data.charAt(idx++); + if ((c == '\n') || (c == '\r')) + break; + sb.append(c); + } + ret.token = sb.toString(); + break; + } else if (c == '"') { + ret.type = PgnToken.STRING; + StringBuilder sb = new StringBuilder();; + while (true) { + c = data.charAt(idx++); + if (c == '"') { + break; + } else if (c == '\\') { + c = data.charAt(idx++); + } + sb.append(c); + } + ret.token = sb.toString(); + break; + } else if (c == '$') { + ret.type = PgnToken.NAG; + StringBuilder sb = new StringBuilder();; + while (true) { + c = data.charAt(idx++); + if (!Character.isDigit(c)) { + idx--; + break; + } + sb.append(c); + } + ret.token = sb.toString(); + break; + } else { // Start of symbol or integer + ret.type = PgnToken.SYMBOL; + StringBuilder sb = new StringBuilder(); + sb.append(c); + boolean onlyDigits = Character.isDigit(c); + final String term = ".*[](){;\"$"; + while (true) { + c = data.charAt(idx++); + if (Character.isWhitespace(c) || (term.indexOf(c) >= 0)) { + idx--; + break; + } + sb.append(c); + if (!Character.isDigit(c)) + onlyDigits = false; + } + if (onlyDigits) { + ret.type = PgnToken.INTEGER; + } + ret.token = sb.toString(); + break; + } + } + } catch (StringIndexOutOfBoundsException e) { + ret.type = PgnToken.EOF; + } + return ret; + } + + final PgnToken nextTokenDropComments() { + while (true) { + PgnToken tok = nextToken(); + if (tok.type != PgnToken.COMMENT) + return tok; + } + } + } + + /** Import PGN data. */ + public final boolean readPGN(String pgn, PGNOptions options) throws ChessParseError { + PgnScanner scanner = new PgnScanner(pgn); + PgnToken tok = scanner.nextToken(); + + // Parse tag section + List tagPairs = new ArrayList(); + while (tok.type == PgnToken.LEFT_BRACKET) { + TagPair tp = new TagPair(); + tok = scanner.nextTokenDropComments(); + if (tok.type != PgnToken.SYMBOL) + break; + tp.tagName = tok.token; + tok = scanner.nextTokenDropComments(); + if (tok.type != PgnToken.STRING) + break; + tp.tagValue = tok.token; + tok = scanner.nextTokenDropComments(); + if (tok.type != PgnToken.RIGHT_BRACKET) { + // In a well-formed PGN, there is nothing between the string + // and the right bracket, but broken headers with non-escaped + // " characters sometimes occur. Try to do something useful + // for such headers here. + PgnToken prevTok = new PgnToken(PgnToken.STRING, ""); + while ((tok.type == PgnToken.STRING) || (tok.type == PgnToken.SYMBOL)) { + if (tok.type != prevTok.type) + tp.tagValue += '"'; + if ((tok.type == PgnToken.SYMBOL) && (prevTok.type == PgnToken.SYMBOL)) + tp.tagValue += ' '; + tp.tagValue += tok.token; + prevTok = tok; + tok = scanner.nextTokenDropComments(); + } + } + tagPairs.add(tp); + tok = scanner.nextToken(); + } + scanner.putBack(tok); + + // Parse move section + Node gameRoot = new Node(); + Node.parsePgn(scanner, gameRoot, options); + + // Store parsed data in GameTree + if ((tagPairs.size() == 0) && (gameRoot.children.size() == 0)) + return false; + + String fen = TextIO.startPosFEN; + int nTags = tagPairs.size(); + for (int i = 0; i < nTags; i++) { + if (tagPairs.get(i).tagName.equals("FEN")) { + fen = tagPairs.get(i).tagValue; + } + } + setStartPos(TextIO.readFEN(fen)); + + String result = ""; + for (int i = 0; i < nTags; i++) { + String name = tagPairs.get(i).tagName; + String val = tagPairs.get(i).tagValue; + if (name.equals("FEN") || name.equals("Setup")) { + // Already handled + } else if (name.equals("Event")) { + event = val; + } else if (name.equals("Site")) { + site = val; + } else if (name.equals("Date")) { + date = val; + } else if (name.equals("Round")) { + round = val; + } else if (name.equals("White")) { + white = val; + } else if (name.equals("Black")) { + black = val; + } else if (name.equals("Result")) { + result = val; + } else if (name.equals("TimeControl")) { + timeControl = val; + } else { + this.tagPairs.add(tagPairs.get(i)); + } + } + + rootNode = gameRoot; + currentNode = rootNode; + + // If result indicated draw by agreement or a resigned game, + // add that info to the game tree. + { + // Go to end of mainline + while (variations().size() > 0) + goForward(0); + GameState state = getGameState(); + if (state == GameState.ALIVE) { + if (result.equals("1-0")) { + if (currentPos.whiteMove) { + currentNode.playerAction = "resign"; + } else { + addMove("--", "resign", 0, "", ""); + } + } else if (result.equals("0-1")) { + if (!currentPos.whiteMove) { + currentNode.playerAction = "resign"; + } else { + addMove("--", "resign", 0, "", ""); + } + } else if (result.equals("1/2-1/2") || result.equals("1/2")) { + currentNode.playerAction = "draw offer"; + addMove("--", "draw accept", 0, "", ""); + } + } + // Go back to the root + while (currentNode != rootNode) + goBack(); + } + + updateListener(); + return true; + } + + /** Serialize to byte array. */ + public final byte[] toByteArray() { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(32768); + DataOutputStream dos = new DataOutputStream(baos); + dos.writeUTF(event); + dos.writeUTF(site); + dos.writeUTF(date); + dos.writeUTF(round); + dos.writeUTF(white); + dos.writeUTF(black); + dos.writeUTF(TextIO.toFEN(startPos)); + dos.writeUTF(timeControl); + int nTags = tagPairs.size(); + dos.writeInt(nTags); + for (int i = 0; i < nTags; i++) { + dos.writeUTF(tagPairs.get(i).tagName); + dos.writeUTF(tagPairs.get(i).tagValue); + } + Node.writeToStream(dos, rootNode); + List pathFromRoot = currentNode.getPathFromRoot(); + int pathLen = pathFromRoot.size(); + dos.writeInt(pathLen); + for (int i = 0; i < pathLen; i++) + dos.writeInt(pathFromRoot.get(i)); + dos.flush(); + dos.close(); + byte[] ret = baos.toByteArray(); + baos.close(); + return ret; + } catch (IOException e) { + return null; + } + } + + /** De-serialize from byte array. */ + public final void fromByteArray(byte[] data) { + try { + ByteArrayInputStream bais = new ByteArrayInputStream(data); + DataInputStream dis = new DataInputStream(bais); + event = dis.readUTF(); + site = dis.readUTF(); + date = dis.readUTF(); + round = dis.readUTF(); + white = dis.readUTF(); + black = dis.readUTF(); + startPos = TextIO.readFEN(dis.readUTF()); + currentPos = new Position(startPos); + timeControl = dis.readUTF(); + int nTags = dis.readInt(); + tagPairs.clear(); + for (int i = 0; i < nTags; i++) { + TagPair tp = new TagPair(); + tp.tagName = dis.readUTF(); + tp.tagValue = dis.readUTF(); + tagPairs.add(tp); + } + rootNode = new Node(); + Node.readFromStream(dis, rootNode); + currentNode = rootNode; + int pathLen = dis.readInt(); + for (int i = 0; i < pathLen; i++) + goForward(dis.readInt()); + dis.close(); + bais.close(); + } catch (IOException e) { + } catch (ChessParseError e) { + } + updateListener(); + } + + + /** Go backward in game tree. */ + public final void goBack() { + if (currentNode.parent != null) { + currentPos.unMakeMove(currentNode.move, currentNode.ui); + currentNode = currentNode.parent; + } + } + + /** Go forward in game tree. + * @param variation Which variation to follow. -1 to follow default variation. + */ + public final void goForward(int variation) { + goForward(variation, true); + } + public final void goForward(int variation, boolean updateDefault) { + if (currentNode.verifyChildren(currentPos)) + updateListener(); + if (variation < 0) + variation = currentNode.defaultChild; + int numChildren = currentNode.children.size(); + if (variation >= numChildren) + variation = 0; + if (updateDefault) + currentNode.defaultChild = variation; + if (numChildren > 0) { + currentNode = currentNode.children.get(variation); + currentPos.makeMove(currentNode.move, currentNode.ui); + TextIO.fixupEPSquare(currentPos); + } + } + + /** List of possible continuation moves. */ + public final List variations() { + if (currentNode.verifyChildren(currentPos)) + updateListener(); + List ret = new ArrayList(); + for (Node child : currentNode.children) + ret.add(child.move); + return ret; + } + + /** Add a move last in the list of variations. + * @return Move number in variations list. -1 if moveStr is not a valid move + */ + public final int addMove(String moveStr, String playerAction, int nag, String preComment, String postComment) { + if (currentNode.verifyChildren(currentPos)) + updateListener(); + int idx = currentNode.children.size(); + Node node = new Node(currentNode, moveStr, playerAction, Integer.MIN_VALUE, nag, preComment, postComment); + Move move = TextIO.UCIstringToMove(moveStr); + if (move == null) + move = TextIO.stringToMove(currentPos, moveStr); + if (move == null) + return -1; + node.moveStr = TextIO.moveToString(currentPos, move, false); + node.move = move; + node.ui = new UndoInfo(); + currentNode.children.add(node); + updateListener(); + return idx; + } + + /** Move a variation in the ordered list of variations. */ + public final void reorderVariation(int varNo, int newPos) { + if (currentNode.verifyChildren(currentPos)) + updateListener(); + int nChild = currentNode.children.size(); + if ((varNo < 0) || (varNo >= nChild) || (newPos < 0) || (newPos >= nChild)) + return; + Node var = currentNode.children.get(varNo); + currentNode.children.remove(varNo); + currentNode.children.add(newPos, var); + + int newDef = currentNode.defaultChild; + if (varNo == newDef) { + newDef = newPos; + } else { + if (varNo < newDef) newDef--; + if (newPos <= newDef) newDef++; + } + currentNode.defaultChild = newDef; + updateListener(); + } + + /** Delete a variation. */ + public final void deleteVariation(int varNo) { + if (currentNode.verifyChildren(currentPos)) + updateListener(); + int nChild = currentNode.children.size(); + if ((varNo < 0) || (varNo >= nChild)) + return; + currentNode.children.remove(varNo); + if (varNo == currentNode.defaultChild) { + currentNode.defaultChild = 0; + } else if (varNo < currentNode.defaultChild) { + currentNode.defaultChild--; + } + updateListener(); + } + + /* Get linear game history, using default variations at branch points. */ + public final Pair, Integer> getMoveList() { + List ret = new ArrayList(); + Node node = currentNode; + while (node != rootNode) { + ret.add(node); + node = node.parent; + } + Collections.reverse(ret); + int numMovesPlayed = ret.size(); + node = currentNode; + Position pos = new Position(currentPos); + UndoInfo ui = new UndoInfo(); + boolean changed = false; + while (true) { + if (node.verifyChildren(pos)) + changed = true; + if (node.defaultChild >= node.children.size()) + break; + Node child = node.children.get(node.defaultChild); + ret.add(child); + pos.makeMove(child.move, ui); + node = child; + } + if (changed) + updateListener(); + return new Pair, Integer>(ret, numMovesPlayed); + } + + final void setRemainingTime(int remaining) { + currentNode.remainingTime = remaining; + } + + final int getRemainingTime(boolean whiteMove, int initialTime) { + int undef = Integer.MIN_VALUE; + int remainingTime = undef; + Node node = currentNode; + boolean wtm = currentPos.whiteMove; + while (true) { + if (wtm != whiteMove) { // If wtm in current mode, black made last move + remainingTime = node.remainingTime; + if (remainingTime != undef) + break; + } + Node parent = node.parent; + if (parent == null) + break; + wtm = !wtm; + node = parent; + } + if (remainingTime == undef) { + remainingTime = initialTime; + } + return remainingTime; + } + + final GameState getGameState() { + Position pos = currentPos; + String action = currentNode.playerAction; + if (action.equals("resign")) { + // Player made null move to resign, causing whiteMove to toggle + return pos.whiteMove ? GameState.RESIGN_BLACK : GameState.RESIGN_WHITE; + } + ArrayList moves = new MoveGen().pseudoLegalMoves(pos); + moves = MoveGen.removeIllegal(pos, moves); + if (moves.size() == 0) { + if (MoveGen.inCheck(pos)) { + return pos.whiteMove ? GameState.BLACK_MATE : GameState.WHITE_MATE; + } else { + return pos.whiteMove ? GameState.WHITE_STALEMATE : GameState.BLACK_STALEMATE; + } + } + if (insufficientMaterial(pos)) { + return GameState.DRAW_NO_MATE; + } + + if (action.startsWith("draw accept")) { + return GameState.DRAW_AGREE; + } + if (action.startsWith("draw rep")) { + return GameState.DRAW_REP; + } + if (action.startsWith("draw 50")) { + return GameState.DRAW_50; + } + return GameState.ALIVE; + } + + /** Get additional info affecting gameState. A player "draw" or "resign" command. */ + final String getGameStateInfo() { + String ret = ""; + String action = currentNode.playerAction; + if (action.startsWith("draw rep ")) { + ret = action.substring(9).trim(); + } + if (action.startsWith("draw 50 ")) { + ret = action.substring(8).trim(); + } + return ret; + } + + public final String getPGNResultString() { + String gameResult = "*"; + switch (getGameState()) { + case ALIVE: + break; + case WHITE_MATE: + case RESIGN_BLACK: + gameResult = "1-0"; + break; + case BLACK_MATE: + case RESIGN_WHITE: + gameResult = "0-1"; + break; + case WHITE_STALEMATE: + case BLACK_STALEMATE: + case DRAW_REP: + case DRAW_50: + case DRAW_NO_MATE: + case DRAW_AGREE: + gameResult = "1/2-1/2"; + break; + } + return gameResult; + } + + private static final boolean insufficientMaterial(Position pos) { + if (pos.nPieces(Piece.WQUEEN) > 0) return false; + if (pos.nPieces(Piece.WROOK) > 0) return false; + if (pos.nPieces(Piece.WPAWN) > 0) return false; + if (pos.nPieces(Piece.BQUEEN) > 0) return false; + if (pos.nPieces(Piece.BROOK) > 0) return false; + if (pos.nPieces(Piece.BPAWN) > 0) return false; + int wb = pos.nPieces(Piece.WBISHOP); + int wn = pos.nPieces(Piece.WKNIGHT); + int bb = pos.nPieces(Piece.BBISHOP); + int bn = pos.nPieces(Piece.BKNIGHT); + if (wb + wn + bb + bn <= 1) { + return true; // King + bishop/knight vs king is draw + } + if (wn + bn == 0) { + // Only bishops. If they are all on the same color, the position is a draw. + boolean bSquare = false; + boolean wSquare = false; + for (int x = 0; x < 8; x++) { + for (int y = 0; y < 8; y++) { + int p = pos.getPiece(Position.getSquare(x, y)); + if ((p == Piece.BBISHOP) || (p == Piece.WBISHOP)) { + if (Position.darkSquare(x, y)) { + bSquare = true; + } else { + wSquare = true; + } + } + } + } + if (!bSquare || !wSquare) { + return true; + } + } + return false; + } + + + /** Keep track of current move and side to move. Used for move number printing. */ + private static final class MoveNumber { + final int moveNo; + final boolean wtm; // White to move + MoveNumber(int moveNo, boolean wtm) { + this.moveNo = moveNo; + this.wtm = wtm; + } + public final MoveNumber next() { + if (wtm) return new MoveNumber(moveNo, false); + else return new MoveNumber(moveNo + 1, true); + } + public final MoveNumber prev() { + if (wtm) return new MoveNumber(moveNo - 1, false); + else return new MoveNumber(moveNo, true); + } + } + + /** + * A node object represents a position in the game tree. + * The position is defined by the move that leads to the position from the parent position. + * The root node is special in that it doesn't have a move. + */ + public static class Node { + String moveStr; // String representation of move leading to this node. Empty string root node. + Move move; // Computed on demand for better PGN parsing performance. + // Subtrees of invalid moves will be dropped when detected. + // Always valid for current node. + private UndoInfo ui; // Computed when move is computed + String playerAction; // Player action. Draw claim/offer/accept or resign. + + int remainingTime; // Remaining time in ms for side that played moveStr, or INT_MIN if unknown. + int nag; // Numeric annotation glyph + String preComment; // Comment before move + String postComment; // Comment after move + + private Node parent; // Null if root node + int defaultChild; + private List children; + + public Node() { + this.moveStr = ""; + this.move = null; + this.ui = null; + this.playerAction = ""; + this.remainingTime = Integer.MIN_VALUE; + this.parent = null; + this.children = new ArrayList(); + this.defaultChild = 0; + this.nag = 0; + this.preComment = ""; + this.postComment = ""; + } + + public Node(Node parent, String moveStr, String playerAction, int remainingTime, int nag, + String preComment, String postComment) { + this.moveStr = moveStr; + this.move = null; + this.ui = null; + this.playerAction = playerAction; + this.remainingTime = remainingTime; + this.parent = parent; + this.children = new ArrayList(); + this.defaultChild = 0; + this.nag = nag; + this.preComment = preComment; + this.postComment = postComment; + } + + /** nodePos must represent the same position as this Node object. */ + private final boolean verifyChildren(Position nodePos) { + boolean anyToRemove = false; + for (Node child : children) { + if (child.move == null) { + Move move = TextIO.stringToMove(nodePos, child.moveStr); + if (move != null) { + child.moveStr = TextIO.moveToString(nodePos, move, false); + child.move = move; + child.ui = new UndoInfo(); + } else { + anyToRemove = true; + } + } + } + if (anyToRemove) { + List validChildren = new ArrayList(); + for (Node child : children) + if (child.move != null) + validChildren.add(child); + children = validChildren; + } + return anyToRemove; + } + + final List getPathFromRoot() { + List ret = new ArrayList(64); + Node node = this; + while (node.parent != null) { + ret.add(node.parent.defaultChild); + node = node.parent; + } + Collections.reverse(ret); + return ret; + } + + static final void writeToStream(DataOutputStream dos, Node node) throws IOException { + while (true) { + dos.writeUTF(node.moveStr); + if (node.move != null) { + dos.writeByte(node.move.from); + dos.writeByte(node.move.to); + dos.writeByte(node.move.promoteTo); + } else { + dos.writeByte(-1); + } + dos.writeUTF(node.playerAction); + dos.writeInt(node.remainingTime); + dos.writeInt(node.nag); + dos.writeUTF(node.preComment); + dos.writeUTF(node.postComment); + dos.writeInt(node.defaultChild); + int nChildren = node.children.size(); + dos.writeInt(nChildren); + if (nChildren == 0) + break; + for (int i = 1; i < nChildren; i++) { + writeToStream(dos, node.children.get(i)); + } + node = node.children.get(0); + } + } + + static final void readFromStream(DataInputStream dis, Node node) throws IOException { + while (true) { + node.moveStr = dis.readUTF(); + int from = dis.readByte(); + if (from >= 0) { + int to = dis.readByte(); + int prom = dis.readByte(); + node.move = new Move(from, to, prom); + node.ui = new UndoInfo(); + } + node.playerAction = dis.readUTF(); + node.remainingTime = dis.readInt(); + node.nag = dis.readInt(); + node.preComment = dis.readUTF(); + node.postComment = dis.readUTF(); + node.defaultChild = dis.readInt(); + int nChildren = dis.readInt(); + if (nChildren == 0) + break; + for (int i = 1; i < nChildren; i++) { + Node child = new Node(); + child.parent = node; + readFromStream(dis, child); + node.children.add(child); + } + Node child = new Node(); + child.parent = node; + node.children.add(0, child); + node = child; + } + } + + /** Export whole tree rooted at "node" in PGN format. */ + public static final void addPgnData(PgnToken.PgnTokenReceiver out, Node node, + MoveNumber moveNum, PGNOptions options) { + boolean needMoveNr = node.addPgnDataOneNode(out, moveNum, true, options); + while (true) { + int nChild = node.children.size(); + if (nChild == 0) + break; + MoveNumber nextMN = moveNum.next(); + needMoveNr = node.children.get(0).addPgnDataOneNode(out, nextMN, needMoveNr, options); + if (options.exp.variations) { + for (int i = 1; i < nChild; i++) { + out.processToken(node, PgnToken.LEFT_PAREN, null); + addPgnData(out, node.children.get(i), nextMN, options); + out.processToken(node, PgnToken.RIGHT_PAREN, null); + needMoveNr = true; + } + } + node = node.children.get(0); + moveNum = moveNum.next(); + } + } + + /** Export this node in PGN format. */ + private final boolean addPgnDataOneNode(PgnToken.PgnTokenReceiver out, MoveNumber mn, + boolean needMoveNr, PGNOptions options) { + if ((preComment.length() > 0) && options.exp.comments) { + out.processToken(this, PgnToken.COMMENT, preComment); + needMoveNr = true; + } + if (moveStr.length() > 0) { + boolean nullSkip = moveStr.equals("--") && (playerAction.length() > 0) && !options.exp.playerAction; + if (!nullSkip) { + if (mn.wtm) { + out.processToken(this, PgnToken.INTEGER, Integer.valueOf(mn.moveNo).toString()); + out.processToken(this, PgnToken.PERIOD, null); + } else { + if (needMoveNr) { + out.processToken(this, PgnToken.INTEGER, Integer.valueOf(mn.moveNo).toString()); + for (int i = 0; i < 3; i++) + out.processToken(this, PgnToken.PERIOD, null); + } + } + String str = moveStr; + if (options.exp.pgnPromotions && (move != null) && (move.promoteTo != Piece.EMPTY)) { + str = TextIO.pgnPromotion(str); + } + out.processToken(this, PgnToken.SYMBOL, str); + needMoveNr = false; + } + } + if ((nag > 0) && options.exp.nag) { + out.processToken(this, PgnToken.NAG, Integer.valueOf(nag).toString()); + if (options.exp.moveNrAfterNag) + needMoveNr = true; + } + if ((postComment.length() > 0) && options.exp.comments) { + out.processToken(this, PgnToken.COMMENT, postComment); + needMoveNr = true; + } + if ((playerAction.length() > 0) && options.exp.playerAction) { + addExtendedInfo(out, "playeraction", playerAction); + needMoveNr = true; + } + if ((remainingTime != Integer.MIN_VALUE) && options.exp.clockInfo) { + addExtendedInfo(out, "clk", getTimeStr(remainingTime)); + needMoveNr = true; + } + return needMoveNr; + } + + private final void addExtendedInfo(PgnToken.PgnTokenReceiver out, + String extCmd, String extData) { + out.processToken(this, PgnToken.COMMENT, "{[%" + extCmd + " " + extData + "]}"); + } + + private static final String getTimeStr(int remainingTime) { + int secs = (int)Math.floor((remainingTime + 999) / 1000.0); + boolean neg = false; + if (secs < 0) { + neg = true; + secs = -secs; + } + int mins = secs / 60; + secs -= mins * 60; + int hours = mins / 60; + mins -= hours * 60; + StringBuilder ret = new StringBuilder(); + if (neg) ret.append('-'); + if (hours < 10) ret.append('0'); + ret.append(hours); + ret.append(':'); + if (mins < 10) ret.append('0'); + ret.append(mins); + ret.append(':'); + if (secs < 10) ret.append('0'); + ret.append(secs); + return ret.toString(); + } + + private final Node addChild(Node child) { + child.parent = this; + children.add(child); + return child; + } + + public static final void parsePgn(PgnScanner scanner, Node node, PGNOptions options) { + Node nodeToAdd = new Node(); + boolean moveAdded = false; + while (true) { + PgnToken tok = scanner.nextToken(); + switch (tok.type) { + case PgnToken.INTEGER: + case PgnToken.PERIOD: + break; + case PgnToken.LEFT_PAREN: + if (moveAdded) { + node = node.addChild(nodeToAdd); + nodeToAdd = new Node(); + moveAdded = false; + } + if ((node.parent != null) && options.imp.variations) { + parsePgn(scanner, node.parent, options); + } else { + int nestLevel = 1; + while (nestLevel > 0) { + switch (scanner.nextToken().type) { + case PgnToken.LEFT_PAREN: nestLevel++; break; + case PgnToken.RIGHT_PAREN: nestLevel--; break; + case PgnToken.EOF: return; // Broken PGN file. Just give up. + } + } + } + break; + case PgnToken.NAG: + if (moveAdded && options.imp.nag) { // NAG must be after move + try { + nodeToAdd.nag = Integer.parseInt(tok.token); + } catch (NumberFormatException e) { + nodeToAdd.nag = 0; + } + } + break; + case PgnToken.SYMBOL: + if (tok.token.equals("1-0") || tok.token.equals("0-1") || tok.token.equals("1/2-1/2")) { + if (moveAdded) node.addChild(nodeToAdd); + return; + } + char lastChar = tok.token.charAt(tok.token.length() - 1); + if (lastChar == '+') + tok.token = tok.token.substring(0, tok.token.length() - 1); + if ((lastChar == '!') || (lastChar == '?')) { + int movLen = tok.token.length() - 1; + while (movLen > 0) { + char c = tok.token.charAt(movLen - 1); + if ((c == '!') || (c == '?')) + movLen--; + else + break; + } + String ann = tok.token.substring(movLen); + tok.token = tok.token.substring(0, movLen); + int nag = 0; + if (ann.equals("!")) nag = 1; + else if (ann.equals("?")) nag = 2; + else if (ann.equals("!!")) nag = 3; + else if (ann.equals("??")) nag = 4; + else if (ann.equals("!?")) nag = 5; + else if (ann.equals("?!")) nag = 6; + if (nag > 0) + scanner.putBack(new PgnToken(PgnToken.NAG, Integer.valueOf(nag).toString())); + } + if (tok.token.length() > 0) { + if (moveAdded) { + node = node.addChild(nodeToAdd); + nodeToAdd = new Node(); + moveAdded = false; + } + nodeToAdd.moveStr = tok.token; + moveAdded = true; + } + break; + case PgnToken.COMMENT: + try { + while (true) { + Pair ret = extractExtInfo(tok.token, "clk"); + tok.token = ret.first; + String cmdPars = ret.second; + if (cmdPars == null) + break; + nodeToAdd.remainingTime = parseTimeString(cmdPars); + } + while (true) { + Pair ret = extractExtInfo(tok.token, "playeraction"); + tok.token = ret.first; + String cmdPars = ret.second; + if (cmdPars == null) + break; + nodeToAdd.playerAction = cmdPars; + } + } catch (IndexOutOfBoundsException e) { + } + if (options.imp.comments) { + if (moveAdded) + nodeToAdd.postComment += tok.token; + else + nodeToAdd.preComment += tok.token; + } + break; + case PgnToken.ASTERISK: + case PgnToken.LEFT_BRACKET: + case PgnToken.RIGHT_BRACKET: + case PgnToken.STRING: + case PgnToken.RIGHT_PAREN: + case PgnToken.EOF: + if (moveAdded) node.addChild(nodeToAdd); + return; + } + } + } + + private static final Pair extractExtInfo(String comment, String cmd) { + comment = comment.replaceAll("\n|\r|\t", " "); + String remaining = comment; + String param = null; + String match = "[%" + cmd + " "; + int start = comment.indexOf(match); + if (start >= 0) { + int end = comment.indexOf("]", start); + if (end >= 0) { + remaining = comment.substring(0, start) + comment.substring(end + 1); + param = comment.substring(start + match.length(), end); + } + } + return new Pair(remaining, param); + } + + /** Convert hh:mm:ss to milliseconds */ + private static final int parseTimeString(String str) { + str = str.trim(); + int ret = 0; + boolean neg = false; + int i = 0; + if (str.charAt(0) == '-') { + neg = true; + i++; + } + int num = 0; + final int len = str.length(); + for ( ; i < len; i++) { + char c = str.charAt(i); + if ((c >= '0') && (c <= '9')) { + num = num * 10 + c - '0'; + } else if (c == ':') { + ret += num; + num = 0; + ret *= 60; + } + } + ret += num; + ret *= 1000; + if (neg) + ret = -ret; + return ret; + } + + public final static String nagStr(int nag) { + switch (nag) { + case 1: return "!"; + case 2: return "?"; + case 3: return "!!"; + case 4: return "??"; + case 5: return "!?"; + case 6: return "?!"; + case 11: return " ="; + case 13: return " ∞"; + case 14: return " +/="; + case 15: return " =/+"; + case 16: return " +/-"; + case 17: return " -/+"; + case 18: return " +-"; + case 19: return " -+"; + default: return ""; + } + } + + public final static int strToNag(String str) { + if (str.equals("!")) return 1; + else if (str.equals("?")) return 2; + else if (str.equals("!!")) return 3; + else if (str.equals("??")) return 4; + else if (str.equals("!?")) return 5; + else if (str.equals("?!")) return 6; + else if (str.equals("=")) return 11; + else if (str.equals("∞")) return 13; + else if (str.equals("+/=")) return 14; + else if (str.equals("=/+")) return 15; + else if (str.equals("+/-")) return 16; + else if (str.equals("-/+")) return 17; + else if (str.equals("+-")) return 18; + else if (str.equals("-+")) return 19; + else { + try { + str = str.replace("$", ""); + int nag = Integer.parseInt(str); + return nag; + } catch (NumberFormatException nfe) { + } + return 0; + } + } + } + + void setHeaders(ArrayList tags, ArrayList vals) { + for (int i = 0 ; i < tags.size(); i++) { + String tag = tags.get(i); + String val = vals.get(i); + if (tag.equals("Event")) event = val; + else if (tag.equals("Site")) site = val; + else if (tag.equals("Date")) date = val; + else if (tag.equals("Round")) round = val; + else if (tag.equals("White")) white = val; + else if (tag.equals("Black")) black = val; + else { + TagPair tp = new TagPair(); + tp.tagName = tag; + tp.tagValue = val; + tagPairs.add(tp); + } + } + } + + void getHeaders(ArrayList tags, ArrayList vals) { + tags.add("Event"); vals.add(event); + tags.add("Site"); vals.add(site); + tags.add("Date"); vals.add(date); + tags.add("Round"); vals.add(round); + tags.add("White"); vals.add(white); + tags.add("Black"); vals.add(black); + for (int i = 0; i < tagPairs.size(); i++) { + tags.add(tagPairs.get(i).tagName); + vals.add(tagPairs.get(i).tagValue); + } + } +} diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/Move.java b/DroidFish/src/org/petero/droidfish/gamelogic/Move.java new file mode 100644 index 0000000..e1203a8 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/gamelogic/Move.java @@ -0,0 +1,70 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.gamelogic; + +/** + * + * @author petero + */ +public class Move { + /** From square, 0-63. */ + public int from; + + /** To square, 0-63. */ + public int to; + + /** Promotion piece. */ + public int promoteTo; + + /** Create a move object. */ + public Move(int from, int to, int promoteTo) { + this.from = from; + this.to = to; + this.promoteTo = promoteTo; + } + + public Move(Move m) { + this.from = m.from; + this.to = m.to; + this.promoteTo = m.promoteTo; + } + + @Override + public boolean equals(Object o) { + if ((o == null) || (o.getClass() != this.getClass())) + return false; + Move other = (Move)o; + if (from != other.from) + return false; + if (to != other.to) + return false; + if (promoteTo != other.promoteTo) + return false; + return true; + } + @Override + public int hashCode() { + return (from * 64 + to) * 16 + promoteTo; + } + + /** Useful for debugging. */ + public final String toString() { + return TextIO.moveToUCIString(this); + } +} diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/MoveGen.java b/DroidFish/src/org/petero/droidfish/gamelogic/MoveGen.java new file mode 100644 index 0000000..f09e229 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/gamelogic/MoveGen.java @@ -0,0 +1,355 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.gamelogic; + +import java.util.ArrayList; + + +/** + * + * @author petero + */ +public class MoveGen { + static MoveGen instance; + static { + instance = new MoveGen(); + } + + /** + * Generate and return a list of pseudo-legal moves. + * Pseudo-legal means that the moves doesn't necessarily defend from check threats. + */ + public final ArrayList pseudoLegalMoves(Position pos) { + ArrayList moveList = getMoveListObj(); + final boolean wtm = pos.whiteMove; + for (int x = 0; x < 8; x++) { + for (int y = 0; y < 8; y++) { + int sq = Position.getSquare(x, y); + int p = pos.getPiece(sq); + if ((p == Piece.EMPTY) || (Piece.isWhite(p) != wtm)) { + continue; + } + if ((p == Piece.WROOK) || (p == Piece.BROOK) || (p == Piece.WQUEEN) || (p == Piece.BQUEEN)) { + if (addDirection(moveList, pos, sq, 7-x, 1)) return moveList; + if (addDirection(moveList, pos, sq, 7-y, 8)) return moveList; + if (addDirection(moveList, pos, sq, x, -1)) return moveList; + if (addDirection(moveList, pos, sq, y, -8)) return moveList; + } + if ((p == Piece.WBISHOP) || (p == Piece.BBISHOP) || (p == Piece.WQUEEN) || (p == Piece.BQUEEN)) { + if (addDirection(moveList, pos, sq, Math.min(7-x, 7-y), 9)) return moveList; + if (addDirection(moveList, pos, sq, Math.min( x, 7-y), 7)) return moveList; + if (addDirection(moveList, pos, sq, Math.min( x, y), -9)) return moveList; + if (addDirection(moveList, pos, sq, Math.min(7-x, y), -7)) return moveList; + } + if ((p == Piece.WKNIGHT) || (p == Piece.BKNIGHT)) { + if (x < 6 && y < 7 && addDirection(moveList, pos, sq, 1, 10)) return moveList; + if (x < 7 && y < 6 && addDirection(moveList, pos, sq, 1, 17)) return moveList; + if (x > 0 && y < 6 && addDirection(moveList, pos, sq, 1, 15)) return moveList; + if (x > 1 && y < 7 && addDirection(moveList, pos, sq, 1, 6)) return moveList; + if (x > 1 && y > 0 && addDirection(moveList, pos, sq, 1, -10)) return moveList; + if (x > 0 && y > 1 && addDirection(moveList, pos, sq, 1, -17)) return moveList; + if (x < 7 && y > 1 && addDirection(moveList, pos, sq, 1, -15)) return moveList; + if (x < 6 && y > 0 && addDirection(moveList, pos, sq, 1, -6)) return moveList; + } + if ((p == Piece.WKING) || (p == Piece.BKING)) { + if (x < 7 && addDirection(moveList, pos, sq, 1, 1)) return moveList; + if (x < 7 && y < 7 && addDirection(moveList, pos, sq, 1, 9)) return moveList; + if ( y < 7 && addDirection(moveList, pos, sq, 1, 8)) return moveList; + if (x > 0 && y < 7 && addDirection(moveList, pos, sq, 1, 7)) return moveList; + if (x > 0 && addDirection(moveList, pos, sq, 1, -1)) return moveList; + if (x > 0 && y > 0 && addDirection(moveList, pos, sq, 1, -9)) return moveList; + if ( y > 0 && addDirection(moveList, pos, sq, 1, -8)) return moveList; + if (x < 7 && y > 0 && addDirection(moveList, pos, sq, 1, -7)) return moveList; + + int k0 = wtm ? Position.getSquare(4,0) : Position.getSquare(4,7); + if (Position.getSquare(x,y) == k0) { + int aCastle = wtm ? Position.A1_CASTLE : Position.A8_CASTLE; + int hCastle = wtm ? Position.H1_CASTLE : Position.H8_CASTLE; + int rook = wtm ? Piece.WROOK : Piece.BROOK; + if (((pos.getCastleMask() & (1 << hCastle)) != 0) && + (pos.getPiece(k0 + 1) == Piece.EMPTY) && + (pos.getPiece(k0 + 2) == Piece.EMPTY) && + (pos.getPiece(k0 + 3) == rook) && + !sqAttacked(pos, k0) && + !sqAttacked(pos, k0 + 1)) { + moveList.add(getMoveObj(k0, k0 + 2, Piece.EMPTY)); + } + if (((pos.getCastleMask() & (1 << aCastle)) != 0) && + (pos.getPiece(k0 - 1) == Piece.EMPTY) && + (pos.getPiece(k0 - 2) == Piece.EMPTY) && + (pos.getPiece(k0 - 3) == Piece.EMPTY) && + (pos.getPiece(k0 - 4) == rook) && + !sqAttacked(pos, k0) && + !sqAttacked(pos, k0 - 1)) { + moveList.add(getMoveObj(k0, k0 - 2, Piece.EMPTY)); + } + } + } + if ((p == Piece.WPAWN) || (p == Piece.BPAWN)) { + int yDir = wtm ? 8 : -8; + if (pos.getPiece(sq + yDir) == Piece.EMPTY) { // non-capture + addPawnMoves(moveList, sq, sq + yDir); + if ((y == (wtm ? 1 : 6)) && + (pos.getPiece(sq + 2 * yDir) == Piece.EMPTY)) { // double step + addPawnMoves(moveList, sq, sq + yDir * 2); + } + } + if (x > 0) { // Capture to the left + int toSq = sq + yDir - 1; + int cap = pos.getPiece(toSq); + if (cap != Piece.EMPTY) { + if (Piece.isWhite(cap) != wtm) { + if (cap == (wtm ? Piece.BKING : Piece.WKING)) { + returnMoveList(moveList); + moveList = getMoveListObj(); + moveList.add(getMoveObj(sq, toSq, Piece.EMPTY)); + return moveList; + } else { + addPawnMoves(moveList, sq, toSq); + } + } + } else if (toSq == pos.getEpSquare()) { + addPawnMoves(moveList, sq, toSq); + } + } + if (x < 7) { // Capture to the right + int toSq = sq + yDir + 1; + int cap = pos.getPiece(toSq); + if (cap != Piece.EMPTY) { + if (Piece.isWhite(cap) != wtm) { + if (cap == (wtm ? Piece.BKING : Piece.WKING)) { + returnMoveList(moveList); + moveList = getMoveListObj(); + moveList.add(getMoveObj(sq, toSq, Piece.EMPTY)); + return moveList; + } else { + addPawnMoves(moveList, sq, toSq); + } + } + } else if (toSq == pos.getEpSquare()) { + addPawnMoves(moveList, sq, toSq); + } + } + } + } + } + return moveList; + } + + /** + * Return true if the side to move is in check. + */ + public static final boolean inCheck(Position pos) { + int kingSq = pos.getKingSq(pos.whiteMove); + if (kingSq < 0) + return false; + return sqAttacked(pos, kingSq); + } + + /** + * Return true if a square is attacked by the opposite side. + */ + public static final boolean sqAttacked(Position pos, int sq) { + int x = Position.getX(sq); + int y = Position.getY(sq); + boolean isWhiteMove = pos.whiteMove; + + final int oQueen= isWhiteMove ? Piece.BQUEEN: Piece.WQUEEN; + final int oRook = isWhiteMove ? Piece.BROOK : Piece.WROOK; + final int oBish = isWhiteMove ? Piece.BBISHOP : Piece.WBISHOP; + final int oKnight = isWhiteMove ? Piece.BKNIGHT : Piece.WKNIGHT; + + int p; + if (y > 0) { + p = checkDirection(pos, sq, y, -8); if ((p == oQueen) || (p == oRook)) return true; + p = checkDirection(pos, sq, Math.min( x, y), -9); if ((p == oQueen) || (p == oBish)) return true; + p = checkDirection(pos, sq, Math.min(7-x, y), -7); if ((p == oQueen) || (p == oBish)) return true; + if (x > 1 ) { p = checkDirection(pos, sq, 1, -10); if (p == oKnight) return true; } + if (x > 0 && y > 1) { p = checkDirection(pos, sq, 1, -17); if (p == oKnight) return true; } + if (x < 7 && y > 1) { p = checkDirection(pos, sq, 1, -15); if (p == oKnight) return true; } + if (x < 6 ) { p = checkDirection(pos, sq, 1, -6); if (p == oKnight) return true; } + + if (!isWhiteMove) { + if (x < 7 && y > 1) { p = checkDirection(pos, sq, 1, -7); if (p == Piece.WPAWN) return true; } + if (x > 0 && y > 1) { p = checkDirection(pos, sq, 1, -9); if (p == Piece.WPAWN) return true; } + } + } + if (y < 7) { + p = checkDirection(pos, sq, 7-y, 8); if ((p == oQueen) || (p == oRook)) return true; + p = checkDirection(pos, sq, Math.min(7-x, 7-y), 9); if ((p == oQueen) || (p == oBish)) return true; + p = checkDirection(pos, sq, Math.min( x, 7-y), 7); if ((p == oQueen) || (p == oBish)) return true; + if (x < 6 ) { p = checkDirection(pos, sq, 1, 10); if (p == oKnight) return true; } + if (x < 7 && y < 6) { p = checkDirection(pos, sq, 1, 17); if (p == oKnight) return true; } + if (x > 0 && y < 6) { p = checkDirection(pos, sq, 1, 15); if (p == oKnight) return true; } + if (x > 1 ) { p = checkDirection(pos, sq, 1, 6); if (p == oKnight) return true; } + if (isWhiteMove) { + if (x < 7 && y < 6) { p = checkDirection(pos, sq, 1, 9); if (p == Piece.BPAWN) return true; } + if (x > 0 && y < 6) { p = checkDirection(pos, sq, 1, 7); if (p == Piece.BPAWN) return true; } + } + } + p = checkDirection(pos, sq, 7-x, 1); if ((p == oQueen) || (p == oRook)) return true; + p = checkDirection(pos, sq, x, -1); if ((p == oQueen) || (p == oRook)) return true; + + int oKingSq = pos.getKingSq(!isWhiteMove); + if (oKingSq >= 0) { + int ox = Position.getX(oKingSq); + int oy = Position.getY(oKingSq); + if ((Math.abs(x - ox) <= 1) && (Math.abs(y - oy) <= 1)) + return true; + } + + return false; + } + + /** + * Remove all illegal moves from moveList. + * "moveList" is assumed to be a list of pseudo-legal moves. + * This function removes the moves that don't defend from check threats. + */ + public static final ArrayList removeIllegal(Position pos, ArrayList moveList) { + ArrayList ret = new ArrayList(); + UndoInfo ui = new UndoInfo(); + int mlSize = moveList.size(); + for (int mi = 0; mi < mlSize; mi++) { + Move m = moveList.get(mi); + pos.makeMove(m, ui); + pos.setWhiteMove(!pos.whiteMove); + if (!inCheck(pos)) + ret.add(m); + pos.setWhiteMove(!pos.whiteMove); + pos.unMakeMove(m, ui); + } + return ret; + } + + /** + * Add all moves from square sq0 in direction delta. + * @param maxSteps Max steps until reaching a border. Set to 1 for non-sliding pieces. + * @ return True if the enemy king could be captured, false otherwise. + */ + private final boolean addDirection(ArrayList moveList, Position pos, int sq0, int maxSteps, int delta) { + int sq = sq0; + boolean wtm = pos.whiteMove; + final int oKing = (wtm ? Piece.BKING : Piece.WKING); + while (maxSteps > 0) { + sq += delta; + int p = pos.getPiece(sq); + if (p == Piece.EMPTY) { + moveList.add(getMoveObj(sq0, sq, Piece.EMPTY)); + } else { + if (Piece.isWhite(p) != wtm) { + if (p == oKing) { + returnMoveList(moveList); + moveList = getMoveListObj(); // Ugly! this only works because we get back the same object + moveList.add(getMoveObj(sq0, sq, Piece.EMPTY)); + return true; + } else { + moveList.add(getMoveObj(sq0, sq, Piece.EMPTY)); + } + } + break; + } + maxSteps--; + } + return false; + } + + /** + * Generate all possible pawn moves from (x0,y0) to (x1,y1), taking pawn promotions into account. + */ + private final void addPawnMoves(ArrayList moveList, int sq0, int sq1) { + if (sq1 >= 56) { // White promotion + moveList.add(getMoveObj(sq0, sq1, Piece.WQUEEN)); + moveList.add(getMoveObj(sq0, sq1, Piece.WKNIGHT)); + moveList.add(getMoveObj(sq0, sq1, Piece.WROOK)); + moveList.add(getMoveObj(sq0, sq1, Piece.WBISHOP)); + } else if (sq1 < 8) { // Black promotion + moveList.add(getMoveObj(sq0, sq1, Piece.BQUEEN)); + moveList.add(getMoveObj(sq0, sq1, Piece.BKNIGHT)); + moveList.add(getMoveObj(sq0, sq1, Piece.BROOK)); + moveList.add(getMoveObj(sq0, sq1, Piece.BBISHOP)); + } else { // No promotion + moveList.add(getMoveObj(sq0, sq1, Piece.EMPTY)); + } + } + + /** + * Check if there is an attacking piece in a given direction starting from sq. + * The direction is given by delta. + * @param maxSteps Max steps until reaching a border. Set to 1 for non-sliding pieces. + * @return The first piece in the given direction, or EMPTY if there is no piece + * in that direction. + */ + private static final int checkDirection(Position pos, int sq, int maxSteps, int delta) { + while (maxSteps > 0) { + sq += delta; + int p = pos.getPiece(sq); + if (p != Piece.EMPTY) + return p; + maxSteps--; + } + return Piece.EMPTY; + } + + // Code to handle the Move cache. + + private Move[] moveCache = new Move[2048]; + private int movesInCache = 0; + private Object[] moveListCache = new Object[200]; + private int moveListsInCache = 0; + + private final Move getMoveObj(int from, int to, int promoteTo) { + if (movesInCache > 0) { + Move m = moveCache[--movesInCache]; + m.from = from; + m.to = to; + m.promoteTo = promoteTo; + return m; + } + return new Move(from, to, promoteTo); + } + + @SuppressWarnings("unchecked") + private final ArrayList getMoveListObj() { + if (moveListsInCache > 0) { + return (ArrayList)moveListCache[--moveListsInCache]; + } + return new ArrayList(60); + } + + /** Return all move objects in moveList to the move cache. */ + public final void returnMoveList(ArrayList moveList) { + if (movesInCache + moveList.size() <= moveCache.length) { + int mlSize = moveList.size(); + for (int mi = 0; mi < mlSize; mi++) { + moveCache[movesInCache++] = moveList.get(mi); + } + } + moveList.clear(); + if (moveListsInCache < moveListCache.length) { + moveListCache[moveListsInCache++] = moveList; + } + } + + public final void returnMove(Move m) { + if (movesInCache < moveCache.length) { + moveCache[movesInCache++] = m; + } + } +} diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/Pair.java b/DroidFish/src/org/petero/droidfish/gamelogic/Pair.java new file mode 100644 index 0000000..0a51973 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/gamelogic/Pair.java @@ -0,0 +1,33 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.gamelogic; + +/** + * A small helper class that makes it possible to return two values from a function. + * @author petero + */ +public final class Pair { + public final T1 first; + public final T2 second; + + public Pair(T1 first, T2 second) { + this.first = first; + this.second = second; + } +} diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/PgnToken.java b/DroidFish/src/org/petero/droidfish/gamelogic/PgnToken.java new file mode 100644 index 0000000..8d03673 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/gamelogic/PgnToken.java @@ -0,0 +1,62 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.gamelogic; + +/** A token in a PGN data stream. Used by the PGN parser. */ +public class PgnToken { + // These are tokens according to the PGN spec + public static final int STRING = 0; + public static final int INTEGER = 1; + public static final int PERIOD = 2; + public static final int ASTERISK = 3; + public static final int LEFT_BRACKET = 4; + public static final int RIGHT_BRACKET = 5; + public static final int LEFT_PAREN = 6; + public static final int RIGHT_PAREN = 7; + public static final int NAG = 8; + public static final int SYMBOL = 9; + + // These are not tokens according to the PGN spec, but the parser + // extracts these anyway for convenience. + public static final int COMMENT = 10; + public static final int EOF = 11; + + // Actual token data + int type; + String token; + + PgnToken(int type, String token) { + this.type = type; + this.token = token; + } + + public interface PgnTokenReceiver { + /** If this method returns false, the object needs a full reinitialization, using clear() and processToken(). */ + public boolean isUpToDate(); + + /** Clear object state. */ + public void clear(); + + /** Update object state with one token from a PGN game. */ + public void processToken(GameTree.Node node, int type, String token); + + /** Change current move number. */ + public void setCurrent(GameTree.Node node); + }; +} diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/Piece.java b/DroidFish/src/org/petero/droidfish/gamelogic/Piece.java new file mode 100644 index 0000000..a94a53b --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/gamelogic/Piece.java @@ -0,0 +1,57 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.gamelogic; + +/** + * Constants for different piece types. + * @author petero + */ +public class Piece { + public static final int EMPTY = 0; + + public static final int WKING = 1; + public static final int WQUEEN = 2; + public static final int WROOK = 3; + public static final int WBISHOP = 4; + public static final int WKNIGHT = 5; + public static final int WPAWN = 6; + + public static final int BKING = 7; + public static final int BQUEEN = 8; + public static final int BROOK = 9; + public static final int BBISHOP = 10; + public static final int BKNIGHT = 11; + public static final int BPAWN = 12; + + public static final int nPieceTypes = 13; + + /** + * Return true if p is a white piece, false otherwise. + * Note that if p is EMPTY, an unspecified value is returned. + */ + public static boolean isWhite(int pType) { + return pType < BKING; + } + public static int makeWhite(int pType) { + return pType < BKING ? pType : pType - (BKING - WKING); + } + public static int makeBlack(int pType) { + return ((pType >= WKING) && (pType <= WPAWN)) ? pType + (BKING - WKING) : pType; + } +} diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/Position.java b/DroidFish/src/org/petero/droidfish/gamelogic/Position.java new file mode 100644 index 0000000..af613b2 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/gamelogic/Position.java @@ -0,0 +1,430 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.gamelogic; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Stores the state of a chess position. + * All required state is stored, except for all previous positions + * since the last capture or pawn move. That state is only needed + * for three-fold repetition draw detection, and is better stored + * in a separate hash table. + * @author petero + */ +public class Position { + private int[] squares; + + public boolean whiteMove; + + /** Bit definitions for the castleMask bit mask. */ + public static final int A1_CASTLE = 0; /** White long castle. */ + public static final int H1_CASTLE = 1; /** White short castle. */ + public static final int A8_CASTLE = 2; /** Black long castle. */ + public static final int H8_CASTLE = 3; /** Black short castle. */ + + private int castleMask; + + private int epSquare; + + /** Number of half-moves since last 50-move reset. */ + public int halfMoveClock; + + /** Game move number, starting from 1. */ + public int fullMoveCounter; + + private long hashKey; // Cached Zobrist hash key + private int wKingSq, bKingSq; // Cached king positions + + /** Initialize board to empty position. */ + public Position() { + squares = new int[64]; + for (int i = 0; i < 64; i++) + squares[i] = Piece.EMPTY; + whiteMove = true; + castleMask = 0; + epSquare = -1; + halfMoveClock = 0; + fullMoveCounter = 1; + hashKey = computeZobristHash(); + wKingSq = bKingSq = -1; + } + + public Position(Position other) { + squares = new int[64]; + System.arraycopy(other.squares, 0, squares, 0, 64); + whiteMove = other.whiteMove; + castleMask = other.castleMask; + epSquare = other.epSquare; + halfMoveClock = other.halfMoveClock; + fullMoveCounter = other.fullMoveCounter; + hashKey = other.hashKey; + wKingSq = other.wKingSq; + bKingSq = other.bKingSq; + } + + @Override + public boolean equals(Object o) { + if ((o == null) || (o.getClass() != this.getClass())) + return false; + Position other = (Position)o; + if (!drawRuleEquals(other)) + return false; + if (halfMoveClock != other.halfMoveClock) + return false; + if (fullMoveCounter != other.fullMoveCounter) + return false; + if (hashKey != other.hashKey) + return false; + return true; + } + @Override + public int hashCode() { + return (int)hashKey; + } + + /** + * Return Zobrish hash value for the current position. + * Everything except the move counters are included in the hash value. + */ + public final long zobristHash() { + return hashKey; + } + + /** + * Decide if two positions are equal in the sense of the draw by repetition rule. + * @return True if positions are equal, false otherwise. + */ + final public boolean drawRuleEquals(Position other) { + for (int i = 0; i < 64; i++) { + if (squares[i] != other.squares[i]) + return false; + } + if (whiteMove != other.whiteMove) + return false; + if (castleMask != other.castleMask) + return false; + if (epSquare != other.epSquare) + return false; + return true; + } + + public final void setWhiteMove(boolean whiteMove) { + if (whiteMove != this.whiteMove) { + hashKey ^= whiteHashKey; + this.whiteMove = whiteMove; + } + } + /** Return index in squares[] vector corresponding to (x,y). */ + public final static int getSquare(int x, int y) { + return y * 8 + x; + } + /** Return x position (file) corresponding to a square. */ + public final static int getX(int square) { + return square & 7; + } + /** Return y position (rank) corresponding to a square. */ + public final static int getY(int square) { + return square >> 3; + } + /** Return true if (x,y) is a dark square. */ + public final static boolean darkSquare(int x, int y) { + return (x & 1) == (y & 1); + } + + /** Return piece occuping a square. */ + public final int getPiece(int square) { + return squares[square]; + } + /** Set a square to a piece value. */ + public final void setPiece(int square, int piece) { + // Update hash key + int oldPiece = squares[square]; + hashKey ^= psHashKeys[oldPiece][square]; + hashKey ^= psHashKeys[piece][square]; + + // Update board + squares[square] = piece; + + // Update king position + if (piece == Piece.WKING) { + wKingSq = square; + } else if (piece == Piece.BKING) { + bKingSq = square; + } + } + + /** Return true if white long castling right has not been lost. */ + public final boolean a1Castle() { + return (castleMask & (1 << A1_CASTLE)) != 0; + } + /** Return true if white short castling right has not been lost. */ + public final boolean h1Castle() { + return (castleMask & (1 << H1_CASTLE)) != 0; + } + /** Return true if black long castling right has not been lost. */ + public final boolean a8Castle() { + return (castleMask & (1 << A8_CASTLE)) != 0; + } + /** Return true if black short castling right has not been lost. */ + public final boolean h8Castle() { + return (castleMask & (1 << H8_CASTLE)) != 0; + } + /** Bitmask describing castling rights. */ + public final int getCastleMask() { + return castleMask; + } + public final void setCastleMask(int castleMask) { + hashKey ^= castleHashKeys[this.castleMask]; + hashKey ^= castleHashKeys[castleMask]; + this.castleMask = castleMask; + } + + /** En passant square, or -1 if no ep possible. */ + public final int getEpSquare() { + return epSquare; + } + public final void setEpSquare(int epSquare) { + if (this.epSquare != epSquare) { + hashKey ^= epHashKeys[(this.epSquare >= 0) ? getX(this.epSquare) + 1 : 0]; + hashKey ^= epHashKeys[(epSquare >= 0) ? getX(epSquare) + 1 : 0]; + this.epSquare = epSquare; + } + } + + + public final int getKingSq(boolean whiteMove) { + return whiteMove ? wKingSq : bKingSq; + } + + /** + * Count number of pieces of a certain type. + */ + public final int nPieces(int pType) { + int ret = 0; + for (int sq = 0; sq < 64; sq++) { + if (squares[sq] == pType) + ret++; + } + return ret; + } + + /** Apply a move to the current position. */ + public final void makeMove(Move move, UndoInfo ui) { + ui.capturedPiece = squares[move.to]; + ui.castleMask = castleMask; + ui.epSquare = epSquare; + ui.halfMoveClock = halfMoveClock; + boolean wtm = whiteMove; + + int p = squares[move.from]; + int capP = squares[move.to]; + + boolean nullMove = (move.from == 0) && (move.to == 0); + + if (nullMove || (capP != Piece.EMPTY) || (p == (wtm ? Piece.WPAWN : Piece.BPAWN))) { + halfMoveClock = 0; + } else { + halfMoveClock++; + } + if (!wtm) { + fullMoveCounter++; + } + + // Handle castling + int king = wtm ? Piece.WKING : Piece.BKING; + int k0 = move.from; + if (p == king) { + if (move.to == k0 + 2) { // O-O + setPiece(k0 + 1, squares[k0 + 3]); + setPiece(k0 + 3, Piece.EMPTY); + } else if (move.to == k0 - 2) { // O-O-O + setPiece(k0 - 1, squares[k0 - 4]); + setPiece(k0 - 4, Piece.EMPTY); + } + if (wtm) { + setCastleMask(castleMask & ~(1 << Position.A1_CASTLE)); + setCastleMask(castleMask & ~(1 << Position.H1_CASTLE)); + } else { + setCastleMask(castleMask & ~(1 << Position.A8_CASTLE)); + setCastleMask(castleMask & ~(1 << Position.H8_CASTLE)); + } + } + if (!nullMove) { + int rook = wtm ? Piece.WROOK : Piece.BROOK; + if (p == rook) { + removeCastleRights(move.from); + } + int oRook = wtm ? Piece.BROOK : Piece.WROOK; + if (capP == oRook) { + removeCastleRights(move.to); + } + } + + // Handle en passant and epSquare + int prevEpSquare = epSquare; + setEpSquare(-1); + if (p == Piece.WPAWN) { + if (move.to - move.from == 2 * 8) { + int x = Position.getX(move.to); + if ( ((x > 0) && (squares[move.to - 1] == Piece.BPAWN)) || + ((x < 7) && (squares[move.to + 1] == Piece.BPAWN))) { + setEpSquare(move.from + 8); + } + } else if (move.to == prevEpSquare) { + setPiece(move.to - 8, Piece.EMPTY); + } + } else if (p == Piece.BPAWN) { + if (move.to - move.from == -2 * 8) { + int x = Position.getX(move.to); + if ( ((x > 0) && (squares[move.to - 1] == Piece.WPAWN)) || + ((x < 7) && (squares[move.to + 1] == Piece.WPAWN))) { + setEpSquare(move.from - 8); + } + } else if (move.to == prevEpSquare) { + setPiece(move.to + 8, Piece.EMPTY); + } + } + + // Perform move + setPiece(move.from, Piece.EMPTY); + // Handle promotion + if (move.promoteTo != Piece.EMPTY) { + setPiece(move.to, move.promoteTo); + } else { + setPiece(move.to, p); + } + setWhiteMove(!wtm); + } + + public final void unMakeMove(Move move, UndoInfo ui) { + setWhiteMove(!whiteMove); + int p = squares[move.to]; + setPiece(move.from, p); + setPiece(move.to, ui.capturedPiece); + setCastleMask(ui.castleMask); + setEpSquare(ui.epSquare); + halfMoveClock = ui.halfMoveClock; + boolean wtm = whiteMove; + if (move.promoteTo != Piece.EMPTY) { + p = wtm ? Piece.WPAWN : Piece.BPAWN; + setPiece(move.from, p); + } + if (!wtm) { + fullMoveCounter--; + } + + // Handle castling + int king = wtm ? Piece.WKING : Piece.BKING; + int k0 = move.from; + if (p == king) { + if (move.to == k0 + 2) { // O-O + setPiece(k0 + 3, squares[k0 + 1]); + setPiece(k0 + 1, Piece.EMPTY); + } else if (move.to == k0 - 2) { // O-O-O + setPiece(k0 - 4, squares[k0 - 1]); + setPiece(k0 - 1, Piece.EMPTY); + } + } + + // Handle en passant + if (move.to == epSquare) { + if (p == Piece.WPAWN) { + setPiece(move.to - 8, Piece.BPAWN); + } else if (p == Piece.BPAWN) { + setPiece(move.to + 8, Piece.WPAWN); + } + } + } + + private final void removeCastleRights(int square) { + if (square == Position.getSquare(0, 0)) { + setCastleMask(castleMask & ~(1 << Position.A1_CASTLE)); + } else if (square == Position.getSquare(7, 0)) { + setCastleMask(castleMask & ~(1 << Position.H1_CASTLE)); + } else if (square == Position.getSquare(0, 7)) { + setCastleMask(castleMask & ~(1 << Position.A8_CASTLE)); + } else if (square == Position.getSquare(7, 7)) { + setCastleMask(castleMask & ~(1 << Position.H8_CASTLE)); + } + } + + /* ------------- Hashing code ------------------ */ + + private static long[][] psHashKeys; // [piece][square] + private static long whiteHashKey; + private static long[] castleHashKeys; // [castleMask] + private static long[] epHashKeys; // [epFile + 1] (epFile==-1 for no ep) + + static { + psHashKeys = new long[Piece.nPieceTypes][64]; + castleHashKeys = new long[16]; + epHashKeys = new long[9]; + int rndNo = 0; + for (int p = 0; p < Piece.nPieceTypes; p++) { + for (int sq = 0; sq < 64; sq++) { + psHashKeys[p][sq] = getRandomHashVal(rndNo++); + } + } + whiteHashKey = getRandomHashVal(rndNo++); + for (int cm = 0; cm < castleHashKeys.length; cm++) + castleHashKeys[cm] = getRandomHashVal(rndNo++); + for (int f = 0; f < epHashKeys.length; f++) + epHashKeys[f] = getRandomHashVal(rndNo++); + } + + /** + * Compute the Zobrist hash value non-incrementally. Only useful for test programs. + */ + final long computeZobristHash() { + long hash = 0; + for (int sq = 0; sq < 64; sq++) { + int p = squares[sq]; + hash ^= psHashKeys[p][sq]; + } + if (whiteMove) + hash ^= whiteHashKey; + hash ^= castleHashKeys[castleMask]; + hash ^= epHashKeys[(epSquare >= 0) ? getX(epSquare) + 1 : 0]; + return hash; + } + + private final static long getRandomHashVal(int rndNo) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] input = new byte[4]; + for (int i = 0; i < 4; i++) + input[i] = (byte)((rndNo >> (i * 8)) & 0xff); + byte[] digest = md.digest(input); + long ret = 0; + for (int i = 0; i < 8; i++) { + ret ^= ((long)digest[i]) << (i * 8); + } + return ret; + } catch (NoSuchAlgorithmException ex) { + throw new UnsupportedOperationException("SHA-1 not available"); + } + } + + /** Useful for debugging. */ + public final String toString() { + return TextIO.asciiBoard(this); + } +} diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/SearchListener.java b/DroidFish/src/org/petero/droidfish/gamelogic/SearchListener.java new file mode 100644 index 0000000..1daa7d1 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/gamelogic/SearchListener.java @@ -0,0 +1,80 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.gamelogic; + +import java.util.ArrayList; +import java.util.List; + + +/** + * Used to get various search information during search + */ +public interface SearchListener { + public final static class PvInfo { + int depth; + int score; + int time; + int nodes; + int nps; + boolean isMate; + boolean upperBound; + boolean lowerBound; + ArrayList pv; + String pvStr = ""; + + public PvInfo(PvInfo pvi) { + depth = pvi.depth; + score = pvi.score; + time = pvi.time; + nodes = pvi.nodes; + nps = pvi.nps; + isMate = pvi.isMate; + upperBound = pvi.upperBound; + lowerBound = pvi.lowerBound; + pv = new ArrayList(pvi.pv.size()); + for (int i = 0; i < pvi.pv.size(); i++) + pv.add(pvi.pv.get(i)); + pvStr = pvi.pvStr; + } + + public PvInfo(int depth, int score, int time, int nodes, int nps, + boolean isMate, boolean upperBound, boolean lowerBound, ArrayList pv) { + this.depth = depth; + this.score = score; + this.time = time; + this.nodes = nodes; + this.nps = nps; + this.isMate = isMate; + this.upperBound = upperBound; + this.lowerBound = lowerBound; + this.pv = pv; + } + + public final void removeFirstMove() { + if (!pv.isEmpty()) + pv.remove(0); + } + } + + public void notifyDepth(int depth); + public void notifyCurrMove(Position pos, Move m, int moveNr); + public void notifyPV(Position pos, ArrayList pvInfo, boolean isPonder); + public void notifyStats(int nodes, int nps, int time); + public void notifyBookInfo(String bookInfo, List moveList); +} diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/TextIO.java b/DroidFish/src/org/petero/droidfish/gamelogic/TextIO.java new file mode 100644 index 0000000..2e56b9c --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/gamelogic/TextIO.java @@ -0,0 +1,708 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.gamelogic; + +import java.util.ArrayList; +import java.util.List; + + +/** + * + * @author petero + */ +public class TextIO { + static public final String startPosFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + + /** Parse a FEN string and return a chess Position object. */ + public static final Position readFEN(String fen) throws ChessParseError { + Position pos = new Position(); + String[] words = fen.split(" "); + if (words.length < 2) { + throw new ChessParseError("Too few spaces"); + } + for (int i = 0; i < words.length; i++) { + words[i] = words[i].trim(); + } + + // Piece placement + int row = 7; + int col = 0; + for (int i = 0; i < words[0].length(); i++) { + char c = words[0].charAt(i); + switch (c) { + case '1': col += 1; break; + case '2': col += 2; break; + case '3': col += 3; break; + case '4': col += 4; break; + case '5': col += 5; break; + case '6': col += 6; break; + case '7': col += 7; break; + case '8': col += 8; break; + case '/': row--; col = 0; break; + case 'P': safeSetPiece(pos, col, row, Piece.WPAWN); col++; break; + case 'N': safeSetPiece(pos, col, row, Piece.WKNIGHT); col++; break; + case 'B': safeSetPiece(pos, col, row, Piece.WBISHOP); col++; break; + case 'R': safeSetPiece(pos, col, row, Piece.WROOK); col++; break; + case 'Q': safeSetPiece(pos, col, row, Piece.WQUEEN); col++; break; + case 'K': safeSetPiece(pos, col, row, Piece.WKING); col++; break; + case 'p': safeSetPiece(pos, col, row, Piece.BPAWN); col++; break; + case 'n': safeSetPiece(pos, col, row, Piece.BKNIGHT); col++; break; + case 'b': safeSetPiece(pos, col, row, Piece.BBISHOP); col++; break; + case 'r': safeSetPiece(pos, col, row, Piece.BROOK); col++; break; + case 'q': safeSetPiece(pos, col, row, Piece.BQUEEN); col++; break; + case 'k': safeSetPiece(pos, col, row, Piece.BKING); col++; break; + default: throw new ChessParseError("Invalid piece", pos); + } + } + if (words[1].length() == 0) { + throw new ChessParseError("Invalid side", pos); + } + pos.setWhiteMove(words[1].charAt(0) == 'w'); + + // Castling rights + int castleMask = 0; + if (words.length > 2) { + for (int i = 0; i < words[2].length(); i++) { + char c = words[2].charAt(i); + switch (c) { + case 'K': + castleMask |= (1 << Position.H1_CASTLE); + break; + case 'Q': + castleMask |= (1 << Position.A1_CASTLE); + break; + case 'k': + castleMask |= (1 << Position.H8_CASTLE); + break; + case 'q': + castleMask |= (1 << Position.A8_CASTLE); + break; + case '-': + break; + default: + throw new ChessParseError("Invalid castling flags", pos); + } + } + } + pos.setCastleMask(castleMask); + removeBogusCastleFlags(pos); + + if (words.length > 3) { + // En passant target square + String epString = words[3]; + if (!epString.equals("-")) { + if (epString.length() < 2) { + throw new ChessParseError("Invalid en passant square", pos); + } + pos.setEpSquare(getSquare(epString)); + } + } + + try { + if (words.length > 4) { + pos.halfMoveClock = Integer.parseInt(words[4]); + } + if (words.length > 5) { + pos.fullMoveCounter = Integer.parseInt(words[5]); + } + } catch (NumberFormatException nfe) { + // Ignore errors here, since the fields are optional + } + + // Each side must have exactly one king + int wKings = 0; + int bKings = 0; + for (int x = 0; x < 8; x++) { + for (int y = 0; y < 8; y++) { + int p = pos.getPiece(Position.getSquare(x, y)); + if (p == Piece.WKING) { + wKings++; + } else if (p == Piece.BKING) { + bKings++; + } + } + } + if (wKings != 1) { + throw new ChessParseError("White must have exactly one king", pos); + } + if (bKings != 1) { + throw new ChessParseError("Black must have exactly one king", pos); + } + + // Make sure king can not be captured + Position pos2 = new Position(pos); + pos2.setWhiteMove(!pos.whiteMove); + if (MoveGen.inCheck(pos2)) { + throw new ChessParseError("King capture possible", pos); + } + + fixupEPSquare(pos); + + return pos; + } + + public static final void removeBogusCastleFlags(Position pos) { + int castleMask = pos.getCastleMask(); + int validCastle = 0; + if (pos.getPiece(4) == Piece.WKING) { + if (pos.getPiece(0) == Piece.WROOK) validCastle |= (1 << Position.A1_CASTLE); + if (pos.getPiece(7) == Piece.WROOK) validCastle |= (1 << Position.H1_CASTLE); + } + if (pos.getPiece(60) == Piece.BKING) { + if (pos.getPiece(56) == Piece.BROOK) validCastle |= (1 << Position.A8_CASTLE); + if (pos.getPiece(63) == Piece.BROOK) validCastle |= (1 << Position.H8_CASTLE); + } + castleMask &= validCastle; + pos.setCastleMask(castleMask); + } + + /** Remove pseudo-legal EP square if it is not legal, ie would leave king in check. */ + public static final void fixupEPSquare(Position pos) { + int epSquare = pos.getEpSquare(); + if (epSquare >= 0) { + ArrayList moves = MoveGen.instance.pseudoLegalMoves(pos); + moves = MoveGen.removeIllegal(pos, moves); + boolean epValid = false; + for (Move m : moves) { + if (m.to == epSquare) { + if (pos.getPiece(m.from) == (pos.whiteMove ? Piece.WPAWN : Piece.BPAWN)) { + epValid = true; + break; + } + } + } + if (!epValid) { + pos.setEpSquare(-1); + } + } + } + + private static final void safeSetPiece(Position pos, int col, int row, int p) throws ChessParseError { + if (row < 0) throw new ChessParseError("Too many rows"); + if (col > 7) throw new ChessParseError("Too many columns"); + if ((p == Piece.WPAWN) || (p == Piece.BPAWN)) { + if ((row == 0) || (row == 7)) + throw new ChessParseError("Pawn on first/last rank"); + } + pos.setPiece(Position.getSquare(col, row), p); + } + + /** Return a FEN string corresponding to a chess Position object. */ + public static final String toFEN(Position pos) { + StringBuilder ret = new StringBuilder(); + // Piece placement + for (int r = 7; r >=0; r--) { + int numEmpty = 0; + for (int c = 0; c < 8; c++) { + int p = pos.getPiece(Position.getSquare(c, r)); + if (p == Piece.EMPTY) { + numEmpty++; + } else { + if (numEmpty > 0) { + ret.append(numEmpty); + numEmpty = 0; + } + switch (p) { + case Piece.WKING: ret.append('K'); break; + case Piece.WQUEEN: ret.append('Q'); break; + case Piece.WROOK: ret.append('R'); break; + case Piece.WBISHOP: ret.append('B'); break; + case Piece.WKNIGHT: ret.append('N'); break; + case Piece.WPAWN: ret.append('P'); break; + case Piece.BKING: ret.append('k'); break; + case Piece.BQUEEN: ret.append('q'); break; + case Piece.BROOK: ret.append('r'); break; + case Piece.BBISHOP: ret.append('b'); break; + case Piece.BKNIGHT: ret.append('n'); break; + case Piece.BPAWN: ret.append('p'); break; + default: throw new RuntimeException(); + } + } + } + if (numEmpty > 0) { + ret.append(numEmpty); + } + if (r > 0) { + ret.append('/'); + } + } + ret.append(pos.whiteMove ? " w " : " b "); + + // Castling rights + boolean anyCastle = false; + if (pos.h1Castle()) { + ret.append('K'); + anyCastle = true; + } + if (pos.a1Castle()) { + ret.append('Q'); + anyCastle = true; + } + if (pos.h8Castle()) { + ret.append('k'); + anyCastle = true; + } + if (pos.a8Castle()) { + ret.append('q'); + anyCastle = true; + } + if (!anyCastle) { + ret.append('-'); + } + + // En passant target square + { + ret.append(' '); + if (pos.getEpSquare() >= 0) { + int x = Position.getX(pos.getEpSquare()); + int y = Position.getY(pos.getEpSquare()); + ret.append((char)(x + 'a')); + ret.append((char)(y + '1')); + } else { + ret.append('-'); + } + } + + // Move counters + ret.append(' '); + ret.append(pos.halfMoveClock); + ret.append(' '); + ret.append(pos.fullMoveCounter); + + return ret.toString(); + } + + /** + * Convert a chess move to human readable form. + * @param pos The chess position. + * @param move The executed move. + * @param longForm If true, use long notation, eg Ng1-f3. + * Otherwise, use short notation, eg Nf3 + */ + public static final String moveToString(Position pos, Move move, boolean longForm) { + ArrayList moves = MoveGen.instance.pseudoLegalMoves(pos); + moves = MoveGen.removeIllegal(pos, moves); + return moveToString(pos, move, longForm, moves); + } + private static final String moveToString(Position pos, Move move, boolean longForm, + List moves) { + if (move.equals(new Move(0, 0, 0))) + return "--"; + StringBuilder ret = new StringBuilder(); + int wKingOrigPos = Position.getSquare(4, 0); + int bKingOrigPos = Position.getSquare(4, 7); + if (move.from == wKingOrigPos && pos.getPiece(wKingOrigPos) == Piece.WKING) { + // Check white castle + if (move.to == Position.getSquare(6, 0)) { + ret.append("O-O"); + } else if (move.to == Position.getSquare(2, 0)) { + ret.append("O-O-O"); + } + } else if (move.from == bKingOrigPos && pos.getPiece(bKingOrigPos) == Piece.BKING) { + // Check white castle + if (move.to == Position.getSquare(6, 7)) { + ret.append("O-O"); + } else if (move.to == Position.getSquare(2, 7)) { + ret.append("O-O-O"); + } + } + if (ret.length() == 0) { + int p = pos.getPiece(move.from); + ret.append(pieceToChar(p)); + int x1 = Position.getX(move.from); + int y1 = Position.getY(move.from); + int x2 = Position.getX(move.to); + int y2 = Position.getY(move.to); + if (longForm) { + ret.append((char)(x1 + 'a')); + ret.append((char) (y1 + '1')); + ret.append(isCapture(pos, move) ? 'x' : '-'); + } else { + if (p == (pos.whiteMove ? Piece.WPAWN : Piece.BPAWN)) { + if (isCapture(pos, move)) { + ret.append((char) (x1 + 'a')); + } + } else { + int numSameTarget = 0; + int numSameFile = 0; + int numSameRow = 0; + int mSize = moves.size(); + for (int mi = 0; mi < mSize; mi++) { + Move m = moves.get(mi); + if ((pos.getPiece(m.from) == p) && (m.to == move.to)) { + numSameTarget++; + if (Position.getX(m.from) == x1) + numSameFile++; + if (Position.getY(m.from) == y1) + numSameRow++; + } + } + if (numSameTarget < 2) { + // No file/row info needed + } else if (numSameFile < 2) { + ret.append((char) (x1 + 'a')); // Only file info needed + } else if (numSameRow < 2) { + ret.append((char) (y1 + '1')); // Only row info needed + } else { + ret.append((char) (x1 + 'a')); // File and row info needed + ret.append((char) (y1 + '1')); + } + } + if (isCapture(pos, move)) { + ret.append('x'); + } + } + ret.append((char) (x2 + 'a')); + ret.append((char) (y2 + '1')); + if (move.promoteTo != Piece.EMPTY) + ret.append(pieceToChar(move.promoteTo)); + } + UndoInfo ui = new UndoInfo(); + pos.makeMove(move, ui); + boolean givesCheck = MoveGen.inCheck(pos); + if (givesCheck) { + ArrayList nextMoves = MoveGen.instance.pseudoLegalMoves(pos); + nextMoves = MoveGen.removeIllegal(pos, nextMoves); + if (nextMoves.size() == 0) { + ret.append('#'); + } else { + ret.append('+'); + } + } + pos.unMakeMove(move, ui); + + return ret.toString(); + } + + private static final boolean isCapture(Position pos, Move move) { + if (pos.getPiece(move.to) == Piece.EMPTY) { + int p = pos.getPiece(move.from); + if ((p == (pos.whiteMove ? Piece.WPAWN : Piece.BPAWN)) && (move.to == pos.getEpSquare())) { + return true; + } else { + return false; + } + } else { + return true; + } + } + + private final static class MoveInfo { + int piece; // -1 for unspecified + int fromX, fromY, toX, toY; // -1 for unspecified + int promPiece; // -1 for unspecified + MoveInfo() { piece = fromX = fromY = toX = toY = promPiece = -1; } + } + + /** + * Convert a chess move string to a Move object. + * The string may specify any combination of piece/source/target/promotion + * information as long as it matches exactly one valid move. + */ + public static final Move stringToMove(Position pos, String strMove) { + if (strMove.equals("--")) + return new Move(0, 0, 0); + + strMove = strMove.replaceAll("=", ""); + strMove = strMove.replaceAll("\\+", ""); + strMove = strMove.replaceAll("#", ""); + boolean wtm = pos.whiteMove; + + MoveInfo info = new MoveInfo(); + boolean capture = false; + if (strMove.equals("O-O") || strMove.equals("0-0") || strMove.equals("o-o")) { + info.piece = wtm ? Piece.WKING : Piece.BKING; + info.fromX = 4; + info.toX = 6; + info.fromY = info.toY = wtm ? 0 : 7; + info.promPiece= Piece.EMPTY; + } else if (strMove.equals("O-O-O") || strMove.equals("0-0-0") || strMove.equals("o-o-o")) { + info.piece = wtm ? Piece.WKING : Piece.BKING; + info.fromX = 4; + info.toX = 2; + info.fromY = info.toY = wtm ? 0 : 7; + info.promPiece= Piece.EMPTY; + } else { + boolean atToSq = false; + for (int i = 0; i < strMove.length(); i++) { + char c = strMove.charAt(i); + if (i == 0) { + int piece = charToPiece(wtm, c); + if (piece >= 0) { + info.piece = piece; + continue; + } + } + int tmpX = c - 'a'; + if ((tmpX >= 0) && (tmpX < 8)) { + if (atToSq || (info.fromX >= 0)) + info.toX = tmpX; + else + info.fromX = tmpX; + } + int tmpY = c - '1'; + if ((tmpY >= 0) && (tmpY < 8)) { + if (atToSq || (info.fromY >= 0)) + info.toY = tmpY; + else + info.fromY = tmpY; + } + if ((c == 'x') || (c == '-')) { + atToSq = true; + if (c == 'x') + capture = true; + } + if (i == strMove.length() - 1) { + int promPiece = charToPiece(wtm, c); + if (promPiece >= 0) { + info.promPiece = promPiece; + } + } + } + if ((info.fromX >= 0) && (info.toX < 0)) { + info.toX = info.fromX; + info.fromX = -1; + } + if ((info.fromY >= 0) && (info.toY < 0)) { + info.toY = info.fromY; + info.fromY = -1; + } + if (info.piece < 0) { + boolean haveAll = (info.fromX >= 0) && (info.fromY >= 0) && + (info.toX >= 0) && (info.toY >= 0); + if (!haveAll) + info.piece = wtm ? Piece.WPAWN : Piece.BPAWN; + } + if (info.promPiece < 0) + info.promPiece = Piece.EMPTY; + } + + ArrayList moves = MoveGen.instance.pseudoLegalMoves(pos); + moves = MoveGen.removeIllegal(pos, moves); + + ArrayList matches = new ArrayList(2); + for (int i = 0; i < moves.size(); i++) { + Move m = moves.get(i); + int p = pos.getPiece(m.from); + boolean match = true; + if ((info.piece >= 0) && (info.piece != p)) + match = false; + if ((info.fromX >= 0) && (info.fromX != Position.getX(m.from))) + match = false; + if ((info.fromY >= 0) && (info.fromY != Position.getY(m.from))) + match = false; + if ((info.toX >= 0) && (info.toX != Position.getX(m.to))) + match = false; + if ((info.toY >= 0) && (info.toY != Position.getY(m.to))) + match = false; + if ((info.promPiece >= 0) && (info.promPiece != m.promoteTo)) + match = false; + if (match) { + matches.add(m); + } + } + int nMatches = matches.size(); + if (nMatches == 0) + return null; + else if (nMatches == 1) + return matches.get(0); + if (!capture) + return null; + Move move = null; + for (int i = 0; i < matches.size(); i++) { + Move m = matches.get(i); + int capt = pos.getPiece(m.to); + if (capt != Piece.EMPTY) { + if (move == null) + move = m; + else + return null; + } + } + return move; + } + + /** Convert a move object to UCI string format. */ + public static final String moveToUCIString(Move m) { + String ret = squareToString(m.from); + ret += squareToString(m.to); + switch (m.promoteTo) { + case Piece.WQUEEN: + case Piece.BQUEEN: + ret += "q"; + break; + case Piece.WROOK: + case Piece.BROOK: + ret += "r"; + break; + case Piece.WBISHOP: + case Piece.BBISHOP: + ret += "b"; + break; + case Piece.WKNIGHT: + case Piece.BKNIGHT: + ret += "n"; + break; + default: + break; + } + return ret; + } + + /** + * Convert a string in UCI move format to a Move object. + * @return A move object, or null if move has invalid syntax + */ + public static final Move UCIstringToMove(String move) { + Move m = null; + if ((move.length() < 4) || (move.length() > 5)) + return m; + int fromSq = TextIO.getSquare(move.substring(0, 2)); + int toSq = TextIO.getSquare(move.substring(2, 4)); + if ((fromSq < 0) || (toSq < 0)) { + return m; + } + char prom = ' '; + boolean white = true; + if (move.length() == 5) { + prom = move.charAt(4); + if (Position.getY(toSq) == 7) { + white = true; + } else if (Position.getY(toSq) == 0) { + white = false; + } else { + return m; + } + } + int promoteTo; + switch (prom) { + case ' ': + promoteTo = Piece.EMPTY; + break; + case 'q': + promoteTo = white ? Piece.WQUEEN : Piece.BQUEEN; + break; + case 'r': + promoteTo = white ? Piece.WROOK : Piece.BROOK; + break; + case 'b': + promoteTo = white ? Piece.WBISHOP : Piece.BBISHOP; + break; + case 'n': + promoteTo = white ? Piece.WKNIGHT : Piece.BKNIGHT; + break; + default: + return m; + } + m = new Move(fromSq, toSq, promoteTo); + return m; + } + + /** + * Convert a string, such as "e4" to a square number. + * @return The square number, or -1 if not a legal square. + */ + public static final int getSquare(String s) { + int x = s.charAt(0) - 'a'; + int y = s.charAt(1) - '1'; + if ((x < 0) || (x > 7) || (y < 0) || (y > 7)) + return -1; + return Position.getSquare(x, y); + } + + /** + * Convert a square number to a string, such as "e4". + */ + public static final String squareToString(int square) { + StringBuilder ret = new StringBuilder(); + int x = Position.getX(square); + int y = Position.getY(square); + ret.append((char) (x + 'a')); + ret.append((char) (y + '1')); + return ret.toString(); + } + + /** + * Create an ascii representation of a position. + */ + public static final String asciiBoard(Position pos) { + StringBuilder ret = new StringBuilder(400); + String nl = String.format("%n"); + ret.append(" +----+----+----+----+----+----+----+----+"); ret.append(nl); + for (int y = 7; y >= 0; y--) { + ret.append(" |"); + for (int x = 0; x < 8; x++) { + ret.append(' '); + int p = pos.getPiece(Position.getSquare(x, y)); + if (p == Piece.EMPTY) { + boolean dark = Position.darkSquare(x, y); + ret.append(dark ? ".. |" : " |"); + } else { + ret.append(Piece.isWhite(p) ? ' ' : '*'); + String pieceName = pieceToChar(p); + if (pieceName.length() == 0) + pieceName = "P"; + ret.append(pieceName); + ret.append(" |"); + } + } + ret.append(nl); + ret.append(" +----+----+----+----+----+----+----+----+"); + ret.append(nl); + } + return ret.toString(); + } + + private final static String pieceToChar(int p) { + switch (p) { + case Piece.WQUEEN: case Piece.BQUEEN: return "Q"; + case Piece.WROOK: case Piece.BROOK: return "R"; + case Piece.WBISHOP: case Piece.BBISHOP: return "B"; + case Piece.WKNIGHT: case Piece.BKNIGHT: return "N"; + case Piece.WKING: case Piece.BKING: return "K"; + } + return ""; + } + + private final static int charToPiece(boolean white, char c) { + switch (c) { + case 'Q': case 'q': return white ? Piece.WQUEEN : Piece.BQUEEN; + case 'R': case 'r': return white ? Piece.WROOK : Piece.BROOK; + case 'B': return white ? Piece.WBISHOP : Piece.BBISHOP; + case 'N': case 'n': return white ? Piece.WKNIGHT : Piece.BKNIGHT; + case 'K': case 'k': return white ? Piece.WKING : Piece.BKING; + case 'P': case 'p': return white ? Piece.WPAWN : Piece.BPAWN; + } + return -1; + } + + /** Add an = sign to a promotion move, as required by the PGN standard. */ + public final static String pgnPromotion(String str) { + int idx = str.length() - 1; + while (idx > 0) { + char c = str.charAt(idx); + if ((c != '#') && (c != '+')) + break; + idx--; + } + if ((idx > 0) && (charToPiece(true, str.charAt(idx)) != -1)) + idx--; + return str.substring(0, idx + 1) + '=' + str.substring(idx + 1, str.length()); + } +} diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/TimeControl.java b/DroidFish/src/org/petero/droidfish/gamelogic/TimeControl.java new file mode 100644 index 0000000..89fe393 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/gamelogic/TimeControl.java @@ -0,0 +1,131 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.gamelogic; + +public class TimeControl { + private long timeControl; + private int movesPerSession; + private long increment; + + private long whiteBaseTime; + private long blackBaseTime; + + int currentMove; + boolean whiteToMove; + + long elapsed; // Accumulated elapsed time for this move. + long timerT0; // Time when timer started. 0 if timer is stopped. + + + /** Constructor. Sets time control to "game in 5min". */ + public TimeControl() { + setTimeControl(5 * 60 * 1000, 0, 0); + reset(); + } + + public final void reset() { + currentMove = 1; + whiteToMove = true; + elapsed = 0; + timerT0 = 0; + } + + /** Set time control to "moves" moves in "time" milliseconds, + inc milliseconds per move. */ + public final void setTimeControl(long time, int moves, long inc) { + timeControl = time; + movesPerSession = moves; + increment = inc; + } + + public final void setCurrentMove(int move, boolean whiteToMove, long whiteBaseTime, long blackBaseTime) { + currentMove = move; + this.whiteToMove = whiteToMove; + this.whiteBaseTime = whiteBaseTime; + this.blackBaseTime = blackBaseTime; + timerT0 = 0; + elapsed = 0; + } + + public final boolean clockRunning() { + return timerT0 != 0; + } + + public final void startTimer(long now) { + if (!clockRunning()) { + timerT0 = now; + } + } + + public final void stopTimer(long now) { + if (clockRunning()) { + long timerT1 = now; + long currElapsed = timerT1 - timerT0; + timerT0 = 0; + if (currElapsed > 0) { + elapsed += currElapsed; + } + } + } + + /** Compute new remaining time after a move is made. */ + public final int moveMade(long now, boolean useIncrement) { + stopTimer(now); + long remaining = getRemainingTime(whiteToMove, now); + if (useIncrement) { + remaining += increment; + if (getMovesToTC() == 1) + remaining += timeControl; + } + elapsed = 0; + return (int)remaining; + } + + /** Get remaining time */ + public final int getRemainingTime(boolean whiteToMove, long now) { + long remaining = whiteToMove ? whiteBaseTime : blackBaseTime; + if (whiteToMove == this.whiteToMove) { + remaining -= elapsed; + if (timerT0 != 0) { + remaining -= now - timerT0; + } + } + return (int)remaining; + } + + public final int getInitialTime() { + return (int)timeControl; + } + + public final int getIncrement() { + return (int)increment; + } + + public final int getMovesToTC() { + if (movesPerSession <= 0) + return 0; + int nextTC = 1; + while (nextTC <= currentMove) + nextTC += movesPerSession; + return nextTC - currentMove; + } + + public final int getMovesPerSession() { + return movesPerSession; + } +} diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/UndoInfo.java b/DroidFish/src/org/petero/droidfish/gamelogic/UndoInfo.java new file mode 100644 index 0000000..7f86785 --- /dev/null +++ b/DroidFish/src/org/petero/droidfish/gamelogic/UndoInfo.java @@ -0,0 +1,31 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + 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 . +*/ + +package org.petero.droidfish.gamelogic; + +/** + * Contains enough information to undo a previous move. + * Set by makeMove(). Used by unMakeMove(). + * @author petero + */ +public class UndoInfo { + int capturedPiece; + int castleMask; + int epSquare; + int halfMoveClock; +}