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'