DroidFish: Implemented support for external UCI engines.

This commit is contained in:
Peter Osterlund
2012-01-01 22:36:25 +00:00
parent a1fdce1942
commit c653ef8ec2
14 changed files with 364 additions and 83 deletions

View File

@@ -42,6 +42,10 @@
<item <item
android:id="@+id/select_book" android:id="@+id/select_book"
android:title="@string/option_select_book"> android:title="@string/option_select_book">
</item>
<item
android:id="@+id/select_engine"
android:title="@string/option_select_engine">
</item> </item>
<item <item
android:id="@+id/set_color_theme" android:id="@+id/set_color_theme"

View File

@@ -1,14 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">DroidFish</string> <string name="app_name">DroidFish</string>
<string-array name="engine_texts">
<item>Stockfish</item>
<item>CuckooChess</item>
</string-array>
<string-array name="engine_values">
<item>stockfish</item>
<item>cuckoochess</item>
</string-array>
<string-array name="engine_threads_texts"> <string-array name="engine_threads_texts">
<item>Automatisch</item> <item>Automatisch</item>
<item>1</item> <item>1</item>
@@ -414,8 +407,6 @@ wenn Sie es nicht aktiv nutzen.\
<string name="prefs_autoSwapSides_title">Automatischer Seitenwechsel</string> <string name="prefs_autoSwapSides_title">Automatischer Seitenwechsel</string>
<string name="prefs_autoSwapSides_summary">Seiten beim Start einer neuen Partie automatisch wechseln (Einstellung <i>Ansicht drehen</i> ignorieren)</string> <string name="prefs_autoSwapSides_summary">Seiten beim Start einer neuen Partie automatisch wechseln (Einstellung <i>Ansicht drehen</i> ignorieren)</string>
<string name="prefs_engine_settings">Engine-Einstellungen</string> <string name="prefs_engine_settings">Engine-Einstellungen</string>
<string name="prefs_engine_title">Schach-Engine</string>
<string name="prefs_engine_summary">Auswahl der Schach-Engine</string>
<string name="prefs_strength_title">Spielstärke</string> <string name="prefs_strength_title">Spielstärke</string>
<string name="prefs_ponderMode_title">Vorausberechnung</string> <string name="prefs_ponderMode_title">Vorausberechnung</string>
<string name="prefs_ponderMode_summary">Vorausberechnung von Zügen durch die Engine, wenn der Spieler am Zug ist</string> <string name="prefs_ponderMode_summary">Vorausberechnung von Zügen durch die Engine, wenn der Spieler am Zug ist</string>

View File

