DroidFish: Made it possible to configure more than one network engine.

This commit is contained in:
Peter Osterlund 2012-08-26 22:00:27 +00:00
parent 6a37ea341d
commit feb5fabb22
11 changed files with 403 additions and 47 deletions

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<TextView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:paddingRight="10dp"
android:text="@string/engine_name">
</TextView>
<EditText
android:id="@+id/create_network_engine"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:inputType="text">
</EditText>
</LinearLayout>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<TextView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:paddingRight="10dp"
android:text="@string/host_name">
</TextView>
<EditText
android:id="@+id/network_engine_host"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:inputType="text">
</EditText>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<TextView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:paddingRight="10dp"
android:text="@string/network_port">
</TextView>
<EditText
android:id="@+id/network_engine_port"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:inputType="number">
</EditText>
</LinearLayout>
</LinearLayout>

View File

@ -44,8 +44,8 @@
android:title="@string/option_select_book">
</item>
<item
android:id="@+id/select_engine"
android:title="@string/option_select_engine">
android:id="@+id/manage_engines"
android:title="@string/option_manage_engines">
</item>
<item
android:id="@+id/set_color_theme"

View File

@ -76,6 +76,7 @@ you are not actively using the program.\
<string name="reading_pgn_file">Reading PGN file</string>
<string name="reading_scid_file">Reading Scid file</string>
<string name="new_file">&lt;New File&gt;</string>
<string name="new_engine">&lt;New Engine&gt;</string>
<string name="please_wait">Please wait…</string>
<string name="no_pgn_files">No files found in directory DroidFish/pgn on the SD card</string>
<string name="no_scid_files">No files found in directory scid on the SD card</string>
@ -161,6 +162,7 @@ you are not actively using the program.\
<string name="heavy_cpu_usage">Heavy CPU usage</string>
<string name="background_processing">Background processing</string>
<string name="lot_cpu_power">DroidFish is using a lot of CPU power</string>
<string name="delete">Delete</string>
<string name="delete_game">Delete game?</string>
<string name="delete_file">Delete file</string>
<string name="delete_file_question">Delete file?</string>
@ -175,14 +177,21 @@ you are not actively using the program.\
<string name="after_selected">After Selected</string>
<string name="replace_selected">Replace Selected</string>
<string name="engine">Engine</string>
<string name="engine_name">Engine name</string>
<string name="engine_name_in_use">Engine name in use</string>
<string name="engine_error">Engine error</string>
<string name="stockfish_engine">Stockfish</string>
<string name="cuckoochess_engine">CuckooChess</string>
<string name="select_engine">Select Engine</string>
<string name="configure_network_engine">Configure Network Engine</string>
<string name="create_network_engine">Create Network Engine</string>
<string name="host_name">Host name</string>
<string name="network_port">Port</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="network_engine_config_error">Network engine configuration 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>
@ -209,6 +218,7 @@ you are not actively using the program.\
<string name="option_force_computer_move">Force Computer Move</string>
<string name="option_draw">Claim/Offer/Accept Draw</string>
<string name="option_select_book">Select Opening Book</string>
<string name="option_manage_engines">Manage Chess Engines</string>
<string name="option_select_engine">Select Chess Engine</string>
<string name="option_color_theme">Set Color Theme</string>
<string name="option_about">About / Help</string>
@ -228,8 +238,6 @@ 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>

View File

@ -52,12 +52,6 @@
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">

View File

