diff --git a/DroidFish/res/menu/options_menu.xml b/DroidFish/res/menu/options_menu.xml
index 84abaa8..f5c6cb1 100644
--- a/DroidFish/res/menu/options_menu.xml
+++ b/DroidFish/res/menu/options_menu.xml
@@ -44,6 +44,10 @@
android:title="@string/option_select_book">
-
+
+ -
diff --git a/DroidFish/res/values-de/strings.xml b/DroidFish/res/values-de/strings.xml
index b71d5b0..a3b6030 100644
--- a/DroidFish/res/values-de/strings.xml
+++ b/DroidFish/res/values-de/strings.xml
@@ -1,14 +1,7 @@
DroidFish
-
- - Stockfish
- - CuckooChess
-
-
- - stockfish
- - cuckoochess
-
+
- Automatisch
- 1
@@ -414,8 +407,6 @@ wenn Sie es nicht aktiv nutzen.\
Automatischer Seitenwechsel
Seiten beim Start einer neuen Partie automatisch wechseln (Einstellung Ansicht drehen ignorieren)
Engine-Einstellungen
- Schach-Engine
- Auswahl der Schach-Engine
Spielstärke
Vorausberechnung
Vorausberechnung von Zügen durch die Engine, wenn der Spieler am Zug ist
diff --git a/DroidFish/res/values/strings.xml b/DroidFish/res/values/strings.xml
index 60b1ab1..a761dd3 100644
--- a/DroidFish/res/values/strings.xml
+++ b/DroidFish/res/values/strings.xml
@@ -1,15 +1,6 @@
DroidFish
-
- - Stockfish
- - CuckooChess
-
-
- - stockfish
- - cuckoochess
-
- stockfish
- Automatic
@@ -305,6 +296,7 @@ you are not actively using the program.\
Edit Move Counters
<Internal Book>
Select opening book file
+ Select Chess Engine
Select PGN file to open
Save to PGN file
Select Scid file to open
@@ -401,6 +393,9 @@ you are not actively using the program.\
After Selected
Replace Selected
Engine
+ Engine error
+ Stockfish
+ CuckooChess
Too few spaces
Invalid piece
@@ -425,6 +420,7 @@ you are not actively using the program.\
Force Computer Move
Claim/Offer/Accept Draw
Select Opening Book
+ Select Chess Engine
Set Color Theme
About / Help
@@ -434,8 +430,6 @@ you are not actively using the program.\
Auto Swap Sides
Automatically swap sides when new game started. Also overrides Flip Board setting
Engine Settings
- Engine
- Chess Engine
Strength
Pondering
Let engine think while waiting for opponent\'s move
diff --git a/DroidFish/res/xml/preferences.xml b/DroidFish/res/xml/preferences.xml
index 72480eb..cf4be24 100644
--- a/DroidFish/res/xml/preferences.xml
+++ b/DroidFish/res/xml/preferences.xml
@@ -18,14 +18,6 @@
-
-
= ids.length))
+ return;
+ Editor editor = settings.edit();
+ String engine = ids[item];
+ editor.putString("engine", engine);
+ editor.commit();
+ dialog.dismiss();
+ int strength = settings.getInt("strength", 1000);
+ setEngineStrength(engine, strength);
+ }
+ });
+ AlertDialog alert = builder.create();
+ return alert;
+ }
case SELECT_PGN_FILE_DIALOG: {
final String[] fileNames = findFilesInDirectory(pgnDir, null);
final int numFiles = fileNames.length;
@@ -1773,6 +1818,13 @@ public class DroidFish extends Activity implements GUIInterface {
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
}
+ @Override
+ public void reportEngineError(String errMsg) {
+ String msg = String.format("%s: %s",
+ getString(R.string.engine_error), errMsg);
+ Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show();
+ }
+
@Override
public void computerMoveMade() {
if (soundEnabled) {
diff --git a/DroidFish/src/org/petero/droidfish/GUIInterface.java b/DroidFish/src/org/petero/droidfish/GUIInterface.java
index 7b6bc6b..85e7d38 100644
--- a/DroidFish/src/org/petero/droidfish/GUIInterface.java
+++ b/DroidFish/src/org/petero/droidfish/GUIInterface.java
@@ -67,6 +67,9 @@ public interface GUIInterface {
/** Report UCI engine name. */
public void reportEngineName(String engine);
+ /** Report UCI engine error message. */
+ public void reportEngineError(String errMsg);
+
/** Called when computer made a move. GUI can notify user, for example by playing a sound. */
public void computerMoveMade();
diff --git a/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java b/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java
index 3e68c60..92e3f52 100644
--- a/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java
+++ b/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java
@@ -25,7 +25,6 @@ import java.util.ArrayList;
import org.petero.droidfish.book.BookOptions;
import org.petero.droidfish.book.DroidBook;
-import org.petero.droidfish.engine.cuckoochess.CuckooChessEngine;
import org.petero.droidfish.gamelogic.Move;
import org.petero.droidfish.gamelogic.MoveGen;
import org.petero.droidfish.gamelogic.Pair;
@@ -292,10 +291,10 @@ public class DroidComputerPlayer {
/** Stop the engine process. */
public final synchronized void shutdownEngine() {
if (uciEngine != null) {
- uciEngine.shutDown();
- uciEngine = null;
engineMonitor.interrupt();
engineMonitor = null;
+ uciEngine.shutDown();
+ uciEngine = null;
}
engineState.state = MainState.DEAD;
}
@@ -543,15 +542,20 @@ public class DroidComputerPlayer {
myAssert(searchRequest != null);
engineName = "Computer";
- if ("cuckoochess".equals(searchRequest.engine))
- uciEngine = new CuckooChessEngine();
- else
- uciEngine = new StockFishJNI();
+ uciEngine = UCIEngineBase.getEngine(searchRequest.engine, new UCIEngine.Report() {
+ @Override
+ public void reportError(String errMsg) {
+ if (errMsg != null) {
+ listener.reportEngineError(errMsg);
+ }
+ }
+ });
uciEngine.initialize();
+ final UCIEngine uci = uciEngine;
engineMonitor = new Thread(new Runnable() {
public void run() {
- monitorLoop();
+ monitorLoop(uci);
}
});
engineMonitor.start();
@@ -569,16 +573,17 @@ public class DroidComputerPlayer {
private final static long guiUpdateInterval = 100;
private long lastGUIUpdate = 0;
- private final void monitorLoop() {
+ private final void monitorLoop(UCIEngine uci) {
while (true) {
int timeout = getReadTimeout();
- UCIEngine uci = uciEngine;
- if (uci == null)
- return;
- String s = uci.readLineFromEngine(timeout);
if (Thread.currentThread().isInterrupted())
return;
- processEngineOutput(s);
+ String s = uci.readLineFromEngine(timeout);
+ if ((s == null) || Thread.currentThread().isInterrupted())
+ return;
+ processEngineOutput(uci, s);
+ if (Thread.currentThread().isInterrupted())
+ return;
notifyGUI();
if (Thread.currentThread().isInterrupted())
return;
@@ -586,7 +591,10 @@ public class DroidComputerPlayer {
}
/** Process one line of data from the engine. */
- private final synchronized void processEngineOutput(String s) {
+ private final synchronized void processEngineOutput(UCIEngine uci, String s) {
+ if (Thread.currentThread().isInterrupted())
+ return;
+
if (s == null) {
shutdownEngine();
return;
@@ -595,17 +603,13 @@ public class DroidComputerPlayer {
if (s.length() == 0)
return;
- UCIEngine uci = uciEngine;
- if (uci == null)
- return;
switch (engineState.state) {
case READ_OPTIONS: {
if (readUCIOption(s)) {
- if (!"cuckoochess".equals(engineState.engine))
- uci.setOption("Hash", 16);
+ uci.initOptions();
uci.setOption("Ponder", false);
uci.writeLineToEngine("ucinewgame");
- uciEngine.writeLineToEngine("isready");
+ uci.writeLineToEngine("isready");
engineState.state = MainState.WAIT_READY;
}
break;
@@ -642,7 +646,7 @@ public class DroidComputerPlayer {
case STOP_SEARCH: {
String[] tokens = tokenize(s);
if (tokens[0].equals("bestmove")) {
- uciEngine.writeLineToEngine("isready");
+ uci.writeLineToEngine("isready");
engineState.state = MainState.WAIT_READY;
}
break;
@@ -860,6 +864,9 @@ public class DroidComputerPlayer {
/** Notify GUI about search statistics. */
private final synchronized void notifyGUI() {
+ if (Thread.currentThread().isInterrupted())
+ return;
+
long now = System.currentTimeMillis();
if (now < lastGUIUpdate + guiUpdateInterval)
return;
diff --git a/DroidFish/src/org/petero/droidfish/engine/ExternalEngine.java b/DroidFish/src/org/petero/droidfish/engine/ExternalEngine.java
new file mode 100644
index 0000000..f4de2e0
--- /dev/null
+++ b/DroidFish/src/org/petero/droidfish/engine/ExternalEngine.java
@@ -0,0 +1,183 @@
+package org.petero.droidfish.engine;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.channels.FileChannel;
+import java.util.LinkedList;
+import java.util.List;
+
+public class ExternalEngine extends UCIEngineBase {
+ private File engineFileName;
+ private static final String exePath = "/data/data/org.petero.droidfish/engine.exe";
+ private final Report report;
+ private Process engineProc;
+ private Thread stdInThread;
+ private Thread stdErrThread;
+ private List inLines;
+
+ public ExternalEngine(String engine, Report report) {
+ this.report = report;
+ engineFileName = new File(engine);
+ engineProc = null;
+ stdInThread = null;
+ stdErrThread = null;
+ inLines = new LinkedList();
+ }
+
+ /** @inheritDoc */
+ @Override
+ protected void startProcess() {
+ try {
+ copyFile(engineFileName, new File(exePath));
+ chmod(exePath);
+ ProcessBuilder pb = new ProcessBuilder(exePath);
+ engineProc = pb.start();
+
+ // Start a thread to read stdin
+ stdInThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Process ep = engineProc;
+ if (ep == null)
+ return;
+ InputStream is = ep.getInputStream();
+ InputStreamReader isr = new InputStreamReader(is);
+ BufferedReader br = new BufferedReader(isr);
+ String line;
+ try {
+ while ((line = br.readLine()) != null) {
+ if ((ep == null) || Thread.currentThread().isInterrupted())
+ return;
+ synchronized (inLines) {
+ inLines.add(line);
+ inLines.notify();
+ }
+ }
+ } catch (IOException e) {
+ return;
+ }
+ }
+ });
+ stdInThread.start();
+
+ // Start a thread to ignore stderr
+ stdErrThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ byte[] buffer = new byte[128];
+ while (true) {
+ Process ep = engineProc;
+ if ((ep == null) || Thread.currentThread().isInterrupted())
+ return;
+ try {
+ ep.getErrorStream().read(buffer);
+ } catch (IOException e) {
+ return;
+ }
+ }
+ }
+ });
+ stdErrThread.start();
+ } catch (IOException ex) {
+ report.reportError(ex.getMessage());
+ }
+ }
+
+ /** @inheritDoc */
+ @Override
+ public void initOptions() {
+ setOption("Hash", 16);
+ }
+
+ /** @inheritDoc */
+ @Override
+ public void setStrength(int strength) {
+ }
+
+ /** @inheritDoc */
+ @Override
+ public String readLineFromEngine(int timeoutMillis) {
+ try {
+ synchronized (inLines) {
+ if (inLines.size() == 0) {
+ Thread inThread = stdInThread;
+ if ((inThread == null) || !inThread.isAlive())
+ return null;
+ inLines.wait(timeoutMillis);
+ }
+ }
+ synchronized (inLines) {
+ if (inLines.size() > 0) {
+ String ret = inLines.get(0);
+ inLines.remove(0);
+// System.out.printf("Engine -> GUI: %s\n", ret);
+ return ret;
+ }
+ }
+ } catch (InterruptedException e) {
+ }
+ return "";
+ }
+
+ /** @inheritDoc */
+ @Override
+ public void writeLineToEngine(String data) {
+// System.out.printf("GUI -> Engine: %s\n", data);
+ data += "\n";
+ try {
+ Process ep = engineProc;
+ if (ep != null)
+ ep.getOutputStream().write(data.getBytes());
+ } catch (IOException e) {
+ }
+ }
+
+ /** @inheritDoc */
+ @Override
+ public void shutDown() {
+ super.shutDown();
+ if (engineProc != null)
+ engineProc.destroy();
+ engineProc = null;
+ if (stdInThread != null)
+ stdInThread.interrupt();
+ if (stdErrThread != null)
+ stdErrThread.interrupt();
+ }
+
+ private final static void copyFile(File from, File to) throws IOException {
+ if (to.exists() && (from.length() == to.length()) && (from.lastModified() == to.lastModified()))
+ return;
+ if (to.exists())
+ to.delete();
+ to.createNewFile();
+ FileChannel inFC = null;
+ FileChannel outFC = null;
+ try {
+ inFC = new FileInputStream(from).getChannel();
+ outFC = new FileOutputStream(to).getChannel();
+ long cnt = outFC.transferFrom(inFC, 0, inFC.size());
+ if (cnt < inFC.size())
+ throw new IOException("File copy failed");
+ } finally {
+ if (inFC != null) { try { inFC.close(); } catch (IOException ex) {} }
+ if (outFC != null) { try { outFC.close(); } catch (IOException ex) {} }
+ to.setLastModified(from.lastModified());
+ }
+ }
+
+ private final void chmod(String exePath) throws IOException {
+ Process proc = Runtime.getRuntime().exec(new String[]{"chmod", "744", exePath});
+ try {
+ proc.waitFor();
+ } catch (InterruptedException e) {
+ proc.destroy();
+ throw new IOException("chmod failed");
+ }
+ }
+}
diff --git a/DroidFish/src/org/petero/droidfish/engine/StockFishJNI.java b/DroidFish/src/org/petero/droidfish/engine/StockFishJNI.java
index 539e2a6..e5b7417 100644
--- a/DroidFish/src/org/petero/droidfish/engine/StockFishJNI.java
+++ b/DroidFish/src/org/petero/droidfish/engine/StockFishJNI.java
@@ -23,18 +23,19 @@ public class StockFishJNI extends UCIEngineBase {
System.loadLibrary("stockfishjni");
}
+ /** @inheritDoc */
@Override
- public void setStrength(int strength) {
+ public final void initOptions() {
+ setOption("Hash", 16);
+ }
+
+ /** @inheritDoc */
+ @Override
+ public final void setStrength(int strength) {
setOption("Skill Level", strength/50);
}
- /**
- * Read a line from the process.
- * @param timeoutMillis Maximum time to wait for data
- * @return The line, without terminating newline characters,
- * or empty string if no data available,
- * or null if I/O error.
- */
+ /** @inheritDoc */
@Override
public final String readLineFromEngine(int timeoutMillis) {
String ret = readFromProcess(timeoutMillis);
@@ -46,14 +47,15 @@ public class StockFishJNI extends UCIEngineBase {
return ret;
}
- /** Write a line to the process. \n will be added automatically. */
+ /** @inheritDoc */
@Override
public final synchronized void writeLineToEngine(String data) {
// System.out.printf("GUI -> Engine: %s\n", data);
writeToProcess(data + "\n");
}
- /** Start the child process. */
+ /** @inheritDoc */
+ @Override
protected final native void startProcess();
/**
diff --git a/DroidFish/src/org/petero/droidfish/engine/UCIEngine.java b/DroidFish/src/org/petero/droidfish/engine/UCIEngine.java
index f965505..d38cc16 100644
--- a/DroidFish/src/org/petero/droidfish/engine/UCIEngine.java
+++ b/DroidFish/src/org/petero/droidfish/engine/UCIEngine.java
@@ -20,15 +20,24 @@ package org.petero.droidfish.engine;
public interface UCIEngine {
+ /** For reporting engine error messages. */
+ public interface Report {
+ /** Report error message to GUI. */
+ void reportError(String errMsg);
+ }
+
/** Start engine. */
public void initialize();
+ /** Initialize default options. */
+ public void initOptions();
+
/** Shut down engine. */
public void shutDown();
/**
* Read a line from the engine.
- * @param timeoutMillis Maximum time to wait for data
+ * @param timeoutMillis Maximum time to wait for data.
* @return The line, without terminating newline characters,
* or empty string if no data available,
* or null if I/O error.
diff --git a/DroidFish/src/org/petero/droidfish/engine/UCIEngineBase.java b/DroidFish/src/org/petero/droidfish/engine/UCIEngineBase.java
index 37dafb6..f96c927 100644
--- a/DroidFish/src/org/petero/droidfish/engine/UCIEngineBase.java
+++ b/DroidFish/src/org/petero/droidfish/engine/UCIEngineBase.java
@@ -2,10 +2,21 @@ package org.petero.droidfish.engine;
import java.util.HashMap;
+import org.petero.droidfish.engine.cuckoochess.CuckooChessEngine;
+
public abstract class UCIEngineBase implements UCIEngine {
private boolean processAlive;
+ public static UCIEngine getEngine(String engine, Report report) {
+ if ("cuckoochess".equals(engine))
+ return new CuckooChessEngine(report);
+ else if ("stockfish".equals(engine))
+ return new StockFishJNI();
+ else
+ return new ExternalEngine(engine, report);
+ }
+
protected UCIEngineBase() {
processAlive = false;
}
@@ -21,7 +32,7 @@ public abstract class UCIEngineBase implements UCIEngine {
}
@Override
- public final void shutDown() {
+ public void shutDown() {
if (processAlive) {
writeLineToEngine("quit");
processAlive = false;
diff --git a/DroidFish/src/org/petero/droidfish/engine/cuckoochess/CuckooChessEngine.java b/DroidFish/src/org/petero/droidfish/engine/cuckoochess/CuckooChessEngine.java
index 205f93f..c12bc0b 100644
--- a/DroidFish/src/org/petero/droidfish/engine/cuckoochess/CuckooChessEngine.java
+++ b/DroidFish/src/org/petero/droidfish/engine/cuckoochess/CuckooChessEngine.java
@@ -51,12 +51,8 @@ public class CuckooChessEngine extends UCIEngineBase {
private NioInputStream inFromEngine;
private Thread engineThread;
- public CuckooChessEngine() {
- try {
- pos = TextIO.readFEN(TextIO.startPosFEN);
- } catch (ChessParseError ex) {
- throw new RuntimeException();
- }
+ public CuckooChessEngine(Report report) {
+ pos = null;
moves = new ArrayList();
quit = false;
try {
@@ -64,14 +60,12 @@ public class CuckooChessEngine extends UCIEngineBase {
engineToGui = Pipe.open();
inFromEngine = new NioInputStream(engineToGui);
} catch (IOException e) {
+ report.reportError(e.getMessage());
}
}
+ /** @inheritDoc */
@Override
- public void setStrength(int strength) {
- setOption("strength", strength);
- }
-
protected final void startProcess() {
engineThread = new Thread(new Runnable() {
public void run() {
@@ -87,6 +81,17 @@ public class CuckooChessEngine extends UCIEngineBase {
engineThread.start();
}
+ /** @inheritDoc */
+ @Override
+ public final void initOptions() {
+ }
+
+ /** @inheritDoc */
+ @Override
+ public final void setStrength(int strength) {
+ setOption("strength", strength);
+ }
+
private final void mainLoop(NioInputStream is, NioPrintStream os) {
String line;
while ((line = is.readLine()) != null) {
@@ -97,6 +102,7 @@ public class CuckooChessEngine extends UCIEngineBase {
}
}
+ /** @inheritDoc */
@Override
public final String readLineFromEngine(int timeoutMillis) {
if ((engineThread != null) && !engineThread.isAlive())
@@ -110,6 +116,7 @@ public class CuckooChessEngine extends UCIEngineBase {
return ret;
}
+ /** @inheritDoc */
@Override
public final synchronized void writeLineToEngine(String data) {
// System.out.printf("GUI -> Engine: %s\n", data);
@@ -224,6 +231,13 @@ public class CuckooChessEngine extends UCIEngineBase {
sPar.infinite = true;
}
}
+ if (pos == null) {
+ try {
+ pos = TextIO.readFEN(TextIO.startPosFEN);
+ } catch (ChessParseError ex) {
+ throw new RuntimeException();
+ }
+ }
if (ponder) {
engine.startPonder(pos, moves, sPar);
} else {
diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java b/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java
index 0bccb70..7907db5 100644
--- a/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java
+++ b/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java
@@ -157,6 +157,11 @@ public class DroidChessController {
}
}
+ /** Return current engine identifier. */
+ public final synchronized String getEngine() {
+ return engine;
+ }
+
/** Notify controller that preferences has changed. */
public final synchronized void prefsChanged() {
updateBookHints();
@@ -649,6 +654,8 @@ public class DroidChessController {
tmpPos.makeMove(ponderMove, ui);
}
for (Move m : pv.pv) {
+ if (m == null)
+ break;
String moveStr = TextIO.moveToString(tmpPos, m, false);
buf.append(String.format(" %s", moveStr));
tmpPos.makeMove(m, ui);
@@ -701,6 +708,15 @@ public class DroidChessController {
}
});
}
+
+ @Override
+ public void reportEngineError(final String errMsg) {
+ gui.runOnUIThread(new Runnable() {
+ public void run() {
+ gui.reportEngineError(errMsg);
+ }
+ });
+ }
}
/** Discard current search. Return true if GUI update needed. */
diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/SearchListener.java b/DroidFish/src/org/petero/droidfish/gamelogic/SearchListener.java
index c30b786..4de4755 100644
--- a/DroidFish/src/org/petero/droidfish/gamelogic/SearchListener.java
+++ b/DroidFish/src/org/petero/droidfish/gamelogic/SearchListener.java
@@ -90,4 +90,7 @@ public interface SearchListener {
/** Report engine name. */
public void notifyEngineName(String engineName);
+
+ /** Report engine error. */
+ public void reportEngineError(String errMsg);
}