@@ -1,15 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">DroidFish</string> <string name="app_name">DroidFish</string>
<string-array name="engine_texts">
<item>Stockfish</item>
<item>CuckooChess</item>
</string-array>
<string-array name="engine_values">
<item>stockfish</item>
<item>cuckoochess</item>
</string-array>
<string name="engine_default">stockfish</string>
<string-array name="engine_threads_texts"> <string-array name="engine_threads_texts">
<item>Automatic</item> <item>Automatic</item>
@@ -305,6 +296,7 @@ you are not actively using the program.\
<string name="edit_move_counters">Edit Move Counters</string> <string name="edit_move_counters">Edit Move Counters</string>
<string name="internal_book">&lt;Internal Book&gt;</string> <string name="internal_book">&lt;Internal Book&gt;</string>
<string name="select_opening_book_file">Select opening book file</string> <string name="select_opening_book_file">Select opening book file</string>
<string name="select_chess_engine">Select Chess Engine</string>
<string name="select_pgn_file">Select PGN file to open</string> <string name="select_pgn_file">Select PGN file to open</string>
<string name="select_pgn_file_save">Save to PGN file</string> <string name="select_pgn_file_save">Save to PGN file</string>
<string name="select_scid_file">Select Scid file to open</string> <string name="select_scid_file">Select Scid file to open</string>
@@ -401,6 +393,9 @@ you are not actively using the program.\
<string name="after_selected">After Selected</string> <string name="after_selected">After Selected</string>
<string name="replace_selected">Replace Selected</string> <string name="replace_selected">Replace Selected</string>
<string name="engine">Engine</string> <string name="engine">Engine</string>
<string name="engine_error">Engine error</string>
<string name="stockfish_engine">Stockfish</string>
<string name="cuckoochess_engine">CuckooChess</string>
<string name="err_too_few_spaces">Too few spaces</string> <string name="err_too_few_spaces">Too few spaces</string>
<string name="err_invalid_piece">Invalid piece</string> <string name="err_invalid_piece">Invalid piece</string>
@@ -425,6 +420,7 @@ you are not actively using the program.\
<string name="option_force_computer_move">Force Computer Move</string> <string name="option_force_computer_move">Force Computer Move</string>
<string name="option_draw">Claim/Offer/Accept Draw</string> <string name="option_draw">Claim/Offer/Accept Draw</string>
<string name="option_select_book">Select Opening Book</string> <string name="option_select_book">Select Opening Book</string>
<string name="option_select_engine">Select Chess Engine</string>
<string name="option_color_theme">Set Color Theme</string> <string name="option_color_theme">Set Color Theme</string>
<string name="option_about">About / Help</string> <string name="option_about">About / Help</string>
@@ -434,8 +430,6 @@ you are not actively using the program.\
<string name="prefs_autoSwapSides_title">Auto Swap Sides</string> <string name="prefs_autoSwapSides_title">Auto Swap Sides</string>
<string name="prefs_autoSwapSides_summary">Automatically swap sides when new game started. Also overrides Flip Board setting</string> <string name="prefs_autoSwapSides_summary">Automatically swap sides when new game started. Also overrides Flip Board setting</string>
<string name="prefs_engine_settings">Engine Settings</string> <string name="prefs_engine_settings">Engine Settings</string>
<string name="prefs_engine_title">Engine</string>
<string name="prefs_engine_summary">Chess Engine</string>
<string name="prefs_strength_title">Strength</string> <string name="prefs_strength_title">Strength</string>
<string name="prefs_ponderMode_title">Pondering</string> <string name="prefs_ponderMode_title">Pondering</string>
<string name="prefs_ponderMode_summary">Let engine think while waiting for opponent\'s move</string> <string name="prefs_ponderMode_summary">Let engine think while waiting for opponent\'s move</string>

View File

@@ -18,14 +18,6 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:title="@string/prefs_engine_settings"> android:title="@string/prefs_engine_settings">
<ListPreference
android:key="engine"
android:title="@string/prefs_engine_title"
android:summary="@string/prefs_engine_summary"
android:entryValues="@array/engine_values"
android:entries="@array/engine_texts"
android:defaultValue="@string/engine_default">
</ListPreference>
<org.petero.droidfish.SeekBarPreference <org.petero.droidfish.SeekBarPreference
android:key="strength" android:key="strength"
android:defaultValue="1000" android:defaultValue="1000"

View File