@ -21,6 +21,8 @@ package org.petero.droidfish;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@ -748,7 +750,6 @@ 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);
@ -800,7 +801,7 @@ public class DroidFish extends Activity implements GUIInterface {
gtbPath = extDir.getAbsolutePath() + sep + gtbDefaultDir;
}
engineOptions.gtbPath = gtbPath;
setEngineOptions();
setEngineOptions(false);
setEgtbHints(cb.getSelectedSquare());
updateThinkingInfo();
@ -895,8 +896,6 @@ 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 :
@ -939,12 +938,25 @@ public class DroidFish extends Activity implements GUIInterface {
private boolean egtbForceReload = false;
private final void setEngineOptions() {
ctrl.setEngineOptions(new EngineOptions(engineOptions));
private final void setEngineOptions(boolean restart) {
computeNetEngineID();
ctrl.setEngineOptions(new EngineOptions(engineOptions), restart);
Probe.getInstance().setPath(engineOptions.gtbPath, egtbForceReload);
egtbForceReload = false;
}
private final void computeNetEngineID() {
String id = "";
try {
String engine = settings.getString("engine", "stockfish");
String[] lines = Util.readFile(engine);
if (lines.length >= 3)
id = lines[1] + ":" + lines[2];
} catch (IOException e) {
}
engineOptions.networkID = id;
}
private final void setEgtbHints(int sq) {
if (!engineOptions.hints || (sq < 0)) {
cb.setSquareDecorations(null);
@ -1027,9 +1039,8 @@ public class DroidFish extends Activity implements GUIInterface {
removeDialog(SELECT_BOOK_DIALOG);
showDialog(SELECT_BOOK_DIALOG);
return true;
case R.id.select_engine:
removeDialog(SELECT_ENGINE_DIALOG);
showDialog(SELECT_ENGINE_DIALOG);
case R.id.manage_engines:
showDialog(MANAGE_ENGINES_DIALOG);
return true;
case R.id.set_color_theme:
showDialog(SET_COLOR_THEME_DIALOG);
@ -1423,6 +1434,10 @@ public class DroidFish extends Activity implements GUIInterface {
static private final int CUSTOM1_BUTTON_DIALOG = 17;
static private final int CUSTOM2_BUTTON_DIALOG = 18;
static private final int CUSTOM3_BUTTON_DIALOG = 19;
static private final int MANAGE_ENGINES_DIALOG = 20;
static private final int NETWORK_ENGINE_DIALOG = 21;
static private final int NEW_NETWORK_ENGINE_DIALOG = 22;
static private final int NETWORK_ENGINE_CONFIG_DIALOG = 23;
@Override
protected Dialog onCreateDialog(int id) {
@ -1590,8 +1605,6 @@ public class DroidFish extends Activity implements GUIInterface {
} catch (UnsupportedEncodingException e1) {
} catch (IOException e) {
}
System.out.printf("%.3f DroidFish.onCreateDialog(): data:%s\n",
System.currentTimeMillis() * 1e-3, data);
wv.loadDataWithBaseURL(null, data, "text/html", "utf-8", null);
try {
PackageInfo pi = getPackageManager().getPackageInfo("org.petero.droidfish", 0);
@ -1683,11 +1696,15 @@ public class DroidFish extends Activity implements GUIInterface {
return alert;
}
case SELECT_ENGINE_DIALOG: {
String[] fileNames = findFilesInDirectory(engineDir, null);
String[] fileNames = findFilesInDirectory(engineDir, new FileNameFilter() {
@Override
public boolean accept(String filename) {
return !internalEngine(filename);
}
});
final int numFiles = fileNames.length;
boolean haveSf = EngineUtil.internalStockFishName() != null;
boolean haveNet = engineOptions.networkEngine.length() > 0;
final int nEngines = numFiles + 1 + (haveSf ? 1 : 0) + (haveNet ? 1 : 0);
final int nEngines = numFiles + 1 + (haveSf ? 1 : 0);
final String[] items = new String[nEngines];
final String[] ids = new String[nEngines];
int idx = 0;
@ -1695,9 +1712,6 @@ 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++) {
@ -1725,6 +1739,7 @@ public class DroidFish extends Activity implements GUIInterface {
editor.commit();
dialog.dismiss();
int strength = settings.getInt("strength", 1000);
setEngineOptions(false);
setEngineStrength(engine, strength);
}
});
@ -2193,10 +2208,229 @@ public class DroidFish extends Activity implements GUIInterface {
return makeButtonDialog(custom2ButtonActions);
case CUSTOM3_BUTTON_DIALOG:
return makeButtonDialog(custom3ButtonActions);
case MANAGE_ENGINES_DIALOG:
return manageEnginesDialog();
case NETWORK_ENGINE_DIALOG:
return networkEngineDialog();
case NEW_NETWORK_ENGINE_DIALOG:
return newNetworkEngineDialog();
case NETWORK_ENGINE_CONFIG_DIALOG:
return networkEngineConfigDialog();
}
return null;
}
private final static boolean internalEngine(String name) {
return "cuckoochess".equals(name) ||
"stockfish".equals(name);
}
private final Dialog manageEnginesDialog() {
final CharSequence[] items = {
getString(R.string.select_engine),
getString(R.string.network_engine)
};
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.option_manage_engines);
builder.setItems(items, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
switch (item) {
case 0:
removeDialog(SELECT_ENGINE_DIALOG);
showDialog(SELECT_ENGINE_DIALOG);
break;
case 1:
removeDialog(NETWORK_ENGINE_DIALOG);
showDialog(NETWORK_ENGINE_DIALOG);
break;
}
}
});
AlertDialog alert = builder.create();
return alert;
}
private final Dialog networkEngineDialog() {
String[] fileNames = findFilesInDirectory(engineDir, new FileNameFilter() {
@Override
public boolean accept(String filename) {
if (internalEngine(filename))
return false;
try {
InputStream inStream = new FileInputStream(filename);
InputStreamReader inFile = new InputStreamReader(inStream);
char[] buf = new char[4];
boolean ret = (inFile.read(buf) == 4) && "NETE".equals(new String(buf));
inFile.close();
return ret;
} catch (IOException e) {
return false;
}
}
});
final int numFiles = fileNames.length;
final int numItems = numFiles + 1;
final String[] items = new String[numItems];
final String[] ids = new String[numItems];
int idx = 0;
String sep = File.separator;
String base = Environment.getExternalStorageDirectory() + sep + engineDir + sep;
for (int i = 0; i < numFiles; i++) {
ids[idx] = base + fileNames[i];
items[idx] = fileNames[i];
idx++;
}
ids[idx] = ""; items[idx] = getString(R.string.new_engine); idx++;
String currEngine = ctrl.getEngine();
int defaultItem = 0;
for (int i = 0; i < numItems; i++)
if (ids[i].equals(currEngine)) {
defaultItem = i;
break;
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.configure_network_engine);
builder.setSingleChoiceItems(items, defaultItem, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
if ((item < 0) || (item >= numItems))
return;
dialog.dismiss();
if (item == numItems - 1) {
showDialog(NEW_NETWORK_ENGINE_DIALOG);
} else {
networkEngineToConfig = ids[item];
removeDialog(NETWORK_ENGINE_CONFIG_DIALOG);
showDialog(NETWORK_ENGINE_CONFIG_DIALOG);
}
}
});
AlertDialog alert = builder.create();
return alert;
}
// Filename of network engine to configure
private String networkEngineToConfig = "";
// Ask for name of new network engine
private final Dialog newNetworkEngineDialog() {
View content = View.inflate(this, R.layout.create_network_engine, null);
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setView(content);
builder.setTitle(R.string.create_network_engine);
final EditText engineNameView = (EditText)content.findViewById(R.id.create_network_engine);
engineNameView.setText("");
final Runnable createEngine = new Runnable() {
public void run() {
String engineName = engineNameView.getText().toString();
String sep = File.separator;
String pathName = Environment.getExternalStorageDirectory() + sep + engineDir + sep + engineName;
File file = new File(pathName);
if (internalEngine(engineName) || file.exists()) {
Toast.makeText(getApplicationContext(), R.string.engine_name_in_use, Toast.LENGTH_LONG).show();
return;
}
networkEngineToConfig = pathName;
removeDialog(NETWORK_ENGINE_CONFIG_DIALOG);
showDialog(NETWORK_ENGINE_CONFIG_DIALOG);
}
};
builder.setPositiveButton(android.R.string.ok, new Dialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
createEngine.run();
}
});
builder.setNegativeButton(R.string.cancel, null);
final Dialog dialog = builder.create();
engineNameView.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
createEngine.run();
dialog.cancel();
return true;
}
return false;
}
});
return dialog;
}
// Configure network engine settings
private final Dialog networkEngineConfigDialog() {
View content = View.inflate(this, R.layout.network_engine_config, null);
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setView(content);
builder.setTitle(R.string.configure_network_engine);
final EditText hostNameView = (EditText)content.findViewById(R.id.network_engine_host);
final EditText portView = (EditText)content.findViewById(R.id.network_engine_port);
String hostName = "";
String port = "0";
try {
String[] lines = Util.readFile(networkEngineToConfig);
if ((lines.length >= 1) && lines[0].equals("NETE")) {
if (lines.length > 1)
hostName = lines[1];
if (lines.length > 2)
port = lines[2];
}
} catch (IOException e1) {
}
hostNameView.setText(hostName);
portView.setText(port);
final Runnable writeConfig = new Runnable() {
public void run() {
String hostName = hostNameView.getText().toString();
String port = portView.getText().toString();
try {
FileWriter fw = new FileWriter(new File(networkEngineToConfig), false);
fw.write("NETE\n");
fw.write(hostName); fw.write("\n");
fw.write(port); fw.write("\n");
fw.close();
setEngineOptions(true);
} catch (IOException e) {
Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show();
}
}
};
builder.setPositiveButton(android.R.string.ok, new Dialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
writeConfig.run();
}
});
builder.setNegativeButton(R.string.cancel, null);
builder.setNeutralButton(R.string.delete, new Dialog.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new File(networkEngineToConfig).delete();
String engine = settings.getString("engine", "stockfish");
if (engine.equals(networkEngineToConfig)) {
engine = "stockfish";
Editor editor = settings.edit();
editor.putString("engine", engine);
editor.commit();
dialog.dismiss();
int strength = settings.getInt("strength", 1000);
setEngineOptions(false);
setEngineStrength(engine, strength);
}
}
});
final Dialog dialog = builder.create();
hostNameView.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
writeConfig.run();
dialog.cancel();
return true;
}
return false;
}
});
return dialog;
}
private final void shareGame() {
Intent i = new Intent(Intent.ACTION_SEND);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);

