mirror of
https://github.com/peterosterlund2/droidfish.git
synced 2024-11-27 14:15:16 +01:00
DroidFish: Added support for running the chess engine on a different computer, communicating with it using a network socket.
This commit is contained in:
parent
04f1225d3e
commit
a7e21224b8
|
@ -7,6 +7,7 @@
|
|||
<supports-screens android:largeScreens="true"
|
||||
android:anyDensity="true" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-sdk android:minSdkVersion="3"
|
||||
|
|
|
@ -177,9 +177,12 @@ you are not actively using the program.\
|
|||
<string name="engine_error">Engine error</string>
|
||||
<string name="stockfish_engine">Stockfish</string>
|
||||
<string name="cuckoochess_engine">CuckooChess</string>
|
||||
<string name="network_engine">Network Engine</string>
|
||||
<string name="failed_to_start_engine">Failed to start engine</string>
|
||||
<string name="engine_terminated">Engine terminated</string>
|
||||
<string name="uci_protocol_error">UCI protocol error</string>
|
||||
<string name="network_host_syntax_error">Network host:port syntax error</string>
|
||||
<string name="invalid_network_port">Invalid network port</string>
|
||||
<string name="start_new_game">Start New Game?</string>
|
||||
<string name="strength_cuckoo_hint">Use the CuckooChess engine for even lower strength.</string>
|
||||
<string name="err_too_few_spaces">Too few spaces</string>
|
||||
|
@ -223,6 +226,8 @@ you are not actively using the program.\
|
|||
<string name="prefs_threads_summary">Number of engine threads (CPU cores) to use. Not supported by all engines.</string>
|
||||
<string name="prefs_hash_title">Hash Table</string>
|
||||
<string name="prefs_hash_summary">Hash table size in megabytes</string>
|
||||
<string name="prefs_networkEngine_title">Network engine</string>
|
||||
<string name="prefs_networkEngine_summary">Enter host:port to connect to an engine on a computer on the network</string>
|
||||
<string name="prefs_time_control">Time Control</string>
|
||||
<string name="prefs_movesPerSession_title">Moves</string>
|
||||
<string name="prefs_movesPerSession_summary">Number of moves between time controls</string>
|
||||
|
|
|
@ -52,6 +52,12 @@
|
|||
android:entries="@array/engine_hash_texts"
|
||||
android:defaultValue="@string/engine_hash_default">
|
||||
</ListPreference>
|
||||
<EditTextPreference
|
||||
android:key="networkEngine"
|
||||
android:title="@string/prefs_networkEngine_title"
|
||||
android:summary="@string/prefs_networkEngine_summary"
|
||||
android:defaultValue="">
|
||||
</EditTextPreference>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:title="@string/prefs_time_control">
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
|
|
255
DroidFish/src/org/petero/droidfish/engine/NetworkEngine.java
Normal file
255
DroidFish/src/org/petero/droidfish/engine/NetworkEngine.java
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -33,13 +33,16 @@ public abstract class UCIEngineBase implements UCIEngine {
|
|||
private HashMap<String, String> 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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user