@@ -160,6 +160,7 @@ public class DroidFish extends Activity implements GUIInterface {
private final static String bookDir = "DroidFish"; private final static String bookDir = "DroidFish";
private final static String pgnDir = "DroidFish" + File.separator + "pgn"; private final static String pgnDir = "DroidFish" + File.separator + "pgn";
private final static String engineDir = "DroidFish" + File.separator + "uci";
private BookOptions bookOptions = new BookOptions(); private BookOptions bookOptions = new BookOptions();
private PGNOptions pgnOptions = new PGNOptions(); private PGNOptions pgnOptions = new PGNOptions();
@@ -732,6 +733,10 @@ public class DroidFish extends Activity implements GUIInterface {
removeDialog(SELECT_BOOK_DIALOG); removeDialog(SELECT_BOOK_DIALOG);
showDialog(SELECT_BOOK_DIALOG); showDialog(SELECT_BOOK_DIALOG);
return true; return true;
case R.id.select_engine:
removeDialog(SELECT_ENGINE_DIALOG);
showDialog(SELECT_ENGINE_DIALOG);
return true;
case R.id.set_color_theme: case R.id.set_color_theme:
showDialog(SET_COLOR_THEME_DIALOG); showDialog(SET_COLOR_THEME_DIALOG);
return true; return true;
@@ -1004,16 +1009,17 @@ public class DroidFish extends Activity implements GUIInterface {
static private final int ABOUT_DIALOG = 2; static private final int ABOUT_DIALOG = 2;
static private final int SELECT_MOVE_DIALOG = 3; static private final int SELECT_MOVE_DIALOG = 3;
static private final int SELECT_BOOK_DIALOG = 4; static private final int SELECT_BOOK_DIALOG = 4;
static private final int SELECT_PGN_FILE_DIALOG = 5; static private final int SELECT_ENGINE_DIALOG = 5;
static private final int SELECT_PGN_FILE_SAVE_DIALOG = 6; static private final int SELECT_PGN_FILE_DIALOG = 6;
static private final int SET_COLOR_THEME_DIALOG = 7; static private final int SELECT_PGN_FILE_SAVE_DIALOG = 7;
static private final int GAME_MODE_DIALOG = 8; static private final int SET_COLOR_THEME_DIALOG = 8;
static private final int SELECT_PGN_SAVE_NEWFILE_DIALOG = 9; static private final int GAME_MODE_DIALOG = 9;
static private final int MOVELIST_MENU_DIALOG = 10; static private final int SELECT_PGN_SAVE_NEWFILE_DIALOG = 10;
static private final int THINKING_MENU_DIALOG = 11; static private final int MOVELIST_MENU_DIALOG = 11;
static private final int GO_BACK_MENU_DIALOG = 12; static private final int THINKING_MENU_DIALOG = 12;
static private final int GO_FORWARD_MENU_DIALOG = 13; static private final int GO_BACK_MENU_DIALOG = 13;
static private final int FILE_MENU_DIALOG = 14; static private final int GO_FORWARD_MENU_DIALOG = 14;
static private final int FILE_MENU_DIALOG = 15;
@Override @Override
protected Dialog onCreateDialog(int id) { protected Dialog onCreateDialog(int id) {
@@ -1225,6 +1231,45 @@ public class DroidFish extends Activity implements GUIInterface {
AlertDialog alert = builder.create(); AlertDialog alert = builder.create();
return alert; return alert;
} }
case SELECT_ENGINE_DIALOG: {
String[] fileNames = findFilesInDirectory(engineDir, null);
final int numFiles = fileNames.length;
final String[] items = new String[numFiles + 2];
final String[] ids = new String[numFiles + 2];
ids[0] = "stockfish"; items[0] = getString(R.string.stockfish_engine);
ids[1] = "cuckoochess"; items[1] = getString(R.string.cuckoochess_engine);
String sep = File.separator;
String base = Environment.getExternalStorageDirectory() + sep + engineDir + sep;
for (int i = 0; i < numFiles; i++) {
ids[i+2] = base + fileNames[i];
items[i+2] = fileNames[i];
}
String currEngine = ctrl.getEngine();
int defaultItem = 0;
for (int i = 0; i < ids.length; i++) {
if (ids[i].equals(currEngine)) {
defaultItem = i;
break;
}
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.select_chess_engine);
builder.setSingleChoiceItems(items, defaultItem, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
if ((item < 0) || (item >= 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: { case SELECT_PGN_FILE_DIALOG: {
final String[] fileNames = findFilesInDirectory(pgnDir, null); final String[] fileNames = findFilesInDirectory(pgnDir, null);
final int numFiles = fileNames.length; final int numFiles = fileNames.length;
@@ -1773,6 +1818,13 @@ public class DroidFish extends Activity implements GUIInterface {
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show(); 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 @Override
public void computerMoveMade() { public void computerMoveMade() {
if (soundEnabled) { if (soundEnabled) {

View File

@@ -67,6 +67,9 @@ public interface GUIInterface {
/** Report UCI engine name. */ /** Report UCI engine name. */
public void reportEngineName(String engine); 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. */ /** Called when computer made a move. GUI can notify user, for example by playing a sound. */
public void computerMoveMade(); public void computerMoveMade();

View File

@@ -25,7 +25,6 @@ import java.util.ArrayList;
import org.petero.droidfish.book.BookOptions; import org.petero.droidfish.book.BookOptions;
import org.petero.droidfish.book.DroidBook; import org.petero.droidfish.book.DroidBook;
import org.petero.droidfish.engine.cuckoochess.CuckooChessEngine;
import org.petero.droidfish.gamelogic.Move; import org.petero.droidfish.gamelogic.Move;
import org.petero.droidfish.gamelogic.MoveGen; import org.petero.droidfish.gamelogic.MoveGen;
import org.petero.droidfish.gamelogic.Pair; import org.petero.droidfish.gamelogic.Pair;
@@ -292,10 +291,10 @@ public class DroidComputerPlayer {
/** Stop the engine process. */ /** Stop the engine process. */
public final synchronized void shutdownEngine() { public final synchronized void shutdownEngine() {
if (uciEngine != null) { if (uciEngine != null) {
uciEngine.shutDown();
uciEngine = null;
engineMonitor.interrupt(); engineMonitor.interrupt();
engineMonitor = null; engineMonitor = null;
uciEngine.shutDown();
uciEngine = null;
} }
engineState.state = MainState.DEAD; engineState.state = MainState.DEAD;
} }
@@ -543,15 +542,20 @@ public class DroidComputerPlayer {
myAssert(searchRequest != null); myAssert(searchRequest != null);
engineName = "Computer"; engineName = "Computer";
if ("cuckoochess".equals(searchRequest.engine)) uciEngine = UCIEngineBase.getEngine(searchRequest.engine, new UCIEngine.Report() {
uciEngine = new CuckooChessEngine(); @Override
else public void reportError(String errMsg) {
uciEngine = new StockFishJNI(); if (errMsg != null) {
listener.reportEngineError(errMsg);
}
}
});
uciEngine.initialize(); uciEngine.initialize();
final UCIEngine uci = uciEngine;
engineMonitor = new Thread(new Runnable() { engineMonitor = new Thread(new Runnable() {
public void run() { public void run() {
monitorLoop(); monitorLoop(uci);
} }
}); });
engineMonitor.start(); engineMonitor.start();
@@ -569,16 +573,17 @@ public class DroidComputerPlayer {
private final static long guiUpdateInterval = 100; private final static long guiUpdateInterval = 100;
private long lastGUIUpdate = 0; private long lastGUIUpdate = 0;
private final void monitorLoop() { private final void monitorLoop(UCIEngine uci) {
while (true) { while (true) {
int timeout = getReadTimeout(); int timeout = getReadTimeout();
UCIEngine uci = uciEngine;
if (uci == null)
return;
String s = uci.readLineFromEngine(timeout);
if (Thread.currentThread().isInterrupted()) if (Thread.currentThread().isInterrupted())
return; return;
processEngineOutput(s); String s = uci.readLineFromEngine(timeout);
if ((s == null) || Thread.currentThread().isInterrupted())
return;
processEngineOutput(uci, s);
if (Thread.currentThread().isInterrupted())
return;
notifyGUI(); notifyGUI();
if (Thread.currentThread().isInterrupted()) if (Thread.currentThread().isInterrupted())
return; return;
@@ -586,7 +591,10 @@ public class DroidComputerPlayer {
} }
/** Process one line of data from the engine. */ /** 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) { if (s == null) {
shutdownEngine(); shutdownEngine();
return; return;
@@ -595,17 +603,13 @@ public class DroidComputerPlayer {
if (s.length() == 0) if (s.length() == 0)
return; return;
UCIEngine uci = uciEngine;
if (uci == null)
return;
switch (engineState.state) { switch (engineState.state) {
case READ_OPTIONS: { case READ_OPTIONS: {
if (readUCIOption(s)) { if (readUCIOption(s)) {
if (!"cuckoochess".equals(engineState.engine)) uci.initOptions();
uci.setOption("Hash", 16);
uci.setOption("Ponder", false); uci.setOption("Ponder", false);
uci.writeLineToEngine("ucinewgame"); uci.writeLineToEngine("ucinewgame");
uciEngine.writeLineToEngine("isready"); uci.writeLineToEngine("isready");
engineState.state = MainState.WAIT_READY; engineState.state = MainState.WAIT_READY;
} }
break; break;
@@ -642,7 +646,7 @@ public class DroidComputerPlayer {
case STOP_SEARCH: { case STOP_SEARCH: {
String[] tokens = tokenize(s); String[] tokens = tokenize(s);
if (tokens[0].equals("bestmove")) { if (tokens[0].equals("bestmove")) {
uciEngine.writeLineToEngine("isready"); uci.writeLineToEngine("isready");
engineState.state = MainState.WAIT_READY; engineState.state = MainState.WAIT_READY;
} }
break; break;
@@ -860,6 +864,9 @@ public class DroidComputerPlayer {
/** Notify GUI about search statistics. */ /** Notify GUI about search statistics. */
private final synchronized void notifyGUI() { private final synchronized void notifyGUI() {
if (Thread.currentThread().isInterrupted())
return;
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
if (now < lastGUIUpdate + guiUpdateInterval) if (now < lastGUIUpdate + guiUpdateInterval)
return; return;

View File

@@ -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<String> inLines;
public ExternalEngine(String engine, Report report) {
this.report = report;
engineFileName = new File(engine);
engineProc = null;
stdInThread = null;
stdErrThread = null;
inLines = new LinkedList<String>();
}
/** @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");
}
}
}

View File

@@ -23,18 +23,19 @@ public class StockFishJNI extends UCIEngineBase {
System.loadLibrary("stockfishjni"); System.loadLibrary("stockfishjni");
} }
/** @inheritDoc */
@Override @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); setOption("Skill Level", strength/50);
} }
/** /** @inheritDoc */
* 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.
*/
@Override @Override
public final String readLineFromEngine(int timeoutMillis) { public final String readLineFromEngine(int timeoutMillis) {
String ret = readFromProcess(timeoutMillis); String ret = readFromProcess(timeoutMillis);
@@ -46,14 +47,15 @@ public class StockFishJNI extends UCIEngineBase {
return ret; return ret;
} }
/** Write a line to the process. \n will be added automatically. */ /** @inheritDoc */
@Override @Override
public final synchronized void writeLineToEngine(String data) { public final synchronized void writeLineToEngine(String data) {
// System.out.printf("GUI -> Engine: %s\n", data); // System.out.printf("GUI -> Engine: %s\n", data);
writeToProcess(data + "\n"); writeToProcess(data + "\n");
} }
/** Start the child process. */ /** @inheritDoc */
@Override
protected final native void startProcess(); protected final native void startProcess();
/** /**

View File

@@ -20,15 +20,24 @@ package org.petero.droidfish.engine;
public interface UCIEngine { public interface UCIEngine {
/** For reporting engine error messages. */
public interface Report {
/** Report error message to GUI. */
void reportError(String errMsg);
}
/** Start engine. */ /** Start engine. */
public void initialize(); public void initialize();
/** Initialize default options. */
public void initOptions();
/** Shut down engine. */ /** Shut down engine. */
public void shutDown(); public void shutDown();
/** /**
* Read a line from the engine. * 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, * @return The line, without terminating newline characters,
* or empty string if no data available, * or empty string if no data available,
* or null if I/O error. * or null if I/O error.

View File

@@ -2,10 +2,21 @@ package org.petero.droidfish.engine;
import java.util.HashMap; import java.util.HashMap;
import org.petero.droidfish.engine.cuckoochess.CuckooChessEngine;
public abstract class UCIEngineBase implements UCIEngine { public abstract class UCIEngineBase implements UCIEngine {
private boolean processAlive; 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() { protected UCIEngineBase() {
processAlive = false; processAlive = false;
} }
@@ -21,7 +32,7 @@ public abstract class UCIEngineBase implements UCIEngine {
} }
@Override @Override
public final void shutDown() { public void shutDown() {
if (processAlive) { if (processAlive) {
writeLineToEngine("quit"); writeLineToEngine("quit");
processAlive = false; processAlive = false;

View File

@@ -51,12 +51,8 @@ public class CuckooChessEngine extends UCIEngineBase {
private NioInputStream inFromEngine; private NioInputStream inFromEngine;
private Thread engineThread; private Thread engineThread;
public CuckooChessEngine() { public CuckooChessEngine(Report report) {
try { pos = null;
pos = TextIO.readFEN(TextIO.startPosFEN);
} catch (ChessParseError ex) {
throw new RuntimeException();
}
moves = new ArrayList<Move>(); moves = new ArrayList<Move>();
quit = false; quit = false;
try { try {
@@ -64,14 +60,12 @@ public class CuckooChessEngine extends UCIEngineBase {
engineToGui = Pipe.open(); engineToGui = Pipe.open();
inFromEngine = new NioInputStream(engineToGui); inFromEngine = new NioInputStream(engineToGui);
} catch (IOException e) { } catch (IOException e) {
report.reportError(e.getMessage());
} }
} }
/** @inheritDoc */
@Override @Override
public void setStrength(int strength) {
setOption("strength", strength);
}
protected final void startProcess() { protected final void startProcess() {
engineThread = new Thread(new Runnable() { engineThread = new Thread(new Runnable() {
public void run() { public void run() {
@@ -87,6 +81,17 @@ public class CuckooChessEngine extends UCIEngineBase {
engineThread.start(); 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) { private final void mainLoop(NioInputStream is, NioPrintStream os) {
String line; String line;
while ((line = is.readLine()) != null) { while ((line = is.readLine()) != null) {
@@ -97,6 +102,7 @@ public class CuckooChessEngine extends UCIEngineBase {
} }
} }
/** @inheritDoc */
@Override @Override
public final String readLineFromEngine(int timeoutMillis) { public final String readLineFromEngine(int timeoutMillis) {
if ((engineThread != null) && !engineThread.isAlive()) if ((engineThread != null) && !engineThread.isAlive())
@@ -110,6 +116,7 @@ public class CuckooChessEngine extends UCIEngineBase {
return ret; return ret;
} }
/** @inheritDoc */
@Override @Override
public final synchronized void writeLineToEngine(String data) { public final synchronized void writeLineToEngine(String data) {
// System.out.printf("GUI -> Engine: %s\n", data); // System.out.printf("GUI -> Engine: %s\n", data);
@@ -224,6 +231,13 @@ public class CuckooChessEngine extends UCIEngineBase {
sPar.infinite = true; sPar.infinite = true;
} }
} }
if (pos == null) {
try {
pos = TextIO.readFEN(TextIO.startPosFEN);
} catch (ChessParseError ex) {
throw new RuntimeException();
}
}
if (ponder) { if (ponder) {
engine.startPonder(pos, moves, sPar); engine.startPonder(pos, moves, sPar);
} else { } else {

View File

@@ -157,6 +157,11 @@ public class DroidChessController {
} }
} }
/** Return current engine identifier. */
public final synchronized String getEngine() {
return engine;
}
/** Notify controller that preferences has changed. */ /** Notify controller that preferences has changed. */
public final synchronized void prefsChanged() { public final synchronized void prefsChanged() {
updateBookHints(); updateBookHints();
@@ -649,6 +654,8 @@ public class DroidChessController {
tmpPos.makeMove(ponderMove, ui); tmpPos.makeMove(ponderMove, ui);
} }
for (Move m : pv.pv) { for (Move m : pv.pv) {
if (m == null)
break;
String moveStr = TextIO.moveToString(tmpPos, m, false); String moveStr = TextIO.moveToString(tmpPos, m, false);
buf.append(String.format(" %s", moveStr)); buf.append(String.format(" %s", moveStr));
tmpPos.makeMove(m, ui); 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. */ /** Discard current search. Return true if GUI update needed. */

View File

@@ -90,4 +90,7 @@ public interface SearchListener {
/** Report engine name. */ /** Report engine name. */
public void notifyEngineName(String engineName); public void notifyEngineName(String engineName);
/** Report engine error. */
public void reportEngineError(String errMsg);
} }