View File

@ -26,7 +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 String networkID; // host+port network settings
public EngineOptions() {
hashMB = 16;
@ -35,7 +35,7 @@ public final class EngineOptions {
rootProbe = false;
engineProbe = false;
gtbPath = "";
networkEngine = "";
networkID = "";
}
public EngineOptions(EngineOptions other) {
@ -45,7 +45,7 @@ public final class EngineOptions {
rootProbe = other.rootProbe;
engineProbe = other.engineProbe;
gtbPath = other.gtbPath;
networkEngine = other.networkEngine;
networkID = other.networkID;
}
@Override
@ -60,7 +60,7 @@ public final class EngineOptions {
(rootProbe == other.rootProbe) &&
(engineProbe == other.engineProbe) &&
gtbPath.equals(other.gtbPath) &&
networkEngine.equals(other.networkEngine));
networkID.equals(other.networkID));
}
@Override

View File

@ -1,5 +1,12 @@
package org.petero.droidfish;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import android.os.Build;
public final class Util {
@ -18,4 +25,17 @@ public final class Util {
boldStop = "</b>";
}
}
/** Read a text file. Return string array with one string per line. */
public static String[] readFile(String networkEngineToConfig) throws IOException {
ArrayList<String> ret = new ArrayList<String>();
InputStream inStream = new FileInputStream(networkEngineToConfig);
InputStreamReader inFile = new InputStreamReader(inStream);
BufferedReader inBuf = new BufferedReader(inFile);
String line;
while ((line = inBuf.readLine()) != null)
ret.add(line);
inBuf.close();
return ret.toArray(new String[ret.size()]);
}
}

