diff --git a/DroidFish/AndroidManifest.xml b/DroidFish/AndroidManifest.xml
index 9645163..8f57774 100644
--- a/DroidFish/AndroidManifest.xml
+++ b/DroidFish/AndroidManifest.xml
@@ -7,6 +7,7 @@
+
Engine error
Stockfish
CuckooChess
+ Network Engine
Failed to start engine
Engine terminated
UCI protocol error
+ Network host:port syntax error
+ Invalid network port
Start New Game?
Use the CuckooChess engine for even lower strength.
Too few spaces
@@ -223,6 +226,8 @@ you are not actively using the program.\
Number of engine threads (CPU cores) to use. Not supported by all engines.
Hash Table
Hash table size in megabytes
+ Network engine
+ Enter host:port to connect to an engine on a computer on the network
Time Control
Moves
Number of moves between time controls
diff --git a/DroidFish/res/xml/preferences.xml b/DroidFish/res/xml/preferences.xml
index da8bf6e..c9550cc 100644
--- a/DroidFish/res/xml/preferences.xml
+++ b/DroidFish/res/xml/preferences.xml
@@ -52,6 +52,12 @@
android:entries="@array/engine_hash_texts"
android:defaultValue="@string/engine_hash_default">
+
+
diff --git a/DroidFish/src/org/petero/droidfish/DroidFish.java b/DroidFish/src/org/petero/droidfish/DroidFish.java
index dee9ccf..0e0f550 100644
--- a/DroidFish/src/org/petero/droidfish/DroidFish.java
+++ b/DroidFish/src/org/petero/droidfish/DroidFish.java
@@ -150,6 +150,10 @@ public class DroidFish extends Activity implements GUIInterface {
// FIXME!!! Handle PGN non-file intents with more than one game.
// FIXME!!! File load/save of FEN data
+ // FIXME!!! Strength setting for external engines
+ // FIXME!!! Selection dialog for going into variation
+ // FIXME!!! Use two engines in engine/engine games
+
private ChessBoard cb;
private static DroidChessController ctrl = null;
private boolean mShowThinking;
@@ -743,6 +747,7 @@ public class DroidFish extends Activity implements GUIInterface {
String engine = settings.getString("engine", "stockfish");
int strength = settings.getInt("strength", 1000);
+ engineOptions.networkEngine = settings.getString("networkEngine", "").trim();
setEngineStrength(engine, strength);
mPonderMode = settings.getBoolean("ponderMode", false);
@@ -787,8 +792,7 @@ public class DroidFish extends Activity implements GUIInterface {
engineOptions.hintsEdit = settings.getBoolean("tbHintsEdit", false);
engineOptions.rootProbe = settings.getBoolean("tbRootProbe", true);
engineOptions.engineProbe = settings.getBoolean("tbEngineProbe", true);
- String gtbPath = settings.getString("gtbPath", "");
- gtbPath = gtbPath.trim();
+ String gtbPath = settings.getString("gtbPath", "").trim();
if (gtbPath.length() == 0) {
File extDir = Environment.getExternalStorageDirectory();
String sep = File.separator;
@@ -897,6 +901,8 @@ public class DroidFish extends Activity implements GUIInterface {
int idx = engine.lastIndexOf('/');
String eName = engine.substring(idx + 1);
engineTitleText.setText(eName);
+ } else if (engine.equals("networkEngine")) {
+ engineTitleText.setText(engineOptions.networkEngine);
} else {
String eName = getString(engine.equals("cuckoochess") ?
R.string.cuckoochess_engine :
@@ -1676,7 +1682,8 @@ public class DroidFish extends Activity implements GUIInterface {
String[] fileNames = findFilesInDirectory(engineDir, null);
final int numFiles = fileNames.length;
boolean haveSf = EngineUtil.internalStockFishName() != null;
- final int nEngines = numFiles + (haveSf ? 2 : 1);
+ boolean haveNet = engineOptions.networkEngine.length() > 0;
+ final int nEngines = numFiles + 1 + (haveSf ? 1 : 0) + (haveNet ? 1 : 0);
final String[] items = new String[nEngines];
final String[] ids = new String[nEngines];
int idx = 0;
@@ -1684,6 +1691,9 @@ public class DroidFish extends Activity implements GUIInterface {
ids[idx] = "stockfish"; items[idx] = getString(R.string.stockfish_engine); idx++;
}
ids[idx] = "cuckoochess"; items[idx] = getString(R.string.cuckoochess_engine); idx++;
+ if (haveNet) {
+ ids[idx] = "networkEngine"; items[idx] = getString(R.string.network_engine); idx++;
+ }
String sep = File.separator;
String base = Environment.getExternalStorageDirectory() + sep + engineDir + sep;
for (int i = 0; i < numFiles; i++) {
diff --git a/DroidFish/src/org/petero/droidfish/EngineOptions.java b/DroidFish/src/org/petero/droidfish/EngineOptions.java
index d57d2f6..008f202 100644
--- a/DroidFish/src/org/petero/droidfish/EngineOptions.java
+++ b/DroidFish/src/org/petero/droidfish/EngineOptions.java
@@ -1,3 +1,21 @@
+/*
+ DroidFish - An Android chess program.
+ Copyright (C) 2012 Peter Ă–sterlund, peterosterlund2@gmail.com
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
package org.petero.droidfish;
/** Engine options, including endgame tablebase probing options. */
@@ -8,6 +26,7 @@ public final class EngineOptions {
public boolean rootProbe; // Only search optimal moves at root
public boolean engineProbe; // Let engine use EGTB
public String gtbPath; // GTB directory path
+ public String networkEngine;// Host:port for network engine
public EngineOptions() {
hashMB = 16;
@@ -16,6 +35,7 @@ public final class EngineOptions {
rootProbe = false;
engineProbe = false;
gtbPath = "";
+ networkEngine = "";
}
public EngineOptions(EngineOptions other) {
@@ -25,6 +45,7 @@ public final class EngineOptions {
rootProbe = other.rootProbe;
engineProbe = other.engineProbe;
gtbPath = other.gtbPath;
+ networkEngine = other.networkEngine;
}
@Override
@@ -38,7 +59,8 @@ public final class EngineOptions {
(hintsEdit == other.hintsEdit) &&
(rootProbe == other.rootProbe) &&
(engineProbe == other.engineProbe) &&
- gtbPath.equals(other.gtbPath));
+ gtbPath.equals(other.gtbPath) &&
+ networkEngine.equals(other.networkEngine));
}
@Override
diff --git a/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java b/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java
index 7a4ac11..2d2b078 100644
--- a/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java
+++ b/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java
@@ -602,7 +602,9 @@ public class DroidComputerPlayer {
myAssert(searchRequest != null);
engineName = "Computer";
- uciEngine = UCIEngineBase.getEngine(context, searchRequest.engine, new UCIEngine.Report() {
+ uciEngine = UCIEngineBase.getEngine(context, searchRequest.engine,
+ engineOptions,
+ new UCIEngine.Report() {
@Override
public void reportError(String errMsg) {
if (errMsg == null)
diff --git a/DroidFish/src/org/petero/droidfish/engine/NetworkEngine.java b/DroidFish/src/org/petero/droidfish/engine/NetworkEngine.java
new file mode 100644
index 0000000..861102f
--- /dev/null
+++ b/DroidFish/src/org/petero/droidfish/engine/NetworkEngine.java
@@ -0,0 +1,255 @@
+/*
+ DroidFish - An Android chess program.
+ Copyright (C) 2012 Peter Ă–sterlund, peterosterlund2@gmail.com
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+package org.petero.droidfish.engine;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+import org.petero.droidfish.EngineOptions;
+import org.petero.droidfish.R;
+import android.content.Context;
+
+/** Engine running on a different computer. */
+public class NetworkEngine extends UCIEngineBase {
+ protected final Context context;
+ private final Report report;
+
+ private String networkEngine;
+ private Socket socket;
+ private Thread startupThread;
+ private Thread stdInThread;
+ private Thread stdOutThread;
+ private LocalPipe guiToEngine;
+ private LocalPipe engineToGui;
+ private boolean startedOk;
+ private boolean isRunning;
+ private boolean isError;
+
+ public NetworkEngine(Context context, String engine, EngineOptions engineOptions,
+ Report report) {
+ this.context = context;
+ this.report = report;
+ this.networkEngine = engineOptions.networkEngine;
+ startupThread = null;
+ stdInThread = null;
+ guiToEngine = new LocalPipe();
+ engineToGui = new LocalPipe();
+ startedOk = false;
+ isRunning = false;
+ isError = false;
+ }
+
+ /** Create socket connection to remote server. */
+ private final synchronized void connect() {
+ if (socket == null) {
+ int idx = networkEngine.lastIndexOf(':');
+ if (idx < 0) {
+ isError = true;
+ report.reportError(context.getString(R.string.network_host_syntax_error));
+ } else {
+ try {
+ String host = networkEngine.substring(0, idx);
+ String port = networkEngine.substring(idx+1);
+ int portNr = Integer.parseInt(port);
+ socket = new Socket(host, portNr);
+ socket.setTcpNoDelay(true);
+ } catch (UnknownHostException e) {
+ isError = true;
+ report.reportError(e.getMessage());
+ } catch (NumberFormatException nfe) {
+ isError = true;
+ report.reportError(context.getString(R.string.invalid_network_port));
+ } catch (IOException e) {
+ isError = true;
+ report.reportError(e.getMessage());
+ }
+ }
+ if (socket == null)
+ socket = new Socket();
+ }
+ }
+
+ /** @inheritDoc */
+ @Override
+ protected void startProcess() {
+ // Start thread to check for startup error
+ startupThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(10000);
+ } catch (InterruptedException e) {
+ return;
+ }
+ if (startedOk && isRunning && !isUCI) {
+ isError = true;
+ report.reportError(context.getString(R.string.uci_protocol_error));
+ }
+ }
+ });
+ startupThread.start();
+
+ // Start a thread to read data from engine
+ stdInThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ connect();
+ try {
+ InputStream is = socket.getInputStream();
+ InputStreamReader isr = new InputStreamReader(is);
+ BufferedReader br = new BufferedReader(isr, 8192);
+ String line;
+ boolean first = true;
+ while ((line = br.readLine()) != null) {
+ if (Thread.currentThread().isInterrupted())
+ return;
+ synchronized (engineToGui) {
+ engineToGui.addLine(line);
+ if (first) {
+ startedOk = true;
+ isRunning = true;
+ first = false;
+ }
+ }
+ }
+ } catch (IOException e) {
+ } finally {
+ if (isRunning) {
+ isError = true;
+ isRunning = false;
+ if (!startedOk)
+ report.reportError(context.getString(R.string.failed_to_start_engine));
+ else
+ report.reportError(context.getString(R.string.engine_terminated));
+ }
+ try { socket.close(); } catch (IOException e) {}
+ }
+ engineToGui.close();
+ }
+ });
+ stdInThread.start();
+
+ // Start a thread to write data to engine
+ stdOutThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ connect();
+ String line;
+ while ((line = guiToEngine.readLine()) != null) {
+ if (Thread.currentThread().isInterrupted())
+ return;
+ line += "\n";
+ socket.getOutputStream().write(line.getBytes());
+ }
+ } catch (IOException e) {
+ if (isRunning) {
+ isError = true;
+ report.reportError(e.getMessage());
+ }
+ } finally {
+ if (isRunning && !isError) {
+ isError = true;
+ report.reportError(context.getString(R.string.engine_terminated));
+ }
+ isRunning = false;
+ try { socket.close(); } catch (IOException ex) {}
+ }
+ }
+ });
+ stdOutThread.start();
+ }
+
+ private int hashMB = -1;
+ private String gaviotaTbPath = "";
+ private boolean optionsInitialized = false;
+
+ /** @inheritDoc */
+ @Override
+ public void initOptions(EngineOptions engineOptions) {
+ super.initOptions(engineOptions);
+ hashMB = engineOptions.hashMB;
+ setOption("Hash", engineOptions.hashMB);
+ if (engineOptions.engineProbe) {
+ gaviotaTbPath = engineOptions.gtbPath;
+ setOption("GaviotaTbPath", engineOptions.gtbPath);
+ setOption("GaviotaTbCache", 8);
+ }
+ optionsInitialized = true;
+ }
+
+ /** @inheritDoc */
+ @Override
+ public boolean optionsOk(EngineOptions engineOptions) {
+ if (isError)
+ return false;
+ if (!optionsInitialized)
+ return true;
+ if (!networkEngine.equals(engineOptions.networkEngine))
+ return false;
+ if (hashMB != engineOptions.hashMB)
+ return false;
+ if (haveOption("gaviotatbpath") && !gaviotaTbPath.equals(engineOptions.gtbPath))
+ return false;
+ return true;
+ }
+
+ /** @inheritDoc */
+ @Override
+ public void setStrength(int strength) {
+ }
+
+ /** @inheritDoc */
+ @Override
+ public String readLineFromEngine(int timeoutMillis) {
+ String ret = engineToGui.readLine(timeoutMillis);
+ if (ret == null)
+ return null;
+ if (ret.length() > 0) {
+// System.out.printf("Engine -> GUI: %s\n", ret);
+ }
+ return ret;
+ }
+
+ /** @inheritDoc */
+ @Override
+ public void writeLineToEngine(String data) {
+// System.out.printf("GUI -> Engine: %s\n", data);
+ guiToEngine.addLine(data);
+ }
+
+ /** @inheritDoc */
+ @Override
+ public void shutDown() {
+ isRunning = false;
+ if (startupThread != null)
+ startupThread.interrupt();
+ try { socket.getOutputStream().write("quit\n".getBytes()); } catch (IOException e) {}
+ try { socket.close(); } catch (IOException e) {}
+ super.shutDown();
+ if (stdOutThread != null)
+ stdOutThread.interrupt();
+ if (stdInThread != null)
+ stdInThread.interrupt();
+ }
+}
diff --git a/DroidFish/src/org/petero/droidfish/engine/UCIEngineBase.java b/DroidFish/src/org/petero/droidfish/engine/UCIEngineBase.java
index 0bce0ff..175b7c1 100644
--- a/DroidFish/src/org/petero/droidfish/engine/UCIEngineBase.java
+++ b/DroidFish/src/org/petero/droidfish/engine/UCIEngineBase.java
@@ -33,13 +33,16 @@ public abstract class UCIEngineBase implements UCIEngine {
private HashMap currOptions;
protected boolean isUCI;
- public static UCIEngine getEngine(Context context, String engine, Report report) {
+ public static UCIEngine getEngine(Context context, 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))
return new InternalStockFish(context, report);
+ else if ("networkEngine".equals(engine))
+ return new NetworkEngine(context, engine, engineOptions, report);
else
return new ExternalEngine(context, engine, report);
}