diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c3e00d7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+.idea
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+/release
diff --git a/CuckooChess/.classpath b/CuckooChess/.classpath
deleted file mode 100644
index f6ed43d..0000000
--- a/CuckooChess/.classpath
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/CuckooChess/.externalToolBuilders/jar_builder.launch b/CuckooChess/.externalToolBuilders/jar_builder.launch
deleted file mode 100644
index a646598..0000000
--- a/CuckooChess/.externalToolBuilders/jar_builder.launch
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/CuckooChess/.gitignore b/CuckooChess/.gitignore
index 590b07f..796b96d 100644
--- a/CuckooChess/.gitignore
+++ b/CuckooChess/.gitignore
@@ -1,2 +1 @@
-/bin
-/deploy
+/build
diff --git a/CuckooChess/.project b/CuckooChess/.project
deleted file mode 100644
index e39bad1..0000000
--- a/CuckooChess/.project
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
- CuckooChess
-
-
- CuckooChessEngine
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- org.eclipse.ui.externaltools.ExternalToolBuilder
- auto,full,incremental,
-
-
- LaunchConfigHandle
- <project>/.externalToolBuilders/jar_builder.launch
-
-
-
-
-
- org.eclipse.jdt.core.javanature
-
-
diff --git a/CuckooChess/.settings/org.eclipse.jdt.core.prefs b/CuckooChess/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 44f8e5d..0000000
--- a/CuckooChess/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,12 +0,0 @@
-#Sat May 08 15:39:56 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.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-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/CuckooChess/.settings/org.eclipse.ltk.core.refactoring.prefs b/CuckooChess/.settings/org.eclipse.ltk.core.refactoring.prefs
deleted file mode 100644
index 3c96de9..0000000
--- a/CuckooChess/.settings/org.eclipse.ltk.core.refactoring.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Sun May 09 18:44:06 CEST 2010
-eclipse.preferences.version=1
-org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false
diff --git a/CuckooChess/build.gradle b/CuckooChess/build.gradle
new file mode 100644
index 0000000..146978f
--- /dev/null
+++ b/CuckooChess/build.gradle
@@ -0,0 +1,23 @@
+apply plugin: 'java-library'
+
+jar {
+ manifest {
+ attributes "Main-Class": "tui.Main"
+ }
+ from zipTree('../CuckooChessEngine/build/libs/CuckooChessEngine.jar')
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation project(':CuckooChessEngine')
+
+ // Required -- JUnit 4 framework
+ testImplementation 'junit:junit:4.12'
+ // Optional -- Robolectric environment
+ testImplementation 'androidx.test:core:1.0.0'
+ // Optional -- Mockito framework
+ testImplementation 'org.mockito:mockito-core:1.10.19'
+}
+
+sourceCompatibility = "7"
+targetCompatibility = "7"
diff --git a/CuckooChess/build_jar.xml b/CuckooChess/build_jar.xml
deleted file mode 100644
index 4cf251c..0000000
--- a/CuckooChess/build_jar.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/CuckooChess/src/gui/AppletGUI.form b/CuckooChess/src/main/java/gui/AppletGUI.form
similarity index 100%
rename from CuckooChess/src/gui/AppletGUI.form
rename to CuckooChess/src/main/java/gui/AppletGUI.form
diff --git a/CuckooChess/src/gui/AppletGUI.java b/CuckooChess/src/main/java/gui/AppletGUI.java
similarity index 100%
rename from CuckooChess/src/gui/AppletGUI.java
rename to CuckooChess/src/main/java/gui/AppletGUI.java
diff --git a/CuckooChess/src/gui/ChessBoardPainter.java b/CuckooChess/src/main/java/gui/ChessBoardPainter.java
similarity index 100%
rename from CuckooChess/src/gui/ChessBoardPainter.java
rename to CuckooChess/src/main/java/gui/ChessBoardPainter.java
diff --git a/CuckooChess/src/gui/casefont.ttf b/CuckooChess/src/main/java/gui/casefont.ttf
similarity index 100%
rename from CuckooChess/src/gui/casefont.ttf
rename to CuckooChess/src/main/java/gui/casefont.ttf
diff --git a/CuckooChess/src/tui/Main.java b/CuckooChess/src/main/java/tui/Main.java
similarity index 100%
rename from CuckooChess/src/tui/Main.java
rename to CuckooChess/src/main/java/tui/Main.java
diff --git a/CuckooChess/src/tui/TUIGame.java b/CuckooChess/src/main/java/tui/TUIGame.java
similarity index 100%
rename from CuckooChess/src/tui/TUIGame.java
rename to CuckooChess/src/main/java/tui/TUIGame.java
diff --git a/CuckooChess/src/uci/EngineControl.java b/CuckooChess/src/main/java/uci/EngineControl.java
similarity index 100%
rename from CuckooChess/src/uci/EngineControl.java
rename to CuckooChess/src/main/java/uci/EngineControl.java
diff --git a/CuckooChess/src/uci/SearchParams.java b/CuckooChess/src/main/java/uci/SearchParams.java
similarity index 100%
rename from CuckooChess/src/uci/SearchParams.java
rename to CuckooChess/src/main/java/uci/SearchParams.java
diff --git a/CuckooChess/src/uci/UCIProtocol.java b/CuckooChess/src/main/java/uci/UCIProtocol.java
similarity index 100%
rename from CuckooChess/src/uci/UCIProtocol.java
rename to CuckooChess/src/main/java/uci/UCIProtocol.java
diff --git a/CuckooChess/test/uci/UCIProtocolTest.java b/CuckooChess/src/test/java/uci/UCIProtocolTest.java
similarity index 100%
rename from CuckooChess/test/uci/UCIProtocolTest.java
rename to CuckooChess/src/test/java/uci/UCIProtocolTest.java
diff --git a/CuckooChessAPK/.classpath b/CuckooChessAPK/.classpath
deleted file mode 100644
index d208b4a..0000000
--- a/CuckooChessAPK/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/CuckooChessAPK/.gitignore b/CuckooChessAPK/.gitignore
deleted file mode 100644
index a22392a..0000000
--- a/CuckooChessAPK/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/bin
-/gen
diff --git a/CuckooChessAPK/.project b/CuckooChessAPK/.project
deleted file mode 100644
index f73375d..0000000
--- a/CuckooChessAPK/.project
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
- CuckooChessAPK
-
-
- CuckooChessEngine
-
-
-
- org.eclipse.andmore.ResourceManagerBuilder
-
-
-
-
- org.eclipse.andmore.PreCompilerBuilder
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- org.eclipse.andmore.ApkBuilder
-
-
-
-
-
- org.eclipse.andmore.AndroidNature
- org.eclipse.jdt.core.javanature
-
-
diff --git a/CuckooChessAPK/.settings/org.eclipse.jdt.core.prefs b/CuckooChessAPK/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index ef8a789..0000000
--- a/CuckooChessAPK/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,12 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-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/CuckooChessAPK/project.properties b/CuckooChessAPK/project.properties
deleted file mode 100644
index 64784a8..0000000
--- a/CuckooChessAPK/project.properties
+++ /dev/null
@@ -1,13 +0,0 @@
-# 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-28
diff --git a/CuckooChessApp/.gitignore b/CuckooChessApp/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/CuckooChessApp/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/CuckooChessApp/build.gradle b/CuckooChessApp/build.gradle
new file mode 100644
index 0000000..579e175
--- /dev/null
+++ b/CuckooChessApp/build.gradle
@@ -0,0 +1,45 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 28
+
+ defaultConfig {
+ applicationId "org.petero.cuckoochess"
+ minSdkVersion 14
+ targetSdkVersion 28
+ versionCode 2
+ versionName "1.05"
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+
+ if(project.hasProperty("RELEASE_STORE_FILE")) {
+ signingConfigs {
+ release {
+ storeFile file(RELEASE_STORE_FILE)
+ storePassword RELEASE_STORE_PASSWORD
+ keyAlias RELEASE_KEY_ALIAS
+ keyPassword RELEASE_KEY_PASSWORD
+ }
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ if(project.hasProperty("RELEASE_STORE_FILE")) {
+ signingConfig signingConfigs.release
+ }
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation 'com.android.support:appcompat-v7:28.0.0'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+ implementation project(':CuckooChessEngine')
+}
diff --git a/CuckooChessApp/proguard-rules.pro b/CuckooChessApp/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/CuckooChessApp/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/CuckooChessAPK/AndroidManifest.xml b/CuckooChessApp/src/main/AndroidManifest.xml
similarity index 77%
rename from CuckooChessAPK/AndroidManifest.xml
rename to CuckooChessApp/src/main/AndroidManifest.xml
index 2e632f0..1b485e0 100644
--- a/CuckooChessAPK/AndroidManifest.xml
+++ b/CuckooChessApp/src/main/AndroidManifest.xml
@@ -1,11 +1,8 @@
+ android:label="@string/app_name">
@@ -15,5 +12,4 @@
-
diff --git a/CuckooChessAPK/assets/casefont.ttf b/CuckooChessApp/src/main/assets/casefont.ttf
similarity index 100%
rename from CuckooChessAPK/assets/casefont.ttf
rename to CuckooChessApp/src/main/assets/casefont.ttf
diff --git a/CuckooChessAPK/src/org/petero/cuckoochess/ChessBoard.java b/CuckooChessApp/src/main/java/org/petero/cuckoochess/ChessBoard.java
similarity index 100%
rename from CuckooChessAPK/src/org/petero/cuckoochess/ChessBoard.java
rename to CuckooChessApp/src/main/java/org/petero/cuckoochess/ChessBoard.java
diff --git a/CuckooChessAPK/src/org/petero/cuckoochess/CuckooChess.java b/CuckooChessApp/src/main/java/org/petero/cuckoochess/CuckooChess.java
similarity index 100%
rename from CuckooChessAPK/src/org/petero/cuckoochess/CuckooChess.java
rename to CuckooChessApp/src/main/java/org/petero/cuckoochess/CuckooChess.java
diff --git a/CuckooChessAPK/src/org/petero/cuckoochess/Preferences.java b/CuckooChessApp/src/main/java/org/petero/cuckoochess/Preferences.java
similarity index 100%
rename from CuckooChessAPK/src/org/petero/cuckoochess/Preferences.java
rename to CuckooChessApp/src/main/java/org/petero/cuckoochess/Preferences.java
diff --git a/CuckooChessAPK/res/drawable-hdpi/icon.png b/CuckooChessApp/src/main/res/drawable-hdpi/icon.png
similarity index 100%
rename from CuckooChessAPK/res/drawable-hdpi/icon.png
rename to CuckooChessApp/src/main/res/drawable-hdpi/icon.png
diff --git a/CuckooChessAPK/res/drawable-ldpi/icon.png b/CuckooChessApp/src/main/res/drawable-ldpi/icon.png
similarity index 100%
rename from CuckooChessAPK/res/drawable-ldpi/icon.png
rename to CuckooChessApp/src/main/res/drawable-ldpi/icon.png
diff --git a/CuckooChessAPK/res/drawable-mdpi/icon.png b/CuckooChessApp/src/main/res/drawable-mdpi/icon.png
similarity index 100%
rename from CuckooChessAPK/res/drawable-mdpi/icon.png
rename to CuckooChessApp/src/main/res/drawable-mdpi/icon.png
diff --git a/CuckooChessAPK/res/layout-land/main.xml b/CuckooChessApp/src/main/res/layout-land/main.xml
similarity index 100%
rename from CuckooChessAPK/res/layout-land/main.xml
rename to CuckooChessApp/src/main/res/layout-land/main.xml
diff --git a/CuckooChessAPK/res/layout/main.xml b/CuckooChessApp/src/main/res/layout/main.xml
similarity index 100%
rename from CuckooChessAPK/res/layout/main.xml
rename to CuckooChessApp/src/main/res/layout/main.xml
diff --git a/CuckooChessAPK/res/menu/options_menu.xml b/CuckooChessApp/src/main/res/menu/options_menu.xml
similarity index 100%
rename from CuckooChessAPK/res/menu/options_menu.xml
rename to CuckooChessApp/src/main/res/menu/options_menu.xml
diff --git a/CuckooChessAPK/res/values/strings.xml b/CuckooChessApp/src/main/res/values/strings.xml
similarity index 100%
rename from CuckooChessAPK/res/values/strings.xml
rename to CuckooChessApp/src/main/res/values/strings.xml
diff --git a/CuckooChessAPK/res/xml/preferences.xml b/CuckooChessApp/src/main/res/xml/preferences.xml
similarity index 100%
rename from CuckooChessAPK/res/xml/preferences.xml
rename to CuckooChessApp/src/main/res/xml/preferences.xml
diff --git a/CuckooChessEngine/.classpath b/CuckooChessEngine/.classpath
deleted file mode 100644
index fb50116..0000000
--- a/CuckooChessEngine/.classpath
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/CuckooChessEngine/.externalToolBuilders/BinBook_Builder.launch b/CuckooChessEngine/.externalToolBuilders/BinBook_Builder.launch
deleted file mode 100644
index 5c9eeaa..0000000
--- a/CuckooChessEngine/.externalToolBuilders/BinBook_Builder.launch
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/CuckooChessEngine/.gitignore b/CuckooChessEngine/.gitignore
index 5e56e04..4dca41f 100644
--- a/CuckooChessEngine/.gitignore
+++ b/CuckooChessEngine/.gitignore
@@ -1 +1,2 @@
-/bin
+build
+src/main/resources/book.bin
diff --git a/CuckooChessEngine/.project b/CuckooChessEngine/.project
deleted file mode 100644
index 36bf309..0000000
--- a/CuckooChessEngine/.project
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
- CuckooChessEngine
-
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- org.eclipse.ui.externaltools.ExternalToolBuilder
- auto,full,incremental,
-
-
- LaunchConfigHandle
- <project>/.externalToolBuilders/BinBook_Builder.launch
-
-
-
-
-
- org.eclipse.jdt.core.javanature
-
-
diff --git a/CuckooChessEngine/.settings/org.eclipse.jdt.core.prefs b/CuckooChessEngine/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 12a57ed..0000000
--- a/CuckooChessEngine/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,12 +0,0 @@
-#Sun May 09 18:17:45 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.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-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/CuckooChessEngine/build.gradle b/CuckooChessEngine/build.gradle
new file mode 100644
index 0000000..6465261
--- /dev/null
+++ b/CuckooChessEngine/build.gradle
@@ -0,0 +1,26 @@
+apply plugin: 'java-library'
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+
+ // Required -- JUnit 4 framework
+ testImplementation 'junit:junit:4.12'
+ // Optional -- Robolectric environment
+ testImplementation 'androidx.test:core:1.0.0'
+ // Optional -- Mockito framework
+ testImplementation 'org.mockito:mockito-core:1.10.19'
+}
+
+sourceCompatibility = "7"
+targetCompatibility = "7"
+
+
+// Build internal opening book
+task buildBook {
+ def a = "CuckooChessEngine/src/main/book.txt"
+ def b = "CuckooChessEngine/src/main/resources/book.bin"
+ chess.Book.main2(a, b)
+}
+tasks.withType(JavaCompile) {
+ t -> t.dependsOn buildBook
+}
diff --git a/CuckooChessEngine/build_binbook.xml b/CuckooChessEngine/build_binbook.xml
deleted file mode 100644
index 0b27cdf..0000000
--- a/CuckooChessEngine/build_binbook.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/CuckooChessEngine/src/.gitignore b/CuckooChessEngine/src/.gitignore
deleted file mode 100644
index de9093c..0000000
--- a/CuckooChessEngine/src/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/book.bin
diff --git a/CuckooChessEngine/src/book.txt b/CuckooChessEngine/src/main/book.txt
similarity index 100%
rename from CuckooChessEngine/src/book.txt
rename to CuckooChessEngine/src/main/book.txt
diff --git a/CuckooChessEngine/src/chess/BitBoard.java b/CuckooChessEngine/src/main/java/chess/BitBoard.java
similarity index 100%
rename from CuckooChessEngine/src/chess/BitBoard.java
rename to CuckooChessEngine/src/main/java/chess/BitBoard.java
diff --git a/CuckooChessEngine/src/main/java/chess/Book.java b/CuckooChessEngine/src/main/java/chess/Book.java
new file mode 100644
index 0000000..1c7f1a3
--- /dev/null
+++ b/CuckooChessEngine/src/main/java/chess/Book.java
@@ -0,0 +1,195 @@
+/*
+ CuckooChess - A java 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 chess;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+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.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+/** Implements an opening book. */
+public class Book {
+ public static class BookEntry {
+ Move move;
+ int count;
+ BookEntry(Move move) {
+ this.move = move;
+ count = 1;
+ }
+ }
+ private static Map> bookMap;
+ private static Random rndGen;
+ private static int numBookMoves = -1;
+ private boolean verbose;
+
+ public Book(boolean verbose) {
+ this.verbose = verbose;
+ }
+
+ private void initBook() {
+ if (numBookMoves >= 0)
+ return;
+ long t0 = System.currentTimeMillis();
+ bookMap = new HashMap>();
+ rndGen = new SecureRandom();
+ rndGen.setSeed(System.currentTimeMillis());
+ 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();
+ }
+ if (verbose) {
+ 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 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.count++;
+ return;
+ }
+ }
+ BookEntry be = new BookEntry(moveToAdd);
+ ent.add(be);
+ numBookMoves++;
+ }
+
+ /** Return a random book move for a position, or null if out of book. */
+ public final Move getBookMove(Position pos) {
+ initBook();
+ List bookMoves = bookMap.get(pos.zobristHash());
+ if (bookMoves == null) {
+ return null;
+ }
+
+ MoveGen.MoveList legalMoves = new MoveGen().pseudoLegalMoves(pos);
+ MoveGen.removeIllegal(pos, legalMoves);
+ int sum = 0;
+ for (int i = 0; i < bookMoves.size(); i++) {
+ BookEntry be = bookMoves.get(i);
+ boolean contains = false;
+ for (int mi = 0; mi < legalMoves.size; mi++)
+ if (legalMoves.m[mi].equals(be.move)) {
+ contains = true;
+ break;
+ }
+ if (!contains) {
+ // If an illegal move was found, it means there was a hash collision.
+ return null;
+ }
+ sum += getWeight(bookMoves.get(i).count);
+ }
+ if (sum <= 0) {
+ return null;
+ }
+ int rnd = rndGen.nextInt(sum);
+ sum = 0;
+ for (int i = 0; i < bookMoves.size(); i++) {
+ sum += getWeight(bookMoves.get(i).count);
+ if (rnd < sum) {
+ return bookMoves.get(i).move;
+ }
+ }
+ // Should never get here
+ throw new RuntimeException();
+ }
+
+ final private int getWeight(int count) {
+ double tmp = Math.sqrt(count);
+ return (int)(tmp * Math.sqrt(tmp) * 100 + 1);
+ }
+
+ /** Return a string describing all book moves. */
+ public final String getAllBookMoves(Position pos) {
+ initBook();
+ StringBuilder ret = new StringBuilder();
+ List bookMoves = bookMap.get(pos.zobristHash());
+ if (bookMoves != null) {
+ for (BookEntry be : bookMoves) {
+ String moveStr = TextIO.moveToString(pos, be.move, false);
+ ret.append(moveStr);
+ ret.append("(");
+ ret.append(be.count);
+ ret.append(") ");
+ }
+ }
+ return ret.toString();
+ }
+
+ 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/CuckooChessEngine/src/chess/ChessParseError.java b/CuckooChessEngine/src/main/java/chess/ChessParseError.java
similarity index 100%
rename from CuckooChessEngine/src/chess/ChessParseError.java
rename to CuckooChessEngine/src/main/java/chess/ChessParseError.java
diff --git a/CuckooChessEngine/src/chess/ComputerPlayer.java b/CuckooChessEngine/src/main/java/chess/ComputerPlayer.java
similarity index 100%
rename from CuckooChessEngine/src/chess/ComputerPlayer.java
rename to CuckooChessEngine/src/main/java/chess/ComputerPlayer.java
diff --git a/CuckooChessEngine/src/chess/Evaluate.java b/CuckooChessEngine/src/main/java/chess/Evaluate.java
similarity index 100%
rename from CuckooChessEngine/src/chess/Evaluate.java
rename to CuckooChessEngine/src/main/java/chess/Evaluate.java
diff --git a/CuckooChessEngine/src/chess/Game.java b/CuckooChessEngine/src/main/java/chess/Game.java
similarity index 100%
rename from CuckooChessEngine/src/chess/Game.java
rename to CuckooChessEngine/src/main/java/chess/Game.java
diff --git a/CuckooChessEngine/src/chess/History.java b/CuckooChessEngine/src/main/java/chess/History.java
similarity index 100%
rename from CuckooChessEngine/src/chess/History.java
rename to CuckooChessEngine/src/main/java/chess/History.java
diff --git a/CuckooChessEngine/src/chess/HumanPlayer.java b/CuckooChessEngine/src/main/java/chess/HumanPlayer.java
similarity index 100%
rename from CuckooChessEngine/src/chess/HumanPlayer.java
rename to CuckooChessEngine/src/main/java/chess/HumanPlayer.java
diff --git a/CuckooChessEngine/src/chess/KillerTable.java b/CuckooChessEngine/src/main/java/chess/KillerTable.java
similarity index 100%
rename from CuckooChessEngine/src/chess/KillerTable.java
rename to CuckooChessEngine/src/main/java/chess/KillerTable.java
diff --git a/CuckooChessEngine/src/chess/Move.java b/CuckooChessEngine/src/main/java/chess/Move.java
similarity index 100%
rename from CuckooChessEngine/src/chess/Move.java
rename to CuckooChessEngine/src/main/java/chess/Move.java
diff --git a/CuckooChessEngine/src/chess/MoveGen.java b/CuckooChessEngine/src/main/java/chess/MoveGen.java
similarity index 100%
rename from CuckooChessEngine/src/chess/MoveGen.java
rename to CuckooChessEngine/src/main/java/chess/MoveGen.java
diff --git a/CuckooChessEngine/src/chess/Parameters.java b/CuckooChessEngine/src/main/java/chess/Parameters.java
similarity index 100%
rename from CuckooChessEngine/src/chess/Parameters.java
rename to CuckooChessEngine/src/main/java/chess/Parameters.java
diff --git a/CuckooChessEngine/src/chess/Piece.java b/CuckooChessEngine/src/main/java/chess/Piece.java
similarity index 100%
rename from CuckooChessEngine/src/chess/Piece.java
rename to CuckooChessEngine/src/main/java/chess/Piece.java
diff --git a/CuckooChessEngine/src/chess/Player.java b/CuckooChessEngine/src/main/java/chess/Player.java
similarity index 100%
rename from CuckooChessEngine/src/chess/Player.java
rename to CuckooChessEngine/src/main/java/chess/Player.java
diff --git a/CuckooChessEngine/src/chess/Position.java b/CuckooChessEngine/src/main/java/chess/Position.java
similarity index 100%
rename from CuckooChessEngine/src/chess/Position.java
rename to CuckooChessEngine/src/main/java/chess/Position.java
diff --git a/CuckooChessEngine/src/chess/Search.java b/CuckooChessEngine/src/main/java/chess/Search.java
similarity index 99%
rename from CuckooChessEngine/src/chess/Search.java
rename to CuckooChessEngine/src/main/java/chess/Search.java
index 64898ca..0fcefe0 100644
--- a/CuckooChessEngine/src/chess/Search.java
+++ b/CuckooChessEngine/src/main/java/chess/Search.java
@@ -122,7 +122,7 @@ public class Search {
void notifyDepth(int depth);
void notifyCurrMove(Move m, int moveNr);
void notifyPV(int depth, int score, int time, long nodes, int nps,
- boolean isMate, boolean upperBound, boolean lowerBound, ArrayList pv);
+ boolean isMate, boolean upperBound, boolean lowerBound, ArrayList pv);
void notifyStats(long nodes, int nps, int time);
}
diff --git a/CuckooChessEngine/src/chess/TextIO.java b/CuckooChessEngine/src/main/java/chess/TextIO.java
similarity index 100%
rename from CuckooChessEngine/src/chess/TextIO.java
rename to CuckooChessEngine/src/main/java/chess/TextIO.java
diff --git a/CuckooChessEngine/src/chess/TranspositionTable.java b/CuckooChessEngine/src/main/java/chess/TranspositionTable.java
similarity index 100%
rename from CuckooChessEngine/src/chess/TranspositionTable.java
rename to CuckooChessEngine/src/main/java/chess/TranspositionTable.java
diff --git a/CuckooChessEngine/src/chess/TreeLogger.java b/CuckooChessEngine/src/main/java/chess/TreeLogger.java
similarity index 100%
rename from CuckooChessEngine/src/chess/TreeLogger.java
rename to CuckooChessEngine/src/main/java/chess/TreeLogger.java
diff --git a/CuckooChessEngine/src/chess/TwoReturnValues.java b/CuckooChessEngine/src/main/java/chess/TwoReturnValues.java
similarity index 100%
rename from CuckooChessEngine/src/chess/TwoReturnValues.java
rename to CuckooChessEngine/src/main/java/chess/TwoReturnValues.java
diff --git a/CuckooChessEngine/src/chess/UndoInfo.java b/CuckooChessEngine/src/main/java/chess/UndoInfo.java
similarity index 100%
rename from CuckooChessEngine/src/chess/UndoInfo.java
rename to CuckooChessEngine/src/main/java/chess/UndoInfo.java
diff --git a/CuckooChessEngine/src/guibase/ChessController.java b/CuckooChessEngine/src/main/java/guibase/ChessController.java
similarity index 99%
rename from CuckooChessEngine/src/guibase/ChessController.java
rename to CuckooChessEngine/src/main/java/guibase/ChessController.java
index eadeeec..9d5743a 100644
--- a/CuckooChessEngine/src/guibase/ChessController.java
+++ b/CuckooChessEngine/src/main/java/guibase/ChessController.java
@@ -372,7 +372,7 @@ public class ChessController {
updateGUI();
setSelection();
}
- } else if (game.getGameState() != Game.GameState.ALIVE) {
+ } else if (game.getGameState() != GameState.ALIVE) {
if (game.getLastMove() != null) {
game.processString("undo");
if (!humansTurn()) {
@@ -471,7 +471,7 @@ public class ChessController {
final private void setStatusString() {
String str = game.pos.whiteMove ? "White's move" : "Black's move";
if (computerThread != null) str += " (thinking)";
- if (game.getGameState() != Game.GameState.ALIVE) {
+ if (game.getGameState() != GameState.ALIVE) {
str = game.getGameStateString();
}
gui.setStatusString(str);
diff --git a/CuckooChessEngine/src/guibase/GUIInterface.java b/CuckooChessEngine/src/main/java/guibase/GUIInterface.java
similarity index 100%
rename from CuckooChessEngine/src/guibase/GUIInterface.java
rename to CuckooChessEngine/src/main/java/guibase/GUIInterface.java
diff --git a/CuckooChessEngine/src/kpk.bitbase b/CuckooChessEngine/src/main/resources/kpk.bitbase
similarity index 100%
rename from CuckooChessEngine/src/kpk.bitbase
rename to CuckooChessEngine/src/main/resources/kpk.bitbase
diff --git a/CuckooChessEngine/src/krkp.winmasks b/CuckooChessEngine/src/main/resources/krkp.winmasks
similarity index 100%
rename from CuckooChessEngine/src/krkp.winmasks
rename to CuckooChessEngine/src/main/resources/krkp.winmasks
diff --git a/CuckooChessEngineTest/src/chess/BitBoardTest.java b/CuckooChessEngine/src/test/java/chess/BitBoardTest.java
similarity index 100%
rename from CuckooChessEngineTest/src/chess/BitBoardTest.java
rename to CuckooChessEngine/src/test/java/chess/BitBoardTest.java
diff --git a/CuckooChessEngineTest/src/chess/BookTest.java b/CuckooChessEngine/src/test/java/chess/BookTest.java
similarity index 100%
rename from CuckooChessEngineTest/src/chess/BookTest.java
rename to CuckooChessEngine/src/test/java/chess/BookTest.java
diff --git a/CuckooChessEngineTest/src/chess/ComputerPlayerTest.java b/CuckooChessEngine/src/test/java/chess/ComputerPlayerTest.java
similarity index 100%
rename from CuckooChessEngineTest/src/chess/ComputerPlayerTest.java
rename to CuckooChessEngine/src/test/java/chess/ComputerPlayerTest.java
diff --git a/CuckooChessEngineTest/src/chess/EvaluateTest.java b/CuckooChessEngine/src/test/java/chess/EvaluateTest.java
similarity index 100%
rename from CuckooChessEngineTest/src/chess/EvaluateTest.java
rename to CuckooChessEngine/src/test/java/chess/EvaluateTest.java
diff --git a/CuckooChessEngineTest/src/chess/GameTest.java b/CuckooChessEngine/src/test/java/chess/GameTest.java
similarity index 100%
rename from CuckooChessEngineTest/src/chess/GameTest.java
rename to CuckooChessEngine/src/test/java/chess/GameTest.java
diff --git a/CuckooChessEngineTest/src/chess/HistoryTest.java b/CuckooChessEngine/src/test/java/chess/HistoryTest.java
similarity index 100%
rename from CuckooChessEngineTest/src/chess/HistoryTest.java
rename to CuckooChessEngine/src/test/java/chess/HistoryTest.java
diff --git a/CuckooChessEngineTest/src/chess/KillerTableTest.java b/CuckooChessEngine/src/test/java/chess/KillerTableTest.java
similarity index 100%
rename from CuckooChessEngineTest/src/chess/KillerTableTest.java
rename to CuckooChessEngine/src/test/java/chess/KillerTableTest.java
diff --git a/CuckooChessEngineTest/src/chess/MoveGenTest.java b/CuckooChessEngine/src/test/java/chess/MoveGenTest.java
similarity index 100%
rename from CuckooChessEngineTest/src/chess/MoveGenTest.java
rename to CuckooChessEngine/src/test/java/chess/MoveGenTest.java
diff --git a/CuckooChessEngineTest/src/chess/MoveTest.java b/CuckooChessEngine/src/test/java/chess/MoveTest.java
similarity index 100%
rename from CuckooChessEngineTest/src/chess/MoveTest.java
rename to CuckooChessEngine/src/test/java/chess/MoveTest.java
diff --git a/CuckooChessEngineTest/src/chess/PieceTest.java b/CuckooChessEngine/src/test/java/chess/PieceTest.java
similarity index 100%
rename from CuckooChessEngineTest/src/chess/PieceTest.java
rename to CuckooChessEngine/src/test/java/chess/PieceTest.java
diff --git a/CuckooChessEngineTest/src/chess/PositionTest.java b/CuckooChessEngine/src/test/java/chess/PositionTest.java
similarity index 100%
rename from CuckooChessEngineTest/src/chess/PositionTest.java
rename to CuckooChessEngine/src/test/java/chess/PositionTest.java
diff --git a/CuckooChessEngineTest/src/chess/SearchTest.java b/CuckooChessEngine/src/test/java/chess/SearchTest.java
similarity index 100%
rename from CuckooChessEngineTest/src/chess/SearchTest.java
rename to CuckooChessEngine/src/test/java/chess/SearchTest.java
diff --git a/CuckooChessEngineTest/src/chess/TextIOTest.java b/CuckooChessEngine/src/test/java/chess/TextIOTest.java
similarity index 100%
rename from CuckooChessEngineTest/src/chess/TextIOTest.java
rename to CuckooChessEngine/src/test/java/chess/TextIOTest.java
diff --git a/CuckooChessEngineTest/src/chess/TranspositionTableTest.java b/CuckooChessEngine/src/test/java/chess/TranspositionTableTest.java
similarity index 100%
rename from CuckooChessEngineTest/src/chess/TranspositionTableTest.java
rename to CuckooChessEngine/src/test/java/chess/TranspositionTableTest.java
diff --git a/CuckooChessEngineTest/src/guibase/ChessControllerTest.java b/CuckooChessEngine/src/test/java/guibase/ChessControllerTest.java
similarity index 100%
rename from CuckooChessEngineTest/src/guibase/ChessControllerTest.java
rename to CuckooChessEngine/src/test/java/guibase/ChessControllerTest.java
diff --git a/CuckooChessEngineTest/.classpath b/CuckooChessEngineTest/.classpath
deleted file mode 100644
index e3254f3..0000000
--- a/CuckooChessEngineTest/.classpath
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/CuckooChessEngineTest/.gitignore b/CuckooChessEngineTest/.gitignore
deleted file mode 100644
index 5e56e04..0000000
--- a/CuckooChessEngineTest/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/bin
diff --git a/CuckooChessEngineTest/.project b/CuckooChessEngineTest/.project
deleted file mode 100644
index fda19fa..0000000
--- a/CuckooChessEngineTest/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
- CuckooChessEngineTest
-
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
-
- org.eclipse.jdt.core.javanature
-
-
diff --git a/CuckooChessEngineTest/.settings/org.eclipse.jdt.core.prefs b/CuckooChessEngineTest/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 7341ab1..0000000
--- a/CuckooChessEngineTest/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,11 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.7
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.7
diff --git a/DroidFish/.classpath b/DroidFish/.classpath
deleted file mode 100644
index 496fad3..0000000
--- a/DroidFish/.classpath
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/DroidFish/.externalToolBuilders/ECO_Builder.launch b/DroidFish/.externalToolBuilders/ECO_Builder.launch
deleted file mode 100644
index c6be1c8..0000000
--- a/DroidFish/.externalToolBuilders/ECO_Builder.launch
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/DroidFish/.externalToolBuilders/Native_Builder.launch b/DroidFish/.externalToolBuilders/Native_Builder.launch
deleted file mode 100644
index 35d4921..0000000
--- a/DroidFish/.externalToolBuilders/Native_Builder.launch
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/DroidFish/.externalToolBuilders/copy_stockfish.launch b/DroidFish/.externalToolBuilders/copy_stockfish.launch
deleted file mode 100644
index 0e24aea..0000000
--- a/DroidFish/.externalToolBuilders/copy_stockfish.launch
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/DroidFish/.gitignore b/DroidFish/.gitignore
deleted file mode 100644
index cf9d724..0000000
--- a/DroidFish/.gitignore
+++ /dev/null
@@ -1,7 +0,0 @@
-/libs
-/obj
-/bin
-/gen
-/proguard
-/copy_stockfish.log
-/Native_Builder.log
diff --git a/DroidFish/.project b/DroidFish/.project
deleted file mode 100644
index b8f62f7..0000000
--- a/DroidFish/.project
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
- DroidFish
-
-
-
-
-
- org.eclipse.andmore.ResourceManagerBuilder
-
-
-
-
- org.eclipse.andmore.PreCompilerBuilder
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- org.eclipse.ui.externaltools.ExternalToolBuilder
- auto,full,incremental,
-
-
- LaunchConfigHandle
- <project>/.externalToolBuilders/Native_Builder.launch
-
-
-
-
- org.eclipse.ui.externaltools.ExternalToolBuilder
- auto,full,incremental,
-
-
- LaunchConfigHandle
- <project>/.externalToolBuilders/copy_stockfish.launch
-
-
-
-
- org.eclipse.ui.externaltools.ExternalToolBuilder
- auto,full,incremental,
-
-
- LaunchConfigHandle
- <project>/.externalToolBuilders/ECO_Builder.launch
-
-
-
-
- org.eclipse.andmore.ApkBuilder
-
-
-
-
-
- org.eclipse.andmore.AndroidNature
- org.eclipse.jdt.core.javanature
-
-
diff --git a/DroidFish/.settings/org.eclipse.jdt.core.prefs b/DroidFish/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 3d0c687..0000000
--- a/DroidFish/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,14 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.emptyStatement=warning
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/DroidFish/assets/.gitignore b/DroidFish/assets/.gitignore
deleted file mode 100644
index 0eabddc..0000000
--- a/DroidFish/assets/.gitignore
+++ /dev/null
@@ -1,12 +0,0 @@
-/stockfish-arm64-v8a
-/stockfish-armeabi
-/stockfish-armeabi-nopie
-/stockfish-armeabi-v7a
-/stockfish-armeabi-v7a-nopie
-/stockfish-mips
-/stockfish-mips-nopie
-/stockfish-mips64
-/stockfish-x86
-/stockfish-x86_64
-/stockfish-x86-nopie
-/eco.dat
diff --git a/DroidFish/build_copy_exe.xml b/DroidFish/build_copy_exe.xml
deleted file mode 100644
index 58788c0..0000000
--- a/DroidFish/build_copy_exe.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/DroidFish/build_eco.xml b/DroidFish/build_eco.xml
deleted file mode 100644
index 0efe845..0000000
--- a/DroidFish/build_eco.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/DroidFish/libs/android-support-v4.jar b/DroidFish/libs/android-support-v4.jar
deleted file mode 100644
index 2ff47f4..0000000
Binary files a/DroidFish/libs/android-support-v4.jar and /dev/null differ
diff --git a/DroidFish/proguard-project.txt b/DroidFish/proguard-project.txt
deleted file mode 100644
index e9caf63..0000000
--- a/DroidFish/proguard-project.txt
+++ /dev/null
@@ -1 +0,0 @@
--dontobfuscate
diff --git a/DroidFish/project.properties b/DroidFish/project.properties
deleted file mode 100644
index 6a6f52c..0000000
--- a/DroidFish/project.properties
+++ /dev/null
@@ -1,14 +0,0 @@
-# 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-28
-proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
diff --git a/DroidFish/res/mipmap-mdpi/icon.png b/DroidFish/res/mipmap-mdpi/icon.png
deleted file mode 100644
index 7c9c8e5..0000000
Binary files a/DroidFish/res/mipmap-mdpi/icon.png and /dev/null differ
diff --git a/DroidFish/res/values-v14/colors.xml b/DroidFish/res/values-v14/colors.xml
deleted file mode 100644
index 78e2bd8..0000000
--- a/DroidFish/res/values-v14/colors.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
- @android:color/holo_blue_dark
- @android:color/holo_blue_light
- @android:color/darker_gray
- #FFFFFF
-
diff --git a/DroidFishApp/.gitignore b/DroidFishApp/.gitignore
new file mode 100644
index 0000000..2a34f42
--- /dev/null
+++ b/DroidFishApp/.gitignore
@@ -0,0 +1,6 @@
+/build
+src/main/assets/arm64-v8a
+src/main/assets/armeabi-v7a
+src/main/assets/x86
+src/main/assets/x86_64
+src/main/assets/eco.dat
diff --git a/DroidFishApp/build.gradle b/DroidFishApp/build.gradle
new file mode 100644
index 0000000..2a50be8
--- /dev/null
+++ b/DroidFishApp/build.gradle
@@ -0,0 +1,79 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 28
+ defaultConfig {
+ applicationId "org.petero.droidfish"
+ minSdkVersion 16
+ targetSdkVersion 28
+ versionCode 82
+ versionName "1.73"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ externalNativeBuild {
+ ndkBuild {
+ arguments '-j8'
+ }
+ }
+ }
+
+ if(project.hasProperty("RELEASE_STORE_FILE")) {
+ signingConfigs {
+ release {
+ storeFile file(RELEASE_STORE_FILE)
+ storePassword RELEASE_STORE_PASSWORD
+ keyAlias RELEASE_KEY_ALIAS
+ keyPassword RELEASE_KEY_PASSWORD
+ }
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ if(project.hasProperty("RELEASE_STORE_FILE")) {
+ signingConfig signingConfigs.release
+ }
+ }
+ }
+
+ externalNativeBuild {
+ ndkBuild {
+ path file('src/main/cpp/Android.mk')
+ }
+ }
+
+ lintOptions {
+ abortOnError false
+ }
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation 'com.android.support:appcompat-v7:28.0.0'
+ implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+ implementation 'com.android.support:design:28.0.0'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+ implementation project(':CuckooChessEngine')
+}
+
+// Build the ECO database
+task buildEco {
+ def a = "buildSrc/src/main/java/org/petero/droidfish/buildtools/eco.pgn"
+ def b = "DroidFishApp/src/main/assets/eco.dat"
+ org.petero.droidfish.buildtools.EcoBuilder.main2(a, b)
+}
+preBuild.dependsOn buildEco
+
+// Copy Stockfish executables to assets directory
+task copyToAssets(type: Copy, dependsOn: 'externalNativeBuildRelease') {
+ from('build/intermediates/ndkBuild/release/obj/local') {
+ include '*/stockfish'
+ }
+ into 'src/main/assets'
+}
+tasks.withType(JavaCompile) {
+ t -> t.dependsOn copyToAssets
+}
diff --git a/DroidFishApp/proguard-rules.pro b/DroidFishApp/proguard-rules.pro
new file mode 100644
index 0000000..55835b3
--- /dev/null
+++ b/DroidFishApp/proguard-rules.pro
@@ -0,0 +1,23 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+
+-dontobfuscate
diff --git a/DroidFishTest/src/org/petero/droidfish/ObjectCacheTest.java b/DroidFishApp/src/androidTest/java/org/petero/droidfish/ObjectCacheTest.java
similarity index 100%
rename from DroidFishTest/src/org/petero/droidfish/ObjectCacheTest.java
rename to DroidFishApp/src/androidTest/java/org/petero/droidfish/ObjectCacheTest.java
diff --git a/DroidFishTest/src/org/petero/droidfish/book/BookTest.java b/DroidFishApp/src/androidTest/java/org/petero/droidfish/book/BookTest.java
similarity index 100%
rename from DroidFishTest/src/org/petero/droidfish/book/BookTest.java
rename to DroidFishApp/src/androidTest/java/org/petero/droidfish/book/BookTest.java
diff --git a/DroidFishTest/src/org/petero/droidfish/book/EcoTest.java b/DroidFishApp/src/androidTest/java/org/petero/droidfish/book/EcoTest.java
similarity index 96%
rename from DroidFishTest/src/org/petero/droidfish/book/EcoTest.java
rename to DroidFishApp/src/androidTest/java/org/petero/droidfish/book/EcoTest.java
index b31b779..a4b8616 100644
--- a/DroidFishTest/src/org/petero/droidfish/book/EcoTest.java
+++ b/DroidFishApp/src/androidTest/java/org/petero/droidfish/book/EcoTest.java
@@ -23,14 +23,22 @@ import org.petero.droidfish.gamelogic.Game;
import org.petero.droidfish.gamelogic.GameTree;
import org.petero.droidfish.gamelogic.TimeControlData;
-import android.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
/** Test of EcoDb class. */
-public class EcoTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class EcoTest {
public EcoTest() {
}
+ @Test
public void testEco() throws Throwable {
EcoDb ecoDb = EcoDb.getInstance();
{
@@ -136,6 +144,7 @@ public class EcoTest extends AndroidTestCase {
}
}
+ @Test
public void testEcoFromFEN() throws Throwable {
EcoDb ecoDb = EcoDb.getInstance();
GameTree gt = gtFromFEN("rnbqkbnr/ppp2ppp/4p3/3P4/3P4/8/PPP2PPP/RNBQKBNR b KQkq - 0 3");
diff --git a/DroidFishTest/src/org/petero/droidfish/book/PolyglotBookTest.java b/DroidFishApp/src/androidTest/java/org/petero/droidfish/book/PolyglotBookTest.java
similarity index 100%
rename from DroidFishTest/src/org/petero/droidfish/book/PolyglotBookTest.java
rename to DroidFishApp/src/androidTest/java/org/petero/droidfish/book/PolyglotBookTest.java
diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/GameTest.java b/DroidFishApp/src/androidTest/java/org/petero/droidfish/gamelogic/GameTest.java
similarity index 100%
rename from DroidFishTest/src/org/petero/droidfish/gamelogic/GameTest.java
rename to DroidFishApp/src/androidTest/java/org/petero/droidfish/gamelogic/GameTest.java
diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/GameTreeTest.java b/DroidFishApp/src/androidTest/java/org/petero/droidfish/gamelogic/GameTreeTest.java
similarity index 100%
rename from DroidFishTest/src/org/petero/droidfish/gamelogic/GameTreeTest.java
rename to DroidFishApp/src/androidTest/java/org/petero/droidfish/gamelogic/GameTreeTest.java
diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/MoveGenTest.java b/DroidFishApp/src/androidTest/java/org/petero/droidfish/gamelogic/MoveGenTest.java
similarity index 100%
rename from DroidFishTest/src/org/petero/droidfish/gamelogic/MoveGenTest.java
rename to DroidFishApp/src/androidTest/java/org/petero/droidfish/gamelogic/MoveGenTest.java
diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/MoveTest.java b/DroidFishApp/src/androidTest/java/org/petero/droidfish/gamelogic/MoveTest.java
similarity index 100%
rename from DroidFishTest/src/org/petero/droidfish/gamelogic/MoveTest.java
rename to DroidFishApp/src/androidTest/java/org/petero/droidfish/gamelogic/MoveTest.java
diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/PGNFileTest.java b/DroidFishApp/src/androidTest/java/org/petero/droidfish/gamelogic/PGNFileTest.java
similarity index 100%
rename from DroidFishTest/src/org/petero/droidfish/gamelogic/PGNFileTest.java
rename to DroidFishApp/src/androidTest/java/org/petero/droidfish/gamelogic/PGNFileTest.java
diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/PieceTest.java b/DroidFishApp/src/androidTest/java/org/petero/droidfish/gamelogic/PieceTest.java
similarity index 100%
rename from DroidFishTest/src/org/petero/droidfish/gamelogic/PieceTest.java
rename to DroidFishApp/src/androidTest/java/org/petero/droidfish/gamelogic/PieceTest.java
diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/PositionTest.java b/DroidFishApp/src/androidTest/java/org/petero/droidfish/gamelogic/PositionTest.java
similarity index 100%
rename from DroidFishTest/src/org/petero/droidfish/gamelogic/PositionTest.java
rename to DroidFishApp/src/androidTest/java/org/petero/droidfish/gamelogic/PositionTest.java
diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/SpeechTest.java b/DroidFishApp/src/androidTest/java/org/petero/droidfish/gamelogic/SpeechTest.java
similarity index 100%
rename from DroidFishTest/src/org/petero/droidfish/gamelogic/SpeechTest.java
rename to DroidFishApp/src/androidTest/java/org/petero/droidfish/gamelogic/SpeechTest.java
diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/TextIOTest.java b/DroidFishApp/src/androidTest/java/org/petero/droidfish/gamelogic/TextIOTest.java
similarity index 100%
rename from DroidFishTest/src/org/petero/droidfish/gamelogic/TextIOTest.java
rename to DroidFishApp/src/androidTest/java/org/petero/droidfish/gamelogic/TextIOTest.java
diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/TimeControlTest.java b/DroidFishApp/src/androidTest/java/org/petero/droidfish/gamelogic/TimeControlTest.java
similarity index 100%
rename from DroidFishTest/src/org/petero/droidfish/gamelogic/TimeControlTest.java
rename to DroidFishApp/src/androidTest/java/org/petero/droidfish/gamelogic/TimeControlTest.java
diff --git a/DroidFishTest/src/org/petero/droidfish/tb/ProbeResultTest.java b/DroidFishApp/src/androidTest/java/org/petero/droidfish/tb/ProbeResultTest.java
similarity index 100%
rename from DroidFishTest/src/org/petero/droidfish/tb/ProbeResultTest.java
rename to DroidFishApp/src/androidTest/java/org/petero/droidfish/tb/ProbeResultTest.java
diff --git a/DroidFishTest/src/org/petero/droidfish/tb/ProbeTest.java b/DroidFishApp/src/androidTest/java/org/petero/droidfish/tb/ProbeTest.java
similarity index 100%
rename from DroidFishTest/src/org/petero/droidfish/tb/ProbeTest.java
rename to DroidFishApp/src/androidTest/java/org/petero/droidfish/tb/ProbeTest.java
diff --git a/DroidFish/AndroidManifest.xml b/DroidFishApp/src/main/AndroidManifest.xml
similarity index 98%
rename from DroidFish/AndroidManifest.xml
rename to DroidFishApp/src/main/AndroidManifest.xml
index 542cd76..f996605 100644
--- a/DroidFish/AndroidManifest.xml
+++ b/DroidFishApp/src/main/AndroidManifest.xml
@@ -1,16 +1,13 @@
-
+
CREATOR =
- new Parcelable.Creator() {
+ public static final Creator CREATOR =
+ new Creator() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@@ -327,4 +327,4 @@ public class ColorPickerPreference
}
};
}
-}
\ No newline at end of file
+}
diff --git a/DroidFish/src/net/margaritov/preference/colorpicker/ColorPickerView.java b/DroidFishApp/src/main/java/net/margaritov/preference/colorpicker/ColorPickerView.java
similarity index 100%
rename from DroidFish/src/net/margaritov/preference/colorpicker/ColorPickerView.java
rename to DroidFishApp/src/main/java/net/margaritov/preference/colorpicker/ColorPickerView.java
diff --git a/DroidFish/src/org/petero/droidfish/ActionFactory.java b/DroidFishApp/src/main/java/org/petero/droidfish/ActionFactory.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/ActionFactory.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/ActionFactory.java
diff --git a/DroidFish/src/org/petero/droidfish/ButtonActions.java b/DroidFishApp/src/main/java/org/petero/droidfish/ButtonActions.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/ButtonActions.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/ButtonActions.java
diff --git a/DroidFish/src/org/petero/droidfish/ChessBoardPlay.java b/DroidFishApp/src/main/java/org/petero/droidfish/ChessBoardPlay.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/ChessBoardPlay.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/ChessBoardPlay.java
diff --git a/DroidFish/src/org/petero/droidfish/ColorTheme.java b/DroidFishApp/src/main/java/org/petero/droidfish/ColorTheme.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/ColorTheme.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/ColorTheme.java
diff --git a/DroidFish/src/org/petero/droidfish/DroidFish.java b/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java
similarity index 97%
rename from DroidFish/src/org/petero/droidfish/DroidFish.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java
index 01f795b..3dda42c 100644
--- a/DroidFish/src/org/petero/droidfish/DroidFish.java
+++ b/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java
@@ -248,9 +248,9 @@ public class DroidFish extends Activity
}
private AutoMode autoMode = AutoMode.OFF;
- private final int ECO_HINTS_OFF = 0;
- private final int ECO_HINTS_AUTO = 1;
- private final int ECO_HINTS_ALWAYS = 2;
+ private int ECO_HINTS_OFF = 0;
+ private int ECO_HINTS_AUTO = 1;
+ private int ECO_HINTS_ALWAYS = 2;
/** State of requested permissions. */
private static enum PermissionState {
@@ -262,12 +262,12 @@ public class DroidFish extends Activity
/** State of WRITE_EXTERNAL_STORAGE permission. */
private PermissionState storagePermission = PermissionState.UNKNOWN;
- private final static String bookDir = "DroidFish/book";
- private final static String pgnDir = "DroidFish/pgn";
- private final static String fenDir = "DroidFish/epd";
- private final static String engineDir = "DroidFish/uci";
- private final static String gtbDefaultDir = "DroidFish/gtb";
- private final static String rtbDefaultDir = "DroidFish/rtb";
+ private static String bookDir = "DroidFish/book";
+ private static String pgnDir = "DroidFish/pgn";
+ private static String fenDir = "DroidFish/epd";
+ private static String engineDir = "DroidFish/uci";
+ private static String gtbDefaultDir = "DroidFish/gtb";
+ private static String rtbDefaultDir = "DroidFish/rtb";
private BookOptions bookOptions = new BookOptions();
private PGNOptions pgnOptions = new PGNOptions();
private EngineOptions engineOptions = new EngineOptions();
@@ -667,7 +667,7 @@ public class DroidFish extends Activity
guides.add(tg);
tg.setOverlay(new Overlay()
- .setOnClickListener(new View.OnClickListener() {
+ .setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
guideShowOnStart = false;
@@ -682,7 +682,7 @@ public class DroidFish extends Activity
Sequence sequence = new Sequence.SequenceBuilder()
.add(guides.toArray(new TourGuide[guides.size()]))
.setDefaultOverlay(new Overlay()
- .setOnClickListener(new View.OnClickListener() {
+ .setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
tourGuide.next();
@@ -702,7 +702,7 @@ public class DroidFish extends Activity
Piece.NOTATION_QUEEN + " " +
Piece.NOTATION_KING;
- private final void setPieceNames(int pieceType) {
+ private void setPieceNames(int pieceType) {
if (pieceType == PGNOptions.PT_FIGURINE) {
TextIO.setPieceNames(figurinePieceNames);
} else {
@@ -711,7 +711,7 @@ public class DroidFish extends Activity
}
/** Create directory structure on SD card. */
- private final void createDirectories() {
+ private void createDirectories() {
if (storagePermission == PermissionState.UNKNOWN) {
String extStorage = Manifest.permission.WRITE_EXTERNAL_STORAGE;
if (ContextCompat.checkSelfPermission(this, extStorage) ==
@@ -756,7 +756,7 @@ public class DroidFish extends Activity
* Return PGN/FEN data or filename from the Intent. Both can not be non-null.
* @return Pair of PGN/FEN data and filename.
*/
- private final Pair getPgnOrFenIntent() {
+ private Pair getPgnOrFenIntent() {
String pgnOrFen = null;
String filename = null;
try {
@@ -824,7 +824,7 @@ public class DroidFish extends Activity
return new Pair(pgnOrFen,filename);
}
- private final byte[] strToByteArr(String str) {
+ private byte[] strToByteArr(String str) {
if (str == null)
return null;
int nBytes = str.length() / 2;
@@ -837,7 +837,7 @@ public class DroidFish extends Activity
return ret;
}
- private final String byteArrToString(byte[] data) {
+ private String byteArrToString(byte[] data) {
if (data == null)
return null;
StringBuilder ret = new StringBuilder(32768);
@@ -859,7 +859,7 @@ public class DroidFish extends Activity
}
/** Re-initialize UI when layout should change because of rotation or handedness change. */
- private final void reInitUI() {
+ private void reInitUI() {
ChessBoardPlay oldCB = cb;
String statusStr = status.getText().toString();
initUI();
@@ -889,17 +889,17 @@ public class DroidFish extends Activity
}
/** Return true if the current orientation is landscape. */
- private final boolean landScapeView() {
+ private boolean landScapeView() {
return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
}
/** Return true if left-handed layout should be used. */
- private final boolean leftHandedView() {
+ private boolean leftHandedView() {
return settings.getBoolean("leftHanded", false) && landScapeView();
}
/** Re-read preferences settings. */
- private final void handlePrefsChange() {
+ private void handlePrefsChange() {
if (leftHanded != leftHandedView())
reInitUI();
else
@@ -908,7 +908,7 @@ public class DroidFish extends Activity
ctrl.setGameMode(gameMode);
}
- private final void initUI() {
+ private void initUI() {
leftHanded = leftHandedView();
setContentView(leftHanded ? R.layout.main_left_handed : R.layout.main);
overrideViewAttribs();
@@ -1249,13 +1249,13 @@ public class DroidFish extends Activity
super.onDestroy();
}
- private final int getIntSetting(String settingName, int defaultValue) {
+ private int getIntSetting(String settingName, int defaultValue) {
String tmp = settings.getString(settingName, String.format(Locale.US, "%d", defaultValue));
int value = Integer.parseInt(tmp);
return value;
}
- private final void readPrefs() {
+ private void readPrefs() {
int modeNr = getIntSetting("gameMode", 1);
gameMode = new GameMode(modeNr);
String oldPlayerName = playerName;
@@ -1395,7 +1395,7 @@ public class DroidFish extends Activity
Util.overrideViewAttribs(findViewById(R.id.main));
}
- private final void setLanguage(String lang) {
+ private void setLanguage(String lang) {
Locale newLocale;
if (lang.equals("default")) {
newLocale = Resources.getSystem().getConfiguration().locale;
@@ -1418,7 +1418,7 @@ public class DroidFish extends Activity
/**
* Change the Pieces into figurine or regular (i.e. letters) display
*/
- private final void setFigurineNotation(boolean displayAsFigures, int fontSize) {
+ private void setFigurineNotation(boolean displayAsFigures, int fontSize) {
if (displayAsFigures) {
// increase the font cause it has different kerning and looks small
float increaseFontSize = fontSize * 1.1f;
@@ -1433,7 +1433,7 @@ public class DroidFish extends Activity
}
/** Enable/disable title bar scrolling. */
- private final void setTitleScrolling() {
+ private void setTitleScrolling() {
TextUtils.TruncateAt where = autoScrollTitle ? TextUtils.TruncateAt.MARQUEE
: TextUtils.TruncateAt.END;
whiteTitleText.setEllipsize(where);
@@ -1442,7 +1442,7 @@ public class DroidFish extends Activity
blackFigText.setEllipsize(where);
}
- private final void updateButtons() {
+ private void updateButtons() {
boolean largeButtons = settings.getBoolean("largeButtons", false);
Resources r = getResources();
int bWidth = (int)Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 36, r.getDisplayMetrics()));
@@ -1474,7 +1474,7 @@ public class DroidFish extends Activity
}
@SuppressWarnings("deprecation")
- private final void setButtonData(ImageButton button, int bWidth, int bHeight,
+ private void setButtonData(ImageButton button, int bWidth, int bHeight,
int svgResId, SVG touched) {
SVG svg = SVGParser.getSVGFromResource(getResources(), svgResId);
button.setBackgroundDrawable(new SVGPictureDrawable(svg));
@@ -1499,7 +1499,7 @@ public class DroidFish extends Activity
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
- private final void setEngineStrength(String engine, int strength) {
+ private void setEngineStrength(String engine, int strength) {
if (!storageAvailable()) {
if (!"stockfish".equals(engine) && !"cuckoochess".equals(engine))
engine = "stockfish";
@@ -1508,7 +1508,7 @@ public class DroidFish extends Activity
setEngineTitle(engine, strength);
}
- private final void setEngineTitle(String engine, int strength) {
+ private void setEngineTitle(String engine, int strength) {
String eName = "";
if (EngineUtil.isOpenExchangeEngine(engine)) {
String engineFileName = new File(engine).getName();
@@ -1566,7 +1566,7 @@ public class DroidFish extends Activity
blackFigText.setText(diff.black);
}
- private final void setBookOptions() {
+ private void setBookOptions() {
BookOptions options = new BookOptions(bookOptions);
if (options.filename.isEmpty())
options.filename = "internal:";
@@ -1582,7 +1582,7 @@ public class DroidFish extends Activity
private boolean egtbForceReload = false;
- private final void setEngineOptions(boolean restart) {
+ private void setEngineOptions(boolean restart) {
computeNetEngineID();
ctrl.setEngineOptions(new EngineOptions(engineOptions), restart);
Probe.getInstance().setPath(engineOptions.gtbPath, engineOptions.rtbPath,
@@ -1590,7 +1590,7 @@ public class DroidFish extends Activity
egtbForceReload = false;
}
- private final void computeNetEngineID() {
+ private void computeNetEngineID() {
String id = "";
try {
String engine = settings.getString("engine", "stockfish");
@@ -1604,7 +1604,7 @@ public class DroidFish extends Activity
engineOptions.networkID = id;
}
- private final void setEgtbHints(int sq) {
+ private void setEgtbHints(int sq) {
if (!engineOptions.hints || (sq < 0)) {
cb.setSquareDecorations(null);
return;
@@ -1876,7 +1876,7 @@ public class DroidFish extends Activity
}
/** Set new game mode. */
- private final void newGameMode(int gameModeType) {
+ private void newGameMode(int gameModeType) {
Editor editor = settings.edit();
String gameModeStr = String.format(Locale.US, "%d", gameModeType);
editor.putString("gameMode", gameModeStr);
@@ -1892,14 +1892,14 @@ public class DroidFish extends Activity
return uri.getPath();
}
- private final String getParseErrString(ChessParseError e) {
+ private String getParseErrString(ChessParseError e) {
if (e.resourceId == -1)
return e.getMessage();
else
return getString(e.resourceId);
}
- private final int nameMatchScore(String name, String match) {
+ private int nameMatchScore(String name, String match) {
if (name == null)
return 0;
String lName = name.toLowerCase(Locale.US);
@@ -1919,25 +1919,25 @@ public class DroidFish extends Activity
return 0;
}
- private final void setBoardFlip() {
+ private void setBoardFlip() {
setBoardFlip(false);
}
/** Set a boolean preference setting. */
- private final void setBooleanPref(String name, boolean value) {
+ private void setBooleanPref(String name, boolean value) {
Editor editor = settings.edit();
editor.putBoolean(name, value);
editor.commit();
}
/** Toggle a boolean preference setting. Return new value. */
- private final boolean toggleBooleanPref(String name) {
+ private boolean toggleBooleanPref(String name) {
boolean value = !settings.getBoolean(name, false);
setBooleanPref(name, value);
return value;
}
- private final void setBoardFlip(boolean matchPlayerNames) {
+ private void setBoardFlip(boolean matchPlayerNames) {
boolean flipped = boardFlipped;
if (playerNameFlip && matchPlayerNames && (ctrl != null)) {
final TreeMap headers = new TreeMap();
@@ -2028,7 +2028,7 @@ public class DroidFish extends Activity
setStatusString(str);
}
- private final void setStatusString(String str) {
+ private void setStatusString(String str) {
status.setText(str);
}
@@ -2119,7 +2119,7 @@ public class DroidFish extends Activity
return line.substring(0, maxLen);
}
- private final void updateThinkingInfo() {
+ private void updateThinkingInfo() {
boolean thinkingEmpty = true;
{
StringBuilder sb = new StringBuilder(128);
@@ -2264,7 +2264,7 @@ public class DroidFish extends Activity
return null;
}
- private final Dialog newGameDialog() {
+ private Dialog newGameDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.option_new_game);
builder.setMessage(R.string.start_new_game);
@@ -2289,7 +2289,7 @@ public class DroidFish extends Activity
return builder.create();
}
- private final void startNewGame(int type) {
+ private void startNewGame(int type) {
if (type != 2) {
int gameModeType = (type == 0) ? GameMode.PLAYER_WHITE : GameMode.PLAYER_BLACK;
Editor editor = settings.edit();
@@ -2308,7 +2308,7 @@ public class DroidFish extends Activity
updateEngineTitle();
}
- private final Dialog promoteDialog() {
+ private Dialog promoteDialog() {
final CharSequence[] items = {
getString(R.string.queen), getString(R.string.rook),
getString(R.string.bishop), getString(R.string.knight)
@@ -2324,7 +2324,7 @@ public class DroidFish extends Activity
return alert;
}
- private final Dialog clipBoardDialog() {
+ private Dialog clipBoardDialog() {
final int COPY_GAME = 0;
final int COPY_POSITION = 1;
final int PASTE = 2;
@@ -2379,7 +2379,7 @@ public class DroidFish extends Activity
return alert;
}
- private final Dialog boardMenuDialog() {
+ private Dialog boardMenuDialog() {
final int CLIPBOARD = 0;
final int FILEMENU = 1;
final int SHARE_GAME = 2;
@@ -2438,7 +2438,7 @@ public class DroidFish extends Activity
return alert;
}
- private final void shareGameOrText(boolean game) {
+ private void shareGameOrText(boolean game) {
Intent i = new Intent(Intent.ACTION_SEND);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
i.setType(game ? "application/x-chess-pgn" : "text/plain");
@@ -2472,7 +2472,7 @@ public class DroidFish extends Activity
}
}
- private final void shareImage() {
+ private void shareImage() {
View v = findViewById(R.id.chessboard);
Bitmap b = Bitmap.createBitmap(v.getWidth(), v.getHeight(),
Bitmap.Config.ARGB_8888);
@@ -2507,7 +2507,7 @@ public class DroidFish extends Activity
}
}
- private final Dialog fileMenuDialog() {
+ private Dialog fileMenuDialog() {
final int LOAD_LAST_FILE = 0;
final int LOAD_GAME = 1;
final int LOAD_POS = 2;
@@ -2577,7 +2577,7 @@ public class DroidFish extends Activity
}
}
- private final Dialog aboutDialog() {
+ private Dialog aboutDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
String title = getString(R.string.app_name);
WebView wv = new WebView(this);
@@ -2598,7 +2598,7 @@ public class DroidFish extends Activity
return alert;
}
- private final Dialog selectBookDialog() {
+ private Dialog selectBookDialog() {
String[] fileNames = findFilesInDirectory(bookDir, new FileNameFilter() {
@Override
public boolean accept(String filename) {
@@ -2652,13 +2652,13 @@ public class DroidFish extends Activity
return alert;
}
- private final static boolean reservedEngineName(String name) {
+ private static boolean reservedEngineName(String name) {
return "cuckoochess".equals(name) ||
"stockfish".equals(name) ||
name.endsWith(".ini");
}
- private final Dialog selectEngineDialog(final boolean abortOnCancel) {
+ private Dialog selectEngineDialog(final boolean abortOnCancel) {
final ArrayList items = new ArrayList();
final ArrayList ids = new ArrayList();
ids.add("stockfish"); items.add(getString(R.string.stockfish_engine));
@@ -2743,7 +2743,7 @@ public class DroidFish extends Activity
void load(String pathName);
}
- private final Dialog selectPgnFileDialog() {
+ private Dialog selectPgnFileDialog() {
return selectFileDialog(pgnDir, R.string.select_pgn_file, R.string.no_pgn_files,
"currentPGNFile", new Loader() {
@Override
@@ -2753,7 +2753,7 @@ public class DroidFish extends Activity
});
}
- private final Dialog selectFenFileDialog() {
+ private Dialog selectFenFileDialog() {
return selectFileDialog(fenDir, R.string.select_fen_file, R.string.no_fen_files,
"currentFENFile", new Loader() {
@Override
@@ -2763,7 +2763,7 @@ public class DroidFish extends Activity
});
}
- private final Dialog selectFileDialog(final String defaultDir, int selectFileMsg, int noFilesMsg,
+ private Dialog selectFileDialog(final String defaultDir, int selectFileMsg, int noFilesMsg,
String settingsName, final Loader loader) {
setAutoMode(AutoMode.OFF);
final String[] fileNames = findFilesInDirectory(defaultDir, null);
@@ -2798,7 +2798,7 @@ public class DroidFish extends Activity
return alert;
}
- private final Dialog selectPgnFileSaveDialog() {
+ private Dialog selectPgnFileSaveDialog() {
setAutoMode(AutoMode.OFF);
final String[] fileNames = findFilesInDirectory(pgnDir, null);
final int numFiles = fileNames.length;
@@ -2832,11 +2832,10 @@ public class DroidFish extends Activity
}
}
});
- AlertDialog alert = builder.create();
- return alert;
+ return builder.create();
}
- private final Dialog selectPgnSaveNewFileDialog() {
+ private Dialog selectPgnSaveNewFileDialog() {
setAutoMode(AutoMode.OFF);
View content = View.inflate(this, R.layout.create_pgn_file, null);
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
@@ -2875,7 +2874,7 @@ public class DroidFish extends Activity
return dialog;
}
- private final Dialog setColorThemeDialog() {
+ private Dialog setColorThemeDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.select_color_theme);
String[] themeNames = new String[ColorTheme.themeNames.length];
@@ -2894,7 +2893,7 @@ public class DroidFish extends Activity
return builder.create();
}
- private final Dialog gameModeDialog() {
+ private Dialog gameModeDialog() {
final CharSequence[] items = {
getString(R.string.analysis_mode),
getString(R.string.edit_replay_game),
@@ -2929,7 +2928,7 @@ public class DroidFish extends Activity
return alert;
}
- private final Dialog moveListMenuDialog() {
+ private Dialog moveListMenuDialog() {
final int EDIT_HEADERS = 0;
final int EDIT_COMMENTS = 1;
final int ADD_ECO = 2;
@@ -3092,7 +3091,7 @@ public class DroidFish extends Activity
builder.show();
}
- private final Dialog thinkingMenuDialog() {
+ private Dialog thinkingMenuDialog() {
final int ADD_ANALYSIS = 0;
final int MULTIPV_SET = 1;
final int SHOW_WHOLE_VARS = 2;
@@ -3282,7 +3281,7 @@ public class DroidFish extends Activity
}
}
- private final Dialog goBackMenuDialog() {
+ private Dialog goBackMenuDialog() {
final int GOTO_START_GAME = 0;
final int GOTO_START_VAR = 1;
final int GOTO_PREV_VAR = 2;
@@ -3325,7 +3324,7 @@ public class DroidFish extends Activity
return alert;
}
- private final Dialog goForwardMenuDialog() {
+ private Dialog goForwardMenuDialog() {
final int GOTO_END_VAR = 0;
final int GOTO_NEXT_VAR = 1;
final int LOAD_NEXT_GAME = 2;
@@ -3388,7 +3387,7 @@ public class DroidFish extends Activity
return builder.create();
}
- private final Dialog manageEnginesDialog() {
+ private Dialog manageEnginesDialog() {
final int SELECT_ENGINE = 0;
final int SET_ENGINE_OPTIONS = 1;
final int CONFIG_NET_ENGINE = 2;
@@ -3422,7 +3421,7 @@ public class DroidFish extends Activity
}
/** Return true if engine UCI options can be set now. */
- private final boolean canSetEngineOptions() {
+ private boolean canSetEngineOptions() {
if (!storageAvailable())
return false;
UCIOptions uciOpts = ctrl.getUCIOptions();
@@ -3435,7 +3434,7 @@ public class DroidFish extends Activity
}
/** Start activity to set engine options. */
- private final void setEngineOptions() {
+ private void setEngineOptions() {
Intent i = new Intent(DroidFish.this, EditOptions.class);
UCIOptions uciOpts = ctrl.getUCIOptions();
if (uciOpts != null) {
@@ -3445,7 +3444,7 @@ public class DroidFish extends Activity
}
}
- private final Dialog networkEngineDialog() {
+ private Dialog networkEngineDialog() {
String[] fileNames = findFilesInDirectory(engineDir, new FileNameFilter() {
@Override
public boolean accept(String filename) {
@@ -3503,7 +3502,7 @@ public class DroidFish extends Activity
private String networkEngineToConfig = "";
// Ask for name of new network engine
- private final Dialog newNetworkEngineDialog() {
+ private Dialog newNetworkEngineDialog() {
View content = View.inflate(this, R.layout.create_network_engine, null);
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setView(content);
@@ -3567,7 +3566,7 @@ public class DroidFish extends Activity
}
// Configure network engine settings
- private final Dialog networkEngineConfigDialog() {
+ private Dialog networkEngineConfigDialog() {
View content = View.inflate(this, R.layout.network_engine_config, null);
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setView(content);
@@ -3707,16 +3706,16 @@ public class DroidFish extends Activity
}
}
- private final boolean hasScidProvider() {
+ private boolean hasScidProvider() {
try {
getPackageManager().getPackageInfo("org.scid.android", 0);
return true;
- } catch (PackageManager.NameNotFoundException ex) {
+ } catch (NameNotFoundException ex) {
return false;
}
}
- private final void selectScidFile() {
+ private void selectScidFile() {
setAutoMode(AutoMode.OFF);
Intent intent = new Intent();
intent.setComponent(new ComponentName("org.scid.android",
@@ -3736,7 +3735,7 @@ public class DroidFish extends Activity
return (resolvers != null) && (resolvers.size() > 0);
}
- private final void getFen() {
+ private void getFen() {
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.setType("application/x-chess-fen");
try {
@@ -3751,12 +3750,12 @@ public class DroidFish extends Activity
final static int FT_SCID = 2;
final static int FT_FEN = 3;
- private final int currFileType() {
+ private int currFileType() {
return settings.getInt("currFT", FT_NONE);
}
/** Return path name for the last used PGN or SCID file. */
- private final String currPathName() {
+ private String currPathName() {
int ft = settings.getInt("currFT", FT_NONE);
switch (ft) {
case FT_PGN: {
@@ -3779,7 +3778,7 @@ public class DroidFish extends Activity
boolean accept(String filename);
}
- private final String[] findFilesInDirectory(String dirName, final FileNameFilter filter) {
+ private String[] findFilesInDirectory(String dirName, final FileNameFilter filter) {
File extDir = Environment.getExternalStorageDirectory();
String sep = File.separator;
File dir = new File(extDir.getAbsolutePath() + sep + dirName);
@@ -3801,7 +3800,7 @@ public class DroidFish extends Activity
}
/** Save current game to a PGN file. */
- private final void savePGNToFile(String pathName, boolean silent) {
+ private void savePGNToFile(String pathName, boolean silent) {
String pgn = ctrl.getPGN();
String pgnToken = cache.storeString(pgn);
Editor editor = settings.edit();
@@ -3817,7 +3816,7 @@ public class DroidFish extends Activity
}
/** Load a PGN game from a file. */
- private final void loadPGNFromFile(String pathName) {
+ private void loadPGNFromFile(String pathName) {
Editor editor = settings.edit();
editor.putString("currentPGNFile", pathName);
editor.putInt("currFT", FT_PGN);
@@ -3829,7 +3828,7 @@ public class DroidFish extends Activity
}
/** Load a FEN position from a file. */
- private final void loadFENFromFile(String pathName) {
+ private void loadFENFromFile(String pathName) {
if (pathName == null)
return;
Editor editor = settings.edit();
@@ -3842,7 +3841,7 @@ public class DroidFish extends Activity
startActivityForResult(i, RESULT_LOAD_FEN);
}
- private final void setFenHelper(String fen) {
+ private void setFenHelper(String fen) {
if (fen == null)
return;
try {
@@ -3924,7 +3923,7 @@ public class DroidFish extends Activity
}
/** Decide if user should be warned about heavy CPU usage. */
- private final void updateNotification() {
+ private void updateNotification() {
boolean warn = false;
if (lastVisibleMillis != 0) { // GUI not visible
warn = lastComputationMillis >= lastVisibleMillis + 60000;
@@ -3935,7 +3934,7 @@ public class DroidFish extends Activity
private boolean notificationActive = false;
/** Set/clear the "heavy CPU usage" notification. */
- private final void setNotification(boolean show) {
+ private void setNotification(boolean show) {
if (notificationActive == show)
return;
notificationActive = show;
@@ -3968,7 +3967,7 @@ public class DroidFish extends Activity
}
}
- private final String timeToString(int time) {
+ private String timeToString(int time) {
int secs = (int)Math.floor((time + 999) / 1000.0);
boolean neg = false;
if (secs < 0) {
@@ -4094,8 +4093,8 @@ public class DroidFish extends Activity
int paraStart = 0;
int paraIndent = 0;
boolean paraBold = false;
- private final void newLine() { newLine(false); }
- private final void newLine(boolean eof) {
+ private void newLine() { newLine(false); }
+ private void newLine(boolean eof) {
if (!col0) {
if (paraIndent > 0) {
int paraEnd = sb.length();
diff --git a/DroidFish/src/org/petero/droidfish/DroidFishApp.java b/DroidFishApp/src/main/java/org/petero/droidfish/DroidFishApp.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/DroidFishApp.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/DroidFishApp.java
diff --git a/DroidFish/src/org/petero/droidfish/EngineOptions.java b/DroidFishApp/src/main/java/org/petero/droidfish/EngineOptions.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/EngineOptions.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/EngineOptions.java
diff --git a/DroidFish/src/org/petero/droidfish/FileUtil.java b/DroidFishApp/src/main/java/org/petero/droidfish/FileUtil.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/FileUtil.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/FileUtil.java
diff --git a/DroidFish/src/org/petero/droidfish/GUIInterface.java b/DroidFishApp/src/main/java/org/petero/droidfish/GUIInterface.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/GUIInterface.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/GUIInterface.java
diff --git a/DroidFish/src/org/petero/droidfish/GameMode.java b/DroidFishApp/src/main/java/org/petero/droidfish/GameMode.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/GameMode.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/GameMode.java
diff --git a/DroidFish/src/org/petero/droidfish/ObjectCache.java b/DroidFishApp/src/main/java/org/petero/droidfish/ObjectCache.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/ObjectCache.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/ObjectCache.java
diff --git a/DroidFish/src/org/petero/droidfish/PGNOptions.java b/DroidFishApp/src/main/java/org/petero/droidfish/PGNOptions.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/PGNOptions.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/PGNOptions.java
diff --git a/DroidFish/src/org/petero/droidfish/SVGPictureDrawable.java b/DroidFishApp/src/main/java/org/petero/droidfish/SVGPictureDrawable.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/SVGPictureDrawable.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/SVGPictureDrawable.java
diff --git a/DroidFish/src/org/petero/droidfish/Speech.java b/DroidFishApp/src/main/java/org/petero/droidfish/Speech.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/Speech.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/Speech.java
diff --git a/DroidFish/src/org/petero/droidfish/UIAction.java b/DroidFishApp/src/main/java/org/petero/droidfish/UIAction.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/UIAction.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/UIAction.java
diff --git a/DroidFish/src/org/petero/droidfish/Util.java b/DroidFishApp/src/main/java/org/petero/droidfish/Util.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/Util.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/Util.java
diff --git a/DroidFish/src/org/petero/droidfish/activities/BufferedRandomAccessFileReader.java b/DroidFishApp/src/main/java/org/petero/droidfish/activities/BufferedRandomAccessFileReader.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/activities/BufferedRandomAccessFileReader.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/activities/BufferedRandomAccessFileReader.java
diff --git a/DroidFish/src/org/petero/droidfish/activities/CPUWarning.java b/DroidFishApp/src/main/java/org/petero/droidfish/activities/CPUWarning.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/activities/CPUWarning.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/activities/CPUWarning.java
diff --git a/DroidFish/src/org/petero/droidfish/activities/ChessBoardEdit.java b/DroidFishApp/src/main/java/org/petero/droidfish/activities/ChessBoardEdit.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/activities/ChessBoardEdit.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/activities/ChessBoardEdit.java
diff --git a/DroidFish/src/org/petero/droidfish/activities/EditBoard.java b/DroidFishApp/src/main/java/org/petero/droidfish/activities/EditBoard.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/activities/EditBoard.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/activities/EditBoard.java
diff --git a/DroidFish/src/org/petero/droidfish/activities/EditOptions.java b/DroidFishApp/src/main/java/org/petero/droidfish/activities/EditOptions.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/activities/EditOptions.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/activities/EditOptions.java
diff --git a/DroidFish/src/org/petero/droidfish/activities/EditPGN.java b/DroidFishApp/src/main/java/org/petero/droidfish/activities/EditPGN.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/activities/EditPGN.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/activities/EditPGN.java
diff --git a/DroidFish/src/org/petero/droidfish/activities/EditPGNLoad.java b/DroidFishApp/src/main/java/org/petero/droidfish/activities/EditPGNLoad.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/activities/EditPGNLoad.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/activities/EditPGNLoad.java
diff --git a/DroidFish/src/org/petero/droidfish/activities/EditPGNSave.java b/DroidFishApp/src/main/java/org/petero/droidfish/activities/EditPGNSave.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/activities/EditPGNSave.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/activities/EditPGNSave.java
diff --git a/DroidFish/src/org/petero/droidfish/activities/FENFile.java b/DroidFishApp/src/main/java/org/petero/droidfish/activities/FENFile.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/activities/FENFile.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/activities/FENFile.java
diff --git a/DroidFish/src/org/petero/droidfish/activities/LoadFEN.java b/DroidFishApp/src/main/java/org/petero/droidfish/activities/LoadFEN.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/activities/LoadFEN.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/activities/LoadFEN.java
diff --git a/DroidFish/src/org/petero/droidfish/activities/LoadScid.java b/DroidFishApp/src/main/java/org/petero/droidfish/activities/LoadScid.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/activities/LoadScid.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/activities/LoadScid.java
diff --git a/DroidFish/src/org/petero/droidfish/activities/PGNFile.java b/DroidFishApp/src/main/java/org/petero/droidfish/activities/PGNFile.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/activities/PGNFile.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/activities/PGNFile.java
diff --git a/DroidFish/src/org/petero/droidfish/activities/Preferences.java b/DroidFishApp/src/main/java/org/petero/droidfish/activities/Preferences.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/activities/Preferences.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/activities/Preferences.java
diff --git a/DroidFish/src/org/petero/droidfish/activities/SeekBarPreference.java b/DroidFishApp/src/main/java/org/petero/droidfish/activities/SeekBarPreference.java
similarity index 99%
rename from DroidFish/src/org/petero/droidfish/activities/SeekBarPreference.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/activities/SeekBarPreference.java
index de974b0..b17ee7a 100644
--- a/DroidFish/src/org/petero/droidfish/activities/SeekBarPreference.java
+++ b/DroidFishApp/src/main/java/org/petero/droidfish/activities/SeekBarPreference.java
@@ -67,6 +67,7 @@ public class SeekBarPreference extends Preference
@Override
protected View onCreateView(ViewGroup parent) {
+ super.onCreateView(parent);
TextView name = new TextView(getContext());
name.setText(getTitle());
name.setTextAppearance(getContext(), android.R.style.TextAppearance_Large);
diff --git a/DroidFish/src/org/petero/droidfish/book/BookOptions.java b/DroidFishApp/src/main/java/org/petero/droidfish/book/BookOptions.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/book/BookOptions.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/book/BookOptions.java
diff --git a/DroidFish/src/org/petero/droidfish/book/CtgBook.java b/DroidFishApp/src/main/java/org/petero/droidfish/book/CtgBook.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/book/CtgBook.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/book/CtgBook.java
diff --git a/DroidFish/src/org/petero/droidfish/book/DroidBook.java b/DroidFishApp/src/main/java/org/petero/droidfish/book/DroidBook.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/book/DroidBook.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/book/DroidBook.java
diff --git a/DroidFish/src/org/petero/droidfish/book/EcoBook.java b/DroidFishApp/src/main/java/org/petero/droidfish/book/EcoBook.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/book/EcoBook.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/book/EcoBook.java
diff --git a/DroidFish/src/org/petero/droidfish/book/EcoDb.java b/DroidFishApp/src/main/java/org/petero/droidfish/book/EcoDb.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/book/EcoDb.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/book/EcoDb.java
diff --git a/DroidFish/src/org/petero/droidfish/book/IOpeningBook.java b/DroidFishApp/src/main/java/org/petero/droidfish/book/IOpeningBook.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/book/IOpeningBook.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/book/IOpeningBook.java
diff --git a/DroidFish/src/org/petero/droidfish/book/InternalBook.java b/DroidFishApp/src/main/java/org/petero/droidfish/book/InternalBook.java
similarity index 98%
rename from DroidFish/src/org/petero/droidfish/book/InternalBook.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/book/InternalBook.java
index 1098816..489ff04 100644
--- a/DroidFish/src/org/petero/droidfish/book/InternalBook.java
+++ b/DroidFishApp/src/main/java/org/petero/droidfish/book/InternalBook.java
@@ -24,6 +24,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import org.petero.droidfish.DroidFishApp;
import org.petero.droidfish.book.DroidBook.BookEntry;
import org.petero.droidfish.gamelogic.ChessParseError;
import org.petero.droidfish.gamelogic.Move;
@@ -33,6 +34,7 @@ import org.petero.droidfish.gamelogic.TextIO;
import org.petero.droidfish.gamelogic.UndoInfo;
import android.annotation.SuppressLint;
+import android.widget.Toast;
@SuppressLint("UseSparseArrays")
final class InternalBook implements IOpeningBook {
diff --git a/DroidFish/src/org/petero/droidfish/book/NoBook.java b/DroidFishApp/src/main/java/org/petero/droidfish/book/NoBook.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/book/NoBook.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/book/NoBook.java
diff --git a/DroidFish/src/org/petero/droidfish/book/NullBook.java b/DroidFishApp/src/main/java/org/petero/droidfish/book/NullBook.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/book/NullBook.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/book/NullBook.java
diff --git a/DroidFish/src/org/petero/droidfish/book/PolyglotBook.java b/DroidFishApp/src/main/java/org/petero/droidfish/book/PolyglotBook.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/book/PolyglotBook.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/book/PolyglotBook.java
diff --git a/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java b/DroidFishApp/src/main/java/org/petero/droidfish/engine/DroidComputerPlayer.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/engine/DroidComputerPlayer.java
diff --git a/DroidFish/src/org/petero/droidfish/engine/EngineUtil.java b/DroidFishApp/src/main/java/org/petero/droidfish/engine/EngineUtil.java
similarity index 86%
rename from DroidFish/src/org/petero/droidfish/engine/EngineUtil.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/engine/EngineUtil.java
index 7c00dba..ba9eff3 100644
--- a/DroidFish/src/org/petero/droidfish/engine/EngineUtil.java
+++ b/DroidFishApp/src/main/java/org/petero/droidfish/engine/EngineUtil.java
@@ -36,20 +36,12 @@ public class EngineUtil {
/** Return file name of the internal stockfish executable. */
public static String internalStockFishName() {
String abi = Build.CPU_ABI;
- boolean noPIE = Build.VERSION.SDK_INT < 21;
- if (abi.equals("x86")) {
- } else if (abi.equals("x86_64")) {
- noPIE = false;
- } else if (abi.equals("armeabi-v7a")) {
- } else if (abi.equals("arm64-v8a")) {
- noPIE = false;
- } else if (abi.equals("mips")) {
- } else if (abi.equals("mips64")) {
- noPIE = false;
- } else {
- abi = "armeabi"; // Unknown ABI, assume original ARM
+ if (!"x86".equals(abi) &&
+ !"x86_64".equals(abi) &&
+ !"arm64-v8a".equals(abi)) {
+ abi = "armeabi-v7a"; // Unknown ABI, assume 32-bit arm
}
- return "stockfish-" + abi + (noPIE ? "-nopie" : "");
+ return abi + "/stockfish";
}
/** Return true if file "engine" is a network engine. */
diff --git a/DroidFish/src/org/petero/droidfish/engine/ExternalEngine.java b/DroidFishApp/src/main/java/org/petero/droidfish/engine/ExternalEngine.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/engine/ExternalEngine.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/engine/ExternalEngine.java
diff --git a/DroidFish/src/org/petero/droidfish/engine/InternalStockFish.java b/DroidFishApp/src/main/java/org/petero/droidfish/engine/InternalStockFish.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/engine/InternalStockFish.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/engine/InternalStockFish.java
diff --git a/DroidFish/src/org/petero/droidfish/engine/LocalPipe.java b/DroidFishApp/src/main/java/org/petero/droidfish/engine/LocalPipe.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/engine/LocalPipe.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/engine/LocalPipe.java
diff --git a/DroidFish/src/org/petero/droidfish/engine/NetworkEngine.java b/DroidFishApp/src/main/java/org/petero/droidfish/engine/NetworkEngine.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/engine/NetworkEngine.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/engine/NetworkEngine.java
diff --git a/DroidFish/src/org/petero/droidfish/engine/OpenExchangeEngine.java b/DroidFishApp/src/main/java/org/petero/droidfish/engine/OpenExchangeEngine.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/engine/OpenExchangeEngine.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/engine/OpenExchangeEngine.java
diff --git a/DroidFish/src/org/petero/droidfish/engine/UCIEngine.java b/DroidFishApp/src/main/java/org/petero/droidfish/engine/UCIEngine.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/engine/UCIEngine.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/engine/UCIEngine.java
diff --git a/DroidFish/src/org/petero/droidfish/engine/UCIEngineBase.java b/DroidFishApp/src/main/java/org/petero/droidfish/engine/UCIEngineBase.java
similarity index 98%
rename from DroidFish/src/org/petero/droidfish/engine/UCIEngineBase.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/engine/UCIEngineBase.java
index ed8721e..7c386e7 100644
--- a/DroidFish/src/org/petero/droidfish/engine/UCIEngineBase.java
+++ b/DroidFishApp/src/main/java/org/petero/droidfish/engine/UCIEngineBase.java
@@ -39,8 +39,6 @@ public abstract class UCIEngineBase implements UCIEngine {
public static UCIEngine getEngine(String engine,
EngineOptions engineOptions, Report report) {
- if ("stockfish".equals(engine) && (EngineUtil.internalStockFishName() == null))
- engine = "cuckoochess";
if ("cuckoochess".equals(engine))
return new CuckooChessEngine(report);
else if ("stockfish".equals(engine))
diff --git a/DroidFish/src/org/petero/droidfish/engine/UCIOptions.java b/DroidFishApp/src/main/java/org/petero/droidfish/engine/UCIOptions.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/engine/UCIOptions.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/engine/UCIOptions.java
diff --git a/DroidFish/src/org/petero/droidfish/engine/cuckoochess/CuckooChessEngine.java b/DroidFishApp/src/main/java/org/petero/droidfish/engine/cuckoochess/CuckooChessEngine.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/engine/cuckoochess/CuckooChessEngine.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/engine/cuckoochess/CuckooChessEngine.java
diff --git a/DroidFish/src/org/petero/droidfish/engine/cuckoochess/DroidEngineControl.java b/DroidFishApp/src/main/java/org/petero/droidfish/engine/cuckoochess/DroidEngineControl.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/engine/cuckoochess/DroidEngineControl.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/engine/cuckoochess/DroidEngineControl.java
diff --git a/DroidFish/src/org/petero/droidfish/engine/cuckoochess/SearchParams.java b/DroidFishApp/src/main/java/org/petero/droidfish/engine/cuckoochess/SearchParams.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/engine/cuckoochess/SearchParams.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/engine/cuckoochess/SearchParams.java
diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/ChessParseError.java b/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/ChessParseError.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/gamelogic/ChessParseError.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/ChessParseError.java
diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java b/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/DroidChessController.java
similarity index 99%
rename from DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/DroidChessController.java
index dbdd139..f53605e 100644
--- a/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java
+++ b/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/DroidChessController.java
@@ -984,7 +984,7 @@ public class DroidChessController {
if (!computerPlayer.sameSearchId(searchId)) {
if (analysis) {
Pair> ph = game.getUCIHistory();
- SearchRequest sr = DroidComputerPlayer.SearchRequest.analyzeRequest(
+ SearchRequest sr = SearchRequest.analyzeRequest(
searchId, ph.first, ph.second,
new Position(game.currPos()),
game.haveDrawOffer(), engine, numPV);
@@ -1008,7 +1008,7 @@ public class DroidChessController {
if (ponder)
game.timeController.advanceMove(-1);
final Move fPonderMove = ponder ? ponderMove : null;
- SearchRequest sr = DroidComputerPlayer.SearchRequest.searchRequest(
+ SearchRequest sr = SearchRequest.searchRequest(
searchId, now, ph.first, ph.second, currPos,
game.haveDrawOffer(),
wTime, bTime, wInc, bInc, movesToGo,
@@ -1135,10 +1135,10 @@ public class DroidChessController {
private final void updateGUI() {
GUIInterface.GameStatus s = new GUIInterface.GameStatus();
s.state = game.getGameState();
- if (s.state == Game.GameState.ALIVE) {
+ if (s.state == GameState.ALIVE) {
s.moveNr = game.currPos().fullMoveCounter;
s.white = game.currPos().whiteMove;
- DroidComputerPlayer.SearchType st = SearchType.NONE;
+ SearchType st = SearchType.NONE;
if (computerPlayer != null)
st = computerPlayer.getSearchType();
switch (st) {
diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/Game.java b/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/Game.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/gamelogic/Game.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/Game.java
diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/GameTree.java b/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/GameTree.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/gamelogic/GameTree.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/GameTree.java
diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/Move.java b/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/Move.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/gamelogic/Move.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/Move.java
diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/MoveGen.java b/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/MoveGen.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/gamelogic/MoveGen.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/MoveGen.java
diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/Pair.java b/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/Pair.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/gamelogic/Pair.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/Pair.java
diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/PgnToken.java b/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/PgnToken.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/gamelogic/PgnToken.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/PgnToken.java
diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/Piece.java b/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/Piece.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/gamelogic/Piece.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/Piece.java
diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/Position.java b/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/Position.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/gamelogic/Position.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/Position.java
diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/SearchListener.java b/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/SearchListener.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/gamelogic/SearchListener.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/SearchListener.java
diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/TextIO.java b/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/TextIO.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/gamelogic/TextIO.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/TextIO.java
diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/TimeControl.java b/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/TimeControl.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/gamelogic/TimeControl.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/TimeControl.java
diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/TimeControlData.java b/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/TimeControlData.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/gamelogic/TimeControlData.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/TimeControlData.java
diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/UndoInfo.java b/DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/UndoInfo.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/gamelogic/UndoInfo.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/gamelogic/UndoInfo.java
diff --git a/DroidFish/src/org/petero/droidfish/tb/GtbProbe.java b/DroidFishApp/src/main/java/org/petero/droidfish/tb/GtbProbe.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/tb/GtbProbe.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/tb/GtbProbe.java
diff --git a/DroidFish/src/org/petero/droidfish/tb/Probe.java b/DroidFishApp/src/main/java/org/petero/droidfish/tb/Probe.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/tb/Probe.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/tb/Probe.java
diff --git a/DroidFish/src/org/petero/droidfish/tb/ProbeResult.java b/DroidFishApp/src/main/java/org/petero/droidfish/tb/ProbeResult.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/tb/ProbeResult.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/tb/ProbeResult.java
diff --git a/DroidFish/src/org/petero/droidfish/tb/RtbProbe.java b/DroidFishApp/src/main/java/org/petero/droidfish/tb/RtbProbe.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/tb/RtbProbe.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/tb/RtbProbe.java
diff --git a/DroidFish/src/org/petero/droidfish/view/ChessBoard.java b/DroidFishApp/src/main/java/org/petero/droidfish/view/ChessBoard.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/view/ChessBoard.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/view/ChessBoard.java
diff --git a/DroidFish/src/org/petero/droidfish/view/MoveListView.java b/DroidFishApp/src/main/java/org/petero/droidfish/view/MoveListView.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/view/MoveListView.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/view/MoveListView.java
diff --git a/DroidFish/src/org/petero/droidfish/view/MyRelativeLayout.java b/DroidFishApp/src/main/java/org/petero/droidfish/view/MyRelativeLayout.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/view/MyRelativeLayout.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/view/MyRelativeLayout.java
diff --git a/DroidFish/src/org/petero/droidfish/view/MyScrollView.java b/DroidFishApp/src/main/java/org/petero/droidfish/view/MyScrollView.java
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/view/MyScrollView.java
rename to DroidFishApp/src/main/java/org/petero/droidfish/view/MyScrollView.java
diff --git a/DroidFish/src/tourguide/tourguide/FrameLayoutWithHole.java b/DroidFishApp/src/main/java/tourguide/tourguide/FrameLayoutWithHole.java
similarity index 100%
rename from DroidFish/src/tourguide/tourguide/FrameLayoutWithHole.java
rename to DroidFishApp/src/main/java/tourguide/tourguide/FrameLayoutWithHole.java
diff --git a/DroidFish/src/tourguide/tourguide/Overlay.java b/DroidFishApp/src/main/java/tourguide/tourguide/Overlay.java
similarity index 100%
rename from DroidFish/src/tourguide/tourguide/Overlay.java
rename to DroidFishApp/src/main/java/tourguide/tourguide/Overlay.java
diff --git a/DroidFish/src/tourguide/tourguide/Pointer.java b/DroidFishApp/src/main/java/tourguide/tourguide/Pointer.java
similarity index 100%
rename from DroidFish/src/tourguide/tourguide/Pointer.java
rename to DroidFishApp/src/main/java/tourguide/tourguide/Pointer.java
diff --git a/DroidFish/src/tourguide/tourguide/Sequence.java b/DroidFishApp/src/main/java/tourguide/tourguide/Sequence.java
similarity index 100%
rename from DroidFish/src/tourguide/tourguide/Sequence.java
rename to DroidFishApp/src/main/java/tourguide/tourguide/Sequence.java
diff --git a/DroidFish/src/tourguide/tourguide/ToolTip.java b/DroidFishApp/src/main/java/tourguide/tourguide/ToolTip.java
similarity index 100%
rename from DroidFish/src/tourguide/tourguide/ToolTip.java
rename to DroidFishApp/src/main/java/tourguide/tourguide/ToolTip.java
diff --git a/DroidFish/src/tourguide/tourguide/TourGuide.java b/DroidFishApp/src/main/java/tourguide/tourguide/TourGuide.java
similarity index 100%
rename from DroidFish/src/tourguide/tourguide/TourGuide.java
rename to DroidFishApp/src/main/java/tourguide/tourguide/TourGuide.java
diff --git a/DroidFish/res/drawable-hdpi/fab_bg_mini.png b/DroidFishApp/src/main/res/drawable-hdpi/fab_bg_mini.png
similarity index 100%
rename from DroidFish/res/drawable-hdpi/fab_bg_mini.png
rename to DroidFishApp/src/main/res/drawable-hdpi/fab_bg_mini.png
diff --git a/DroidFish/res/drawable-hdpi/fab_bg_normal.png b/DroidFishApp/src/main/res/drawable-hdpi/fab_bg_normal.png
similarity index 100%
rename from DroidFish/res/drawable-hdpi/fab_bg_normal.png
rename to DroidFishApp/src/main/res/drawable-hdpi/fab_bg_normal.png
diff --git a/DroidFish/res/drawable-hdpi/silhouette.png b/DroidFishApp/src/main/res/drawable-hdpi/silhouette.png
similarity index 100%
rename from DroidFish/res/drawable-hdpi/silhouette.png
rename to DroidFishApp/src/main/res/drawable-hdpi/silhouette.png
diff --git a/DroidFish/res/drawable-mdpi/fab_bg_mini.png b/DroidFishApp/src/main/res/drawable-mdpi/fab_bg_mini.png
similarity index 100%
rename from DroidFish/res/drawable-mdpi/fab_bg_mini.png
rename to DroidFishApp/src/main/res/drawable-mdpi/fab_bg_mini.png
diff --git a/DroidFish/res/drawable-mdpi/fab_bg_normal.png b/DroidFishApp/src/main/res/drawable-mdpi/fab_bg_normal.png
similarity index 100%
rename from DroidFish/res/drawable-mdpi/fab_bg_normal.png
rename to DroidFishApp/src/main/res/drawable-mdpi/fab_bg_normal.png
diff --git a/DroidFish/res/drawable-xhdpi/drop_shadow.9.png b/DroidFishApp/src/main/res/drawable-xhdpi/drop_shadow.9.png
similarity index 100%
rename from DroidFish/res/drawable-xhdpi/drop_shadow.9.png
rename to DroidFishApp/src/main/res/drawable-xhdpi/drop_shadow.9.png
diff --git a/DroidFish/res/drawable-xhdpi/fab_bg_mini.png b/DroidFishApp/src/main/res/drawable-xhdpi/fab_bg_mini.png
similarity index 100%
rename from DroidFish/res/drawable-xhdpi/fab_bg_mini.png
rename to DroidFishApp/src/main/res/drawable-xhdpi/fab_bg_mini.png
diff --git a/DroidFish/res/drawable-xhdpi/fab_bg_normal.png b/DroidFishApp/src/main/res/drawable-xhdpi/fab_bg_normal.png
similarity index 100%
rename from DroidFish/res/drawable-xhdpi/fab_bg_normal.png
rename to DroidFishApp/src/main/res/drawable-xhdpi/fab_bg_normal.png
diff --git a/DroidFish/res/drawable-xxhdpi/fab_bg_mini.png b/DroidFishApp/src/main/res/drawable-xxhdpi/fab_bg_mini.png
similarity index 100%
rename from DroidFish/res/drawable-xxhdpi/fab_bg_mini.png
rename to DroidFishApp/src/main/res/drawable-xxhdpi/fab_bg_mini.png
diff --git a/DroidFish/res/drawable-xxhdpi/fab_bg_normal.png b/DroidFishApp/src/main/res/drawable-xxhdpi/fab_bg_normal.png
similarity index 100%
rename from DroidFish/res/drawable-xxhdpi/fab_bg_normal.png
rename to DroidFishApp/src/main/res/drawable-xxhdpi/fab_bg_normal.png
diff --git a/DroidFish/res/drawable-xxxhdpi/fab_bg_mini.png b/DroidFishApp/src/main/res/drawable-xxxhdpi/fab_bg_mini.png
similarity index 100%
rename from DroidFish/res/drawable-xxxhdpi/fab_bg_mini.png
rename to DroidFishApp/src/main/res/drawable-xxxhdpi/fab_bg_mini.png
diff --git a/DroidFish/res/drawable-xxxhdpi/fab_bg_normal.png b/DroidFishApp/src/main/res/drawable-xxxhdpi/fab_bg_normal.png
similarity index 100%
rename from DroidFish/res/drawable-xxxhdpi/fab_bg_normal.png
rename to DroidFishApp/src/main/res/drawable-xxxhdpi/fab_bg_normal.png
diff --git a/DroidFish/res/layout-land/dialog_color_picker.xml b/DroidFishApp/src/main/res/layout-land/dialog_color_picker.xml
similarity index 100%
rename from DroidFish/res/layout-land/dialog_color_picker.xml
rename to DroidFishApp/src/main/res/layout-land/dialog_color_picker.xml
diff --git a/DroidFish/res/layout-land/editboard.xml b/DroidFishApp/src/main/res/layout-land/editboard.xml
similarity index 100%
rename from DroidFish/res/layout-land/editboard.xml
rename to DroidFishApp/src/main/res/layout-land/editboard.xml
diff --git a/DroidFish/res/layout-land/editoptions.xml b/DroidFishApp/src/main/res/layout-land/editoptions.xml
similarity index 100%
rename from DroidFish/res/layout-land/editoptions.xml
rename to DroidFishApp/src/main/res/layout-land/editoptions.xml
diff --git a/DroidFish/res/layout-land/load_fen.xml b/DroidFishApp/src/main/res/layout-land/load_fen.xml
similarity index 100%
rename from DroidFish/res/layout-land/load_fen.xml
rename to DroidFishApp/src/main/res/layout-land/load_fen.xml
diff --git a/DroidFish/res/layout-land/main.xml b/DroidFishApp/src/main/res/layout-land/main.xml
similarity index 100%
rename from DroidFish/res/layout-land/main.xml
rename to DroidFishApp/src/main/res/layout-land/main.xml
diff --git a/DroidFish/res/layout/create_network_engine.xml b/DroidFishApp/src/main/res/layout/create_network_engine.xml
similarity index 100%
rename from DroidFish/res/layout/create_network_engine.xml
rename to DroidFishApp/src/main/res/layout/create_network_engine.xml
diff --git a/DroidFish/res/layout/create_pgn_file.xml b/DroidFishApp/src/main/res/layout/create_pgn_file.xml
similarity index 100%
rename from DroidFish/res/layout/create_pgn_file.xml
rename to DroidFishApp/src/main/res/layout/create_pgn_file.xml
diff --git a/DroidFish/res/layout/dialog_color_picker.xml b/DroidFishApp/src/main/res/layout/dialog_color_picker.xml
similarity index 100%
rename from DroidFish/res/layout/dialog_color_picker.xml
rename to DroidFishApp/src/main/res/layout/dialog_color_picker.xml
diff --git a/DroidFish/res/layout/drawer_list_item.xml b/DroidFishApp/src/main/res/layout/drawer_list_item.xml
similarity index 100%
rename from DroidFish/res/layout/drawer_list_item.xml
rename to DroidFishApp/src/main/res/layout/drawer_list_item.xml
diff --git a/DroidFish/res/layout/edit_comments.xml b/DroidFishApp/src/main/res/layout/edit_comments.xml
similarity index 100%
rename from DroidFish/res/layout/edit_comments.xml
rename to DroidFishApp/src/main/res/layout/edit_comments.xml
diff --git a/DroidFish/res/layout/edit_headers.xml b/DroidFishApp/src/main/res/layout/edit_headers.xml
similarity index 100%
rename from DroidFish/res/layout/edit_headers.xml
rename to DroidFishApp/src/main/res/layout/edit_headers.xml
diff --git a/DroidFish/res/layout/edit_move_counters.xml b/DroidFishApp/src/main/res/layout/edit_move_counters.xml
similarity index 100%
rename from DroidFish/res/layout/edit_move_counters.xml
rename to DroidFishApp/src/main/res/layout/edit_move_counters.xml
diff --git a/DroidFish/res/layout/editboard.xml b/DroidFishApp/src/main/res/layout/editboard.xml
similarity index 100%
rename from DroidFish/res/layout/editboard.xml
rename to DroidFishApp/src/main/res/layout/editboard.xml
diff --git a/DroidFish/res/layout/editoptions.xml b/DroidFishApp/src/main/res/layout/editoptions.xml
similarity index 100%
rename from DroidFish/res/layout/editoptions.xml
rename to DroidFishApp/src/main/res/layout/editoptions.xml
diff --git a/DroidFish/res/layout/left_drawer.xml b/DroidFishApp/src/main/res/layout/left_drawer.xml
similarity index 100%
rename from DroidFish/res/layout/left_drawer.xml
rename to DroidFishApp/src/main/res/layout/left_drawer.xml
diff --git a/DroidFish/res/layout/load_fen.xml b/DroidFishApp/src/main/res/layout/load_fen.xml
similarity index 100%
rename from DroidFish/res/layout/load_fen.xml
rename to DroidFishApp/src/main/res/layout/load_fen.xml
diff --git a/DroidFish/res/layout/main.xml b/DroidFishApp/src/main/res/layout/main.xml
similarity index 100%
rename from DroidFish/res/layout/main.xml
rename to DroidFishApp/src/main/res/layout/main.xml
diff --git a/DroidFish/res/layout/main_left_handed.xml b/DroidFishApp/src/main/res/layout/main_left_handed.xml
similarity index 100%
rename from DroidFish/res/layout/main_left_handed.xml
rename to DroidFishApp/src/main/res/layout/main_left_handed.xml
diff --git a/DroidFish/res/layout/network_engine_config.xml b/DroidFishApp/src/main/res/layout/network_engine_config.xml
similarity index 100%
rename from DroidFish/res/layout/network_engine_config.xml
rename to DroidFishApp/src/main/res/layout/network_engine_config.xml
diff --git a/DroidFish/res/layout/num_variations.xml b/DroidFishApp/src/main/res/layout/num_variations.xml
similarity index 100%
rename from DroidFish/res/layout/num_variations.xml
rename to DroidFishApp/src/main/res/layout/num_variations.xml
diff --git a/DroidFish/res/layout/right_drawer.xml b/DroidFishApp/src/main/res/layout/right_drawer.xml
similarity index 100%
rename from DroidFish/res/layout/right_drawer.xml
rename to DroidFishApp/src/main/res/layout/right_drawer.xml
diff --git a/DroidFish/res/layout/select_game.xml b/DroidFishApp/src/main/res/layout/select_game.xml
similarity index 100%
rename from DroidFish/res/layout/select_game.xml
rename to DroidFishApp/src/main/res/layout/select_game.xml
diff --git a/DroidFish/res/layout/select_game_list_item.xml b/DroidFishApp/src/main/res/layout/select_game_list_item.xml
similarity index 100%
rename from DroidFish/res/layout/select_game_list_item.xml
rename to DroidFishApp/src/main/res/layout/select_game_list_item.xml
diff --git a/DroidFish/res/layout/select_percentage.xml b/DroidFishApp/src/main/res/layout/select_percentage.xml
similarity index 100%
rename from DroidFish/res/layout/select_percentage.xml
rename to DroidFishApp/src/main/res/layout/select_percentage.xml
diff --git a/DroidFish/res/layout/title.xml b/DroidFishApp/src/main/res/layout/title.xml
similarity index 100%
rename from DroidFish/res/layout/title.xml
rename to DroidFishApp/src/main/res/layout/title.xml
diff --git a/DroidFish/res/layout/tooltip.xml b/DroidFishApp/src/main/res/layout/tooltip.xml
similarity index 100%
rename from DroidFish/res/layout/tooltip.xml
rename to DroidFishApp/src/main/res/layout/tooltip.xml
diff --git a/DroidFish/res/layout/uci_option_button.xml b/DroidFishApp/src/main/res/layout/uci_option_button.xml
similarity index 100%
rename from DroidFish/res/layout/uci_option_button.xml
rename to DroidFishApp/src/main/res/layout/uci_option_button.xml
diff --git a/DroidFish/res/layout/uci_option_check.xml b/DroidFishApp/src/main/res/layout/uci_option_check.xml
similarity index 100%
rename from DroidFish/res/layout/uci_option_check.xml
rename to DroidFishApp/src/main/res/layout/uci_option_check.xml
diff --git a/DroidFish/res/layout/uci_option_combo.xml b/DroidFishApp/src/main/res/layout/uci_option_combo.xml
similarity index 100%
rename from DroidFish/res/layout/uci_option_combo.xml
rename to DroidFishApp/src/main/res/layout/uci_option_combo.xml
diff --git a/DroidFish/res/layout/uci_option_spin.xml b/DroidFishApp/src/main/res/layout/uci_option_spin.xml
similarity index 100%
rename from DroidFish/res/layout/uci_option_spin.xml
rename to DroidFishApp/src/main/res/layout/uci_option_spin.xml
diff --git a/DroidFish/res/layout/uci_option_string.xml b/DroidFishApp/src/main/res/layout/uci_option_string.xml
similarity index 100%
rename from DroidFish/res/layout/uci_option_string.xml
rename to DroidFishApp/src/main/res/layout/uci_option_string.xml
diff --git a/DroidFish/res/menu/edit_file_options_menu.xml b/DroidFishApp/src/main/res/menu/edit_file_options_menu.xml
similarity index 100%
rename from DroidFish/res/menu/edit_file_options_menu.xml
rename to DroidFishApp/src/main/res/menu/edit_file_options_menu.xml
diff --git a/DroidFish/res/mipmap-hdpi/icon.png b/DroidFishApp/src/main/res/mipmap-hdpi/icon.png
similarity index 100%
rename from DroidFish/res/mipmap-hdpi/icon.png
rename to DroidFishApp/src/main/res/mipmap-hdpi/icon.png
diff --git a/DroidFish/res/mipmap-ldpi/icon.png b/DroidFishApp/src/main/res/mipmap-ldpi/icon.png
similarity index 100%
rename from DroidFish/res/mipmap-ldpi/icon.png
rename to DroidFishApp/src/main/res/mipmap-ldpi/icon.png
diff --git a/DroidFish/res/mipmap-xhdpi/icon.png b/DroidFishApp/src/main/res/mipmap-xhdpi/icon.png
similarity index 100%
rename from DroidFish/res/mipmap-xhdpi/icon.png
rename to DroidFishApp/src/main/res/mipmap-xhdpi/icon.png
diff --git a/DroidFish/res/mipmap-xxhdpi/icon.png b/DroidFishApp/src/main/res/mipmap-xxhdpi/icon.png
similarity index 100%
rename from DroidFish/res/mipmap-xxhdpi/icon.png
rename to DroidFishApp/src/main/res/mipmap-xxhdpi/icon.png
diff --git a/DroidFish/res/mipmap-xxxhdpi/icon.png b/DroidFishApp/src/main/res/mipmap-xxxhdpi/icon.png
similarity index 100%
rename from DroidFish/res/mipmap-xxxhdpi/icon.png
rename to DroidFishApp/src/main/res/mipmap-xxxhdpi/icon.png
diff --git a/DroidFish/res/raw-be/about.html b/DroidFishApp/src/main/res/raw-be/about.html
similarity index 100%
rename from DroidFish/res/raw-be/about.html
rename to DroidFishApp/src/main/res/raw-be/about.html
diff --git a/DroidFish/res/raw-de/about.html b/DroidFishApp/src/main/res/raw-de/about.html
similarity index 100%
rename from DroidFish/res/raw-de/about.html
rename to DroidFishApp/src/main/res/raw-de/about.html
diff --git a/DroidFish/res/raw-es/about.html b/DroidFishApp/src/main/res/raw-es/about.html
similarity index 100%
rename from DroidFish/res/raw-es/about.html
rename to DroidFishApp/src/main/res/raw-es/about.html
diff --git a/DroidFish/res/raw-fr/about.html b/DroidFishApp/src/main/res/raw-fr/about.html
similarity index 100%
rename from DroidFish/res/raw-fr/about.html
rename to DroidFishApp/src/main/res/raw-fr/about.html
diff --git a/DroidFish/res/raw-it/about.html b/DroidFishApp/src/main/res/raw-it/about.html
similarity index 100%
rename from DroidFish/res/raw-it/about.html
rename to DroidFishApp/src/main/res/raw-it/about.html
diff --git a/DroidFish/res/raw-ko/about.html b/DroidFishApp/src/main/res/raw-ko/about.html
similarity index 100%
rename from DroidFish/res/raw-ko/about.html
rename to DroidFishApp/src/main/res/raw-ko/about.html
diff --git a/DroidFish/res/raw-nl/about.html b/DroidFishApp/src/main/res/raw-nl/about.html
similarity index 100%
rename from DroidFish/res/raw-nl/about.html
rename to DroidFishApp/src/main/res/raw-nl/about.html
diff --git a/DroidFish/res/raw-pl/about.html b/DroidFishApp/src/main/res/raw-pl/about.html
similarity index 100%
rename from DroidFish/res/raw-pl/about.html
rename to DroidFishApp/src/main/res/raw-pl/about.html
diff --git a/DroidFish/res/raw-pt/about.html b/DroidFishApp/src/main/res/raw-pt/about.html
similarity index 100%
rename from DroidFish/res/raw-pt/about.html
rename to DroidFishApp/src/main/res/raw-pt/about.html
diff --git a/DroidFish/res/raw-ru/about.html b/DroidFishApp/src/main/res/raw-ru/about.html
similarity index 100%
rename from DroidFish/res/raw-ru/about.html
rename to DroidFishApp/src/main/res/raw-ru/about.html
diff --git a/DroidFish/res/raw-tr/about.html b/DroidFishApp/src/main/res/raw-tr/about.html
similarity index 100%
rename from DroidFish/res/raw-tr/about.html
rename to DroidFishApp/src/main/res/raw-tr/about.html
diff --git a/DroidFish/res/raw-uk/about.html b/DroidFishApp/src/main/res/raw-uk/about.html
similarity index 100%
rename from DroidFish/res/raw-uk/about.html
rename to DroidFishApp/src/main/res/raw-uk/about.html
diff --git a/DroidFish/res/raw-zh-rCN/about.html b/DroidFishApp/src/main/res/raw-zh-rCN/about.html
similarity index 100%
rename from DroidFish/res/raw-zh-rCN/about.html
rename to DroidFishApp/src/main/res/raw-zh-rCN/about.html
diff --git a/DroidFish/res/raw/about.html b/DroidFishApp/src/main/res/raw/about.html
similarity index 100%
rename from DroidFish/res/raw/about.html
rename to DroidFishApp/src/main/res/raw/about.html
diff --git a/DroidFish/res/raw/analyze.svg b/DroidFishApp/src/main/res/raw/analyze.svg
similarity index 100%
rename from DroidFish/res/raw/analyze.svg
rename to DroidFishApp/src/main/res/raw/analyze.svg
diff --git a/DroidFish/res/raw/blind.svg b/DroidFishApp/src/main/res/raw/blind.svg
similarity index 100%
rename from DroidFish/res/raw/blind.svg
rename to DroidFishApp/src/main/res/raw/blind.svg
diff --git a/DroidFish/res/raw/book.svg b/DroidFishApp/src/main/res/raw/book.svg
similarity index 100%
rename from DroidFish/res/raw/book.svg
rename to DroidFishApp/src/main/res/raw/book.svg
diff --git a/DroidFish/res/raw/comment.svg b/DroidFishApp/src/main/res/raw/comment.svg
similarity index 100%
rename from DroidFish/res/raw/comment.svg
rename to DroidFishApp/src/main/res/raw/comment.svg
diff --git a/DroidFish/res/raw/custom.svg b/DroidFishApp/src/main/res/raw/custom.svg
similarity index 100%
rename from DroidFish/res/raw/custom.svg
rename to DroidFishApp/src/main/res/raw/custom.svg
diff --git a/DroidFish/res/raw/droidfish.svg b/DroidFishApp/src/main/res/raw/droidfish.svg
similarity index 100%
rename from DroidFish/res/raw/droidfish.svg
rename to DroidFishApp/src/main/res/raw/droidfish.svg
diff --git a/DroidFish/res/raw/engine.svg b/DroidFishApp/src/main/res/raw/engine.svg
similarity index 100%
rename from DroidFish/res/raw/engine.svg
rename to DroidFishApp/src/main/res/raw/engine.svg
diff --git a/DroidFish/res/raw/flip.svg b/DroidFishApp/src/main/res/raw/flip.svg
similarity index 100%
rename from DroidFish/res/raw/flip.svg
rename to DroidFishApp/src/main/res/raw/flip.svg
diff --git a/DroidFish/res/raw/header.svg b/DroidFishApp/src/main/res/raw/header.svg
similarity index 100%
rename from DroidFish/res/raw/header.svg
rename to DroidFishApp/src/main/res/raw/header.svg
diff --git a/DroidFish/res/raw/left.svg b/DroidFishApp/src/main/res/raw/left.svg
similarity index 100%
rename from DroidFish/res/raw/left.svg
rename to DroidFishApp/src/main/res/raw/left.svg
diff --git a/DroidFish/res/raw/magnify.svg b/DroidFishApp/src/main/res/raw/magnify.svg
similarity index 100%
rename from DroidFish/res/raw/magnify.svg
rename to DroidFishApp/src/main/res/raw/magnify.svg
diff --git a/DroidFish/res/raw/mode.svg b/DroidFishApp/src/main/res/raw/mode.svg
similarity index 100%
rename from DroidFish/res/raw/mode.svg
rename to DroidFishApp/src/main/res/raw/mode.svg
diff --git a/DroidFish/res/raw/movesound.ogg b/DroidFishApp/src/main/res/raw/movesound.ogg
similarity index 100%
rename from DroidFish/res/raw/movesound.ogg
rename to DroidFishApp/src/main/res/raw/movesound.ogg
diff --git a/DroidFish/res/raw/open_last_file.svg b/DroidFishApp/src/main/res/raw/open_last_file.svg
similarity index 100%
rename from DroidFish/res/raw/open_last_file.svg
rename to DroidFishApp/src/main/res/raw/open_last_file.svg
diff --git a/DroidFish/res/raw/right.svg b/DroidFishApp/src/main/res/raw/right.svg
similarity index 100%
rename from DroidFish/res/raw/right.svg
rename to DroidFishApp/src/main/res/raw/right.svg
diff --git a/DroidFish/res/raw/silhouette.svg b/DroidFishApp/src/main/res/raw/silhouette.svg
similarity index 100%
rename from DroidFish/res/raw/silhouette.svg
rename to DroidFishApp/src/main/res/raw/silhouette.svg
diff --git a/DroidFish/res/raw/thinking.svg b/DroidFishApp/src/main/res/raw/thinking.svg
similarity index 100%
rename from DroidFish/res/raw/thinking.svg
rename to DroidFishApp/src/main/res/raw/thinking.svg
diff --git a/DroidFish/res/raw/touch.svg b/DroidFishApp/src/main/res/raw/touch.svg
similarity index 100%
rename from DroidFish/res/raw/touch.svg
rename to DroidFishApp/src/main/res/raw/touch.svg
diff --git a/DroidFish/res/raw/variation.svg b/DroidFishApp/src/main/res/raw/variation.svg
similarity index 100%
rename from DroidFish/res/raw/variation.svg
rename to DroidFishApp/src/main/res/raw/variation.svg
diff --git a/DroidFish/res/values-be/strings.xml b/DroidFishApp/src/main/res/values-be/strings.xml
similarity index 100%
rename from DroidFish/res/values-be/strings.xml
rename to DroidFishApp/src/main/res/values-be/strings.xml
diff --git a/DroidFish/res/values-de/strings.xml b/DroidFishApp/src/main/res/values-de/strings.xml
similarity index 100%
rename from DroidFish/res/values-de/strings.xml
rename to DroidFishApp/src/main/res/values-de/strings.xml
diff --git a/DroidFish/res/values-es/strings.xml b/DroidFishApp/src/main/res/values-es/strings.xml
similarity index 100%
rename from DroidFish/res/values-es/strings.xml
rename to DroidFishApp/src/main/res/values-es/strings.xml
diff --git a/DroidFish/res/values-fr/strings.xml b/DroidFishApp/src/main/res/values-fr/strings.xml
similarity index 100%
rename from DroidFish/res/values-fr/strings.xml
rename to DroidFishApp/src/main/res/values-fr/strings.xml
diff --git a/DroidFish/res/values-it/strings.xml b/DroidFishApp/src/main/res/values-it/strings.xml
similarity index 100%
rename from DroidFish/res/values-it/strings.xml
rename to DroidFishApp/src/main/res/values-it/strings.xml
diff --git a/DroidFish/res/values-ko/strings.xml b/DroidFishApp/src/main/res/values-ko/strings.xml
similarity index 100%
rename from DroidFish/res/values-ko/strings.xml
rename to DroidFishApp/src/main/res/values-ko/strings.xml
diff --git a/DroidFish/res/values-nl/strings.xml b/DroidFishApp/src/main/res/values-nl/strings.xml
similarity index 100%
rename from DroidFish/res/values-nl/strings.xml
rename to DroidFishApp/src/main/res/values-nl/strings.xml
diff --git a/DroidFish/res/values-pl/strings.xml b/DroidFishApp/src/main/res/values-pl/strings.xml
similarity index 100%
rename from DroidFish/res/values-pl/strings.xml
rename to DroidFishApp/src/main/res/values-pl/strings.xml
diff --git a/DroidFish/res/values-pt/strings.xml b/DroidFishApp/src/main/res/values-pt/strings.xml
similarity index 100%
rename from DroidFish/res/values-pt/strings.xml
rename to DroidFishApp/src/main/res/values-pt/strings.xml
diff --git a/DroidFish/res/values-ru/strings.xml b/DroidFishApp/src/main/res/values-ru/strings.xml
similarity index 100%
rename from DroidFish/res/values-ru/strings.xml
rename to DroidFishApp/src/main/res/values-ru/strings.xml
diff --git a/DroidFish/res/values-tr/strings.xml b/DroidFishApp/src/main/res/values-tr/strings.xml
similarity index 100%
rename from DroidFish/res/values-tr/strings.xml
rename to DroidFishApp/src/main/res/values-tr/strings.xml
diff --git a/DroidFish/res/values-uk/strings.xml b/DroidFishApp/src/main/res/values-uk/strings.xml
similarity index 100%
rename from DroidFish/res/values-uk/strings.xml
rename to DroidFishApp/src/main/res/values-uk/strings.xml
diff --git a/DroidFish/res/values-zh-rCN/strings.xml b/DroidFishApp/src/main/res/values-zh-rCN/strings.xml
similarity index 100%
rename from DroidFish/res/values-zh-rCN/strings.xml
rename to DroidFishApp/src/main/res/values-zh-rCN/strings.xml
diff --git a/DroidFish/res/values/attrs.xml b/DroidFishApp/src/main/res/values/attrs.xml
similarity index 100%
rename from DroidFish/res/values/attrs.xml
rename to DroidFishApp/src/main/res/values/attrs.xml
diff --git a/DroidFish/res/values/colors.xml b/DroidFishApp/src/main/res/values/color.xml
similarity index 100%
rename from DroidFish/res/values/colors.xml
rename to DroidFishApp/src/main/res/values/color.xml
diff --git a/DroidFish/res/values/dimens.xml b/DroidFishApp/src/main/res/values/dimen.xml
similarity index 100%
rename from DroidFish/res/values/dimens.xml
rename to DroidFishApp/src/main/res/values/dimen.xml
diff --git a/DroidFish/res/values/ids.xml b/DroidFishApp/src/main/res/values/ids.xml
similarity index 100%
rename from DroidFish/res/values/ids.xml
rename to DroidFishApp/src/main/res/values/ids.xml
diff --git a/DroidFish/res/values/strings.xml b/DroidFishApp/src/main/res/values/strings.xml
similarity index 100%
rename from DroidFish/res/values/strings.xml
rename to DroidFishApp/src/main/res/values/strings.xml
diff --git a/DroidFish/res/xml/filepaths.xml b/DroidFishApp/src/main/res/xml/filepaths.xml
similarity index 100%
rename from DroidFish/res/xml/filepaths.xml
rename to DroidFishApp/src/main/res/xml/filepaths.xml
diff --git a/DroidFish/res/xml/preferences.xml b/DroidFishApp/src/main/res/xml/preferences.xml
similarity index 100%
rename from DroidFish/res/xml/preferences.xml
rename to DroidFishApp/src/main/res/xml/preferences.xml
diff --git a/DroidFishTest/.classpath b/DroidFishTest/.classpath
deleted file mode 100644
index 5efe855..0000000
--- a/DroidFishTest/.classpath
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/DroidFishTest/.gitignore b/DroidFishTest/.gitignore
deleted file mode 100644
index a22392a..0000000
--- a/DroidFishTest/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/bin
-/gen
diff --git a/DroidFishTest/.project b/DroidFishTest/.project
deleted file mode 100644
index cedb0bf..0000000
--- a/DroidFishTest/.project
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
- DroidFishTest
-
-
- DroidFish
-
-
-
- org.eclipse.andmore.ResourceManagerBuilder
-
-
-
-
- org.eclipse.andmore.PreCompilerBuilder
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- org.eclipse.andmore.ApkBuilder
-
-
-
-
-
- org.eclipse.andmore.AndroidNature
- org.eclipse.jdt.core.javanature
-
-
diff --git a/DroidFishTest/.settings/org.eclipse.jdt.core.prefs b/DroidFishTest/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index ef8a789..0000000
--- a/DroidFishTest/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,12 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-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/DroidFishTest/AndroidManifest.xml b/DroidFishTest/AndroidManifest.xml
deleted file mode 100644
index d2ceeed..0000000
--- a/DroidFishTest/AndroidManifest.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/DroidFishTest/proguard.cfg b/DroidFishTest/proguard.cfg
deleted file mode 100644
index b1cdf17..0000000
--- a/DroidFishTest/proguard.cfg
+++ /dev/null
@@ -1,40 +0,0 @@
--optimizationpasses 5
--dontusemixedcaseclassnames
--dontskipnonpubliclibraryclasses
--dontpreverify
--verbose
--optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-
--keep public class * extends android.app.Activity
--keep public class * extends android.app.Application
--keep public class * extends android.app.Service
--keep public class * extends android.content.BroadcastReceiver
--keep public class * extends android.content.ContentProvider
--keep public class * extends android.app.backup.BackupAgentHelper
--keep public class * extends android.preference.Preference
--keep public class com.android.vending.licensing.ILicensingService
-
--keepclasseswithmembernames class * {
- native ;
-}
-
--keepclasseswithmembers class * {
- public (android.content.Context, android.util.AttributeSet);
-}
-
--keepclasseswithmembers class * {
- public (android.content.Context, android.util.AttributeSet, int);
-}
-
--keepclassmembers class * extends android.app.Activity {
- public void *(android.view.View);
-}
-
--keepclassmembers enum * {
- public static **[] values();
- public static ** valueOf(java.lang.String);
-}
-
--keep class * implements android.os.Parcelable {
- public static final android.os.Parcelable$Creator *;
-}
diff --git a/DroidFishTest/project.properties b/DroidFishTest/project.properties
deleted file mode 100644
index 29e368b..0000000
--- a/DroidFishTest/project.properties
+++ /dev/null
@@ -1,11 +0,0 @@
-# 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.
-
-# Project target.
-target=android-28
diff --git a/DroidFishTest/res/drawable-hdpi/ic_launcher.png b/DroidFishTest/res/drawable-hdpi/ic_launcher.png
deleted file mode 100644
index 8074c4c..0000000
Binary files a/DroidFishTest/res/drawable-hdpi/ic_launcher.png and /dev/null differ
diff --git a/DroidFishTest/res/drawable-ldpi/ic_launcher.png b/DroidFishTest/res/drawable-ldpi/ic_launcher.png
deleted file mode 100644
index 1095584..0000000
Binary files a/DroidFishTest/res/drawable-ldpi/ic_launcher.png and /dev/null differ
diff --git a/DroidFishTest/res/drawable-mdpi/ic_launcher.png b/DroidFishTest/res/drawable-mdpi/ic_launcher.png
deleted file mode 100644
index a07c69f..0000000
Binary files a/DroidFishTest/res/drawable-mdpi/ic_launcher.png and /dev/null differ
diff --git a/DroidFishTest/res/layout/main.xml b/DroidFishTest/res/layout/main.xml
deleted file mode 100644
index bc12cd8..0000000
--- a/DroidFishTest/res/layout/main.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/DroidFishTest/res/values/strings.xml b/DroidFishTest/res/values/strings.xml
deleted file mode 100644
index 3a99398..0000000
--- a/DroidFishTest/res/values/strings.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- Hello World!
- DroidFishTestTest
-
-
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..e11a5b3
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,27 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ google()
+ jcenter()
+
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.3.2'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/buildSrc/.gitignore b/buildSrc/.gitignore
new file mode 100644
index 0000000..378eac2
--- /dev/null
+++ b/buildSrc/.gitignore
@@ -0,0 +1 @@
+build
diff --git a/buildSrc/src/main/java/chess/BitBoard.java b/buildSrc/src/main/java/chess/BitBoard.java
new file mode 100644
index 0000000..cc2d6b8
--- /dev/null
+++ b/buildSrc/src/main/java/chess/BitBoard.java
@@ -0,0 +1,382 @@
+/*
+ CuckooChess - A java 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 chess;
+
+public class BitBoard {
+
+ /** Squares attacked by a king on a given square. */
+ public static final long[] kingAttacks;
+ public static final long[] knightAttacks;
+ public static final long[] wPawnAttacks, bPawnAttacks;
+
+ // Squares preventing a pawn from being a passed pawn, if occupied by enemy pawn
+ static final long[] wPawnBlockerMask, bPawnBlockerMask;
+
+ public static final long maskAToGFiles = 0x7F7F7F7F7F7F7F7FL;
+ public static final long maskBToHFiles = 0xFEFEFEFEFEFEFEFEL;
+ public static final long maskAToFFiles = 0x3F3F3F3F3F3F3F3FL;
+ public static final long maskCToHFiles = 0xFCFCFCFCFCFCFCFCL;
+
+ public static final long[] maskFile = {
+ 0x0101010101010101L,
+ 0x0202020202020202L,
+ 0x0404040404040404L,
+ 0x0808080808080808L,
+ 0x1010101010101010L,
+ 0x2020202020202020L,
+ 0x4040404040404040L,
+ 0x8080808080808080L
+ };
+
+ public static final long maskRow1 = 0x00000000000000FFL;
+ public static final long maskRow2 = 0x000000000000FF00L;
+ public static final long maskRow3 = 0x0000000000FF0000L;
+ public static final long maskRow4 = 0x00000000FF000000L;
+ public static final long maskRow5 = 0x000000FF00000000L;
+ public static final long maskRow6 = 0x0000FF0000000000L;
+ public static final long maskRow7 = 0x00FF000000000000L;
+ public static final long maskRow8 = 0xFF00000000000000L;
+ public static final long maskRow1Row8 = 0xFF000000000000FFL;
+
+ public static final long maskDarkSq = 0xAA55AA55AA55AA55L;
+ public static final long maskLightSq = 0x55AA55AA55AA55AAL;
+
+ public static final long maskCorners = 0x8100000000000081L;
+
+ static {
+ // Compute king attacks
+ kingAttacks = new long[64];
+
+ for (int sq = 0; sq < 64; sq++) {
+ long m = 1L << sq;
+ long mask = (((m >>> 1) | (m << 7) | (m >>> 9)) & maskAToGFiles) |
+ (((m << 1) | (m << 9) | (m >>> 7)) & maskBToHFiles) |
+ (m << 8) | (m >>> 8);
+ kingAttacks[sq] = mask;
+ }
+
+ // Compute knight attacks
+ knightAttacks = new long[64];
+ for (int sq = 0; sq < 64; sq++) {
+ long m = 1L << sq;
+ long mask = (((m << 6) | (m >>> 10)) & maskAToFFiles) |
+ (((m << 15) | (m >>> 17)) & maskAToGFiles) |
+ (((m << 17) | (m >>> 15)) & maskBToHFiles) |
+ (((m << 10) | (m >>> 6)) & maskCToHFiles);
+ knightAttacks[sq] = mask;
+ }
+
+ // Compute pawn attacks
+ wPawnAttacks = new long[64];
+ bPawnAttacks = new long[64];
+ wPawnBlockerMask = new long[64];
+ bPawnBlockerMask = new long[64];
+ for (int sq = 0; sq < 64; sq++) {
+ long m = 1L << sq;
+ long mask = ((m << 7) & maskAToGFiles) | ((m << 9) & maskBToHFiles);
+ wPawnAttacks[sq] = mask;
+ mask = ((m >>> 9) & maskAToGFiles) | ((m >>> 7) & maskBToHFiles);
+ bPawnAttacks[sq] = mask;
+
+ int x = Position.getX(sq);
+ int y = Position.getY(sq);
+ m = 0;
+ for (int y2 = y+1; y2 < 8; y2++) {
+ if (x > 0) m |= 1L << Position.getSquare(x-1, y2);
+ m |= 1L << Position.getSquare(x , y2);
+ if (x < 7) m |= 1L << Position.getSquare(x+1, y2);
+ }
+ wPawnBlockerMask[sq] = m;
+ m = 0;
+ for (int y2 = y-1; y2 >= 0; y2--) {
+ if (x > 0) m |= 1L << Position.getSquare(x-1, y2);
+ m |= 1L << Position.getSquare(x , y2);
+ if (x < 7) m |= 1L << Position.getSquare(x+1, y2);
+ }
+ bPawnBlockerMask[sq] = m;
+ }
+ }
+
+ private final static long[][] rTables;
+ private final static long[] rMasks;
+ private final static int[] rBits = { 12, 11, 11, 11, 11, 11, 11, 12,
+ 11, 10, 10, 11, 10, 10, 10, 11,
+ 11, 10, 10, 10, 10, 10, 10, 11,
+ 11, 10, 10, 10, 10, 10, 10, 11,
+ 11, 10, 10, 10, 10, 10, 10, 11,
+ 11, 10, 10, 11, 10, 10, 10, 11,
+ 10, 9, 9, 9, 9, 9, 10, 10,
+ 11, 10, 10, 10, 10, 11, 10, 11 };
+ private final static long[] rMagics = {
+ 0x19a80065ff2bffffL, 0x3fd80075ffebffffL, 0x4010000df6f6fffeL, 0x0050001faffaffffL,
+ 0x0050028004ffffb0L, 0x7f600280089ffff1L, 0x7f5000b0029ffffcL, 0x5b58004848a7fffaL,
+ 0x002a90005547ffffL, 0x000050007f13ffffL, 0x007fa0006013ffffL, 0x006a9005656fffffL,
+ 0x007f600f600affffL, 0x007ec007e6bfffe2L, 0x007ec003eebffffbL, 0x0071d002382fffdaL,
+ 0x009f803000e7fffaL, 0x00680030008bffffL, 0x00606060004f3ffcL, 0x001a00600bff9ffdL,
+ 0x000d006005ff9fffL, 0x0001806003005fffL, 0x00000300040bfffaL, 0x000192500065ffeaL,
+ 0x00fff112d0006800L, 0x007ff037d000c004L, 0x003fd062001a3ff8L, 0x00087000600e1ffcL,
+ 0x000fff0100100804L, 0x0007ff0100080402L, 0x0003ffe0c0060003L, 0x0001ffd53000d300L,
+ 0x00fffd3000600061L, 0x007fff7f95900040L, 0x003fff8c00600060L, 0x001ffe2587a01860L,
+ 0x000fff3fbf40180cL, 0x0007ffc73f400c06L, 0x0003ff86d2c01405L, 0x0001fffeaa700100L,
+ 0x00fffdfdd8005000L, 0x007fff80ebffb000L, 0x003fffdf603f6000L, 0x001fffe050405000L,
+ 0x000fff400700c00cL, 0x0007ff6007bf600aL, 0x0003ffeebffec005L, 0x0001fffdf3feb001L,
+ 0x00ffff39ff484a00L, 0x007fff3fff486300L, 0x003fff99ffac2e00L, 0x001fff31ff2a6a00L,
+ 0x000fff19ff15b600L, 0x0007fff5fff28600L, 0x0003fffddffbfee0L, 0x0001fff5f63c96a0L,
+ 0x00ffff5dff65cfb6L, 0x007fffbaffd1c5aeL, 0x003fff71ff6cbceaL, 0x001fffd9ffd4756eL,
+ 0x000ffff5fff338e6L, 0x0007fffdfffe24f6L, 0x0003ffef27eebe74L, 0x0001ffff23ff605eL
+ };
+ private final static long[][] bTables;
+ private final static long[] bMasks;
+ private final static int[] bBits = { 5, 4, 5, 5, 5, 5, 4, 5,
+ 4, 4, 5, 5, 5, 5, 4, 4,
+ 4, 4, 7, 7, 7, 7, 4, 4,
+ 5, 5, 7, 9, 9, 7, 5, 5,
+ 5, 5, 7, 9, 9, 7, 5, 5,
+ 4, 4, 7, 7, 7, 7, 4, 4,
+ 4, 4, 5, 5, 5, 5, 4, 4,
+ 5, 4, 5, 5, 5, 5, 4, 5 };
+ private final static long[] bMagics = {
+ 0x0006eff5367ff600L, 0x00345835ba77ff2bL, 0x00145f68a3f5dab6L, 0x003a1863fb56f21dL,
+ 0x0012eb6bfe9d93cdL, 0x000d82827f3420d6L, 0x00074bcd9c7fec97L, 0x000034fe99f9ffffL,
+ 0x0000746f8d6717f6L, 0x00003acb32e1a3f7L, 0x0000185daf1ffb8aL, 0x00003a1867f17067L,
+ 0x0000038ee0ccf92eL, 0x000002a2b7ff926eL, 0x000006c9aa93ff14L, 0x00000399b5e5bf87L,
+ 0x00400f342c951ffcL, 0x0020230579ed8ff0L, 0x007b008a0077dbfdL, 0x001d00010c13fd46L,
+ 0x00040022031c1ffbL, 0x000fa00fd1cbff79L, 0x000400a4bc9affdfL, 0x000200085e9cffdaL,
+ 0x002a14560a3dbfbdL, 0x000a0a157b9eafd1L, 0x00060600fd002ffaL, 0x004006000c009010L,
+ 0x001a002042008040L, 0x001a00600fd1ffc0L, 0x000d0ace50bf3f8dL, 0x000183a48434efd1L,
+ 0x001fbd7670982a0dL, 0x000fe24301d81a0fL, 0x0007fbf82f040041L, 0x000040c800008200L,
+ 0x007fe17018086006L, 0x003b7ddf0ffe1effL, 0x001f92f861df4a0aL, 0x000fd713ad98a289L,
+ 0x000fd6aa751e400cL, 0x0007f2a63ae9600cL, 0x0003ff7dfe0e3f00L, 0x000003fd2704ce04L,
+ 0x00007fc421601d40L, 0x007fff5f70900120L, 0x003fa66283556403L, 0x001fe31969aec201L,
+ 0x0007fdfc18ac14bbL, 0x0003fb96fb568a47L, 0x000003f72ea4954dL, 0x00000003f8dc0383L,
+ 0x0000007f3a814490L, 0x00007dc5c9cf62a6L, 0x007f23d3342897acL, 0x003fee36eee1565cL,
+ 0x0003ff3e99fcccc7L, 0x000003ecfcfac5feL, 0x00000003f97f7453L, 0x0000000003f8dc03L,
+ 0x000000007efa8146L, 0x0000007ed3e2ef60L, 0x00007f47243adcd6L, 0x007fb65afabfb3b5L
+ };
+
+ private static final long createPattern(int i, long mask) {
+ long ret = 0L;
+ for (int j = 0; ; j++) {
+ long nextMask = mask & (mask - 1);
+ long bit = mask ^ nextMask;
+ if ((i & (1L << j)) != 0)
+ ret |= bit;
+ mask = nextMask;
+ if (mask == 0)
+ break;
+ }
+ return ret;
+ }
+
+ private static final long addRookRays(int x, int y, long occupied, boolean inner) {
+ long mask = 0;
+ mask = addRay(mask, x, y, 1, 0, occupied, inner);
+ mask = addRay(mask, x, y, -1, 0, occupied, inner);
+ mask = addRay(mask, x, y, 0, 1, occupied, inner);
+ mask = addRay(mask, x, y, 0, -1, occupied, inner);
+ return mask;
+ }
+ private static final long addBishopRays(int x, int y, long occupied, boolean inner) {
+ long mask = 0;
+ mask = addRay(mask, x, y, 1, 1, occupied, inner);
+ mask = addRay(mask, x, y, -1, -1, occupied, inner);
+ mask = addRay(mask, x, y, 1, -1, occupied, inner);
+ mask = addRay(mask, x, y, -1, 1, occupied, inner);
+ return mask;
+ }
+
+ private static final long addRay(long mask, int x, int y, int dx, int dy,
+ long occupied, boolean inner) {
+ int lo = inner ? 1 : 0;
+ int hi = inner ? 6 : 7;
+ while (true) {
+ if (dx != 0) {
+ x += dx; if ((x < lo) || (x > hi)) break;
+ }
+ if (dy != 0) {
+ y += dy; if ((y < lo) || (y > hi)) break;
+ }
+ int sq = Position.getSquare(x, y);
+ mask |= 1L << sq;
+ if ((occupied & (1L << sq)) != 0)
+ break;
+ }
+ return mask;
+ }
+
+ static { // Rook magics
+ rTables = new long[64][];
+ rMasks = new long[64];
+ for (int sq = 0; sq < 64; sq++) {
+ int x = Position.getX(sq);
+ int y = Position.getY(sq);
+ rMasks[sq] = addRookRays(x, y, 0L, true);
+ int tableSize = 1 << rBits[sq];
+ long[] table = new long[tableSize];
+ for (int i = 0; i < tableSize; i++) table[i] = -1;
+ int nPatterns = 1 << Long.bitCount(rMasks[sq]);
+ for (int i = 0; i < nPatterns; i++) {
+ long p = createPattern(i, rMasks[sq]);
+ int entry = (int)((p * rMagics[sq]) >>> (64 - rBits[sq]));
+ long atks = addRookRays(x, y, p, false);
+ if (table[entry] == -1) {
+ table[entry] = atks;
+ } else if (table[entry] != atks) {
+ throw new RuntimeException();
+ }
+ }
+ rTables[sq] = table;
+ }
+ }
+
+ static { // Bishop magics
+ bTables = new long[64][];
+ bMasks = new long[64];
+ for (int sq = 0; sq < 64; sq++) {
+ int x = Position.getX(sq);
+ int y = Position.getY(sq);
+ bMasks[sq] = addBishopRays(x, y, 0L, true);
+ int tableSize = 1 << bBits[sq];
+ long[] table = new long[tableSize];
+ for (int i = 0; i < tableSize; i++) table[i] = -1;
+ int nPatterns = 1 << Long.bitCount(bMasks[sq]);
+ for (int i = 0; i < nPatterns; i++) {
+ long p = createPattern(i, bMasks[sq]);
+ int entry = (int)((p * bMagics[sq]) >>> (64 - bBits[sq]));
+ long atks = addBishopRays(x, y, p, false);
+ if (table[entry] == -1) {
+ table[entry] = atks;
+ } else if (table[entry] != atks) {
+ throw new RuntimeException();
+ }
+ }
+ bTables[sq] = table;
+ }
+ }
+
+ public static final long bishopAttacks(int sq, long occupied) {
+ return bTables[sq][(int)(((occupied & bMasks[sq]) * bMagics[sq]) >>> (64 - bBits[sq]))];
+ }
+
+ public static final long rookAttacks(int sq, long occupied) {
+ return rTables[sq][(int)(((occupied & rMasks[sq]) * rMagics[sq]) >>> (64 - rBits[sq]))];
+ }
+
+ static public final long[][] squaresBetween;
+ static {
+ squaresBetween = new long[64][];
+ for (int sq1 = 0; sq1 < 64; sq1++) {
+ squaresBetween[sq1] = new long[64];
+ for (int j = 0; j < 64; j++)
+ squaresBetween[sq1][j] = 0;
+ for (int dx = -1; dx <= 1; dx++) {
+ for (int dy = -1; dy <= 1; dy++) {
+ if ((dx == 0) && (dy == 0))
+ continue;
+ long m = 0;
+ int x = Position.getX(sq1);
+ int y = Position.getY(sq1);
+ while (true) {
+ x += dx; y += dy;
+ if ((x < 0) || (x > 7) || (y < 0) || (y > 7))
+ break;
+ int sq2 = Position.getSquare(x, y);
+ squaresBetween[sq1][sq2] = m;
+ m |= 1L << sq2;
+ }
+ }
+ }
+ }
+ }
+
+ private static final byte dirTable[] = {
+ -9, 0, 0, 0, 0, 0, 0, -8, 0, 0, 0, 0, 0, 0, -7,
+ 0, 0, -9, 0, 0, 0, 0, 0, -8, 0, 0, 0, 0, 0, -7, 0,
+ 0, 0, 0, -9, 0, 0, 0, 0, -8, 0, 0, 0, 0, -7, 0, 0,
+ 0, 0, 0, 0, -9, 0, 0, 0, -8, 0, 0, 0, -7, 0, 0, 0,
+ 0, 0, 0, 0, 0, -9, 0, 0, -8, 0, 0, -7, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, -9,-17, -8,-15, -7, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,-10, -9, -8, -7, -6, 0, 0, 0, 0, 0,
+ 0, -1, -1, -1, -1, -1, -1, -1, 0, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 6, 7, 8, 9, 10, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 7, 15, 8, 17, 9, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 7, 0, 0, 8, 0, 0, 9, 0, 0, 0, 0,
+ 0, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 9, 0, 0, 0,
+ 0, 0, 0, 7, 0, 0, 0, 0, 8, 0, 0, 0, 0, 9, 0, 0,
+ 0, 0, 7, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 9, 0,
+ 0, 7, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 9
+ };
+
+ static public final int getDirection(int from, int to) {
+ int offs = to + (to|7) - from - (from|7) + 0x77;
+ return dirTable[offs];
+ }
+
+ private static final byte distTable[] = {
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 0, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7,
+ 0, 7, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 7,
+ 0, 7, 6, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 6, 7,
+ 0, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7,
+ 0, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7,
+ 0, 7, 6, 5, 4, 3, 2, 1, 1, 1, 2, 3, 4, 5, 6, 7,
+ 0, 7, 6, 5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5, 6, 7,
+ 0, 7, 6, 5, 4, 3, 2, 1, 1, 1, 2, 3, 4, 5, 6, 7,
+ 0, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7,
+ 0, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7,
+ 0, 7, 6, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 6, 7,
+ 0, 7, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 7,
+ 0, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7,
+ 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
+ };
+
+ public static final int getDistance(int from, int to) {
+ int offs = to + (to|7) - from - (from|7) + 0x77;
+ return distTable[offs];
+ }
+
+ public static final long southFill(long mask) {
+ mask |= (mask >>> 8);
+ mask |= (mask >>> 16);
+ mask |= (mask >>> 32);
+ return mask;
+ }
+
+ public static final long northFill(long mask) {
+ mask |= (mask << 8);
+ mask |= (mask << 16);
+ mask |= (mask << 32);
+ return mask;
+ }
+
+ private static final int trailingZ[] = {
+ 63, 0, 58, 1, 59, 47, 53, 2,
+ 60, 39, 48, 27, 54, 33, 42, 3,
+ 61, 51, 37, 40, 49, 18, 28, 20,
+ 55, 30, 34, 11, 43, 14, 22, 4,
+ 62, 57, 46, 52, 38, 26, 32, 41,
+ 50, 36, 17, 19, 29, 10, 13, 21,
+ 56, 45, 25, 31, 35, 16, 9, 12,
+ 44, 24, 15, 8, 23, 7, 6, 5
+ };
+
+ static public final int numberOfTrailingZeros(long mask) {
+ return trailingZ[(int)(((mask & -mask) * 0x07EDD5E59A4E28C2L) >>> 58)];
+ }
+}
diff --git a/CuckooChessEngine/src/chess/Book.java b/buildSrc/src/main/java/chess/Book.java
similarity index 98%
rename from CuckooChessEngine/src/chess/Book.java
rename to buildSrc/src/main/java/chess/Book.java
index c67f8b7..399aaa7 100644
--- a/CuckooChessEngine/src/chess/Book.java
+++ b/buildSrc/src/main/java/chess/Book.java
@@ -188,6 +188,9 @@ public class Book {
public static void main(String[] args) throws IOException {
String inFile = args[0];
String outFile = args[1];
+ main2(inFile, outFile);
+ }
+ public static void main2(String inFile, String outFile) throws IOException {
List binBook = createBinBook(inFile);
FileOutputStream out = new FileOutputStream(outFile);
int bookLen = binBook.size();
diff --git a/buildSrc/src/main/java/chess/ChessParseError.java b/buildSrc/src/main/java/chess/ChessParseError.java
new file mode 100644
index 0000000..3172681
--- /dev/null
+++ b/buildSrc/src/main/java/chess/ChessParseError.java
@@ -0,0 +1,29 @@
+/*
+ CuckooChess - A java 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 chess;
+
+/** Exception class to represent parse errors in FEN or algebraic notation. */
+public class ChessParseError extends Exception {
+ private static final long serialVersionUID = -6051856171275301175L;
+ public ChessParseError() {
+ }
+ public ChessParseError(String msg) {
+ super(msg);
+ }
+}
diff --git a/buildSrc/src/main/java/chess/Evaluate.java b/buildSrc/src/main/java/chess/Evaluate.java
new file mode 100644
index 0000000..e61e9d7
--- /dev/null
+++ b/buildSrc/src/main/java/chess/Evaluate.java
@@ -0,0 +1,1228 @@
+/*
+ CuckooChess - A java 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 chess;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/** Position evaluation routines. */
+public class Evaluate {
+ static final int pV = 92 + Parameters.instance().getIntPar("pV");
+ static final int nV = 385 + Parameters.instance().getIntPar("nV");
+ static final int bV = 385 + Parameters.instance().getIntPar("bV");
+ static final int rV = 593 + Parameters.instance().getIntPar("rV");
+ static final int qV = 1244 + Parameters.instance().getIntPar("qV");
+ static final int kV = 9900; // Used by SEE algorithm, but not included in board material sums
+
+ static final int[] pieceValue;
+ static {
+ // Initialize material table
+ pieceValue = new int[Piece.nPieceTypes];
+ pieceValue[Piece.WKING ] = kV;
+ pieceValue[Piece.WQUEEN ] = qV;
+ pieceValue[Piece.WROOK ] = rV;
+ pieceValue[Piece.WBISHOP] = bV;
+ pieceValue[Piece.WKNIGHT] = nV;
+ pieceValue[Piece.WPAWN ] = pV;
+ pieceValue[Piece.BKING ] = kV;
+ pieceValue[Piece.BQUEEN ] = qV;
+ pieceValue[Piece.BROOK ] = rV;
+ pieceValue[Piece.BBISHOP] = bV;
+ pieceValue[Piece.BKNIGHT] = nV;
+ pieceValue[Piece.BPAWN ] = pV;
+ pieceValue[Piece.EMPTY ] = 0;
+ }
+
+ /** Piece/square table for king during middle game. */
+ static final int[] kt1b = { -22,-35,-40,-40,-40,-40,-35,-22,
+ -22,-35,-40,-40,-40,-40,-35,-22,
+ -25,-35,-40,-45,-45,-40,-35,-25,
+ -15,-30,-35,-40,-40,-35,-30,-15,
+ -10,-15,-20,-25,-25,-20,-15,-10,
+ 4, -2, -5,-15,-15, -5, -2, 4,
+ 16, 14, 7, -3, -3, 7, 14, 16,
+ 24, 24, 9, 0, 0, 9, 24, 24 };
+
+ /** Piece/square table for king during end game. */
+ static final int[] kt2b = { 0, 8, 16, 24, 24, 16, 8, 0,
+ 8, 16, 24, 32, 32, 24, 16, 8,
+ 16, 24, 32, 40, 40, 32, 24, 16,
+ 24, 32, 40, 48, 48, 40, 32, 24,
+ 24, 32, 40, 48, 48, 40, 32, 24,
+ 16, 24, 32, 40, 40, 32, 24, 16,
+ 8, 16, 24, 32, 32, 24, 16, 8,
+ 0, 8, 16, 24, 24, 16, 8, 0 };
+
+ /** Piece/square table for pawns during middle game. */
+ static final int[] pt1b = { 0, 0, 0, 0, 0, 0, 0, 0,
+ 8, 16, 24, 32, 32, 24, 16, 8,
+ 3, 12, 20, 28, 28, 20, 12, 3,
+ -5, 4, 10, 20, 20, 10, 4, -5,
+ -6, 4, 5, 16, 16, 5, 4, -6,
+ -6, 4, 2, 5, 5, 2, 4, -6,
+ -6, 4, 4,-15,-15, 4, 4, -6,
+ 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ /** Piece/square table for pawns during end game. */
+ static final int[] pt2b = { 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 40, 45, 45, 45, 45, 40, 25,
+ 17, 32, 35, 35, 35, 35, 32, 17,
+ 5, 24, 24, 24, 24, 24, 24, 5,
+ -9, 11, 11, 11, 11, 11, 11, -9,
+ -17, 3, 3, 3, 3, 3, 3,-17,
+ -20, 0, 0, 0, 0, 0, 0,-20,
+ 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ /** Piece/square table for knights during middle game. */
+ static final int[] nt1b = { -53,-42,-32,-21,-21,-32,-42,-53,
+ -42,-32,-10, 0, 0,-10,-32,-42,
+ -21, 5, 10, 16, 16, 10, 5,-21,
+ -18, 0, 10, 21, 21, 10, 0,-18,
+ -18, 0, 3, 21, 21, 3, 0,-18,
+ -21,-10, 0, 0, 0, 0,-10,-21,
+ -42,-32,-10, 0, 0,-10,-32,-42,
+ -53,-42,-32,-21,-21,-32,-42,-53 };
+
+ /** Piece/square table for knights during end game. */
+ static final int[] nt2b = { -56,-44,-34,-22,-22,-34,-44,-56,
+ -44,-34,-10, 0, 0,-10,-34,-44,
+ -22, 5, 10, 17, 17, 10, 5,-22,
+ -19, 0, 10, 22, 22, 10, 0,-19,
+ -19, 0, 3, 22, 22, 3, 0,-19,
+ -22,-10, 0, 0, 0, 0,-10,-22,
+ -44,-34,-10, 0, 0,-10,-34,-44,
+ -56,-44,-34,-22,-22,-34,-44,-56 };
+
+ /** Piece/square table for bishops during middle game. */
+ static final int[] bt1b = { 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 4, 2, 2, 2, 2, 4, 0,
+ 0, 2, 4, 4, 4, 4, 2, 0,
+ 0, 2, 4, 4, 4, 4, 2, 0,
+ 0, 2, 4, 4, 4, 4, 2, 0,
+ 0, 3, 4, 4, 4, 4, 3, 0,
+ 0, 4, 2, 2, 2, 2, 4, 0,
+ -5, -5, -7, -5, -5, -7, -5, -5 };
+
+ /** Piece/square table for bishops during middle game. */
+ static final int[] bt2b = { 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 2, 2, 2, 2, 2, 0,
+ 0, 2, 4, 4, 4, 4, 2, 0,
+ 0, 2, 4, 4, 4, 4, 2, 0,
+ 0, 2, 4, 4, 4, 4, 2, 0,
+ 0, 2, 4, 4, 4, 4, 2, 0,
+ 0, 2, 2, 2, 2, 2, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ /** Piece/square table for queens during middle game. */
+ static final int[] qt1b = { -10, -5, 0, 0, 0, 0, -5,-10,
+ -5, 0, 5, 5, 5, 5, 0, -5,
+ 0, 5, 5, 6, 6, 5, 5, 0,
+ 0, 5, 6, 6, 6, 6, 5, 0,
+ 0, 5, 6, 6, 6, 6, 5, 0,
+ 0, 5, 5, 6, 6, 5, 5, 0,
+ -5, 0, 5, 5, 5, 5, 0, -5,
+ -10, -5, 0, 0, 0, 0, -5,-10 };
+
+ /** Piece/square table for rooks during middle game. */
+ static final int[] rt1b = { 8, 11, 13, 13, 13, 13, 11, 8,
+ 22, 27, 27, 27, 27, 27, 27, 22,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ -2, 0, 0, 0, 0, 0, 0, -2,
+ -2, 0, 0, 2, 2, 0, 0, -2,
+ -3, 2, 5, 5, 5, 5, 2, -3,
+ 0, 3, 5, 5, 5, 5, 3, 0 };
+
+ static final int[] kt1w, qt1w, rt1w, bt1w, nt1w, pt1w, kt2w, bt2w, nt2w, pt2w;
+ static {
+ kt1w = new int[64];
+ qt1w = new int[64];
+ rt1w = new int[64];
+ bt1w = new int[64];
+ nt1w = new int[64];
+ pt1w = new int[64];
+ kt2w = new int[64];
+ bt2w = new int[64];
+ nt2w = new int[64];
+ pt2w = new int[64];
+ for (int i = 0; i < 64; i++) {
+ kt1w[i] = kt1b[63-i];
+ qt1w[i] = qt1b[63-i];
+ rt1w[i] = rt1b[63-i];
+ bt1w[i] = bt1b[63-i];
+ nt1w[i] = nt1b[63-i];
+ pt1w[i] = pt1b[63-i];
+ kt2w[i] = kt2b[63-i];
+ bt2w[i] = bt2b[63-i];
+ nt2w[i] = nt2b[63-i];
+ pt2w[i] = pt2b[63-i];
+ }
+ }
+
+ private static final int[] empty = { 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0};
+ static final int[][] psTab1 = { empty, kt1w, qt1w, rt1w, bt1w, nt1w, pt1w,
+ kt1b, qt1b, rt1b, bt1b, nt1b, pt1b };
+ static final int[][] psTab2 = { empty, kt2w, qt1w, rt1w, bt2w, nt2w, pt2w,
+ kt2b, qt1b, rt1b, bt2b, nt2b, pt2b };
+
+ static final int[][] distToH1A8 = { { 0, 1, 2, 3, 4, 5, 6, 7 },
+ { 1, 2, 3, 4, 5, 6, 7, 6 },
+ { 2, 3, 4, 5, 6, 7, 6, 5 },
+ { 3, 4, 5, 6, 7, 6, 5, 4 },
+ { 4, 5, 6, 7, 6, 5, 4, 3 },
+ { 5, 6, 7, 6, 5, 4, 3, 2 },
+ { 6, 7, 6, 5, 4, 3, 2, 1 },
+ { 7, 6, 5, 4, 3, 2, 1, 0 } };
+
+ static final int[] rookMobScore = {-10,-7,-4,-1,2,5,7,9,11,12,13,14,14,14,14};
+ static final int[] bishMobScore = {-15,-10,-6,-2,2,6,10,13,16,18,20,22,23,24};
+ static final int[] queenMobScore = {-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,9,10,10,10,10,10,10,10,10,10,10,10,10};
+
+ private static final class PawnHashData {
+ long key;
+ int score; // Positive score means good for white
+ short passedBonusW;
+ short passedBonusB;
+ long passedPawnsW; // The most advanced passed pawns for each file
+ long passedPawnsB;
+ }
+ static final PawnHashData[] pawnHash;
+ static {
+ final int numEntries = 1<<16;
+ pawnHash = new PawnHashData[numEntries];
+ for (int i = 0; i < numEntries; i++) {
+ PawnHashData phd = new PawnHashData();
+ phd.key = -1; // Non-zero to avoid collision for positions with no pawns
+ phd.score = 0;
+ pawnHash[i] = phd;
+ }
+ }
+
+ static byte[] kpkTable = null;
+ static byte[] krkpTable = null;
+
+ // King safety variables
+ private long wKingZone, bKingZone; // Squares close to king that are worth attacking
+ private int wKingAttacks, bKingAttacks; // Number of attacks close to white/black king
+ private long wAttacksBB, bAttacksBB;
+ private long wPawnAttacks, bPawnAttacks; // Squares attacked by white/black pawns
+
+ /** Constructor. */
+ public Evaluate() {
+ if (kpkTable == null)
+ kpkTable = readTable("/kpk.bitbase", 2*32*64*48/8);
+ if (krkpTable == null)
+ krkpTable = readTable("/krkp.winmasks", 2*32*48*8);
+ }
+
+ private byte[] readTable(String resource, int length) {
+ byte[] table = new byte[2*32*64*48/8];
+ InputStream inStream = getClass().getResourceAsStream(resource);
+ try {
+ int off = 0;
+ while (off < table.length) {
+ int len = inStream.read(table, off, table.length - off);
+ if (len < 0)
+ throw new RuntimeException();
+ off += len;
+ }
+ inStream.close();
+ return table;
+ } catch (IOException e) {
+ throw new RuntimeException();
+ }
+ }
+
+ /**
+ * Static evaluation of a position.
+ * @param pos The position to evaluate.
+ * @return The evaluation score, measured in centipawns.
+ * Positive values are good for the side to make the next move.
+ */
+ final public int evalPos(Position pos) {
+ int score = pos.wMtrl - pos.bMtrl;
+
+ wKingAttacks = bKingAttacks = 0;
+ wKingZone = BitBoard.kingAttacks[pos.getKingSq(true)]; wKingZone |= wKingZone << 8;
+ bKingZone = BitBoard.kingAttacks[pos.getKingSq(false)]; bKingZone |= bKingZone >>> 8;
+ wAttacksBB = bAttacksBB = 0L;
+
+ long pawns = pos.pieceTypeBB[Piece.WPAWN];
+ wPawnAttacks = ((pawns & BitBoard.maskBToHFiles) << 7) |
+ ((pawns & BitBoard.maskAToGFiles) << 9);
+ pawns = pos.pieceTypeBB[Piece.BPAWN];
+ bPawnAttacks = ((pawns & BitBoard.maskBToHFiles) >>> 9) |
+ ((pawns & BitBoard.maskAToGFiles) >>> 7);
+
+ score += pieceSquareEval(pos);
+ score += pawnBonus(pos);
+ score += tradeBonus(pos);
+ score += castleBonus(pos);
+
+ score += rookBonus(pos);
+ score += bishopEval(pos, score);
+ score += threatBonus(pos);
+ score += kingSafety(pos);
+ score = endGameEval(pos, score);
+
+ if (!pos.whiteMove)
+ score = -score;
+ return score;
+ }
+
+ /** Compute white_material - black_material. */
+ static final int material(Position pos) {
+ return pos.wMtrl - pos.bMtrl;
+ }
+
+ /** Compute score based on piece square tables. Positive values are good for white. */
+ private final int pieceSquareEval(Position pos) {
+ int score = 0;
+ final int wMtrl = pos.wMtrl;
+ final int bMtrl = pos.bMtrl;
+ final int wMtrlPawns = pos.wMtrlPawns;
+ final int bMtrlPawns = pos.bMtrlPawns;
+
+ // Kings
+ {
+ final int t1 = qV + 2 * rV + 2 * bV;
+ final int t2 = rV;
+ {
+ final int k1 = pos.psScore1[Piece.WKING];
+ final int k2 = pos.psScore2[Piece.WKING];
+ final int t = bMtrl - bMtrlPawns;
+ score += interpolate(t, t2, k2, t1, k1);
+ }
+ {
+ final int k1 = pos.psScore1[Piece.BKING];
+ final int k2 = pos.psScore2[Piece.BKING];
+ final int t = wMtrl - wMtrlPawns;
+ score -= interpolate(t, t2, k2, t1, k1);
+ }
+ }
+
+ // Pawns
+ {
+ final int t1 = qV + 2 * rV + 2 * bV;
+ final int t2 = rV;
+ int wp1 = pos.psScore1[Piece.WPAWN];
+ int wp2 = pos.psScore2[Piece.WPAWN];
+ if ((wp1 != 0) || (wp2 != 0)) {
+ final int tw = bMtrl - bMtrlPawns;
+ score += interpolate(tw, t2, wp2, t1, wp1);
+ }
+ int bp1 = pos.psScore1[Piece.BPAWN];
+ int bp2 = pos.psScore2[Piece.BPAWN];
+ if ((bp1 != 0) || (bp2 != 0)) {
+ final int tb = wMtrl - wMtrlPawns;
+ score -= interpolate(tb, t2, bp2, t1, bp1);
+ }
+ }
+
+ // Knights
+ {
+ final int t1 = qV + 2 * rV + 1 * bV + 1 * nV + 6 * pV;
+ final int t2 = nV + 8 * pV;
+ int n1 = pos.psScore1[Piece.WKNIGHT];
+ int n2 = pos.psScore2[Piece.WKNIGHT];
+ if ((n1 != 0) || (n2 != 0)) {
+ score += interpolate(bMtrl, t2, n2, t1, n1);
+ }
+ n1 = pos.psScore1[Piece.BKNIGHT];
+ n2 = pos.psScore2[Piece.BKNIGHT];
+ if ((n1 != 0) || (n2 != 0)) {
+ score -= interpolate(wMtrl, t2, n2, t1, n1);
+ }
+ }
+
+ // Bishops
+ {
+ score += pos.psScore1[Piece.WBISHOP];
+ score -= pos.psScore1[Piece.BBISHOP];
+ }
+
+ // Queens
+ {
+ final long occupied = pos.whiteBB | pos.blackBB;
+ score += pos.psScore1[Piece.WQUEEN];
+ long m = pos.pieceTypeBB[Piece.WQUEEN];
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ long atk = BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied);
+ wAttacksBB |= atk;
+ score += queenMobScore[Long.bitCount(atk & ~(pos.whiteBB | bPawnAttacks))];
+ bKingAttacks += Long.bitCount(atk & bKingZone) * 2;
+ m &= m-1;
+ }
+ score -= pos.psScore1[Piece.BQUEEN];
+ m = pos.pieceTypeBB[Piece.BQUEEN];
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ long atk = BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied);
+ bAttacksBB |= atk;
+ score -= queenMobScore[Long.bitCount(atk & ~(pos.blackBB | wPawnAttacks))];
+ wKingAttacks += Long.bitCount(atk & wKingZone) * 2;
+ m &= m-1;
+ }
+ }
+
+ // Rooks
+ {
+ int r1 = pos.psScore1[Piece.WROOK];
+ if (r1 != 0) {
+ final int nP = bMtrlPawns / pV;
+ final int s = r1 * Math.min(nP, 6) / 6;
+ score += s;
+ }
+ r1 = pos.psScore1[Piece.BROOK];
+ if (r1 != 0) {
+ final int nP = wMtrlPawns / pV;
+ final int s = r1 * Math.min(nP, 6) / 6;
+ score -= s;
+ }
+ }
+
+ return score;
+ }
+
+ /** Implement the "when ahead trade pieces, when behind trade pawns" rule. */
+ private final int tradeBonus(Position pos) {
+ final int wM = pos.wMtrl;
+ final int bM = pos.bMtrl;
+ final int wPawn = pos.wMtrlPawns;
+ final int bPawn = pos.bMtrlPawns;
+ final int deltaScore = wM - bM;
+
+ int pBonus = 0;
+ pBonus += interpolate((deltaScore > 0) ? wPawn : bPawn, 0, -30 * deltaScore / 100, 6 * pV, 0);
+ pBonus += interpolate((deltaScore > 0) ? bM : wM, 0, 30 * deltaScore / 100, qV + 2 * rV + 2 * bV + 2 * nV, 0);
+
+ return pBonus;
+ }
+
+ private static final int[] castleFactor;
+ static {
+ castleFactor = new int[256];
+ for (int i = 0; i < 256; i++) {
+ int h1Dist = 100;
+ boolean h1Castle = (i & (1<<7)) != 0;
+ if (h1Castle)
+ h1Dist = 2 + Long.bitCount(i & 0x0000000000000060L); // f1,g1
+ int a1Dist = 100;
+ boolean a1Castle = (i & 1) != 0;
+ if (a1Castle)
+ a1Dist = 2 + Long.bitCount(i & 0x000000000000000EL); // b1,c1,d1
+ castleFactor[i] = 1024 / Math.min(a1Dist, h1Dist);
+ }
+ }
+
+ /** Score castling ability. */
+ private final int castleBonus(Position pos) {
+ if (pos.getCastleMask() == 0) return 0;
+
+ final int k1 = kt1b[7*8+6] - kt1b[7*8+4];
+ final int k2 = kt2b[7*8+6] - kt2b[7*8+4];
+ final int t1 = qV + 2 * rV + 2 * bV;
+ final int t2 = rV;
+ final int t = pos.bMtrl - pos.bMtrlPawns;
+ final int ks = interpolate(t, t2, k2, t1, k1);
+
+ final int castleValue = ks + rt1b[7*8+5] - rt1b[7*8+7];
+ if (castleValue <= 0)
+ return 0;
+
+ long occupied = pos.whiteBB | pos.blackBB;
+ int tmp = (int) (occupied & 0x6E);
+ if (pos.a1Castle()) tmp |= 1;
+ if (pos.h1Castle()) tmp |= (1 << 7);
+ final int wBonus = (castleValue * castleFactor[tmp]) >> 10;
+
+ tmp = (int) ((occupied >>> 56) & 0x6E);
+ if (pos.a8Castle()) tmp |= 1;
+ if (pos.h8Castle()) tmp |= (1 << 7);
+ final int bBonus = (castleValue * castleFactor[tmp]) >> 10;
+
+ return wBonus - bBonus;
+ }
+
+ private final int pawnBonus(Position pos) {
+ long key = pos.pawnZobristHash();
+ PawnHashData phd = pawnHash[(int)key & (pawnHash.length - 1)];
+ if (phd.key != key)
+ computePawnHashData(pos, phd);
+ int score = phd.score;
+
+ final int hiMtrl = qV + rV;
+ score += interpolate(pos.bMtrl - pos.bMtrlPawns, 0, 2 * phd.passedBonusW, hiMtrl, phd.passedBonusW);
+ score -= interpolate(pos.wMtrl - pos.wMtrlPawns, 0, 2 * phd.passedBonusB, hiMtrl, phd.passedBonusB);
+
+ // Passed pawns are more dangerous if enemy king is far away
+ int bestWPawnDist = 8;
+ int bestWPromSq = -1;
+ long m = phd.passedPawnsW;
+ if (m != 0) {
+ int mtrlNoPawns = pos.bMtrl - pos.bMtrlPawns;
+ if (mtrlNoPawns < hiMtrl) {
+ int kingPos = pos.getKingSq(false);
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ int x = Position.getX(sq);
+ int y = Position.getY(sq);
+ int pawnDist = Math.min(5, 7 - y);
+ int kingDist = BitBoard.getDistance(kingPos, Position.getSquare(x, 7));
+ int kScore = kingDist * 4;
+ if (kingDist > pawnDist) kScore += (kingDist - pawnDist) * (kingDist - pawnDist);
+ score += interpolate(mtrlNoPawns, 0, kScore, hiMtrl, 0);
+ if (!pos.whiteMove)
+ kingDist--;
+ if ((pawnDist < kingDist) && (mtrlNoPawns == 0)) {
+ if ((BitBoard.northFill(1L< pawnDist) kScore += (kingDist - pawnDist) * (kingDist - pawnDist);
+ score -= interpolate(mtrlNoPawns, 0, kScore, hiMtrl, 0);
+ if (pos.whiteMove)
+ kingDist--;
+ if ((pawnDist < kingDist) && (mtrlNoPawns == 0)) {
+ if ((BitBoard.southFill(1L<= 0) {
+ if (bestBPromSq >= 0) {
+ int wPly = bestWPawnDist * 2; if (pos.whiteMove) wPly--;
+ int bPly = bestBPawnDist * 2; if (!pos.whiteMove) bPly--;
+ if (wPly < bPly - 1) {
+ score += 500;
+ } else if (wPly == bPly - 1) {
+ if (BitBoard.getDirection(bestWPromSq, pos.getKingSq(false)) != 0)
+ score += 500;
+ } else if (wPly == bPly + 1) {
+ if (BitBoard.getDirection(bestBPromSq, pos.getKingSq(true)) != 0)
+ score -= 500;
+ } else {
+ score -= 500;
+ }
+ } else
+ score += 500;
+ } else if (bestBPromSq >= 0)
+ score -= 500;
+
+ return score;
+ }
+
+ /** Compute pawn hash data for pos. */
+ private final void computePawnHashData(Position pos, PawnHashData ph) {
+ int score = 0;
+
+ // Evaluate double pawns and pawn islands
+ long wPawns = pos.pieceTypeBB[Piece.WPAWN];
+ long wPawnFiles = BitBoard.southFill(wPawns) & 0xff;
+ int wDouble = Long.bitCount(wPawns) - Long.bitCount(wPawnFiles);
+ int wIslands = Long.bitCount(((~wPawnFiles) >>> 1) & wPawnFiles);
+ int wIsolated = Long.bitCount(~(wPawnFiles<<1) & wPawnFiles & ~(wPawnFiles>>>1));
+
+
+ long bPawns = pos.pieceTypeBB[Piece.BPAWN];
+ long bPawnFiles = BitBoard.southFill(bPawns) & 0xff;
+ int bDouble = Long.bitCount(bPawns) - Long.bitCount(bPawnFiles);
+ int bIslands = Long.bitCount(((~bPawnFiles) >>> 1) & bPawnFiles);
+ int bIsolated = Long.bitCount(~(bPawnFiles<<1) & bPawnFiles & ~(bPawnFiles>>>1));
+
+ score -= (wDouble - bDouble) * 25;
+ score -= (wIslands - bIslands) * 15;
+ score -= (wIsolated - bIsolated) * 15;
+
+ // Evaluate backward pawns, defined as a pawn that guards a friendly pawn,
+ // can't be guarded by friendly pawns, can advance, but can't advance without
+ // being captured by an enemy pawn.
+ long wPawnAttacks = (((wPawns & BitBoard.maskBToHFiles) << 7) |
+ ((wPawns & BitBoard.maskAToGFiles) << 9));
+ long bPawnAttacks = (((bPawns & BitBoard.maskBToHFiles) >>> 9) |
+ ((bPawns & BitBoard.maskAToGFiles) >>> 7));
+ long wBackward = wPawns & ~((wPawns | bPawns) >>> 8) & (bPawnAttacks >>> 8) &
+ ~BitBoard.northFill(wPawnAttacks);
+ wBackward &= (((wPawns & BitBoard.maskBToHFiles) >>> 9) |
+ ((wPawns & BitBoard.maskAToGFiles) >>> 7));
+ wBackward &= ~BitBoard.northFill(bPawnFiles);
+ long bBackward = bPawns & ~((wPawns | bPawns) << 8) & (wPawnAttacks << 8) &
+ ~BitBoard.southFill(bPawnAttacks);
+ bBackward &= (((bPawns & BitBoard.maskBToHFiles) << 7) |
+ ((bPawns & BitBoard.maskAToGFiles) << 9));
+ bBackward &= ~BitBoard.northFill(wPawnFiles);
+ score -= (Long.bitCount(wBackward) - Long.bitCount(bBackward)) * 15;
+
+ // Evaluate passed pawn bonus, white
+ long passedPawnsW = wPawns & ~BitBoard.southFill(bPawns | bPawnAttacks | (wPawns >>> 8));
+ final int[] ppBonus = {-1,24,26,30,36,55,100,-1};
+ int passedBonusW = 0;
+ if (passedPawnsW != 0) {
+ long guardedPassedW = passedPawnsW & (((wPawns & BitBoard.maskBToHFiles) << 7) |
+ ((wPawns & BitBoard.maskAToGFiles) << 9));
+ passedBonusW += 15 * Long.bitCount(guardedPassedW);
+ long m = passedPawnsW;
+ while (m != 0) {
+ int sq = Long .numberOfTrailingZeros(m);
+ int y = Position.getY(sq);
+ passedBonusW += ppBonus[y];
+ m &= m-1;
+ }
+ }
+
+ // Evaluate passed pawn bonus, black
+ long passedPawnsB = bPawns & ~BitBoard.northFill(wPawns | wPawnAttacks | (bPawns << 8));
+ int passedBonusB = 0;
+ if (passedPawnsB != 0) {
+ long guardedPassedB = passedPawnsB & (((bPawns & BitBoard.maskBToHFiles) >>> 9) |
+ ((bPawns & BitBoard.maskAToGFiles) >>> 7));
+ passedBonusB += 15 * Long.bitCount(guardedPassedB);
+ long m = passedPawnsB;
+ while (m != 0) {
+ int sq = Long .numberOfTrailingZeros(m);
+ int y = Position.getY(sq);
+ passedBonusB += ppBonus[7-y];
+ m &= m-1;
+ }
+ }
+
+ // Connected passed pawn bonus. Seems logical but scored -8 elo in tests
+// if (passedPawnsW != 0) {
+// long mask = passedPawnsW;
+// mask = (((mask >> 7) | (mask << 1) | (mask << 9)) & BitBoard.maskBToHFiles) |
+// (((mask >> 9) | (mask >> 1) | (mask << 7)) & BitBoard.maskAToGFiles);
+// passedBonusW += 13 * Long.bitCount(passedPawnsW & mask);
+// }
+// if (passedPawnsB != 0) {
+// long mask = passedPawnsB;
+// mask = (((mask >> 7) | (mask << 1) | (mask << 9)) & BitBoard.maskBToHFiles) |
+// (((mask >> 9) | (mask >> 1) | (mask << 7)) & BitBoard.maskAToGFiles);
+// passedBonusB += 13 * Long.bitCount(passedPawnsB & mask);
+// }
+
+ ph.key = pos.pawnZobristHash();
+ ph.score = score;
+ ph.passedBonusW = (short)passedBonusW;
+ ph.passedBonusB = (short)passedBonusB;
+ ph.passedPawnsW = passedPawnsW;
+ ph.passedPawnsB = passedPawnsB;
+ }
+
+ /** Compute rook bonus. Rook on open/half-open file. */
+ private final int rookBonus(Position pos) {
+ int score = 0;
+ final long wPawns = pos.pieceTypeBB[Piece.WPAWN];
+ final long bPawns = pos.pieceTypeBB[Piece.BPAWN];
+ final long occupied = pos.whiteBB | pos.blackBB;
+ long m = pos.pieceTypeBB[Piece.WROOK];
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ final int x = Position.getX(sq);
+ if ((wPawns & BitBoard.maskFile[x]) == 0) { // At least half-open file
+ score += (bPawns & BitBoard.maskFile[x]) == 0 ? 25 : 12;
+ }
+ long atk = BitBoard.rookAttacks(sq, occupied);
+ wAttacksBB |= atk;
+ score += rookMobScore[Long.bitCount(atk & ~(pos.whiteBB | bPawnAttacks))];
+ if ((atk & bKingZone) != 0)
+ bKingAttacks += Long.bitCount(atk & bKingZone);
+ m &= m-1;
+ }
+ long r7 = (pos.pieceTypeBB[Piece.WROOK] >>> 48) & 0x00ffL;
+ if (((r7 & (r7 - 1)) != 0) &&
+ ((pos.pieceTypeBB[Piece.BKING] & 0xff00000000000000L) != 0))
+ score += 30; // Two rooks on 7:th row
+ m = pos.pieceTypeBB[Piece.BROOK];
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ final int x = Position.getX(sq);
+ if ((bPawns & BitBoard.maskFile[x]) == 0) {
+ score -= (wPawns & BitBoard.maskFile[x]) == 0 ? 25 : 12;
+ }
+ long atk = BitBoard.rookAttacks(sq, occupied);
+ bAttacksBB |= atk;
+ score -= rookMobScore[Long.bitCount(atk & ~(pos.blackBB | wPawnAttacks))];
+ if ((atk & wKingZone) != 0)
+ wKingAttacks += Long.bitCount(atk & wKingZone);
+ m &= m-1;
+ }
+ r7 = pos.pieceTypeBB[Piece.BROOK] & 0xff00L;
+ if (((r7 & (r7 - 1)) != 0) &&
+ ((pos.pieceTypeBB[Piece.WKING] & 0xffL) != 0))
+ score -= 30; // Two rooks on 2:nd row
+ return score;
+ }
+
+ /** Compute bishop evaluation. */
+ private final int bishopEval(Position pos, int oldScore) {
+ int score = 0;
+ final long occupied = pos.whiteBB | pos.blackBB;
+ long wBishops = pos.pieceTypeBB[Piece.WBISHOP];
+ long bBishops = pos.pieceTypeBB[Piece.BBISHOP];
+ if ((wBishops | bBishops) == 0)
+ return 0;
+ long m = wBishops;
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ long atk = BitBoard.bishopAttacks(sq, occupied);
+ wAttacksBB |= atk;
+ score += bishMobScore[Long.bitCount(atk & ~(pos.whiteBB | bPawnAttacks))];
+ if ((atk & bKingZone) != 0)
+ bKingAttacks += Long.bitCount(atk & bKingZone);
+ m &= m-1;
+ }
+ m = bBishops;
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ long atk = BitBoard.bishopAttacks(sq, occupied);
+ bAttacksBB |= atk;
+ score -= bishMobScore[Long.bitCount(atk & ~(pos.blackBB | wPawnAttacks))];
+ if ((atk & wKingZone) != 0)
+ wKingAttacks += Long.bitCount(atk & wKingZone);
+ m &= m-1;
+ }
+
+ boolean whiteDark = (pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskDarkSq ) != 0;
+ boolean whiteLight = (pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskLightSq) != 0;
+ boolean blackDark = (pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskDarkSq ) != 0;
+ boolean blackLight = (pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskLightSq) != 0;
+ int numWhite = (whiteDark ? 1 : 0) + (whiteLight ? 1 : 0);
+ int numBlack = (blackDark ? 1 : 0) + (blackLight ? 1 : 0);
+
+ // Bishop pair bonus
+ if (numWhite == 2) {
+ final int numPawns = pos.wMtrlPawns / pV;
+ score += 28 + (8 - numPawns) * 3;
+ }
+ if (numBlack == 2) {
+ final int numPawns = pos.bMtrlPawns / pV;
+ score -= 28 + (8 - numPawns) * 3;
+ }
+
+ if ((numWhite == 1) && (numBlack == 1) && (whiteDark != blackDark) &&
+ (pos.wMtrl - pos.wMtrlPawns == pos.bMtrl - pos.bMtrlPawns)) {
+ final int penalty = (oldScore + score) / 2;
+ final int loMtrl = 2 * bV;
+ final int hiMtrl = 2 * (qV + rV + bV);
+ int mtrl = pos.wMtrl + pos.bMtrl - pos.wMtrlPawns - pos.bMtrlPawns;
+ score -= interpolate(mtrl, loMtrl, penalty, hiMtrl, 0);
+ }
+
+ // Penalty for bishop trapped behind pawn at a2/h2/a7/h7
+ if (((wBishops | bBishops) & 0x0081000000008100L) != 0) {
+ if ((pos.squares[48] == Piece.WBISHOP) && // a7
+ (pos.squares[41] == Piece.BPAWN) &&
+ (pos.squares[50] == Piece.BPAWN))
+ score -= pV * 3 / 2;
+ if ((pos.squares[55] == Piece.WBISHOP) && // h7
+ (pos.squares[46] == Piece.BPAWN) &&
+ (pos.squares[53] == Piece.BPAWN))
+ score -= (pos.pieceTypeBB[Piece.WQUEEN] != 0) ? pV : pV * 3 / 2;
+ if ((pos.squares[8] == Piece.BBISHOP) && // a2
+ (pos.squares[17] == Piece.WPAWN) &&
+ (pos.squares[10] == Piece.WPAWN))
+ score += pV * 3 / 2;
+ if ((pos.squares[15] == Piece.BBISHOP) && // h2
+ (pos.squares[22] == Piece.WPAWN) &&
+ (pos.squares[13] == Piece.WPAWN))
+ score += (pos.pieceTypeBB[Piece.BQUEEN] != 0) ? pV : pV * 3 / 2;
+ }
+
+ return score;
+ }
+
+ private int threatBonus(Position pos) {
+ int score = 0;
+
+ // Sum values for all black pieces under attack
+ long m = pos.pieceTypeBB[Piece.WKNIGHT];
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ wAttacksBB |= BitBoard.knightAttacks[sq];
+ m &= m-1;
+ }
+ wAttacksBB &= (pos.pieceTypeBB[Piece.BKNIGHT] |
+ pos.pieceTypeBB[Piece.BBISHOP] |
+ pos.pieceTypeBB[Piece.BROOK] |
+ pos.pieceTypeBB[Piece.BQUEEN]);
+ wAttacksBB |= wPawnAttacks;
+ m = wAttacksBB & pos.blackBB & ~pos.pieceTypeBB[Piece.BKING];
+ int tmp = 0;
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ tmp += pieceValue[pos.squares[sq]];
+ m &= m-1;
+ }
+ score += tmp + tmp * tmp / qV;
+
+ // Sum values for all white pieces under attack
+ m = pos.pieceTypeBB[Piece.BKNIGHT];
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ bAttacksBB |= BitBoard.knightAttacks[sq];
+ m &= m-1;
+ }
+ bAttacksBB &= (pos.pieceTypeBB[Piece.WKNIGHT] |
+ pos.pieceTypeBB[Piece.WBISHOP] |
+ pos.pieceTypeBB[Piece.WROOK] |
+ pos.pieceTypeBB[Piece.WQUEEN]);
+ bAttacksBB |= bPawnAttacks;
+ m = bAttacksBB & pos.whiteBB & ~pos.pieceTypeBB[Piece.WKING];
+ tmp = 0;
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ tmp += pieceValue[pos.squares[sq]];
+ m &= m-1;
+ }
+ score -= tmp + tmp * tmp / qV;
+ return score / 64;
+ }
+
+ /** Compute king safety for both kings. */
+ private final int kingSafety(Position pos) {
+ final int minM = rV + bV;
+ final int m = (pos.wMtrl - pos.wMtrlPawns + pos.bMtrl - pos.bMtrlPawns) / 2;
+ if (m <= minM)
+ return 0;
+ final int maxM = qV + 2 * rV + 2 * bV + 2 * nV;
+ int score = kingSafetyKPPart(pos);
+ if (Position.getY(pos.wKingSq) == 0) {
+ if (((pos.pieceTypeBB[Piece.WKING] & 0x60L) != 0) && // King on f1 or g1
+ ((pos.pieceTypeBB[Piece.WROOK] & 0xC0L) != 0) && // Rook on g1 or h1
+ ((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskFile[6]) != 0) &&
+ ((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskFile[7]) != 0)) {
+ score -= 6 * 15;
+ } else
+ if (((pos.pieceTypeBB[Piece.WKING] & 0x6L) != 0) && // King on b1 or c1
+ ((pos.pieceTypeBB[Piece.WROOK] & 0x3L) != 0) && // Rook on a1 or b1
+ ((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskFile[0]) != 0) &&
+ ((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskFile[1]) != 0)) {
+ score -= 6 * 15;
+ }
+ }
+ if (Position.getY(pos.bKingSq) == 7) {
+ if (((pos.pieceTypeBB[Piece.BKING] & 0x6000000000000000L) != 0) && // King on f8 or g8
+ ((pos.pieceTypeBB[Piece.BROOK] & 0xC000000000000000L) != 0) && // Rook on g8 or h8
+ ((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskFile[6]) != 0) &&
+ ((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskFile[7]) != 0)) {
+ score += 6 * 15;
+ } else
+ if (((pos.pieceTypeBB[Piece.BKING] & 0x600000000000000L) != 0) && // King on b8 or c8
+ ((pos.pieceTypeBB[Piece.BROOK] & 0x300000000000000L) != 0) && // Rook on a8 or b8
+ ((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskFile[0]) != 0) &&
+ ((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskFile[1]) != 0)) {
+ score += 6 * 15;
+ }
+ }
+ score += (bKingAttacks - wKingAttacks) * 4;
+ final int kSafety = interpolate(m, minM, 0, maxM, score);
+ return kSafety;
+ }
+
+ private static final class KingSafetyHashData {
+ long key;
+ int score;
+ }
+ private static final KingSafetyHashData[] kingSafetyHash;
+ static {
+ final int numEntries = 1 << 15;
+ kingSafetyHash = new KingSafetyHashData[numEntries];
+ for (int i = 0; i < numEntries; i++) {
+ KingSafetyHashData ksh = new KingSafetyHashData();
+ ksh.key = -1;
+ ksh.score = 0;
+ kingSafetyHash[i] = ksh;
+ }
+ }
+
+ private final int kingSafetyKPPart(Position pos) {
+ final long key = pos.pawnZobristHash() ^ pos.kingZobristHash();
+ KingSafetyHashData ksh = kingSafetyHash[(int)key & (kingSafetyHash.length - 1)];
+ if (ksh.key != key) {
+ int score = 0;
+ long wPawns = pos.pieceTypeBB[Piece.WPAWN];
+ long bPawns = pos.pieceTypeBB[Piece.BPAWN];
+ {
+ int safety = 0;
+ int halfOpenFiles = 0;
+ if (Position.getY(pos.wKingSq) < 2) {
+ long shelter = 1L << Position.getX(pos.wKingSq);
+ shelter |= ((shelter & BitBoard.maskBToHFiles) >>> 1) |
+ ((shelter & BitBoard.maskAToGFiles) << 1);
+ shelter <<= 8;
+ safety += 3 * Long.bitCount(wPawns & shelter);
+ safety -= 2 * Long.bitCount(bPawns & (shelter | (shelter << 8)));
+ shelter <<= 8;
+ safety += 2 * Long.bitCount(wPawns & shelter);
+ shelter <<= 8;
+ safety -= Long.bitCount(bPawns & shelter);
+
+ long wOpen = BitBoard.southFill(shelter) & (~BitBoard.southFill(wPawns)) & 0xff;
+ if (wOpen != 0) {
+ halfOpenFiles += 25 * Long.bitCount(wOpen & 0xe7);
+ halfOpenFiles += 10 * Long.bitCount(wOpen & 0x18);
+ }
+ long bOpen = BitBoard.southFill(shelter) & (~BitBoard.southFill(bPawns)) & 0xff;
+ if (bOpen != 0) {
+ halfOpenFiles += 25 * Long.bitCount(bOpen & 0xe7);
+ halfOpenFiles += 10 * Long.bitCount(bOpen & 0x18);
+ }
+ safety = Math.min(safety, 8);
+ }
+ final int kSafety = (safety - 9) * 15 - halfOpenFiles;
+ score += kSafety;
+ }
+ {
+ int safety = 0;
+ int halfOpenFiles = 0;
+ if (Position.getY(pos.bKingSq) >= 6) {
+ long shelter = 1L << (56 + Position.getX(pos.bKingSq));
+ shelter |= ((shelter & BitBoard.maskBToHFiles) >>> 1) |
+ ((shelter & BitBoard.maskAToGFiles) << 1);
+ shelter >>>= 8;
+ safety += 3 * Long.bitCount(bPawns & shelter);
+ safety -= 2 * Long.bitCount(wPawns & (shelter | (shelter >>> 8)));
+ shelter >>>= 8;
+ safety += 2 * Long.bitCount(bPawns & shelter);
+ shelter >>>= 8;
+ safety -= Long.bitCount(wPawns & shelter);
+
+ long wOpen = BitBoard.southFill(shelter) & (~BitBoard.southFill(wPawns)) & 0xff;
+ if (wOpen != 0) {
+ halfOpenFiles += 25 * Long.bitCount(wOpen & 0xe7);
+ halfOpenFiles += 10 * Long.bitCount(wOpen & 0x18);
+ }
+ long bOpen = BitBoard.southFill(shelter) & (~BitBoard.southFill(bPawns)) & 0xff;
+ if (bOpen != 0) {
+ halfOpenFiles += 25 * Long.bitCount(bOpen & 0xe7);
+ halfOpenFiles += 10 * Long.bitCount(bOpen & 0x18);
+ }
+ safety = Math.min(safety, 8);
+ }
+ final int kSafety = (safety - 9) * 15 - halfOpenFiles;
+ score -= kSafety;
+ }
+ ksh.key = key;
+ ksh.score = score;
+ }
+ return ksh.score;
+ }
+
+ /** Implements special knowledge for some endgame situations. */
+ private final int endGameEval(Position pos, int oldScore) {
+ int score = oldScore;
+ if (pos.wMtrl + pos.bMtrl > 6 * rV)
+ return score;
+ final int wMtrlPawns = pos.wMtrlPawns;
+ final int bMtrlPawns = pos.bMtrlPawns;
+ final int wMtrlNoPawns = pos.wMtrl - wMtrlPawns;
+ final int bMtrlNoPawns = pos.bMtrl - bMtrlPawns;
+
+ boolean handled = false;
+ if ((wMtrlPawns + bMtrlPawns == 0) && (wMtrlNoPawns < rV) && (bMtrlNoPawns < rV)) {
+ // King + minor piece vs king + minor piece is a draw
+ return 0;
+ }
+ if (!handled && (pos.wMtrl == qV) && (pos.bMtrl == pV) && (pos.pieceTypeBB[Piece.WQUEEN] != 0)) {
+ int wk = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WKING]);
+ int wq = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WQUEEN]);
+ int bk = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BKING]);
+ int bp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BPAWN]);
+ score = evalKQKP(wk, wq, bk, bp, pos.whiteMove);
+ handled = true;
+ }
+ if (!handled && (pos.wMtrl == rV) && (pos.pieceTypeBB[Piece.WROOK] != 0)) {
+ if (pos.bMtrl == pV) {
+ int bp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BPAWN]);
+ score = krkpEval(pos.getKingSq(true), pos.getKingSq(false),
+ bp, pos.whiteMove);
+ handled = true;
+ } else if ((pos.bMtrl == bV) && (pos.pieceTypeBB[Piece.BBISHOP] != 0)) {
+ score /= 8;
+ final int kSq = pos.getKingSq(false);
+ final int x = Position.getX(kSq);
+ final int y = Position.getY(kSq);
+ if ((pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskDarkSq) != 0) {
+ score += (7 - distToH1A8[7-y][7-x]) * 7;
+ } else {
+ score += (7 - distToH1A8[7-y][x]) * 7;
+ }
+ handled = true;
+ }
+ }
+ if (!handled && (pos.bMtrl == qV) && (pos.wMtrl == pV) && (pos.pieceTypeBB[Piece.BQUEEN] != 0)) {
+ int bk = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BKING]);
+ int bq = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BQUEEN]);
+ int wk = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WKING]);
+ int wp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WPAWN]);
+ score = -evalKQKP(63-bk, 63-bq, 63-wk, 63-wp, !pos.whiteMove);
+ handled = true;
+ }
+ if (!handled && (pos.bMtrl == rV) && (pos.pieceTypeBB[Piece.BROOK] != 0)) {
+ if (pos.wMtrl == pV) {
+ int wp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WPAWN]);
+ score = -krkpEval(63-pos.getKingSq(false), 63-pos.getKingSq(true),
+ 63-wp, !pos.whiteMove);
+ handled = true;
+ } else if ((pos.wMtrl == bV) && (pos.pieceTypeBB[Piece.WBISHOP] != 0)) {
+ score /= 8;
+ final int kSq = pos.getKingSq(true);
+ final int x = Position.getX(kSq);
+ final int y = Position.getY(kSq);
+ if ((pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskDarkSq) != 0) {
+ score -= (7 - distToH1A8[7-y][7-x]) * 7;
+ } else {
+ score -= (7 - distToH1A8[7-y][x]) * 7;
+ }
+ handled = true;
+ }
+ }
+ if (!handled && (score > 0)) {
+ if ((wMtrlPawns == 0) && (wMtrlNoPawns <= bMtrlNoPawns + bV)) {
+ if (wMtrlNoPawns < rV) {
+ return -pos.bMtrl / 50;
+ } else {
+ score /= 8; // Too little excess material, probably draw
+ handled = true;
+ }
+ } else if ((pos.pieceTypeBB[Piece.WROOK] | pos.pieceTypeBB[Piece.WKNIGHT] |
+ pos.pieceTypeBB[Piece.WQUEEN]) == 0) {
+ // Check for rook pawn + wrong color bishop
+ if (((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskBToHFiles) == 0) &&
+ ((pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskLightSq) == 0) &&
+ ((pos.pieceTypeBB[Piece.BKING] & 0x0303000000000000L) != 0)) {
+ return 0;
+ } else
+ if (((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskAToGFiles) == 0) &&
+ ((pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskDarkSq) == 0) &&
+ ((pos.pieceTypeBB[Piece.BKING] & 0xC0C0000000000000L) != 0)) {
+ return 0;
+ }
+ }
+ }
+ if (!handled) {
+ if (bMtrlPawns == 0) {
+ if (wMtrlNoPawns - bMtrlNoPawns > bV) {
+ int wKnights = Long.bitCount(pos.pieceTypeBB[Piece.WKNIGHT]);
+ int wBishops = Long.bitCount(pos.pieceTypeBB[Piece.WBISHOP]);
+ if ((wKnights == 2) && (pos.wMtrl == 2 * nV) && (bMtrlNoPawns == 0)) {
+ score /= 50; // KNNK is a draw
+ } else if ((wKnights == 1) && (wBishops == 1) && (wMtrlNoPawns == nV + bV) && (bMtrlNoPawns == 0)) {
+ score /= 10;
+ score += nV + bV + 300;
+ final int kSq = pos.getKingSq(false);
+ final int x = Position.getX(kSq);
+ final int y = Position.getY(kSq);
+ if ((pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskDarkSq) != 0) {
+ score += (7 - distToH1A8[7-y][7-x]) * 10;
+ } else {
+ score += (7 - distToH1A8[7-y][x]) * 10;
+ }
+ } else {
+ score += 300; // Enough excess material, should win
+ }
+ handled = true;
+ } else if ((wMtrlNoPawns + bMtrlNoPawns == 0) && (wMtrlPawns == pV)) { // KPK
+ int wp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WPAWN]);
+ score = kpkEval(pos.getKingSq(true), pos.getKingSq(false),
+ wp, pos.whiteMove);
+ handled = true;
+ }
+ }
+ }
+ if (!handled && (score < 0)) {
+ if ((bMtrlPawns == 0) && (bMtrlNoPawns <= wMtrlNoPawns + bV)) {
+ if (bMtrlNoPawns < rV) {
+ return pos.wMtrl / 50;
+ } else {
+ score /= 8; // Too little excess material, probably draw
+ handled = true;
+ }
+ } else if ((pos.pieceTypeBB[Piece.BROOK] | pos.pieceTypeBB[Piece.BKNIGHT] |
+ pos.pieceTypeBB[Piece.BQUEEN]) == 0) {
+ // Check for rook pawn + wrong color bishop
+ if (((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskBToHFiles) == 0) &&
+ ((pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskDarkSq) == 0) &&
+ ((pos.pieceTypeBB[Piece.WKING] & 0x0303L) != 0)) {
+ return 0;
+ } else
+ if (((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskAToGFiles) == 0) &&
+ ((pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskLightSq) == 0) &&
+ ((pos.pieceTypeBB[Piece.WKING] & 0xC0C0L) != 0)) {
+ return 0;
+ }
+ }
+ }
+ if (!handled) {
+ if (wMtrlPawns == 0) {
+ if (bMtrlNoPawns - wMtrlNoPawns > bV) {
+ int bKnights = Long.bitCount(pos.pieceTypeBB[Piece.BKNIGHT]);
+ int bBishops = Long.bitCount(pos.pieceTypeBB[Piece.BBISHOP]);
+ if ((bKnights == 2) && (pos.bMtrl == 2 * nV) && (wMtrlNoPawns == 0)) {
+ score /= 50; // KNNK is a draw
+ } else if ((bKnights == 1) && (bBishops == 1) && (bMtrlNoPawns == nV + bV) && (wMtrlNoPawns == 0)) {
+ score /= 10;
+ score -= nV + bV + 300;
+ final int kSq = pos.getKingSq(true);
+ final int x = Position.getX(kSq);
+ final int y = Position.getY(kSq);
+ if ((pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskDarkSq) != 0) {
+ score -= (7 - distToH1A8[7-y][7-x]) * 10;
+ } else {
+ score -= (7 - distToH1A8[7-y][x]) * 10;
+ }
+ } else {
+ score -= 300; // Enough excess material, should win
+ }
+ handled = true;
+ } else if ((wMtrlNoPawns + bMtrlNoPawns == 0) && (bMtrlPawns == pV)) { // KPK
+ int bp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BPAWN]);
+ score = -kpkEval(63-pos.getKingSq(false), 63-pos.getKingSq(true),
+ 63-bp, !pos.whiteMove);
+ handled = true;
+ }
+ }
+ }
+ return score;
+ }
+
+ private static final int evalKQKP(int wKing, int wQueen, int bKing, int bPawn, boolean whiteMove) {
+ boolean canWin = false;
+ if (((1L << bKing) & 0xFFFF) == 0) {
+ canWin = true; // King doesn't support pawn
+ } else if (Math.abs(Position.getX(bPawn) - Position.getX(bKing)) > 2) {
+ canWin = true; // King doesn't support pawn
+ } else {
+ switch (bPawn) {
+ case 8: // a2
+ canWin = ((1L << wKing) & 0x0F1F1F1F1FL) != 0;
+ if (canWin && (bKing == 0) && (Position.getX(wQueen) == 1) && !whiteMove)
+ canWin = false; // Stale-mate
+ break;
+ case 10: // c2
+ canWin = ((1L << wKing) & 0x071F1F1FL) != 0;
+ break;
+ case 13: // f2
+ canWin = ((1L << wKing) & 0xE0F8F8F8L) != 0;
+ break;
+ case 15: // h2
+ canWin = ((1L << wKing) & 0xF0F8F8F8F8L) != 0;
+ if (canWin && (bKing == 7) && (Position.getX(wQueen) == 6) && !whiteMove)
+ canWin = false; // Stale-mate
+ break;
+ default:
+ canWin = true;
+ break;
+ }
+ }
+
+ final int dist = BitBoard.getDistance(wKing, bPawn);
+ int score = qV - pV - 20 * dist;
+ if (!canWin)
+ score /= 50;
+ return score;
+ }
+
+ private static final int kpkEval(int wKing, int bKing, int wPawn, boolean whiteMove) {
+ if (Position.getX(wKing) >= 4) { // Mirror X
+ wKing ^= 7;
+ bKing ^= 7;
+ wPawn ^= 7;
+ }
+ int index = whiteMove ? 0 : 1;
+ index = index * 32 + Position.getY(wKing)*4+Position.getX(wKing);
+ index = index * 64 + bKing;
+ index = index * 48 + wPawn - 8;
+
+ int bytePos = index / 8;
+ int bitPos = index % 8;
+ boolean draw = (((int)kpkTable[bytePos]) & (1 << bitPos)) == 0;
+ if (draw)
+ return 0;
+ return qV - pV / 4 * (7-Position.getY(wPawn));
+ }
+
+ private static final int krkpEval(int wKing, int bKing, int bPawn, boolean whiteMove) {
+ if (Position.getX(bKing) >= 4) { // Mirror X
+ wKing ^= 7;
+ bKing ^= 7;
+ bPawn ^= 7;
+ }
+ int index = whiteMove ? 0 : 1;
+ index = index * 32 + Position.getY(bKing)*4+Position.getX(bKing);
+ index = index * 48 + bPawn - 8;
+ index = index * 8 + Position.getY(wKing);
+ byte mask = krkpTable[index];
+ boolean canWin = (mask & (1 << Position.getX(wKing))) != 0;
+
+ int score = rV - pV + Position.getY(bPawn) * pV / 4;
+ if (canWin)
+ score += 150;
+ else
+ score /= 50;
+ return score;
+ }
+
+ /**
+ * Interpolate between (x1,y1) and (x2,y2).
+ * If x < x1, return y1, if x > x2 return y2. Otherwise, use linear interpolation.
+ */
+ static final int interpolate(int x, int x1, int y1, int x2, int y2) {
+ if (x > x2) {
+ return y2;
+ } else if (x < x1) {
+ return y1;
+ } else {
+ return (x - x1) * (y2 - y1) / (x2 - x1) + y1;
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/chess/Move.java b/buildSrc/src/main/java/chess/Move.java
new file mode 100644
index 0000000..542b50b
--- /dev/null
+++ b/buildSrc/src/main/java/chess/Move.java
@@ -0,0 +1,107 @@
+/*
+ CuckooChess - A java 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 chess;
+
+import java.util.Comparator;
+
+public class Move {
+ /** From square, 0-63. */
+ public int from;
+
+ /** To square, 0-63. */
+ public int to;
+
+ /** Promotion piece. */
+ public int promoteTo;
+
+ public int score;
+
+ /** Create a move object. */
+ public Move(int from, int to, int promoteTo) {
+ this.from = from;
+ this.to = to;
+ this.promoteTo = promoteTo;
+ this.score = 0;
+ }
+
+ public Move(int from, int to, int promoteTo, int score) {
+ this.from = from;
+ this.to = to;
+ this.promoteTo = promoteTo;
+ this.score = score;
+ }
+
+ static public class SortByScore implements Comparator {
+ public int compare(Move sm1, Move sm2) {
+ return sm2.score - sm1.score;
+ }
+ }
+
+ public Move(Move m) {
+ this.from = m.from;
+ this.to = m.to;
+ this.promoteTo = m.promoteTo;
+ this.score = m.score;
+ }
+
+ public final void copyFrom(Move m) {
+ from = m.from;
+ to = m.to;
+ promoteTo = m.promoteTo;
+// score = m.score;
+ }
+
+ public final void clear() {
+ from = 0;
+ to = 0;
+ promoteTo = 0;
+ score = 0;
+ }
+
+ public final void setMove(int from, int to, int promoteTo, int score) {
+ this.from = from;
+ this.to = to;
+ this.promoteTo = promoteTo;
+ this.score = score;
+ }
+
+ /** Note that score is not included in the comparison. */
+ @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/buildSrc/src/main/java/chess/MoveGen.java b/buildSrc/src/main/java/chess/MoveGen.java
new file mode 100644
index 0000000..f61139c
--- /dev/null
+++ b/buildSrc/src/main/java/chess/MoveGen.java
@@ -0,0 +1,1069 @@
+/*
+ CuckooChess - A java 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 chess;
+
+import java.util.List;
+
+public final class MoveGen {
+ static final MoveGen instance;
+ static {
+ instance = new MoveGen();
+ }
+
+ public final static class MoveList {
+ public final Move[] m;
+ public int size;
+ MoveList() {
+ m = new Move[MAX_MOVES];
+ this.size = 0;
+ }
+ public final void filter(List searchMoves) {
+ int used = 0;
+ for (int i = 0; i < size; i++)
+ if (searchMoves.contains(m[i]))
+ m[used++] = m[i];
+ size = used;
+ }
+ }
+
+ /**
+ * Generate and return a list of pseudo-legal moves.
+ * Pseudo-legal means that the moves don't necessarily defend from check threats.
+ */
+ public final MoveList pseudoLegalMoves(Position pos) {
+ MoveList moveList = getMoveListObj();
+ final long occupied = pos.whiteBB | pos.blackBB;
+ if (pos.whiteMove) {
+ // Queen moves
+ long squares = pos.pieceTypeBB[Piece.WQUEEN];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied)) & ~pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Rook moves
+ squares = pos.pieceTypeBB[Piece.WROOK];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.rookAttacks(sq, occupied) & ~pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Bishop moves
+ squares = pos.pieceTypeBB[Piece.WBISHOP];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.bishopAttacks(sq, occupied) & ~pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // King moves
+ {
+ int sq = pos.getKingSq(true);
+ long m = BitBoard.kingAttacks[sq] & ~pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ final int k0 = 4;
+ if (sq == k0) {
+ final long OO_SQ = 0x60L;
+ final long OOO_SQ = 0xEL;
+ if (((pos.getCastleMask() & (1 << Position.H1_CASTLE)) != 0) &&
+ ((OO_SQ & (pos.whiteBB | pos.blackBB)) == 0) &&
+ (pos.getPiece(k0 + 3) == Piece.WROOK) &&
+ !sqAttacked(pos, k0) &&
+ !sqAttacked(pos, k0 + 1)) {
+ setMove(moveList, k0, k0 + 2, Piece.EMPTY);
+ }
+ if (((pos.getCastleMask() & (1 << Position.A1_CASTLE)) != 0) &&
+ ((OOO_SQ & (pos.whiteBB | pos.blackBB)) == 0) &&
+ (pos.getPiece(k0 - 4) == Piece.WROOK) &&
+ !sqAttacked(pos, k0) &&
+ !sqAttacked(pos, k0 - 1)) {
+ setMove(moveList, k0, k0 - 2, Piece.EMPTY);
+ }
+ }
+ }
+
+ // Knight moves
+ long knights = pos.pieceTypeBB[Piece.WKNIGHT];
+ while (knights != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(knights);
+ long m = BitBoard.knightAttacks[sq] & ~pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ knights &= knights-1;
+ }
+
+ // Pawn moves
+ long pawns = pos.pieceTypeBB[Piece.WPAWN];
+ long m = (pawns << 8) & ~occupied;
+ if (addPawnMovesByMask(moveList, pos, m, -8, true)) return moveList;
+ m = ((m & BitBoard.maskRow3) << 8) & ~occupied;
+ addPawnDoubleMovesByMask(moveList, pos, m, -16);
+
+ int epSquare = pos.getEpSquare();
+ long epMask = (epSquare >= 0) ? (1L << epSquare) : 0L;
+ m = (pawns << 7) & BitBoard.maskAToGFiles & (pos.blackBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, -7, true)) return moveList;
+
+ m = (pawns << 9) & BitBoard.maskBToHFiles & (pos.blackBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, -9, true)) return moveList;
+ } else {
+ // Queen moves
+ long squares = pos.pieceTypeBB[Piece.BQUEEN];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied)) & ~pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Rook moves
+ squares = pos.pieceTypeBB[Piece.BROOK];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.rookAttacks(sq, occupied) & ~pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Bishop moves
+ squares = pos.pieceTypeBB[Piece.BBISHOP];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.bishopAttacks(sq, occupied) & ~pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // King moves
+ {
+ int sq = pos.getKingSq(false);
+ long m = BitBoard.kingAttacks[sq] & ~pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ final int k0 = 60;
+ if (sq == k0) {
+ final long OO_SQ = 0x6000000000000000L;
+ final long OOO_SQ = 0xE00000000000000L;
+ if (((pos.getCastleMask() & (1 << Position.H8_CASTLE)) != 0) &&
+ ((OO_SQ & (pos.whiteBB | pos.blackBB)) == 0) &&
+ (pos.getPiece(k0 + 3) == Piece.BROOK) &&
+ !sqAttacked(pos, k0) &&
+ !sqAttacked(pos, k0 + 1)) {
+ setMove(moveList, k0, k0 + 2, Piece.EMPTY);
+ }
+ if (((pos.getCastleMask() & (1 << Position.A8_CASTLE)) != 0) &&
+ ((OOO_SQ & (pos.whiteBB | pos.blackBB)) == 0) &&
+ (pos.getPiece(k0 - 4) == Piece.BROOK) &&
+ !sqAttacked(pos, k0) &&
+ !sqAttacked(pos, k0 - 1)) {
+ setMove(moveList, k0, k0 - 2, Piece.EMPTY);
+ }
+ }
+ }
+
+ // Knight moves
+ long knights = pos.pieceTypeBB[Piece.BKNIGHT];
+ while (knights != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(knights);
+ long m = BitBoard.knightAttacks[sq] & ~pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ knights &= knights-1;
+ }
+
+ // Pawn moves
+ long pawns = pos.pieceTypeBB[Piece.BPAWN];
+ long m = (pawns >>> 8) & ~occupied;
+ if (addPawnMovesByMask(moveList, pos, m, 8, true)) return moveList;
+ m = ((m & BitBoard.maskRow6) >>> 8) & ~occupied;
+ addPawnDoubleMovesByMask(moveList, pos, m, 16);
+
+ int epSquare = pos.getEpSquare();
+ long epMask = (epSquare >= 0) ? (1L << epSquare) : 0L;
+ m = (pawns >>> 9) & BitBoard.maskAToGFiles & (pos.whiteBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, 9, true)) return moveList;
+
+ m = (pawns >>> 7) & BitBoard.maskBToHFiles & (pos.whiteBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, 7, true)) return moveList;
+ }
+ return moveList;
+ }
+
+ /**
+ * Generate and return a list of pseudo-legal check evasion moves.
+ * Pseudo-legal means that the moves doesn't necessarily defend from check threats.
+ */
+ public final MoveList checkEvasions(Position pos) {
+ MoveList moveList = getMoveListObj();
+ final long occupied = pos.whiteBB | pos.blackBB;
+ if (pos.whiteMove) {
+ long kingThreats = pos.pieceTypeBB[Piece.BKNIGHT] & BitBoard.knightAttacks[pos.wKingSq];
+ long rookPieces = pos.pieceTypeBB[Piece.BROOK] | pos.pieceTypeBB[Piece.BQUEEN];
+ if (rookPieces != 0)
+ kingThreats |= rookPieces & BitBoard.rookAttacks(pos.wKingSq, occupied);
+ long bishPieces = pos.pieceTypeBB[Piece.BBISHOP] | pos.pieceTypeBB[Piece.BQUEEN];
+ if (bishPieces != 0)
+ kingThreats |= bishPieces & BitBoard.bishopAttacks(pos.wKingSq, occupied);
+ kingThreats |= pos.pieceTypeBB[Piece.BPAWN] & BitBoard.wPawnAttacks[pos.wKingSq];
+ long validTargets = 0;
+ if ((kingThreats != 0) && ((kingThreats & (kingThreats-1)) == 0)) { // Exactly one attacking piece
+ int threatSq = BitBoard.numberOfTrailingZeros(kingThreats);
+ validTargets = kingThreats | BitBoard.squaresBetween[pos.wKingSq][threatSq];
+ }
+ validTargets |= pos.pieceTypeBB[Piece.BKING];
+ // Queen moves
+ long squares = pos.pieceTypeBB[Piece.WQUEEN];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied)) &
+ ~pos.whiteBB & validTargets;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Rook moves
+ squares = pos.pieceTypeBB[Piece.WROOK];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.rookAttacks(sq, occupied) & ~pos.whiteBB & validTargets;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Bishop moves
+ squares = pos.pieceTypeBB[Piece.WBISHOP];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.bishopAttacks(sq, occupied) & ~pos.whiteBB & validTargets;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // King moves
+ {
+ int sq = pos.getKingSq(true);
+ long m = BitBoard.kingAttacks[sq] & ~pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ }
+
+ // Knight moves
+ long knights = pos.pieceTypeBB[Piece.WKNIGHT];
+ while (knights != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(knights);
+ long m = BitBoard.knightAttacks[sq] & ~pos.whiteBB & validTargets;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ knights &= knights-1;
+ }
+
+ // Pawn moves
+ long pawns = pos.pieceTypeBB[Piece.WPAWN];
+ long m = (pawns << 8) & ~occupied;
+ if (addPawnMovesByMask(moveList, pos, m & validTargets, -8, true)) return moveList;
+ m = ((m & BitBoard.maskRow3) << 8) & ~occupied;
+ addPawnDoubleMovesByMask(moveList, pos, m & validTargets, -16);
+
+ int epSquare = pos.getEpSquare();
+ long epMask = (epSquare >= 0) ? (1L << epSquare) : 0L;
+ m = (pawns << 7) & BitBoard.maskAToGFiles & ((pos.blackBB & validTargets) | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, -7, true)) return moveList;
+
+ m = (pawns << 9) & BitBoard.maskBToHFiles & ((pos.blackBB & validTargets) | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, -9, true)) return moveList;
+ } else {
+ long kingThreats = pos.pieceTypeBB[Piece.WKNIGHT] & BitBoard.knightAttacks[pos.bKingSq];
+ long rookPieces = pos.pieceTypeBB[Piece.WROOK] | pos.pieceTypeBB[Piece.WQUEEN];
+ if (rookPieces != 0)
+ kingThreats |= rookPieces & BitBoard.rookAttacks(pos.bKingSq, occupied);
+ long bishPieces = pos.pieceTypeBB[Piece.WBISHOP] | pos.pieceTypeBB[Piece.WQUEEN];
+ if (bishPieces != 0)
+ kingThreats |= bishPieces & BitBoard.bishopAttacks(pos.bKingSq, occupied);
+ kingThreats |= pos.pieceTypeBB[Piece.WPAWN] & BitBoard.bPawnAttacks[pos.bKingSq];
+ long validTargets = 0;
+ if ((kingThreats != 0) && ((kingThreats & (kingThreats-1)) == 0)) { // Exactly one attacking piece
+ int threatSq = BitBoard.numberOfTrailingZeros(kingThreats);
+ validTargets = kingThreats | BitBoard.squaresBetween[pos.bKingSq][threatSq];
+ }
+ validTargets |= pos.pieceTypeBB[Piece.WKING];
+ // Queen moves
+ long squares = pos.pieceTypeBB[Piece.BQUEEN];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied)) &
+ ~pos.blackBB & validTargets;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Rook moves
+ squares = pos.pieceTypeBB[Piece.BROOK];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.rookAttacks(sq, occupied) & ~pos.blackBB & validTargets;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Bishop moves
+ squares = pos.pieceTypeBB[Piece.BBISHOP];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.bishopAttacks(sq, occupied) & ~pos.blackBB & validTargets;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // King moves
+ {
+ int sq = pos.getKingSq(false);
+ long m = BitBoard.kingAttacks[sq] & ~pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ }
+
+ // Knight moves
+ long knights = pos.pieceTypeBB[Piece.BKNIGHT];
+ while (knights != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(knights);
+ long m = BitBoard.knightAttacks[sq] & ~pos.blackBB & validTargets;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ knights &= knights-1;
+ }
+
+ // Pawn moves
+ long pawns = pos.pieceTypeBB[Piece.BPAWN];
+ long m = (pawns >>> 8) & ~occupied;
+ if (addPawnMovesByMask(moveList, pos, m & validTargets, 8, true)) return moveList;
+ m = ((m & BitBoard.maskRow6) >>> 8) & ~occupied;
+ addPawnDoubleMovesByMask(moveList, pos, m & validTargets, 16);
+
+ int epSquare = pos.getEpSquare();
+ long epMask = (epSquare >= 0) ? (1L << epSquare) : 0L;
+ m = (pawns >>> 9) & BitBoard.maskAToGFiles & ((pos.whiteBB & validTargets) | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, 9, true)) return moveList;
+
+ m = (pawns >>> 7) & BitBoard.maskBToHFiles & ((pos.whiteBB & validTargets) | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, 7, true)) return moveList;
+ }
+
+ /* Extra debug checks
+ {
+ ArrayList allMoves = pseudoLegalMoves(pos);
+ allMoves = MoveGen.removeIllegal(pos, allMoves);
+ HashSet evMoves = new HashSet();
+ for (Move m : moveList)
+ evMoves.add(TextIO.moveToUCIString(m));
+ for (Move m : allMoves)
+ if (!evMoves.contains(TextIO.moveToUCIString(m)))
+ throw new RuntimeException();
+ }
+ */
+
+ return moveList;
+ }
+
+ /** Generate captures, checks, and possibly some other moves that are too hard to filter out. */
+ public final MoveList pseudoLegalCapturesAndChecks(Position pos) {
+ MoveList moveList = getMoveListObj();
+ long occupied = pos.whiteBB | pos.blackBB;
+ if (pos.whiteMove) {
+ int bKingSq = pos.getKingSq(false);
+ long discovered = 0; // Squares that could generate discovered checks
+ long kRookAtk = BitBoard.rookAttacks(bKingSq, occupied);
+ if ((BitBoard.rookAttacks(bKingSq, occupied & ~kRookAtk) &
+ (pos.pieceTypeBB[Piece.WQUEEN] | pos.pieceTypeBB[Piece.WROOK])) != 0)
+ discovered |= kRookAtk;
+ long kBishAtk = BitBoard.bishopAttacks(bKingSq, occupied);
+ if ((BitBoard.bishopAttacks(bKingSq, occupied & ~kBishAtk) &
+ (pos.pieceTypeBB[Piece.WQUEEN] | pos.pieceTypeBB[Piece.WBISHOP])) != 0)
+ discovered |= kBishAtk;
+
+ // Queen moves
+ long squares = pos.pieceTypeBB[Piece.WQUEEN];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied));
+ if ((discovered & (1L<= 0) ? (1L << epSquare) : 0L;
+ long m = (pawns << 7) & BitBoard.maskAToGFiles & (pos.blackBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, -7, false)) return moveList;
+ m = (pawns << 9) & BitBoard.maskBToHFiles & (pos.blackBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, -9, false)) return moveList;
+
+ // Discovered checks and promotions
+ long pawnAll = discovered | BitBoard.maskRow7;
+ m = ((pawns & pawnAll) << 8) & ~(pos.whiteBB | pos.blackBB);
+ if (addPawnMovesByMask(moveList, pos, m, -8, false)) return moveList;
+ m = ((m & BitBoard.maskRow3) << 8) & ~(pos.whiteBB | pos.blackBB);
+ addPawnDoubleMovesByMask(moveList, pos, m, -16);
+
+ // Normal checks
+ m = ((pawns & ~pawnAll) << 8) & ~(pos.whiteBB | pos.blackBB);
+ if (addPawnMovesByMask(moveList, pos, m & BitBoard.bPawnAttacks[bKingSq], -8, false)) return moveList;
+ m = ((m & BitBoard.maskRow3) << 8) & ~(pos.whiteBB | pos.blackBB);
+ addPawnDoubleMovesByMask(moveList, pos, m & BitBoard.bPawnAttacks[bKingSq], -16);
+ } else {
+ int wKingSq = pos.getKingSq(true);
+ long discovered = 0; // Squares that could generate discovered checks
+ long kRookAtk = BitBoard.rookAttacks(wKingSq, occupied);
+ if ((BitBoard.rookAttacks(wKingSq, occupied & ~kRookAtk) &
+ (pos.pieceTypeBB[Piece.BQUEEN] | pos.pieceTypeBB[Piece.BROOK])) != 0)
+ discovered |= kRookAtk;
+ long kBishAtk = BitBoard.bishopAttacks(wKingSq, occupied);
+ if ((BitBoard.bishopAttacks(wKingSq, occupied & ~kBishAtk) &
+ (pos.pieceTypeBB[Piece.BQUEEN] | pos.pieceTypeBB[Piece.BBISHOP])) != 0)
+ discovered |= kBishAtk;
+
+ // Queen moves
+ long squares = pos.pieceTypeBB[Piece.BQUEEN];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied));
+ if ((discovered & (1L<= 0) ? (1L << epSquare) : 0L;
+ long m = (pawns >>> 9) & BitBoard.maskAToGFiles & (pos.whiteBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, 9, false)) return moveList;
+ m = (pawns >>> 7) & BitBoard.maskBToHFiles & (pos.whiteBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, 7, false)) return moveList;
+
+ // Discovered checks and promotions
+ long pawnAll = discovered | BitBoard.maskRow2;
+ m = ((pawns & pawnAll) >>> 8) & ~(pos.whiteBB | pos.blackBB);
+ if (addPawnMovesByMask(moveList, pos, m, 8, false)) return moveList;
+ m = ((m & BitBoard.maskRow6) >>> 8) & ~(pos.whiteBB | pos.blackBB);
+ addPawnDoubleMovesByMask(moveList, pos, m, 16);
+
+ // Normal checks
+ m = ((pawns & ~pawnAll) >>> 8) & ~(pos.whiteBB | pos.blackBB);
+ if (addPawnMovesByMask(moveList, pos, m & BitBoard.wPawnAttacks[wKingSq], 8, false)) return moveList;
+ m = ((m & BitBoard.maskRow6) >>> 8) & ~(pos.whiteBB | pos.blackBB);
+ addPawnDoubleMovesByMask(moveList, pos, m & BitBoard.wPawnAttacks[wKingSq], 16);
+ }
+
+ return moveList;
+ }
+
+ public final MoveList pseudoLegalCaptures(Position pos) {
+ MoveList moveList = getMoveListObj();
+ long occupied = pos.whiteBB | pos.blackBB;
+ if (pos.whiteMove) {
+ // Queen moves
+ long squares = pos.pieceTypeBB[Piece.WQUEEN];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied)) & pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Rook moves
+ squares = pos.pieceTypeBB[Piece.WROOK];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.rookAttacks(sq, occupied) & pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Bishop moves
+ squares = pos.pieceTypeBB[Piece.WBISHOP];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.bishopAttacks(sq, occupied) & pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Knight moves
+ long knights = pos.pieceTypeBB[Piece.WKNIGHT];
+ while (knights != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(knights);
+ long m = BitBoard.knightAttacks[sq] & pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ knights &= knights-1;
+ }
+
+ // King moves
+ int sq = pos.getKingSq(true);
+ long m = BitBoard.kingAttacks[sq] & pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+
+ // Pawn moves
+ long pawns = pos.pieceTypeBB[Piece.WPAWN];
+ m = (pawns << 8) & ~(pos.whiteBB | pos.blackBB);
+ m &= BitBoard.maskRow8;
+ if (addPawnMovesByMask(moveList, pos, m, -8, false)) return moveList;
+
+ int epSquare = pos.getEpSquare();
+ long epMask = (epSquare >= 0) ? (1L << epSquare) : 0L;
+ m = (pawns << 7) & BitBoard.maskAToGFiles & (pos.blackBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, -7, false)) return moveList;
+ m = (pawns << 9) & BitBoard.maskBToHFiles & (pos.blackBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, -9, false)) return moveList;
+ } else {
+ // Queen moves
+ long squares = pos.pieceTypeBB[Piece.BQUEEN];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied)) & pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Rook moves
+ squares = pos.pieceTypeBB[Piece.BROOK];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.rookAttacks(sq, occupied) & pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Bishop moves
+ squares = pos.pieceTypeBB[Piece.BBISHOP];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.bishopAttacks(sq, occupied) & pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Knight moves
+ long knights = pos.pieceTypeBB[Piece.BKNIGHT];
+ while (knights != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(knights);
+ long m = BitBoard.knightAttacks[sq] & pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ knights &= knights-1;
+ }
+
+ // King moves
+ int sq = pos.getKingSq(false);
+ long m = BitBoard.kingAttacks[sq] & pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+
+ // Pawn moves
+ long pawns = pos.pieceTypeBB[Piece.BPAWN];
+ m = (pawns >>> 8) & ~(pos.whiteBB | pos.blackBB);
+ m &= BitBoard.maskRow1;
+ if (addPawnMovesByMask(moveList, pos, m, 8, false)) return moveList;
+
+ int epSquare = pos.getEpSquare();
+ long epMask = (epSquare >= 0) ? (1L << epSquare) : 0L;
+ m = (pawns >>> 9) & BitBoard.maskAToGFiles & (pos.whiteBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, 9, false)) return moveList;
+ m = (pawns >>> 7) & BitBoard.maskBToHFiles & (pos.whiteBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, 7, false)) return moveList;
+ }
+ 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);
+ return sqAttacked(pos, kingSq);
+ }
+
+ /**
+ * Return the next piece in a given direction, starting from sq.
+ */
+ private static final int nextPiece(Position pos, int sq, int delta) {
+ while (true) {
+ sq += delta;
+ int p = pos.getPiece(sq);
+ if (p != Piece.EMPTY)
+ return p;
+ }
+ }
+
+ /** Like nextPiece(), but handles board edges. */
+ private static final int nextPieceSafe(Position pos, int sq, int delta) {
+ int dx = 0, dy = 0;
+ switch (delta) {
+ case 1: dx=1; dy=0; break;
+ case 9: dx=1; dy=1; break;
+ case 8: dx=0; dy=1; break;
+ case 7: dx=-1; dy=1; break;
+ case -1: dx=-1; dy=0; break;
+ case -9: dx=-1; dy=-1; break;
+ case -8: dx=0; dy=-1; break;
+ case -7: dx=1; dy=-1; break;
+ }
+ int x = Position.getX(sq);
+ int y = Position.getY(sq);
+ while (true) {
+ x += dx;
+ y += dy;
+ if ((x < 0) || (x > 7) || (y < 0) || (y > 7)) {
+ return Piece.EMPTY;
+ }
+ int p = pos.getPiece(Position.getSquare(x, y));
+ if (p != Piece.EMPTY)
+ return p;
+ }
+ }
+
+ /**
+ * Return true if making a move delivers check to the opponent
+ */
+ public static final boolean givesCheck(Position pos, Move m) {
+ boolean wtm = pos.whiteMove;
+ int oKingSq = pos.getKingSq(!wtm);
+ int oKing = wtm ? Piece.BKING : Piece.WKING;
+ int p = Piece.makeWhite(m.promoteTo == Piece.EMPTY ? pos.getPiece(m.from) : m.promoteTo);
+ int d1 = BitBoard.getDirection(m.to, oKingSq);
+ switch (d1) {
+ case 8: case -8: case 1: case -1: // Rook direction
+ if ((p == Piece.WQUEEN) || (p == Piece.WROOK))
+ if ((d1 != 0) && (MoveGen.nextPiece(pos, m.to, d1) == oKing))
+ return true;
+ break;
+ case 9: case 7: case -9: case -7: // Bishop direction
+ if ((p == Piece.WQUEEN) || (p == Piece.WBISHOP)) {
+ if ((d1 != 0) && (MoveGen.nextPiece(pos, m.to, d1) == oKing))
+ return true;
+ } else if (p == Piece.WPAWN) {
+ if (((d1 > 0) == wtm) && (pos.getPiece(m.to + d1) == oKing))
+ return true;
+ }
+ break;
+ default:
+ if (d1 != 0) { // Knight direction
+ if (p == Piece.WKNIGHT)
+ return true;
+ }
+ }
+ int d2 = BitBoard.getDirection(m.from, oKingSq);
+ if ((d2 != 0) && (d2 != d1) && (MoveGen.nextPiece(pos, m.from, d2) == oKing)) {
+ int p2 = MoveGen.nextPieceSafe(pos, m.from, -d2);
+ switch (d2) {
+ case 8: case -8: case 1: case -1: // Rook direction
+ if ((p2 == (wtm ? Piece.WQUEEN : Piece.BQUEEN)) ||
+ (p2 == (wtm ? Piece.WROOK : Piece.BROOK)))
+ return true;
+ break;
+ case 9: case 7: case -9: case -7: // Bishop direction
+ if ((p2 == (wtm ? Piece.WQUEEN : Piece.BQUEEN)) ||
+ (p2 == (wtm ? Piece.WBISHOP : Piece.BBISHOP)))
+ return true;
+ break;
+ }
+ }
+ if ((m.promoteTo != Piece.EMPTY) && (d1 != 0) && (d1 == d2)) {
+ switch (d1) {
+ case 8: case -8: case 1: case -1: // Rook direction
+ if ((p == Piece.WQUEEN) || (p == Piece.WROOK))
+ if ((d1 != 0) && (MoveGen.nextPiece(pos, m.from, d1) == oKing))
+ return true;
+ break;
+ case 9: case 7: case -9: case -7: // Bishop direction
+ if ((p == Piece.WQUEEN) || (p == Piece.WBISHOP)) {
+ if ((d1 != 0) && (MoveGen.nextPiece(pos, m.from, d1) == oKing))
+ return true;
+ }
+ break;
+ }
+ }
+ if (p == Piece.WKING) {
+ if (m.to - m.from == 2) { // O-O
+ if (MoveGen.nextPieceSafe(pos, m.from, -1) == oKing)
+ return true;
+ if (MoveGen.nextPieceSafe(pos, m.from + 1, wtm ? 8 : -8) == oKing)
+ return true;
+ } else if (m.to - m.from == -2) { // O-O-O
+ if (MoveGen.nextPieceSafe(pos, m.from, 1) == oKing)
+ return true;
+ if (MoveGen.nextPieceSafe(pos, m.from - 1, wtm ? 8 : -8) == oKing)
+ return true;
+ }
+ } else if (p == Piece.WPAWN) {
+ if (pos.getPiece(m.to) == Piece.EMPTY) {
+ int dx = Position.getX(m.to) - Position.getX(m.from);
+ if (dx != 0) { // en passant
+ int epSq = m.from + dx;
+ int d3 = BitBoard.getDirection(epSq, oKingSq);
+ switch (d3) {
+ case 9: case 7: case -9: case -7:
+ if (MoveGen.nextPiece(pos, epSq, d3) == oKing) {
+ int p2 = MoveGen.nextPieceSafe(pos, epSq, -d3);
+ if ((p2 == (wtm ? Piece.WQUEEN : Piece.BQUEEN)) ||
+ (p2 == (wtm ? Piece.WBISHOP : Piece.BBISHOP)))
+ return true;
+ }
+ break;
+ case 1:
+ if (MoveGen.nextPiece(pos, Math.max(epSq, m.from), d3) == oKing) {
+ int p2 = MoveGen.nextPieceSafe(pos, Math.min(epSq, m.from), -d3);
+ if ((p2 == (wtm ? Piece.WQUEEN : Piece.BQUEEN)) ||
+ (p2 == (wtm ? Piece.WROOK : Piece.BROOK)))
+ return true;
+ }
+ break;
+ case -1:
+ if (MoveGen.nextPiece(pos, Math.min(epSq, m.from), d3) == oKing) {
+ int p2 = MoveGen.nextPieceSafe(pos, Math.max(epSq, m.from), -d3);
+ if ((p2 == (wtm ? Piece.WQUEEN : Piece.BQUEEN)) ||
+ (p2 == (wtm ? Piece.WROOK : Piece.BROOK)))
+ return true;
+ }
+ break;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return true if the side to move can take the opponents king.
+ */
+ public static final boolean canTakeKing(Position pos) {
+ pos.setWhiteMove(!pos.whiteMove);
+ boolean ret = inCheck(pos);
+ pos.setWhiteMove(!pos.whiteMove);
+ return ret;
+ }
+
+ /**
+ * Return true if a square is attacked by the opposite side.
+ */
+ public static final boolean sqAttacked(Position pos, int sq) {
+ if (pos.whiteMove) {
+ if ((BitBoard.knightAttacks[sq] & pos.pieceTypeBB[Piece.BKNIGHT]) != 0)
+ return true;
+ if ((BitBoard.kingAttacks[sq] & pos.pieceTypeBB[Piece.BKING]) != 0)
+ return true;
+ if ((BitBoard.wPawnAttacks[sq] & pos.pieceTypeBB[Piece.BPAWN]) != 0)
+ return true;
+ long occupied = pos.whiteBB | pos.blackBB;
+ long bbQueen = pos.pieceTypeBB[Piece.BQUEEN];
+ if ((BitBoard.bishopAttacks(sq, occupied) & (pos.pieceTypeBB[Piece.BBISHOP] | bbQueen)) != 0)
+ return true;
+ if ((BitBoard.rookAttacks(sq, occupied) & (pos.pieceTypeBB[Piece.BROOK] | bbQueen)) != 0)
+ return true;
+ } else {
+ if ((BitBoard.knightAttacks[sq] & pos.pieceTypeBB[Piece.WKNIGHT]) != 0)
+ return true;
+ if ((BitBoard.kingAttacks[sq] & pos.pieceTypeBB[Piece.WKING]) != 0)
+ return true;
+ if ((BitBoard.bPawnAttacks[sq] & pos.pieceTypeBB[Piece.WPAWN]) != 0)
+ return true;
+ long occupied = pos.whiteBB | pos.blackBB;
+ long bbQueen = pos.pieceTypeBB[Piece.WQUEEN];
+ if ((BitBoard.bishopAttacks(sq, occupied) & (pos.pieceTypeBB[Piece.WBISHOP] | bbQueen)) != 0)
+ return true;
+ if ((BitBoard.rookAttacks(sq, occupied) & (pos.pieceTypeBB[Piece.WROOK] | bbQueen)) != 0)
+ 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 void removeIllegal(Position pos, MoveList moveList) {
+ int length = 0;
+ UndoInfo ui = new UndoInfo();
+
+ boolean isInCheck = inCheck(pos);
+ final long occupied = pos.whiteBB | pos.blackBB;
+ int kSq = pos.getKingSq(pos.whiteMove);
+ long kingAtks = BitBoard.rookAttacks(kSq, occupied) | BitBoard.bishopAttacks(kSq, occupied);
+ int epSquare = pos.getEpSquare();
+ if (isInCheck) {
+ kingAtks |= pos.pieceTypeBB[pos.whiteMove ? Piece.BKNIGHT : Piece.WKNIGHT];
+ for (int mi = 0; mi < moveList.size; mi++) {
+ Move m = moveList.m[mi];
+ boolean legal;
+ if ((m.from != kSq) && ((kingAtks & (1L<= 56) { // White promotion
+ setMove(moveList, sq0, sq, Piece.WQUEEN);
+ setMove(moveList, sq0, sq, Piece.WKNIGHT);
+ if (allPromotions) {
+ setMove(moveList, sq0, sq, Piece.WROOK);
+ setMove(moveList, sq0, sq, Piece.WBISHOP);
+ }
+ } else { // Black promotion
+ setMove(moveList, sq0, sq, Piece.BQUEEN);
+ setMove(moveList, sq0, sq, Piece.BKNIGHT);
+ if (allPromotions) {
+ setMove(moveList, sq0, sq, Piece.BROOK);
+ setMove(moveList, sq0, sq, Piece.BBISHOP);
+ }
+ }
+ promMask &= (promMask - 1);
+ }
+ while (mask != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(mask);
+ setMove(moveList, sq + delta, sq, Piece.EMPTY);
+ mask &= (mask - 1);
+ }
+ return false;
+ }
+
+ private final static void addPawnDoubleMovesByMask(MoveList moveList, Position pos,
+ long mask, int delta) {
+ while (mask != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(mask);
+ setMove(moveList, sq + delta, sq, Piece.EMPTY);
+ mask &= (mask - 1);
+ }
+ }
+
+ private final static boolean addMovesByMask(MoveList moveList, Position pos, int sq0, long mask) {
+ long oKingMask = pos.pieceTypeBB[pos.whiteMove ? Piece.BKING : Piece.WKING];
+ if ((mask & oKingMask) != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(mask & oKingMask);
+ moveList.size = 0;
+ setMove(moveList, sq0, sq, Piece.EMPTY);
+ return true;
+ }
+ while (mask != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(mask);
+ setMove(moveList, sq0, sq, Piece.EMPTY);
+ mask &= (mask - 1);
+ }
+ return false;
+ }
+
+ private final static void setMove(MoveList moveList, int from, int to, int promoteTo) {
+ Move m = moveList.m[moveList.size++];
+ m.from = from;
+ m.to = to;
+ m.promoteTo = promoteTo;
+ m.score = 0;
+ }
+
+ // Code to handle the Move cache.
+ private Object[] moveListCache = new Object[200];
+ private int moveListsInCache = 0;
+
+ private static final int MAX_MOVES = 256;
+
+ private final MoveList getMoveListObj() {
+ MoveList ml;
+ if (moveListsInCache > 0) {
+ ml = (MoveList)moveListCache[--moveListsInCache];
+ ml.size = 0;
+ } else {
+ ml = new MoveList();
+ for (int i = 0; i < MAX_MOVES; i++)
+ ml.m[i] = new Move(0, 0, Piece.EMPTY);
+ }
+ return ml;
+ }
+
+ /** Return all move objects in moveList to the move cache. */
+ public final void returnMoveList(MoveList moveList) {
+ if (moveListsInCache < moveListCache.length) {
+ moveListCache[moveListsInCache++] = moveList;
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/chess/Parameters.java b/buildSrc/src/main/java/chess/Parameters.java
new file mode 100644
index 0000000..92df884
--- /dev/null
+++ b/buildSrc/src/main/java/chess/Parameters.java
@@ -0,0 +1,166 @@
+package chess;
+
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class Parameters {
+ public static enum Type {
+ CHECK,
+ SPIN,
+ COMBO,
+ BUTTON,
+ STRING
+ }
+
+ public static class ParamBase {
+ public String name;
+ public Type type;
+ public boolean visible;
+ }
+
+ public static final class CheckParam extends ParamBase {
+ public boolean value;
+ public boolean defaultValue;
+ CheckParam(String name, boolean visible, boolean def) {
+ this.name = name;
+ this.type = Type.CHECK;
+ this.visible = visible;
+ this.value = def;
+ this.defaultValue = def;
+ }
+ }
+
+ public static final class SpinParam extends ParamBase {
+ public int minValue;
+ public int maxValue;
+ public int value;
+ public int defaultValue;
+ SpinParam(String name, boolean visible, int minV, int maxV, int def) {
+ this.name = name;
+ this.type = Type.SPIN;
+ this.visible = visible;
+ this.minValue = minV;
+ this.maxValue = maxV;
+ this.value = def;
+ this.defaultValue = def;
+ }
+ }
+
+ public static final class ComboParam extends ParamBase {
+ public String[] allowedValues;
+ public String value;
+ public String defaultValue;
+ ComboParam(String name, boolean visible, String[] allowed, String def) {
+ this.name = name;
+ this.type = Type.COMBO;
+ this.visible = visible;
+ this.allowedValues = allowed;
+ this.value = def;
+ this.defaultValue = def;
+ }
+ }
+
+ public static final class ButtonParam extends ParamBase {
+ ButtonParam(String name, boolean visible) {
+ this.name = name;
+ this.type = Type.BUTTON;
+ this.visible = visible;
+ }
+ }
+
+ public static final class StringParam extends ParamBase {
+ public String value;
+ public String defaultValue;
+ StringParam(String name, boolean visible, String def) {
+ this.name = name;
+ this.type = Type.STRING;
+ this.visible = visible;
+ this.value = def;
+ this.defaultValue = def;
+ }
+ }
+
+ public static Parameters instance() {
+ return inst;
+ }
+ public final String[] getParamNames() {
+ ArrayList parNames = new ArrayList();
+ for (Map.Entry e : params.entrySet())
+ if (e.getValue().visible)
+ parNames.add(e.getKey());
+ return parNames.toArray(new String[parNames.size()]);
+ }
+
+ public final ParamBase getParam(String name) {
+ return params.get(name);
+ }
+
+ private static final Parameters inst = new Parameters();
+ private Map params = new TreeMap();
+
+ private Parameters() {
+ addPar(new SpinParam("qV", false, -200, 200, 0));
+ addPar(new SpinParam("rV", false, -200, 200, 0));
+ addPar(new SpinParam("bV", false, -200, 200, 0));
+ addPar(new SpinParam("nV", false, -200, 200, 0));
+ addPar(new SpinParam("pV", false, -200, 200, 0));
+ }
+
+ private final void addPar(ParamBase p) {
+ params.put(p.name.toLowerCase(), p);
+ }
+
+ final boolean getBooleanPar(String name) {
+ return ((CheckParam)params.get(name.toLowerCase())).value;
+ }
+ final int getIntPar(String name) {
+ int ret = ((SpinParam)params.get(name.toLowerCase())).value;
+ return ret;
+ }
+ final String getStringPar(String name) {
+ return ((StringParam)params.get(name.toLowerCase())).value;
+ }
+
+ public final void set(String name, String value) {
+ ParamBase p = params.get(name.toLowerCase());
+ if (p == null)
+ return;
+ switch (p.type) {
+ case CHECK: {
+ CheckParam cp = (CheckParam)p;
+ if (value.toLowerCase().equals("true"))
+ cp.value = true;
+ else if (value.toLowerCase().equals("false"))
+ cp.value = false;
+ break;
+ }
+ case SPIN: {
+ SpinParam sp = (SpinParam)p;
+ try {
+ int val = Integer.parseInt(value);
+ if ((val >= sp.minValue) && (val <= sp.maxValue))
+ sp.value = val;
+ } catch (NumberFormatException ex) {
+ }
+ break;
+ }
+ case COMBO: {
+ ComboParam cp = (ComboParam)p;
+ for (String allowed : cp.allowedValues)
+ if (allowed.toLowerCase().equals(value.toLowerCase())) {
+ cp.value = allowed;
+ break;
+ }
+ break;
+ }
+ case BUTTON:
+ break;
+ case STRING: {
+ StringParam sp = (StringParam)p;
+ sp.value = value;
+ break;
+ }
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/chess/Piece.java b/buildSrc/src/main/java/chess/Piece.java
new file mode 100644
index 0000000..b93441b
--- /dev/null
+++ b/buildSrc/src/main/java/chess/Piece.java
@@ -0,0 +1,54 @@
+/*
+ CuckooChess - A java 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 chess;
+
+/** Constants for different piece types. */
+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 final boolean isWhite(int pType) {
+ return pType < BKING;
+ }
+ public static final int makeWhite(int pType) {
+ return pType < BKING ? pType : pType - (BKING - WKING);
+ }
+ public static final int makeBlack(int pType) {
+ return ((pType > EMPTY) && (pType < BKING)) ? pType + (BKING - WKING) : pType;
+ }
+}
diff --git a/buildSrc/src/main/java/chess/Position.java b/buildSrc/src/main/java/chess/Position.java
new file mode 100644
index 0000000..3635fcd
--- /dev/null
+++ b/buildSrc/src/main/java/chess/Position.java
@@ -0,0 +1,645 @@
+/*
+ CuckooChess - A java 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 chess;
+
+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.
+ */
+public class Position {
+ public int[] squares;
+
+ // Bitboards
+ public long[] pieceTypeBB;
+ public long whiteBB, blackBB;
+
+ // Piece square table scores
+ public short[] psScore1, psScore2;
+
+ 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. */
+ int halfMoveClock;
+
+ /** Game move number, starting from 1. */
+ public int fullMoveCounter;
+
+ private long hashKey; // Cached Zobrist hash key
+ private long pHashKey;
+ public int wKingSq, bKingSq; // Cached king positions
+ public int wMtrl; // Total value of all white pieces and pawns
+ public int bMtrl; // Total value of all black pieces and pawns
+ public int wMtrlPawns; // Total value of all white pawns
+ public int bMtrlPawns; // Total value of all black pawns
+
+ /** Initialize board to empty position. */
+ public Position() {
+ squares = new int[64];
+ for (int i = 0; i < 64; i++)
+ squares[i] = Piece.EMPTY;
+ pieceTypeBB = new long[Piece.nPieceTypes];
+ psScore1 = new short[Piece.nPieceTypes];
+ psScore2 = new short[Piece.nPieceTypes];
+ for (int i = 0; i < Piece.nPieceTypes; i++) {
+ pieceTypeBB[i] = 0L;
+ psScore1[i] = 0;
+ psScore2[i] = 0;
+ }
+ whiteBB = blackBB = 0L;
+ whiteMove = true;
+ castleMask = 0;
+ epSquare = -1;
+ halfMoveClock = 0;
+ fullMoveCounter = 1;
+ hashKey = computeZobristHash();
+ wKingSq = bKingSq = -1;
+ wMtrl = bMtrl = -Evaluate.kV;
+ wMtrlPawns = bMtrlPawns = 0;
+ }
+
+ public Position(Position other) {
+ squares = new int[64];
+ for (int i = 0; i < 64; i++)
+ squares[i] = other.squares[i];
+ pieceTypeBB = new long[Piece.nPieceTypes];
+ psScore1 = new short[Piece.nPieceTypes];
+ psScore2 = new short[Piece.nPieceTypes];
+ for (int i = 0; i < Piece.nPieceTypes; i++) {
+ pieceTypeBB[i] = other.pieceTypeBB[i];
+ psScore1[i] = other.psScore1[i];
+ psScore2[i] = other.psScore2[i];
+ }
+ whiteBB = other.whiteBB;
+ blackBB = other.blackBB;
+ whiteMove = other.whiteMove;
+ castleMask = other.castleMask;
+ epSquare = other.epSquare;
+ halfMoveClock = other.halfMoveClock;
+ fullMoveCounter = other.fullMoveCounter;
+ hashKey = other.hashKey;
+ pHashKey = other.pHashKey;
+ wKingSq = other.wKingSq;
+ bKingSq = other.bKingSq;
+ wMtrl = other.wMtrl;
+ bMtrl = other.bMtrl;
+ wMtrlPawns = other.wMtrlPawns;
+ bMtrlPawns = other.bMtrlPawns;
+ }
+
+ @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;
+ if (pHashKey != other.pHashKey)
+ return false;
+ return true;
+ }
+ @Override
+ public int hashCode() {
+ return (int)hashKey;
+ }
+
+ /**
+ * Return Zobrist hash value for the current position.
+ * Everything except the move counters are included in the hash value.
+ */
+ public final long zobristHash() {
+ return hashKey;
+ }
+ public final long pawnZobristHash() {
+ return pHashKey;
+ }
+ public final long kingZobristHash() {
+ return psHashKeys[Piece.WKING][wKingSq] ^
+ psHashKeys[Piece.BKING][bKingSq];
+ }
+
+ public final long historyHash() {
+ long ret = hashKey;
+ if (halfMoveClock >= 80) {
+ ret ^= moveCntKeys[Math.min(halfMoveClock, 100)];
+ }
+ return ret;
+ }
+
+ /**
+ * 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 occupying a square. */
+ public final int getPiece(int square) {
+ return squares[square];
+ }
+
+ /** Move a non-pawn piece to an empty square. */
+ private final void movePieceNotPawn(int from, int to) {
+ final int piece = squares[from];
+ hashKey ^= psHashKeys[piece][from];
+ hashKey ^= psHashKeys[piece][to];
+ hashKey ^= psHashKeys[Piece.EMPTY][from];
+ hashKey ^= psHashKeys[Piece.EMPTY][to];
+
+ squares[from] = Piece.EMPTY;
+ squares[to] = piece;
+
+ final long sqMaskF = 1L << from;
+ final long sqMaskT = 1L << to;
+ pieceTypeBB[piece] &= ~sqMaskF;
+ pieceTypeBB[piece] |= sqMaskT;
+ if (Piece.isWhite(piece)) {
+ whiteBB &= ~sqMaskF;
+ whiteBB |= sqMaskT;
+ if (piece == Piece.WKING)
+ wKingSq = to;
+ } else {
+ blackBB &= ~sqMaskF;
+ blackBB |= sqMaskT;
+ if (piece == Piece.BKING)
+ bKingSq = to;
+ }
+
+ psScore1[piece] += Evaluate.psTab1[piece][to] - Evaluate.psTab1[piece][from];
+ psScore2[piece] += Evaluate.psTab2[piece][to] - Evaluate.psTab2[piece][from];
+ }
+
+ /** Set a square to a piece value. */
+ public final void setPiece(int square, int piece) {
+ int removedPiece = squares[square];
+ squares[square] = piece;
+
+ // Update hash key
+ hashKey ^= psHashKeys[removedPiece][square];
+ hashKey ^= psHashKeys[piece][square];
+
+ // Update bitboards
+ final long sqMask = 1L << square;
+ pieceTypeBB[removedPiece] &= ~sqMask;
+ pieceTypeBB[piece] |= sqMask;
+
+ if (removedPiece != Piece.EMPTY) {
+ int pVal = Evaluate.pieceValue[removedPiece];
+ if (Piece.isWhite(removedPiece)) {
+ wMtrl -= pVal;
+ whiteBB &= ~sqMask;
+ if (removedPiece == Piece.WPAWN) {
+ wMtrlPawns -= pVal;
+ pHashKey ^= psHashKeys[Piece.WPAWN][square];
+ }
+ } else {
+ bMtrl -= pVal;
+ blackBB &= ~sqMask;
+ if (removedPiece == Piece.BPAWN) {
+ bMtrlPawns -= pVal;
+ pHashKey ^= psHashKeys[Piece.BPAWN][square];
+ }
+ }
+ }
+
+ if (piece != Piece.EMPTY) {
+ int pVal = Evaluate.pieceValue[piece];
+ if (Piece.isWhite(piece)) {
+ wMtrl += pVal;
+ whiteBB |= sqMask;
+ if (piece == Piece.WPAWN) {
+ wMtrlPawns += pVal;
+ pHashKey ^= psHashKeys[Piece.WPAWN][square];
+ }
+ if (piece == Piece.WKING)
+ wKingSq = square;
+ } else {
+ bMtrl += pVal;
+ blackBB |= sqMask;
+ if (piece == Piece.BPAWN) {
+ bMtrlPawns += pVal;
+ pHashKey ^= psHashKeys[Piece.BPAWN][square];
+ }
+ if (piece == Piece.BKING)
+ bKingSq = square;
+ }
+ }
+
+ // Update piece/square table scores
+ psScore1[removedPiece] -= Evaluate.psTab1[removedPiece][square];
+ psScore2[removedPiece] -= Evaluate.psTab2[removedPiece][square];
+ psScore1[piece] += Evaluate.psTab1[piece][square];
+ psScore2[piece] += Evaluate.psTab2[piece][square];
+ }
+
+ /**
+ * Set a square to a piece value.
+ * Special version that only updates enough of the state for the SEE function to be happy.
+ */
+ public final void setSEEPiece(int square, int piece) {
+ int removedPiece = squares[square];
+
+ // Update board
+ squares[square] = piece;
+
+ // Update bitboards
+ long sqMask = 1L << square;
+ pieceTypeBB[removedPiece] &= ~sqMask;
+ pieceTypeBB[piece] |= sqMask;
+ if (removedPiece != Piece.EMPTY) {
+ if (Piece.isWhite(removedPiece))
+ whiteBB &= ~sqMask;
+ else
+ blackBB &= ~sqMask;
+ }
+ if (piece != Piece.EMPTY) {
+ if (Piece.isWhite(piece))
+ whiteBB |= sqMask;
+ else
+ blackBB |= sqMask;
+ }
+ }
+
+ /** 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 white) {
+ return white ? wKingSq : bKingSq;
+ }
+
+ /** 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;
+
+ final int p = squares[move.from];
+ int capP = squares[move.to];
+ long fromMask = 1L << move.from;
+
+ int prevEpSquare = epSquare;
+ setEpSquare(-1);
+
+ if ((capP != Piece.EMPTY) || (((pieceTypeBB[Piece.WPAWN] | pieceTypeBB[Piece.BPAWN]) & fromMask) != 0)) {
+ halfMoveClock = 0;
+
+ // Handle en passant and epSquare
+ 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);
+ }
+ }
+
+ if (((pieceTypeBB[Piece.WKING] | pieceTypeBB[Piece.BKING]) & fromMask) != 0) {
+ 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));
+ }
+ }
+
+ // Perform move
+ setPiece(move.from, Piece.EMPTY);
+ // Handle promotion
+ if (move.promoteTo != Piece.EMPTY) {
+ setPiece(move.to, move.promoteTo);
+ } else {
+ setPiece(move.to, p);
+ }
+ } else {
+ halfMoveClock++;
+
+ // Handle castling
+ if (((pieceTypeBB[Piece.WKING] | pieceTypeBB[Piece.BKING]) & fromMask) != 0) {
+ int k0 = move.from;
+ if (move.to == k0 + 2) { // O-O
+ movePieceNotPawn(k0 + 3, k0 + 1);
+ } else if (move.to == k0 - 2) { // O-O-O
+ movePieceNotPawn(k0 - 4, k0 - 1);
+ }
+ 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));
+ }
+ }
+
+ // Perform move
+ movePieceNotPawn(move.from, move.to);
+ }
+ if (wtm) {
+ // Update castling rights when rook moves
+ if ((BitBoard.maskCorners & fromMask) != 0) {
+ if (p == Piece.WROOK)
+ removeCastleRights(move.from);
+ }
+ if ((BitBoard.maskCorners & (1L << move.to)) != 0) {
+ if (capP == Piece.BROOK)
+ removeCastleRights(move.to);
+ }
+ } else {
+ fullMoveCounter++;
+ // Update castling rights when rook moves
+ if ((BitBoard.maskCorners & fromMask) != 0) {
+ if (p == Piece.BROOK)
+ removeCastleRights(move.from);
+ }
+ if ((BitBoard.maskCorners & (1L << move.to)) != 0) {
+ if (capP == Piece.WROOK)
+ removeCastleRights(move.to);
+ }
+ }
+
+ hashKey ^= whiteHashKey;
+ whiteMove = !wtm;
+ }
+
+ public final void unMakeMove(Move move, UndoInfo ui) {
+ hashKey ^= whiteHashKey;
+ whiteMove = !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;
+ if (p == king) {
+ int k0 = move.from;
+ if (move.to == k0 + 2) { // O-O
+ movePieceNotPawn(k0 + 1, k0 + 3);
+ } else if (move.to == k0 - 2) { // O-O-O
+ movePieceNotPawn(k0 - 1, k0 - 4);
+ }
+ }
+
+ // 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);
+ }
+ }
+ }
+
+ /**
+ * Apply a move to the current position.
+ * Special version that only updates enough of the state for the SEE function to be happy.
+ */
+ public final void makeSEEMove(Move move, UndoInfo ui) {
+ ui.capturedPiece = squares[move.to];
+
+ int p = squares[move.from];
+
+ // Handle en passant
+ if (move.to == epSquare) {
+ if (p == Piece.WPAWN) {
+ setSEEPiece(move.to - 8, Piece.EMPTY);
+ } else if (p == Piece.BPAWN) {
+ setSEEPiece(move.to + 8, Piece.EMPTY);
+ }
+ }
+
+ // Perform move
+ setSEEPiece(move.from, Piece.EMPTY);
+ setSEEPiece(move.to, p);
+ whiteMove = !whiteMove;
+ }
+
+ public final void unMakeSEEMove(Move move, UndoInfo ui) {
+ whiteMove = !whiteMove;
+ int p = squares[move.to];
+ setSEEPiece(move.from, p);
+ setSEEPiece(move.to, ui.capturedPiece);
+
+ // Handle en passant
+ if (move.to == epSquare) {
+ if (p == Piece.WPAWN) {
+ setSEEPiece(move.to - 8, Piece.BPAWN);
+ } else if (p == Piece.BPAWN) {
+ setSEEPiece(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 ------------------ */
+
+ static final long[][] psHashKeys; // [piece][square]
+ private static final long whiteHashKey;
+ private static final long[] castleHashKeys; // [castleMask]
+ private static final long[] epHashKeys; // [epFile + 1] (epFile==-1 for no ep)
+ private static final long[] moveCntKeys; // [min(halfMoveClock, 100)]
+
+ static {
+ psHashKeys = new long[Piece.nPieceTypes][64];
+ castleHashKeys = new long[16];
+ epHashKeys = new long[9];
+ moveCntKeys = new long[101];
+ 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++);
+ for (int mc = 0; mc < moveCntKeys.length; mc++)
+ moveCntKeys[mc] = 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 ((p == Piece.WPAWN) || (p == Piece.BPAWN))
+ pHashKey ^= 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) + (whiteMove ? "white\n" : "black\n") +
+ Long.toHexString(zobristHash()) + "\n";
+ }
+}
diff --git a/buildSrc/src/main/java/chess/TextIO.java b/buildSrc/src/main/java/chess/TextIO.java
new file mode 100644
index 0000000..0446cec
--- /dev/null
+++ b/buildSrc/src/main/java/chess/TextIO.java
@@ -0,0 +1,610 @@
+/*
+ CuckooChess - A java 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 chess;
+
+import java.util.Locale;
+
+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");
+ }
+
+ // 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");
+ }
+ }
+ if (words[1].length() == 0) {
+ throw new ChessParseError("Invalid side");
+ }
+ 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.setCastleMask(castleMask);
+
+ 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.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");
+ }
+ if (bKings != 1) {
+ throw new ChessParseError("Black must have exactly one king");
+ }
+
+ // 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");
+ }
+
+ fixupEPSquare(pos);
+
+ return pos;
+ }
+
+ /** 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) {
+ MoveGen.MoveList moves = MoveGen.instance.pseudoLegalMoves(pos);
+ MoveGen.removeIllegal(pos, moves);
+ boolean epValid = false;
+ for (int mi = 0; mi < moves.size; mi++) {
+ Move m = moves.m[mi];
+ 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) {
+ MoveGen.MoveList moves = MoveGen.instance.pseudoLegalMoves(pos);
+ MoveGen.removeIllegal(pos, moves);
+ return moveToString(pos, move, longForm, moves);
+ }
+ private static final String moveToString(Position pos, Move move, boolean longForm, MoveGen.MoveList moves) {
+ 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 black 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;
+ for (int mi = 0; mi < moves.size; mi++) {
+ Move m = moves.m[mi];
+ if (m == null)
+ break;
+ 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();
+ if (MoveGen.givesCheck(pos, move)) {
+ pos.makeMove(move, ui);
+ MoveGen.MoveList nextMoves = MoveGen.instance.pseudoLegalMoves(pos);
+ MoveGen.removeIllegal(pos, nextMoves);
+ if (nextMoves.size == 0) {
+ ret.append('#');
+ } else {
+ ret.append('+');
+ }
+ pos.unMakeMove(move, ui);
+ }
+
+ return ret.toString();
+ }
+
+ /** 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 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;
+ }
+
+ 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;
+ }
+ }
+
+ /**
+ * Convert a chess move string to a Move object.
+ * Any prefix of the string representation of a valid move counts as a legal move string,
+ * as long as the string only matches one valid move.
+ */
+ public static final Move stringToMove(Position pos, String strMove) {
+ strMove = strMove.replaceAll("=", "");
+ Move move = null;
+ if (strMove.length() == 0)
+ return move;
+ MoveGen.MoveList moves = MoveGen.instance.pseudoLegalMoves(pos);
+ MoveGen.removeIllegal(pos, moves);
+ {
+ char lastChar = strMove.charAt(strMove.length() - 1);
+ if ((lastChar == '#') || (lastChar == '+')) {
+ MoveGen.MoveList subMoves = new MoveGen.MoveList();
+ int len = 0;
+ for (int mi = 0; mi < moves.size; mi++) {
+ Move m = moves.m[mi];
+ String str1 = TextIO.moveToString(pos, m, true, moves);
+ if (str1.charAt(str1.length() - 1) == lastChar) {
+ subMoves.m[len++] = m;
+ }
+ }
+ subMoves.size = len;
+ moves = subMoves;
+ strMove = normalizeMoveString(strMove);
+ }
+ }
+
+ for (int i = 0; i < 2; i++) {
+ // Search for full match
+ for (int mi = 0; mi < moves.size; mi++) {
+ Move m = moves.m[mi];
+ String str1 = normalizeMoveString(TextIO.moveToString(pos, m, true, moves));
+ String str2 = normalizeMoveString(TextIO.moveToString(pos, m, false, moves));
+ if (i == 0) {
+ if (strMove.equals(str1) || strMove.equals(str2)) {
+ return m;
+ }
+ } else {
+ if (strMove.toLowerCase().equals(str1.toLowerCase()) ||
+ strMove.toLowerCase().equals(str2.toLowerCase())) {
+ return m;
+ }
+ }
+ }
+ }
+
+ for (int i = 0; i < 2; i++) {
+ // Search for unique substring match
+ for (int mi = 0; mi < moves.size; mi++) {
+ Move m = moves.m[mi];
+ String str1 = normalizeMoveString(TextIO.moveToString(pos, m, true));
+ String str2 = normalizeMoveString(TextIO.moveToString(pos, m, false));
+ boolean match;
+ if (i == 0) {
+ match = (str1.startsWith(strMove) || str2.startsWith(strMove));
+ } else {
+ match = (str1.toLowerCase().startsWith(strMove.toLowerCase()) ||
+ str2.toLowerCase().startsWith(strMove.toLowerCase()));
+ }
+ if (match) {
+ if (move != null) {
+ return null; // More than one match, not ok
+ } else {
+ move = m;
+ }
+ }
+ }
+ if (move != null)
+ return move;
+ }
+ return move;
+ }
+
+ /**
+ * 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(Locale.US, "%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();
+ }
+
+ /**
+ * Convert move string to lower case and remove special check/mate symbols.
+ */
+ private static final String normalizeMoveString(String str) {
+ if (str.length() > 0) {
+ char lastChar = str.charAt(str.length() - 1);
+ if ((lastChar == '#') || (lastChar == '+')) {
+ str = str.substring(0, str.length() - 1);
+ }
+ }
+ return str;
+ }
+
+ 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 "";
+ }
+}
diff --git a/buildSrc/src/main/java/chess/UndoInfo.java b/buildSrc/src/main/java/chess/UndoInfo.java
new file mode 100644
index 0000000..9055f66
--- /dev/null
+++ b/buildSrc/src/main/java/chess/UndoInfo.java
@@ -0,0 +1,30 @@
+/*
+ CuckooChess - A java 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 chess;
+
+/**
+ * Contains enough information to undo a previous move.
+ * Set by makeMove(). Used by unMakeMove().
+ */
+public class UndoInfo {
+ int capturedPiece;
+ int castleMask;
+ int epSquare;
+ int halfMoveClock;
+}
diff --git a/buildSrc/src/main/java/org/petero/droidfish/FileUtil.java b/buildSrc/src/main/java/org/petero/droidfish/FileUtil.java
new file mode 100644
index 0000000..5988581
--- /dev/null
+++ b/buildSrc/src/main/java/org/petero/droidfish/FileUtil.java
@@ -0,0 +1,96 @@
+/*
+ DroidFish - An Android chess program.
+ Copyright (C) 2016 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.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+
+public class FileUtil {
+ /** Read a text file. Return string array with one string per line. */
+ public static String[] readFile(String filename) throws IOException {
+ ArrayList ret = new ArrayList();
+ InputStream inStream = new FileInputStream(filename);
+ InputStreamReader inFile = new InputStreamReader(inStream, "UTF-8");
+ BufferedReader inBuf = new BufferedReader(inFile);
+ String line;
+ while ((line = inBuf.readLine()) != null)
+ ret.add(line);
+ inBuf.close();
+ return ret.toArray(new String[ret.size()]);
+ }
+
+ /** Read all data from an input stream. Return null if IO error. */
+ public static String readFromStream(InputStream is) {
+ InputStreamReader isr;
+ try {
+ isr = new InputStreamReader(is, "UTF-8");
+ BufferedReader br = new BufferedReader(isr);
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = br.readLine()) != null) {
+ sb.append(line);
+ sb.append('\n');
+ }
+ br.close();
+ return sb.toString();
+ } catch (UnsupportedEncodingException e) {
+ return null;
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /** Read data from input stream and write to file. */
+ public static void writeFile(InputStream is, String outFile) throws IOException {
+ OutputStream os = new FileOutputStream(outFile);
+ try {
+ byte[] buffer = new byte[16384];
+ while (true) {
+ int len = is.read(buffer);
+ if (len <= 0)
+ break;
+ os.write(buffer, 0, len);
+ }
+ } finally {
+ os.close();
+ }
+ }
+
+ /** Return the length of a file, or -1 if length can not be determined. */
+ public static final long getFileLength(String filename) {
+ try {
+ RandomAccessFile raf = new RandomAccessFile(filename, "r");
+ try {
+ return raf.length();
+ } finally {
+ raf.close();
+ }
+ } catch (IOException ex) {
+ return -1;
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/org/petero/droidfish/PGNOptions.java b/buildSrc/src/main/java/org/petero/droidfish/PGNOptions.java
new file mode 100644
index 0000000..3f7ef04
--- /dev/null
+++ b/buildSrc/src/main/java/org/petero/droidfish/PGNOptions.java
@@ -0,0 +1,65 @@
+/*
+ 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 {
+ /** Pieces displayed as English letters. */
+ public static final int PT_ENGLISH = 0;
+ /** Pieces displayed as local language letters. */
+ public static final int PT_LOCAL = 1;
+ /** Piece displayed in figurine notation, by using UniCode characters
+ * and a special font. */
+ public static final int PT_FIGURINE = 2;
+
+ public static class Viewer {
+ public boolean variations;
+ public boolean comments;
+ public boolean nag;
+ public boolean headers;
+ public int pieceType;
+ }
+ 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 int pieceType;
+ }
+
+ public Viewer view;
+ public Import imp;
+ public Export exp;
+
+ public PGNOptions() {
+ view = new Viewer();
+ imp = new Import();
+ exp = new Export();
+ exp.moveNrAfterNag = true;
+ exp.pieceType = PT_ENGLISH;
+ }
+}
diff --git a/DroidFish/src/org/petero/droidfish/buildtools/EcoBuilder.java b/buildSrc/src/main/java/org/petero/droidfish/buildtools/EcoBuilder.java
similarity index 98%
rename from DroidFish/src/org/petero/droidfish/buildtools/EcoBuilder.java
rename to buildSrc/src/main/java/org/petero/droidfish/buildtools/EcoBuilder.java
index 1e2dc5d..7a3348f 100644
--- a/DroidFish/src/org/petero/droidfish/buildtools/EcoBuilder.java
+++ b/buildSrc/src/main/java/org/petero/droidfish/buildtools/EcoBuilder.java
@@ -35,6 +35,9 @@ public class EcoBuilder {
public static void main(String[] args) throws Throwable {
String ecoPgnFile = args[0];
String ecoDatFile = args[1];
+ main2(ecoPgnFile, ecoDatFile);
+ }
+ public static void main2(String ecoPgnFile, String ecoDatFile) throws Throwable {
(new EcoBuilder()).createECOFile(ecoPgnFile, ecoDatFile);
}
diff --git a/DroidFish/src/org/petero/droidfish/buildtools/eco.pgn b/buildSrc/src/main/java/org/petero/droidfish/buildtools/eco.pgn
similarity index 100%
rename from DroidFish/src/org/petero/droidfish/buildtools/eco.pgn
rename to buildSrc/src/main/java/org/petero/droidfish/buildtools/eco.pgn
diff --git a/buildSrc/src/main/java/org/petero/droidfish/gamelogic/ChessParseError.java b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/ChessParseError.java
new file mode 100644
index 0000000..e65a019
--- /dev/null
+++ b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/ChessParseError.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.gamelogic;
+
+/** Exception class to represent parse errors in FEN or algebraic notation. */
+public class ChessParseError extends Exception {
+ private static final long serialVersionUID = -6051856171275301175L;
+
+ public Position pos;
+ public int resourceId = -1;
+
+ public ChessParseError(String msg) {
+ super(msg);
+ pos = null;
+ }
+ public ChessParseError(String msg, Position pos) {
+ super(msg);
+ this.pos = pos;
+ }
+
+ public ChessParseError(int resourceId) {
+ super("");
+ pos = null;
+ this.resourceId = resourceId;
+ }
+
+ public ChessParseError(int resourceId, Position pos) {
+ super("");
+ this.pos = pos;
+ this.resourceId = resourceId;
+ }
+}
diff --git a/buildSrc/src/main/java/org/petero/droidfish/gamelogic/Game.java b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/Game.java
new file mode 100644
index 0000000..30276e9
--- /dev/null
+++ b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/Game.java
@@ -0,0 +1,543 @@
+/*
+ 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.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.petero.droidfish.PGNOptions;
+import org.petero.droidfish.gamelogic.GameTree.Node;
+
+public class Game {
+ boolean pendingDrawOffer;
+ public GameTree tree;
+ TimeControl timeController;
+ private boolean gamePaused;
+ /** If true, add new moves as mainline moves. */
+ private AddMoveBehavior addMoveBehavior;
+
+ private PgnToken.PgnTokenReceiver gameTextListener;
+
+ public Game(PgnToken.PgnTokenReceiver gameTextListener, TimeControlData tcData) {
+ this.gameTextListener = gameTextListener;
+ timeController = new TimeControl();
+ timeController.setTimeControl(tcData);
+ gamePaused = false;
+ newGame();
+ tree.setTimeControlData(tcData);
+ }
+
+ /** De-serialize from input stream. */
+ final void readFromStream(DataInputStream dis, int version) throws IOException, ChessParseError {
+ tree.readFromStream(dis, version);
+ if (version >= 3)
+ timeController.readFromStream(dis, version);
+ updateTimeControl(true);
+ }
+
+ /** Serialize to output stream. */
+ final synchronized void writeToStream(DataOutputStream dos) throws IOException {
+ tree.writeToStream(dos);
+ timeController.writeToStream(dos);
+ }
+
+ public final void setGamePaused(boolean gamePaused) {
+ if (gamePaused != this.gamePaused) {
+ this.gamePaused = gamePaused;
+ updateTimeControl(false);
+ }
+ }
+
+ /** Controls behavior when a new move is added to the game.*/
+ public static enum AddMoveBehavior {
+ /** Add the new move first in the list of variations. */
+ ADD_FIRST,
+ /** Add the new move last in the list of variations. */
+ ADD_LAST,
+ /** Remove all variations not matching the new move. */
+ REPLACE
+ }
+
+ /** Set whether new moves are entered as mainline moves or variations. */
+ public final void setAddFirst(AddMoveBehavior amb) {
+ addMoveBehavior = amb;
+ }
+
+ /** Sets start position and discards the whole game tree. */
+ final void setPos(Position pos) {
+ tree.setStartPos(new Position(pos));
+ updateTimeControl(false);
+ }
+
+ /** Set game state from a PGN string. */
+ final public boolean readPGN(String pgn, PGNOptions options) throws ChessParseError {
+ boolean ret = tree.readPGN(pgn, options);
+ if (ret) {
+ TimeControlData tcData = tree.getTimeControlData();
+ if (tcData != null)
+ timeController.setTimeControl(tcData);
+ updateTimeControl(tcData != null);
+ }
+ 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 Pair where first item is true if str was understood, false otherwise.
+ * Second item is move played, or null if no move was played. */
+ public final Pair processString(String str) {
+ if (getGameState() != GameState.ALIVE)
+ return new Pair(false, null);
+ if (str.startsWith("draw ")) {
+ String drawCmd = str.substring(str.indexOf(" ") + 1);
+ Move m = handleDrawCmd(drawCmd, true);
+ return new Pair(true, m);
+ } else if (str.equals("resign")) {
+ addToGameTree(new Move(0, 0, 0), "resign");
+ return new Pair(true, null);
+ }
+
+ Move m = TextIO.UCIstringToMove(str);
+ if (m != null)
+ if (!TextIO.isValid(currPos(), m))
+ m = null;
+ if (m == null) {
+ m = TextIO.stringToMove(currPos(), str);
+ if (!TextIO.isValid(currPos(), m))
+ m = null;
+ }
+ if (m == null)
+ return new Pair(false, null);
+
+ addToGameTree(m, pendingDrawOffer ? "draw offer" : "");
+ return new Pair(true, m);
+ }
+
+ /** Try claim a draw using a command string. Does not play the move involved
+ * in the draw claim if the draw claim is invalid. */
+ public final void tryClaimDraw(String str) {
+ if (str.startsWith("draw ")) {
+ String drawCmd = str.substring(str.indexOf(" ") + 1);
+ handleDrawCmd(drawCmd, false);
+ }
+ }
+
+ private final void addToGameTree(Move m, String playerAction) {
+ if (m.equals(new Move(0, 0, 0))) { // Don't create more than one game-ending 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);
+ }
+
+ boolean movePresent = false;
+ int varNo;
+ {
+ ArrayList varMoves = tree.variations();
+ int nVars = varMoves.size();
+ if (addMoveBehavior == AddMoveBehavior.REPLACE) {
+ boolean modified = false;
+ for (int i = nVars-1; i >= 0; i--) {
+ if (!m.equals(varMoves.get(i))) {
+ tree.deleteVariation(i);
+ modified = true;
+ }
+ }
+ if (modified) {
+ varMoves = tree.variations();
+ nVars = varMoves.size();
+ }
+ }
+ Boolean gameEndingMove = null;
+ for (varNo = 0; varNo < nVars; varNo++) {
+ if (varMoves.get(varNo).equals(m)) {
+ boolean match = true;
+ if (playerAction.isEmpty()) {
+ if (gameEndingMove == null)
+ gameEndingMove = gameEndingMove(m);
+ if (!gameEndingMove) {
+ tree.goForward(varNo, false);
+ match = tree.getGameState() == GameState.ALIVE;
+ tree.goBack();
+ }
+ }
+ if (match) {
+ movePresent = true;
+ break;
+ }
+ }
+ }
+ }
+ if (!movePresent) {
+ String moveStr = TextIO.moveToUCIString(m);
+ varNo = tree.addMove(moveStr, playerAction, 0, "", "");
+ }
+ int newPos = 0;
+ if (addMoveBehavior == AddMoveBehavior.ADD_LAST)
+ newPos = varNo;
+ tree.reorderVariation(varNo, newPos);
+ tree.goForward(newPos);
+ int remaining = timeController.moveMade(System.currentTimeMillis(), !gamePaused);
+ tree.setRemainingTime(remaining);
+ updateTimeControl(true);
+ pendingDrawOffer = false;
+ }
+
+ /** Return true if move "m" in the current position ends the game (mate or stalemate). */
+ private boolean gameEndingMove(Move m) {
+ Position pos = currPos();
+ UndoInfo ui = new UndoInfo();
+ pos.makeMove(m, ui);
+ boolean gameEnd = MoveGen.instance.legalMoves(pos).isEmpty();
+ pos.unMakeMove(m, ui);
+ return gameEnd;
+ }
+
+ private final void updateTimeControl(boolean discardElapsed) {
+ Position currPos = currPos();
+ int move = currPos.fullMoveCounter;
+ boolean wtm = currPos.whiteMove;
+ if (discardElapsed || (move != timeController.currentMove) || (wtm != timeController.whiteToMove)) {
+ int whiteBaseTime = tree.getRemainingTime(true, timeController.getInitialTime(true));
+ int blackBaseTime = tree.getRemainingTime(false, timeController.getInitialTime(false));
+ timeController.setCurrentMove(move, wtm, whiteBaseTime, blackBaseTime);
+ }
+ long now = System.currentTimeMillis();
+ boolean stopTimer = gamePaused || (getGameState() != GameState.ALIVE);
+ if (!stopTimer) {
+ try {
+ if (TextIO.readFEN(TextIO.startPosFEN).equals(currPos))
+ stopTimer = true;
+ } catch (ChessParseError e) {
+ }
+ }
+ if (stopTimer) {
+ timeController.stopTimer(now);
+ } else {
+ timeController.startTimer(now);
+ }
+ }
+
+ public final String getDrawInfo(boolean localized) {
+ return tree.getGameStateInfo(localized);
+ }
+
+ /**
+ * 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;
+ }
+
+ /** Get number of variations in current game position. */
+ public final int numVariations() {
+ if (tree.currentNode == tree.rootNode)
+ return 1;
+ tree.goBack();
+ int nChildren = tree.variations().size();
+ tree.goForward(-1);
+ return nChildren;
+ }
+
+ /** Get current variation in current position. */
+ public final int currVariation() {
+ if (tree.currentNode == tree.rootNode)
+ return 0;
+ tree.goBack();
+ int defChild = tree.currentNode.defaultChild;
+ tree.goForward(-1);
+ return defChild;
+ }
+
+ /** Go to a new variation in the game tree. */
+ 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);
+ }
+
+ /** Move current variation up/down in the game tree. */
+ public final void moveVariation(int delta) {
+ int nBack = 0;
+ boolean found = false;
+ while (tree.currentNode != tree.rootNode) {
+ tree.goBack();
+ nBack++;
+ if (((delta < 0) && tree.currentNode.defaultChild > 0) ||
+ ((delta > 0) && tree.currentNode.defaultChild < tree.variations().size() - 1)) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ 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);
+ nBack--;
+ }
+ while (nBack > 0) {
+ tree.goForward(-1);
+ nBack--;
+ }
+ pendingDrawOffer = false;
+ updateTimeControl(true);
+ }
+
+ /** Return true if the current variation can be moved up/down. */
+ public final boolean canMoveVariation(int delta) {
+ int nBack = 0;
+ boolean found = false;
+ while (tree.currentNode != tree.rootNode) {
+ tree.goBack();
+ nBack++;
+ if (((delta < 0) && tree.currentNode.defaultChild > 0) ||
+ ((delta > 0) && tree.currentNode.defaultChild < tree.variations().size() - 1)) {
+ found = true;
+ break;
+ }
+ }
+ while (nBack > 0) {
+ tree.goForward(-1);
+ nBack--;
+ }
+ return found;
+ }
+
+ /** Delete whole game sub-tree rooted at current position. */
+ 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);
+ }
+ }
+
+ /** Go to given node in game tree.
+ * @return True if current node changed, false otherwise. */
+ public final boolean goNode(Node node) {
+ if (!tree.goNode(node))
+ return false;
+ pendingDrawOffer = false;
+ updateTimeControl(true);
+ return true;
+ }
+
+ public final void newGame() {
+ tree = new GameTree(gameTextListener);
+ 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 Move handleDrawCmd(String drawCmd, boolean playDrawMove) {
+ Move ret = null;
+ 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, false);
+ addToGameTree(new Move(0, 0, 0), playerAction);
+ } else {
+ pendingDrawOffer = true;
+ if (m != null && playDrawMove) {
+ ret = processString(ms).second;
+ }
+ }
+ } else if (drawCmd.startsWith("offer ")) {
+ pendingDrawOffer = true;
+ String ms = drawCmd.substring(drawCmd.indexOf(" ") + 1);
+ if (TextIO.stringToMove(pos, ms) != null) {
+ ret = processString(ms).second;
+ }
+ } else if (drawCmd.equals("accept")) {
+ if (haveDrawOffer())
+ addToGameTree(new Move(0, 0, 0), "draw accept");
+ }
+ return ret;
+ }
+}
diff --git a/buildSrc/src/main/java/org/petero/droidfish/gamelogic/GameTree.java b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/GameTree.java
new file mode 100644
index 0000000..77e6870
--- /dev/null
+++ b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/GameTree.java
@@ -0,0 +1,1693 @@
+/*
+ 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.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 java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.petero.droidfish.PGNOptions;
+import org.petero.droidfish.gamelogic.Game.GameState;
+import org.petero.droidfish.gamelogic.TimeControlData.TimeControlField;
+
+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.
+
+ public Position startPos;
+ private String timeControl, whiteTimeControl, blackTimeControl;
+
+ // Non-standard tags
+ static private final class TagPair {
+ String tagName;
+ String tagValue;
+ }
+ private List tagPairs;
+
+ public Node rootNode;
+ public Node currentNode;
+ public Position currentPos; // Cached value. Computable from "currentNode".
+
+ private final PgnToken.PgnTokenReceiver gameStateListener;
+
+ /** Creates an empty GameTree starting at the standard start position.
+ * @param gameStateListener Optional tree change listener.
+ */
+ 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(Locale.US, "%04d.%02d.%02d", year, month, day);
+ }
+ round = "?";
+ white = "?";
+ black = "?";
+ startPos = pos;
+ timeControl = "?";
+ whiteTimeControl = "?";
+ blackTimeControl = "?";
+ tagPairs = new ArrayList();
+ rootNode = new Node();
+ currentNode = rootNode;
+ currentPos = new Position(startPos);
+ updateListener();
+ }
+
+ private final void updateListener() {
+ if (gameStateListener != null)
+ gameStateListener.clear();
+ }
+
+ /** PgnTokenReceiver 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) {
+ }
+ }
+
+ /** Update moveStrLocal in all game nodes. */
+ public final void translateMoves() {
+ List currPath = new ArrayList();
+ while (currentNode != rootNode) {
+ Node child = currentNode;
+ goBack();
+ int childNum = currentNode.children.indexOf(child);
+ currPath.add(childNum);
+ }
+ translateMovesHelper();
+ for (int i = currPath.size() - 1; i >= 0; i--)
+ goForward(currPath.get(i), false);
+ }
+
+ private final void translateMovesHelper() {
+ ArrayList currPath = new ArrayList();
+ currPath.add(0);
+ while (!currPath.isEmpty()) {
+ int last = currPath.size() - 1;
+ int currChild = currPath.get(last);
+ if (currChild == 0) {
+ ArrayList moves = MoveGen.instance.legalMoves(currentPos);
+ currentNode.verifyChildren(currentPos, moves);
+ int nc = currentNode.children.size();
+ for (int i = 0; i < nc; i++) {
+ Node child = currentNode.children.get(i);
+ child.moveStrLocal = TextIO.moveToString(currentPos, child.move, false, true, moves);
+ }
+ }
+ int nc = currentNode.children.size();
+ if (currChild < nc) {
+ goForward(currChild, false);
+ currPath.add(0);
+ } else {
+ currPath.remove(last);
+ last--;
+ if (last >= 0) {
+ currPath.set(last, currPath.get(last) + 1);
+ goBack();
+ }
+ }
+ }
+ }
+
+ /** Export game tree in PGN format. */
+ public final String toPGN(PGNOptions options) {
+ PgnText pgnText = new PgnText();
+ options.exp.pgnPromotions = true;
+ options.exp.pieceType = PGNOptions.PT_ENGLISH;
+ pgnTreeWalker(options, pgnText);
+ return pgnText.getPgnString();
+ }
+
+ /** Walks the game tree in PGN export order. */
+ public final void pgnTreeWalker(PGNOptions options, PgnToken.PgnTokenReceiver out) {
+ String pgnResultString = getPGNResultStringMainLine();
+
+ // 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);
+ if (!whiteTimeControl.equals("?"))
+ addTagPair(out, "WhiteTimeControl", whiteTimeControl);
+ if (!blackTimeControl.equals("?"))
+ addTagPair(out, "BlackTimeControl", blackTimeControl);
+
+ // 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) || c == '\u00a0') {
+ // 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);
+
+ if (tagPairs.size() == 0) {
+ gameRoot.verifyChildren(TextIO.readFEN(TextIO.startPosFEN));
+ if (gameRoot.children.size() == 0)
+ return false;
+ }
+
+ // Store parsed data in GameTree
+ 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 if (name.equals("WhiteTimeControl")) {
+ whiteTimeControl = val;
+ } else if (name.equals("BlackTimeControl")) {
+ blackTimeControl = 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)
+ addResult(result);
+ // Go back to the root
+ while (currentNode != rootNode)
+ goBack();
+ }
+
+ updateListener();
+ return true;
+ }
+
+ /** Add game result to the tree. currentNode must be at the end of the main line. */
+ private void addResult(String result) {
+ 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, "", "");
+ }
+ }
+
+ /** Serialize to output stream. */
+ public final void writeToStream(DataOutputStream dos) throws IOException {
+ 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);
+ dos.writeUTF(whiteTimeControl);
+ dos.writeUTF(blackTimeControl);
+ 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);
+ ArrayList pathFromRoot = currentNode.getPathFromRoot();
+ int pathLen = pathFromRoot.size();
+ dos.writeInt(pathLen);
+ for (int i = 0; i < pathLen; i++)
+ dos.writeInt(pathFromRoot.get(i));
+ }
+
+ /** De-serialize from input stream. */
+ public final void readFromStream(DataInputStream dis, int version) throws IOException, ChessParseError {
+ 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();
+ if (version >= 2) {
+ whiteTimeControl = dis.readUTF();
+ blackTimeControl = dis.readUTF();
+ } else {
+ whiteTimeControl = "?";
+ blackTimeControl = "?";
+ }
+ 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());
+
+ 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);
+ }
+ }
+
+ /** Go to given node in game tree.
+ * @return True if current node changed, false otherwise. */
+ public final boolean goNode(Node node) {
+ if (node == currentNode)
+ return false;
+ ArrayList path = node.getPathFromRoot();
+ while (currentNode != rootNode)
+ goBack();
+ for (Integer c : path)
+ goForward(c);
+ return true;
+ }
+
+ /** List of possible continuation moves. */
+ public final ArrayList variations() {
+ if (currentNode.verifyChildren(currentPos))
+ updateListener();
+ ArrayList 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);
+ ArrayList moves = null;
+ if (move == null) {
+ moves = MoveGen.instance.legalMoves(currentPos);
+ move = TextIO.stringToMove(currentPos, moveStr, moves);
+ }
+ if (move == null)
+ return -1;
+ if (moves == null)
+ moves = MoveGen.instance.legalMoves(currentPos);
+ node.moveStr = TextIO.moveToString(currentPos, move, false, false, moves);
+ node.moveStrLocal = TextIO.moveToString(currentPos, move, false, true, moves);
+ 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) {
+ final 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().legalMoves(pos);
+ 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(boolean localized) {
+ 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();
+ }
+ if (localized) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < ret.length(); i++) {
+ int p = Piece.EMPTY;
+ switch (ret.charAt(i)) {
+ case 'Q': p = Piece.WQUEEN; break;
+ case 'R': p = Piece.WROOK; break;
+ case 'B': p = Piece.WBISHOP; break;
+ case 'N': p = Piece.WKNIGHT; break;
+ case 'K': p = Piece.WKING; break;
+ case 'P': p = Piece.WPAWN; break;
+ }
+ if (p == Piece.EMPTY)
+ sb.append(ret.charAt(i));
+ else
+ sb.append(TextIO.pieceToCharLocalized(p));
+ }
+ ret = sb.toString();
+ }
+ return ret;
+ }
+
+ /** Get PGN result string corresponding to the current position. */
+ 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;
+ }
+
+ /** Evaluate PGN result string at the end of the main line. */
+ public final String getPGNResultStringMainLine() {
+ List currPath = new ArrayList();
+ while (currentNode != rootNode) {
+ Node child = currentNode;
+ goBack();
+ int childNum = currentNode.children.indexOf(child);
+ currPath.add(childNum);
+ }
+ while (variations().size() > 0)
+ goForward(0, false);
+ String res = getPGNResultString();
+ while (currentNode != rootNode)
+ goBack();
+ for (int i = currPath.size() - 1; i >= 0; i--)
+ goForward(currPath.get(i), false);
+ return res;
+ }
+
+ 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 in root node.
+ String moveStrLocal; // Localized version of moveStr
+ public 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 ArrayList children;
+
+ public Node() {
+ this.moveStr = "";
+ this.moveStrLocal = "";
+ 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.moveStrLocal = 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;
+ }
+
+ public Node getParent() {
+ return parent;
+ }
+
+ /** nodePos must represent the same position as this Node object. */
+ private final boolean verifyChildren(Position nodePos) {
+ return verifyChildren(nodePos, null);
+ }
+ private final boolean verifyChildren(Position nodePos, ArrayList moves) {
+ boolean anyToRemove = false;
+ for (Node child : children) {
+ if (child.move == null) {
+ if (moves == null)
+ moves = MoveGen.instance.legalMoves(nodePos);
+ Move move = TextIO.stringToMove(nodePos, child.moveStr, moves);
+ if (move != null) {
+ child.moveStr = TextIO.moveToString(nodePos, move, false, false, moves);
+ child.moveStrLocal = TextIO.moveToString(nodePos, move, false, true, moves);
+ child.move = move;
+ child.ui = new UndoInfo();
+ } else {
+ anyToRemove = true;
+ }
+ }
+ }
+ if (anyToRemove) {
+ ArrayList validChildren = new ArrayList();
+ for (Node child : children)
+ if (child.move != null)
+ validChildren.add(child);
+ children = validChildren;
+ }
+ return anyToRemove;
+ }
+
+ final ArrayList getPathFromRoot() {
+ ArrayList ret = new ArrayList(64);
+ Node node = this;
+ while (node.parent != null) {
+ ret.add(node.getChildNo());
+ node = node.parent;
+ }
+ Collections.reverse(ret);
+ return ret;
+ }
+
+ /** Return this node's position in the parent node child list. */
+ public final int getChildNo() {
+ Node p = parent;
+ for (int i = 0; i < p.children.size(); i++)
+ if (p.children.get(i) == this)
+ return i;
+ throw new RuntimeException();
+ }
+
+ 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();
+ node.moveStrLocal = node.moveStr;
+ 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 (or display text) 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;
+ if (options.exp.pieceType == PGNOptions.PT_ENGLISH) {
+ str = moveStr;
+ if (options.exp.pgnPromotions && (move != null) && (move.promoteTo != Piece.EMPTY))
+ str = TextIO.pgnPromotion(str);
+ } else {
+ str = moveStrLocal;
+ }
+ 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;
+ nodeToAdd.moveStrLocal = 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;
+ }
+ }
+ }
+
+ /** Set PGN header tags and values. Setting a non-required
+ * tag to null causes it to be removed.
+ * @return True if game result changes, false otherwise. */
+ boolean setHeaders(Map headers) {
+ boolean resultChanged = false;
+ for (Entry entry : headers.entrySet()) {
+ String tag = entry.getKey();
+ String val = entry.getValue();
+ 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 if (tag.equals("Result")) {
+ List currPath = new ArrayList();
+ while (currentNode != rootNode) {
+ Node child = currentNode;
+ goBack();
+ int childNum = currentNode.children.indexOf(child);
+ currPath.add(childNum);
+ }
+ while (variations().size() > 0)
+ goForward(0, false);
+ if (!val.equals(getPGNResultString())) {
+ resultChanged = true;
+ GameState state = getGameState();
+ switch (state) {
+ case ALIVE:
+ case DRAW_50:
+ case DRAW_AGREE:
+ case DRAW_REP:
+ case RESIGN_BLACK:
+ case RESIGN_WHITE:
+ currentNode.playerAction = "";
+ if ("--".equals(currentNode.moveStr)) {
+ Node child = currentNode;
+ goBack();
+ int childNum = currentNode.children.indexOf(child);
+ deleteVariation(childNum);
+ }
+ addResult(val);
+ break;
+ default:
+ break;
+ }
+ }
+ while (currentNode != rootNode)
+ goBack();
+ for (int i = currPath.size() - 1; i >= 0; i--)
+ goForward(currPath.get(i), false);
+ } else {
+ if (val != null) {
+ boolean found = false;
+ for (TagPair t : tagPairs) {
+ if (t.tagName.equals(tag)) {
+ t.tagValue = val;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ TagPair tp = new TagPair();
+ tp.tagName = tag;
+ tp.tagValue = val;
+ tagPairs.add(tp);
+ }
+ } else {
+ for (int i = 0; i < tagPairs.size(); i++) {
+ if (tagPairs.get(i).tagName.equals(tag)) {
+ tagPairs.remove(i);
+ break;
+ }
+ }
+ }
+ }
+ }
+ return resultChanged;
+ }
+
+ /** Get PGN header tags and values. */
+ public void getHeaders(Map headers) {
+ headers.put("Event", event);
+ headers.put("Site", site);
+ headers.put("Date", date);
+ headers.put("Round", round);
+ headers.put("White", white);
+ headers.put("Black", black);
+ headers.put("Result", getPGNResultStringMainLine());
+ if (!timeControl.equals("?"))
+ headers.put("TimeControl", timeControl);
+ if (!whiteTimeControl.equals("?"))
+ headers.put("WhiteTimeControl", whiteTimeControl);
+ if (!blackTimeControl.equals("?"))
+ headers.put("BlackTimeControl", blackTimeControl);
+ for (int i = 0; i < tagPairs.size(); i++) {
+ TagPair tp = tagPairs.get(i);
+ headers.put(tp.tagName, tp.tagValue);
+ }
+ }
+
+ private ArrayList stringToTCFields(String tcStr) {
+ String[] fields = tcStr.split(":");
+ int nf = fields.length;
+ ArrayList ret = new ArrayList(nf);
+ for (int i = 0; i < nf; i++) {
+ String f = fields[i].trim();
+ if (f.equals("?") || f.equals("-") || f.contains("*")) {
+ // Not supported
+ } else {
+ try {
+ int moves = 0;
+ int time = 0;
+ int inc = 0;
+ int idx = f.indexOf('/');
+ if (idx > 0)
+ moves = Integer.parseInt(f.substring(0, idx).trim());
+ if (idx >= 0)
+ f = f.substring(idx+1);
+ idx = f.indexOf('+');
+ if (idx >= 0) {
+ if (idx > 0)
+ time = (int)(Double.parseDouble(f.substring(0, idx).trim())*1e3);
+ if (idx >= 0)
+ f = f.substring(idx+1);
+ inc = (int)(Double.parseDouble(f.trim())*1e3);
+ } else {
+ time = (int)(Double.parseDouble(f.trim())*1e3);
+ }
+ ret.add(new TimeControlField(time, moves, inc));
+ } catch (NumberFormatException ex) {
+ // Invalid syntax, ignore
+ }
+ }
+ }
+ return ret;
+ }
+
+ private String tcFieldsToString(ArrayList tcFields) {
+ StringBuilder sb = new StringBuilder();
+ int nf = tcFields.size();
+ for (int i = 0; i < nf; i++) {
+ if (i > 0)
+ sb.append(':');
+ TimeControlField t = tcFields.get(i);
+ if (t.movesPerSession > 0) {
+ sb.append(t.movesPerSession);
+ sb.append('/');
+ }
+ sb.append(t.timeControl / 1000);
+ int ms = t.timeControl % 1000;
+ if (ms > 0) {
+ sb.append('.');
+ sb.append(String.format(Locale.US, "%03d", ms));
+ }
+ if (t.increment > 0) {
+ sb.append('+');
+ sb.append(t.increment / 1000);
+ ms = t.increment % 1000;
+ if (ms > 0) {
+ sb.append('.');
+ sb.append(String.format(Locale.US, "%03d", ms));
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ /** Get time control data, or null if not present. */
+ public TimeControlData getTimeControlData() {
+ if (!whiteTimeControl.equals("?") && !blackTimeControl.equals("?")) {
+ ArrayList tcW = stringToTCFields(whiteTimeControl);
+ ArrayList tcB = stringToTCFields(blackTimeControl);
+ if (!tcW.isEmpty() && !tcB.isEmpty()) {
+ TimeControlData tcData = new TimeControlData();
+ tcData.tcW = tcW;
+ tcData.tcB = tcB;
+ return tcData;
+ }
+ }
+ if (!timeControl.equals("?")) {
+ ArrayList tc = stringToTCFields(timeControl);
+ if (!tc.isEmpty()) {
+ TimeControlData tcData = new TimeControlData();
+ tcData.tcW = tc;
+ tcData.tcB = tc;
+ return tcData;
+ }
+ }
+ return null;
+ }
+
+ public void setTimeControlData(TimeControlData tcData) {
+ if (tcData.isSymmetric()) {
+ timeControl = tcFieldsToString(tcData.tcW);
+ whiteTimeControl = "?";
+ blackTimeControl = "?";
+ } else {
+ whiteTimeControl = tcFieldsToString(tcData.tcW);
+ blackTimeControl = tcFieldsToString(tcData.tcB);
+ timeControl = "?";
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/org/petero/droidfish/gamelogic/Move.java b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/Move.java
new file mode 100644
index 0000000..3cb93e2
--- /dev/null
+++ b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/Move.java
@@ -0,0 +1,76 @@
+/*
+ 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 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;
+ }
+
+ /** Create object from compressed representation. */
+ public static Move fromCompressed(int cm) {
+ return new Move((cm >> 10) & 63, (cm >> 4) & 63, cm & 15);
+ }
+
+ @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 getCompressedMove();
+ }
+
+ /** Get move as a 16-bit value. */
+ public int getCompressedMove() {
+ return (from * 64 + to) * 16 + promoteTo;
+ }
+
+ /** Useful for debugging. */
+ public final String toString() {
+ return TextIO.moveToUCIString(this);
+ }
+}
diff --git a/buildSrc/src/main/java/org/petero/droidfish/gamelogic/MoveGen.java b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/MoveGen.java
new file mode 100644
index 0000000..fcdbe62
--- /dev/null
+++ b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/MoveGen.java
@@ -0,0 +1,313 @@
+/*
+ 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;
+
+
+public class MoveGen {
+ public static MoveGen instance;
+ static {
+ instance = new MoveGen();
+ }
+
+ /** Generate and return a list of legal moves. */
+ public final ArrayList legalMoves(Position pos) {
+ ArrayList moveList = pseudoLegalMoves(pos);
+ moveList = MoveGen.removeIllegal(pos, moveList);
+ return moveList;
+ }
+
+ /**
+ * Generate and return a list of pseudo-legal moves.
+ * Pseudo-legal means that the moves don't necessarily defend from check threats.
+ */
+ public final ArrayList pseudoLegalMoves(Position pos) {
+ ArrayList moveList = new ArrayList(60);
+ 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)) {
+ moveList.clear();
+ 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)) {
+ moveList.clear();
+ 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) {
+ moveList.clear();
+ 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;
+ }
+
+ private static final Move getMoveObj(int from, int to, int promoteTo) {
+ return new Move(from, to, promoteTo);
+ }
+}
diff --git a/buildSrc/src/main/java/org/petero/droidfish/gamelogic/Pair.java b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/Pair.java
new file mode 100644
index 0000000..47fed86
--- /dev/null
+++ b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/Pair.java
@@ -0,0 +1,30 @@
+/*
+ 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 helper class that makes it possible to return two values from a function. */
+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/buildSrc/src/main/java/org/petero/droidfish/gamelogic/PgnToken.java b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/PgnToken.java
new file mode 100644
index 0000000..a34333a
--- /dev/null
+++ b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/PgnToken.java
@@ -0,0 +1,63 @@
+/*
+ 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;
+ }
+
+ /** PGN parser visitor interface. */
+ public interface PgnTokenReceiver {
+ /** If this method returns false, the object needs a full re-initialization, using clear() and processToken(). */
+ boolean isUpToDate();
+
+ /** Clear object state. */
+ void clear();
+
+ /** Update object state with one token from a PGN game. */
+ void processToken(GameTree.Node node, int type, String token);
+
+ /** Change current move number. */
+ void setCurrent(GameTree.Node node);
+ }
+}
diff --git a/buildSrc/src/main/java/org/petero/droidfish/gamelogic/Piece.java b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/Piece.java
new file mode 100644
index 0000000..22936ff
--- /dev/null
+++ b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/Piece.java
@@ -0,0 +1,90 @@
+/*
+ DroidFish - An Android chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+ Copyright (C) 2012 Leo Mayer
+
+ 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. */
+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;
+
+ // Unicode for color neutral chess pieces
+ public static final char NOTATION_KING = 0xe050;
+ public static final char NOTATION_QUEEN = 0xe051;
+ public static final char NOTATION_ROOK = 0xe052;
+ public static final char NOTATION_BISHOP = 0xe053;
+ public static final char NOTATION_KNIGHT = 0xe054;
+ public static final char NOTATION_PAWN = 0xe055;
+
+ // Unicode for white chess pieces
+ public static final char WHITE_KING = 0x2654;
+ public static final char WHITE_QUEEN = 0x2655;
+ public static final char WHITE_ROOK = 0x2656;
+ public static final char WHITE_BISHOP = 0x2657;
+ public static final char WHITE_KNIGHT = 0x2658;
+ public static final char WHITE_PAWN = 0x2659;
+
+ // Unicode for black chess pieces
+ public static final char BLACK_KING = 0x265A;
+ public static final char BLACK_QUEEN = 0x265B;
+ public static final char BLACK_ROOK = 0x265C;
+ public static final char BLACK_BISHOP = 0x265D;
+ public static final char BLACK_KNIGHT = 0x265E;
+ public static final char BLACK_PAWN = 0x265F;
+
+ /**
+ * 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;
+ }
+ public static int swapColor(int pType) {
+ if (pType == EMPTY)
+ return EMPTY;
+ return isWhite(pType) ? pType + (BKING - WKING) : pType - (BKING - WKING);
+ }
+
+ /** Converts the piece into a character for the material diff. */
+ public final static char toUniCode(int p) {
+ // As we assume, the coding of the pieces is sequential, lets do some math.
+ return (char)(WHITE_KING + p - 1);
+ }
+}
diff --git a/buildSrc/src/main/java/org/petero/droidfish/gamelogic/Position.java b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/Position.java
new file mode 100644
index 0000000..d05ecfa
--- /dev/null
+++ b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/Position.java
@@ -0,0 +1,435 @@
+/*
+ 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.
+ */
+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 occupying 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;
+ }
+
+ /** Count total number of pieces. */
+ public final int nPieces() {
+ int ret = 0;
+ for (int sq = 0; sq < 64; sq++)
+ if (squares[sq] != Piece.EMPTY)
+ 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/buildSrc/src/main/java/org/petero/droidfish/gamelogic/TextIO.java b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/TextIO.java
new file mode 100644
index 0000000..9557301
--- /dev/null
+++ b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/TextIO.java
@@ -0,0 +1,804 @@
+/*
+ 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 java.util.Locale;
+
+import org.petero.droidfish.PGNOptions;
+
+
+/** Handle conversion of positions and moves to/from text format. */
+public class TextIO {
+ static public final String startPosFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
+
+ /** Localized version of "P N B R Q K". */
+ private static String[] pieceNames = null;
+
+ /** Set localized piece names. */
+ public static final void setPieceNames(String pieceNames) {
+ String[] pn = pieceNames.split(" ");
+ if (pn.length == 6)
+ TextIO.pieceNames = pn;
+ }
+
+ /** Parse a FEN string and return a chess Position object. */
+ public static final Position readFEN(String fen) throws ChessParseError {
+ fen = fen.trim();
+ Position pos = new Position();
+ String[] words = fen.split(" ");
+ if (words.length < 2) {
+ throw new ChessParseError("R.string.err_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("R.string.err_invalid_piece, pos");
+ }
+ }
+
+ if (words[1].length() > 0) {
+ boolean wtm;
+ switch (words[1].charAt(0)) {
+ case 'w': wtm = true; break;
+ case 'b': wtm = false; break;
+ default: throw new ChessParseError("R.string.err_invalid_side", pos);
+ }
+ pos.setWhiteMove(wtm);
+ } else {
+ throw new ChessParseError("R.string.err_invalid_side", pos);
+ }
+
+ // 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("R.string.err_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("R.string.err_invalid_en_passant_square", pos);
+ int epSq = getSquare(epString);
+ if (epSq != -1) {
+ if (pos.whiteMove) {
+ if ((Position.getY(epSq) != 5) || (pos.getPiece(epSq) != Piece.EMPTY) ||
+ (pos.getPiece(epSq - 8) != Piece.BPAWN))
+ epSq = -1;
+ } else {
+ if ((Position.getY(epSq) != 2) || (pos.getPiece(epSq) != Piece.EMPTY) ||
+ (pos.getPiece(epSq + 8) != Piece.WPAWN))
+ epSq = -1;
+ }
+ pos.setEpSquare(epSq);
+ }
+ }
+ }
+
+ 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[] nPieces = new int[Piece.nPieceTypes];
+ for (int i = 0; i < Piece.nPieceTypes; i++)
+ nPieces[i] = 0;
+ for (int x = 0; x < 8; x++)
+ for (int y = 0; y < 8; y++)
+ nPieces[pos.getPiece(Position.getSquare(x, y))]++;
+ if (nPieces[Piece.WKING] != 1)
+ throw new ChessParseError("R.string.err_white_num_kings", pos);
+ if (nPieces[Piece.BKING] != 1)
+ throw new ChessParseError("R.string.err_black_num_kings", pos);
+
+ // White must not have too many pieces
+ int maxWPawns = 8;
+ maxWPawns -= Math.max(0, nPieces[Piece.WKNIGHT] - 2);
+ maxWPawns -= Math.max(0, nPieces[Piece.WBISHOP] - 2);
+ maxWPawns -= Math.max(0, nPieces[Piece.WROOK ] - 2);
+ maxWPawns -= Math.max(0, nPieces[Piece.WQUEEN ] - 1);
+ if (nPieces[Piece.WPAWN] > maxWPawns)
+ throw new ChessParseError("R.string.err_too_many_white_pieces", pos);
+
+ // Black must not have too many pieces
+ int maxBPawns = 8;
+ maxBPawns -= Math.max(0, nPieces[Piece.BKNIGHT] - 2);
+ maxBPawns -= Math.max(0, nPieces[Piece.BBISHOP] - 2);
+ maxBPawns -= Math.max(0, nPieces[Piece.BROOK ] - 2);
+ maxBPawns -= Math.max(0, nPieces[Piece.BQUEEN ] - 1);
+ if (nPieces[Piece.BPAWN] > maxBPawns)
+ throw new ChessParseError("R.string.err_too_many_black_pieces", pos);
+
+ // Make sure king can not be captured
+ Position pos2 = new Position(pos);
+ pos2.setWhiteMove(!pos.whiteMove);
+ if (MoveGen.inCheck(pos2)) {
+ throw new ChessParseError("R.string.err_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.legalMoves(pos);
+ 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("R.string.err_too_many_rows");
+ if (col > 7) throw new ChessParseError("R.string.err_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.
+ * @param localized If true, use localized piece names.
+ */
+ public static final String moveToString(Position pos, Move move, boolean longForm,
+ boolean localized) {
+ return moveToString(pos, move, longForm, localized, null);
+ }
+ public static final String moveToString(Position pos, Move move, boolean longForm,
+ boolean localized, List moves) {
+ if ((move == null) || 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 black 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) {
+ if (pieceNames == null)
+ localized = false;
+ int p = pos.getPiece(move.from);
+ if (localized)
+ ret.append(pieceToCharLocalized(p));
+ else
+ 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;
+ if (moves == null)
+ moves = MoveGen.instance.legalMoves(pos);
+ 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) {
+ if (localized)
+ ret.append(pieceToCharLocalized(move.promoteTo));
+ else
+ ret.append(pieceToChar(move.promoteTo));
+ }
+ }
+ UndoInfo ui = new UndoInfo();
+ pos.makeMove(move, ui);
+ boolean givesCheck = MoveGen.inCheck(pos);
+ if (givesCheck) {
+ ArrayList nextMoves = MoveGen.instance.legalMoves(pos);
+ 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;
+ }
+ }
+
+ /**
+ * Decide if move is valid in position pos.
+ * @param pos Position for which to test move.
+ * @param move The move to check for validity.
+ * @param moves If non-null, list of valid moves in position pos.
+ * @return True if move is valid in position pos, false otherwise.
+ */
+ public static final boolean isValid(Position pos, Move move) {
+ if (move == null)
+ return false;
+ ArrayList moves = new MoveGen().legalMoves(pos);
+ for (int i = 0; i < moves.size(); i++)
+ if (move.equals(moves.get(i)))
+ return true;
+ return false;
+ }
+
+ 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) {
+ return stringToMove(pos, strMove, null);
+ }
+ public static final Move stringToMove(Position pos, String strMove,
+ ArrayList moves) {
+ 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;
+ }
+
+ if (moves == null)
+ moves = MoveGen.instance.legalMoves(pos);
+
+ 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(Locale.US, "%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();
+ }
+
+ /** Convert a piece and a square to a string, such as Nf3. */
+ public final static String pieceAndSquareToString(int currentPieceType, int p, int sq) {
+ StringBuilder ret = new StringBuilder();
+ if (currentPieceType == PGNOptions.PT_FIGURINE) {
+ ret.append(Piece.toUniCode(p));
+ } else {
+ boolean localized = (currentPieceType != PGNOptions.PT_ENGLISH);
+ if ((p == Piece.WPAWN) || (p == Piece.BPAWN))
+ ret.append(localized ? pieceNames[0] : "P");
+ else
+ ret.append(localized ? pieceToCharLocalized(p) : pieceToChar(p));
+ }
+ ret.append(squareToString(sq));
+ 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 "";
+ }
+
+ public final static String pieceToCharLocalized(int p) {
+ switch (p) {
+ case Piece.WQUEEN: case Piece.BQUEEN: return pieceNames[4];
+ case Piece.WROOK: case Piece.BROOK: return pieceNames[3];
+ case Piece.WBISHOP: case Piece.BBISHOP: return pieceNames[2];
+ case Piece.WKNIGHT: case Piece.BKNIGHT: return pieceNames[1];
+ case Piece.WKING: case Piece.BKING: return pieceNames[5];
+ }
+ 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/buildSrc/src/main/java/org/petero/droidfish/gamelogic/TimeControl.java b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/TimeControl.java
new file mode 100644
index 0000000..7f49705
--- /dev/null
+++ b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/TimeControl.java
@@ -0,0 +1,193 @@
+/*
+ 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.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.petero.droidfish.gamelogic.TimeControlData.TimeControlField;
+
+/** Keep track of time control information for both players. */
+public class TimeControl {
+ TimeControlData tcData;
+
+ private int whiteBaseTime; // Current remaining time, or remaining time when clock started
+ private int blackBaseTime; // Current remaining time, or remaining time when clock started
+
+ int currentMove;
+ boolean whiteToMove;
+
+ private int elapsed; // Accumulated elapsed time for this move.
+ private long timerT0; // Time when timer started. 0 if timer is stopped.
+
+
+ /** Constructor. Sets time control to "game in 5min". */
+ public TimeControl() {
+ tcData = new TimeControlData();
+ reset();
+ }
+
+ public final void reset() {
+ currentMove = 1;
+ whiteToMove = true;
+ elapsed = 0;
+ timerT0 = 0;
+ }
+
+ /** Set time controls for white and black players. */
+ public final void setTimeControl(TimeControlData tcData) {
+ this.tcData = tcData;
+ }
+
+ public final void setCurrentMove(int move, boolean whiteToMove, int whiteBaseTime, int blackBaseTime) {
+ currentMove = move;
+ this.whiteToMove = whiteToMove;
+ this.whiteBaseTime = whiteBaseTime;
+ this.blackBaseTime = blackBaseTime;
+ timerT0 = 0;
+ elapsed = 0;
+ }
+
+ /** Move current move "delta" half-moves forward. */
+ public final void advanceMove(int delta) {
+ while (delta > 0) {
+ if (!whiteToMove)
+ currentMove++;
+ whiteToMove = !whiteToMove;
+ delta--;
+ }
+ while (delta < 0) {
+ whiteToMove = !whiteToMove;
+ if (!whiteToMove)
+ currentMove--;
+ delta++;
+ }
+ }
+
+ 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()) {
+ int currElapsed = (int)(now - 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);
+
+ ArrayList tc = tcData.getTC(whiteToMove);
+ Pair tcInfo = getCurrentTC(whiteToMove);
+ int tcIdx = tcInfo.first;
+ int movesToTc = tcInfo.second;
+
+ int remaining = getRemainingTime(whiteToMove, now);
+ if (useIncrement) {
+ remaining += tc.get(tcIdx).increment;
+ if (movesToTc == 1) {
+ if (tcIdx+1 < tc.size())
+ tcIdx++;
+ remaining += tc.get(tcIdx).timeControl;
+ }
+ }
+ elapsed = 0;
+ return remaining;
+ }
+
+ /** Get remaining time */
+ public final int getRemainingTime(boolean whiteToMove, long now) {
+ int remaining = whiteToMove ? whiteBaseTime : blackBaseTime;
+ if (whiteToMove == this.whiteToMove) {
+ remaining -= elapsed;
+ if (timerT0 != 0)
+ remaining -= now - timerT0;
+ }
+ return remaining;
+ }
+
+ /** Get initial thinking time in milliseconds. */
+ public final int getInitialTime(boolean whiteMove) {
+ ArrayList tc = tcData.getTC(whiteMove);
+ return tc.get(0).timeControl;
+ }
+
+ /** Get time increment in milliseconds after playing next move. */
+ public final int getIncrement(boolean whiteMove) {
+ ArrayList tc = tcData.getTC(whiteMove);
+ int tcIdx = getCurrentTC(whiteMove).first;
+ return tc.get(tcIdx).increment;
+ }
+
+ /** Return number of moves to the next time control, or 0 if "sudden death". */
+ public final int getMovesToTC(boolean whiteMove) {
+ return getCurrentTC(whiteMove).second;
+ }
+
+ /** @return Array containing time control, moves per session and time increment. */
+ public int[] getTimeLimit(boolean whiteMove) {
+ ArrayList tc = tcData.getTC(whiteMove);
+ int tcIdx = getCurrentTC(whiteMove).first;
+ TimeControlField t = tc.get(tcIdx);
+ return new int[]{t.timeControl, t.movesPerSession, t.increment};
+ }
+
+ /** Return the current active time control index and number of moves to next time control. */
+ private Pair getCurrentTC(boolean whiteMove) {
+ ArrayList tc = tcData.getTC(whiteMove);
+ int tcIdx = 0;
+ final int lastTcIdx = tc.size() - 1;
+ int nextTC = 1;
+ int currMove = currentMove;
+ if (!whiteToMove && whiteMove)
+ currMove++;
+ while (true) {
+ if (tc.get(tcIdx).movesPerSession <= 0)
+ return new Pair(tcIdx, 0);
+ nextTC += tc.get(tcIdx).movesPerSession;
+ if (nextTC > currMove)
+ break;
+ if (tcIdx < lastTcIdx)
+ tcIdx++;
+ }
+ return new Pair(tcIdx, nextTC - currMove);
+ }
+
+ /** De-serialize from input stream. */
+ public void readFromStream(DataInputStream dis, int version) throws IOException {
+ tcData.readFromStream(dis, version);
+ }
+
+ /** Serialize to output stream. */
+ public void writeToStream(DataOutputStream dos) throws IOException {
+ tcData.writeToStream(dos);
+ }
+}
diff --git a/buildSrc/src/main/java/org/petero/droidfish/gamelogic/TimeControlData.java b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/TimeControlData.java
new file mode 100644
index 0000000..a77cef8
--- /dev/null
+++ b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/TimeControlData.java
@@ -0,0 +1,122 @@
+/*
+ DroidFish - An Android chess program.
+ Copyright (C) 2013,2016 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.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+public final class TimeControlData {
+ public static final class TimeControlField {
+ int timeControl; // Time in milliseconds
+ int movesPerSession;
+ int increment; // Increment in milliseconds
+
+ public TimeControlField(int time, int moves, int inc) {
+ timeControl = time;
+ movesPerSession = moves;
+ increment = inc;
+ }
+ }
+
+ ArrayList tcW, tcB;
+
+ /** Constructor. Set a default time control. */
+ public TimeControlData() {
+ tcW = new ArrayList();
+ tcW.add(new TimeControlField(5*60*1000, 60, 0));
+ tcB = new ArrayList();
+ tcB.add(new TimeControlField(5*60*1000, 60, 0));
+ }
+
+ /** Set a single time control for both white and black. */
+ public final void setTimeControl(int time, int moves, int inc) {
+ tcW = new ArrayList();
+ tcW.add(new TimeControlField(time, moves, inc));
+ tcB = new ArrayList();
+ tcB.add(new TimeControlField(time, moves, inc));
+ }
+
+ /** Get time control data array for white or black player. */
+ public ArrayList getTC(boolean whiteMove) {
+ return whiteMove ? tcW : tcB;
+ }
+
+ /** Return true if white and black time controls are equal. */
+ public boolean isSymmetric() {
+ return arrayEquals(tcW, tcB);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof TimeControlData))
+ return false;
+ TimeControlData tc2 = (TimeControlData)o;
+ return arrayEquals(tcW, tc2.tcW) && arrayEquals(tcB, tc2.tcB);
+ }
+
+ private static boolean arrayEquals(ArrayList a1,
+ ArrayList a2) {
+ if (a1.size() != a2.size())
+ return false;
+ for (int i = 0; i < a1.size(); i++) {
+ TimeControlField f1 = a1.get(i);
+ TimeControlField f2 = a2.get(i);
+ if ((f1.timeControl != f2.timeControl) ||
+ (f1.movesPerSession != f2.movesPerSession) ||
+ (f1.increment != f2.increment))
+ return false;
+ }
+ return true;
+ }
+
+ /** De-serialize from input stream. */
+ public void readFromStream(DataInputStream dis, int version) throws IOException {
+ for (int c = 0; c < 2; c++) {
+ ArrayList tc = new ArrayList();
+ if (c == 0)
+ tcW = tc;
+ else
+ tcB = tc;
+ int nw = dis.readInt();
+ for (int i = 0; i < nw; i++) {
+ int time = dis.readInt();
+ int moves = dis.readInt();
+ int inc = dis.readInt();
+ tc.add(new TimeControlField(time, moves, inc));
+ }
+ }
+ }
+
+ /** Serialize to output stream. */
+ public void writeToStream(DataOutputStream dos) throws IOException {
+ for (int c = 0; c < 2; c++) {
+ ArrayList tc = (c == 0) ? tcW : tcB;
+ int nw = tc.size();
+ dos.writeInt(nw);
+ for (int i = 0; i < nw; i++) {
+ TimeControlField tcf = tc.get(i);
+ dos.writeInt(tcf.timeControl);
+ dos.writeInt(tcf.movesPerSession);
+ dos.writeInt(tcf.increment);
+ }
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/org/petero/droidfish/gamelogic/UndoInfo.java b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/UndoInfo.java
new file mode 100644
index 0000000..fe3aefe
--- /dev/null
+++ b/buildSrc/src/main/java/org/petero/droidfish/gamelogic/UndoInfo.java
@@ -0,0 +1,30 @@
+/*
+ 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().
+ */
+public class UndoInfo {
+ int capturedPiece;
+ int castleMask;
+ int epSquare;
+ int halfMoveClock;
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..82618ce
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,15 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..75bf8fc
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Mar 08 22:53:49 CET 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..3dca471
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':DroidFishApp', ':CuckooChessEngine', ':CuckooChessApp', ':CuckooChess'