View File

@ -27,6 +27,8 @@ import java.net.UnknownHostException;
import org.petero.droidfish.EngineOptions;
import org.petero.droidfish.R;
import org.petero.droidfish.Util;
import android.content.Context;
/** Engine running on a different computer. */
@ -34,7 +36,8 @@ public class NetworkEngine extends UCIEngineBase {
protected final Context context;
private final Report report;
private String networkEngine;
private String fileName;
private String networkID;
private Socket socket;
private Thread startupThread;
private Thread stdInThread;
@ -45,11 +48,11 @@ public class NetworkEngine extends UCIEngineBase {
private boolean isRunning;
private boolean isError;
public NetworkEngine(Context context, String engine, EngineOptions engineOptions,
Report report) {
public NetworkEngine(Context context, String engine, EngineOptions engineOptions, Report report) {
this.context = context;
this.report = report;
this.networkEngine = engineOptions.networkEngine;
fileName = engine;
networkID = engineOptions.networkID;
startupThread = null;
stdInThread = null;
guiToEngine = new LocalPipe();
@ -62,14 +65,25 @@ public class NetworkEngine extends UCIEngineBase {
/** Create socket connection to remote server. */
private final synchronized void connect() {
if (socket == null) {
int idx = networkEngine.lastIndexOf(':');
if (idx < 0) {
String host = null;
String port = null;
boolean fail = false;
try {
String[] lines = Util.readFile(fileName);
if ((lines.length < 3) || !lines[0].equals("NETE")) {
fail = true;
} else {
host = lines[1];
port = lines[2];
}
} catch (IOException e1) {
fail = true;
}
if (fail) {
isError = true;
report.reportError(context.getString(R.string.network_host_syntax_error));
report.reportError(context.getString(R.string.network_engine_config_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);
@ -205,7 +219,7 @@ public class NetworkEngine extends UCIEngineBase {
return false;
if (!optionsInitialized)
return true;
if (!networkEngine.equals(engineOptions.networkEngine))
if (!networkID.equals(engineOptions.networkID))
return false;
if (hashMB != engineOptions.hashMB)
return false;

View File

@ -18,6 +18,10 @@
package org.petero.droidfish.engine;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.HashSet;
@ -41,10 +45,22 @@ public abstract class UCIEngineBase implements UCIEngine {
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);
else {
boolean netEngine = false;
try {
InputStream inStream = new FileInputStream(engine);
InputStreamReader inFile = new InputStreamReader(inStream);
char[] buf = new char[4];
if ((inFile.read(buf) == 4) && "NETE".equals(new String(buf)))
netEngine = true;
inFile.close();
} catch (IOException e) {
}
if (netEngine)
return new NetworkEngine(context, engine, engineOptions, report);
else
return new ExternalEngine(context, engine, report);
}
}
protected UCIEngineBase() {

View File

@ -153,11 +153,16 @@ public class DroidChessController {
}
}
public final synchronized void setEngineOptions(EngineOptions options) {
public final synchronized void setEngineOptions(EngineOptions options, boolean restart) {
if (!engineOptions.equals(options)) {
engineOptions = options;
if (computerPlayer != null)
computerPlayer.setEngineOptions(engineOptions);
if (restart && (game != null)) {
abortSearch();
updateComputeThreads();
updateGUI();
}
}
}