diff --git a/DroidFish/AndroidManifest.xml b/DroidFish/AndroidManifest.xml
index 4bde503..a6cae17 100644
--- a/DroidFish/AndroidManifest.xml
+++ b/DroidFish/AndroidManifest.xml
@@ -118,5 +118,9 @@
-
+
+
+
diff --git a/DroidFish/res/layout-land/editoptions.xml b/DroidFish/res/layout-land/editoptions.xml
new file mode 100644
index 0000000..5649eb9
--- /dev/null
+++ b/DroidFish/res/layout-land/editoptions.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DroidFish/res/layout/editoptions.xml b/DroidFish/res/layout/editoptions.xml
new file mode 100644
index 0000000..a081618
--- /dev/null
+++ b/DroidFish/res/layout/editoptions.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DroidFish/res/layout/uci_option_button.xml b/DroidFish/res/layout/uci_option_button.xml
new file mode 100644
index 0000000..d09ac41
--- /dev/null
+++ b/DroidFish/res/layout/uci_option_button.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/DroidFish/res/layout/uci_option_check.xml b/DroidFish/res/layout/uci_option_check.xml
new file mode 100644
index 0000000..3cfe697
--- /dev/null
+++ b/DroidFish/res/layout/uci_option_check.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/DroidFish/res/layout/uci_option_combo.xml b/DroidFish/res/layout/uci_option_combo.xml
new file mode 100644
index 0000000..0ddc983
--- /dev/null
+++ b/DroidFish/res/layout/uci_option_combo.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
diff --git a/DroidFish/res/layout/uci_option_spin.xml b/DroidFish/res/layout/uci_option_spin.xml
new file mode 100644
index 0000000..dc739bc
--- /dev/null
+++ b/DroidFish/res/layout/uci_option_spin.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/DroidFish/res/layout/uci_option_string.xml b/DroidFish/res/layout/uci_option_string.xml
new file mode 100644
index 0000000..df2bec2
--- /dev/null
+++ b/DroidFish/res/layout/uci_option_string.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/DroidFish/res/values/strings.xml b/DroidFish/res/values/strings.xml
index 3352952..39c9343 100644
--- a/DroidFish/res/values/strings.xml
+++ b/DroidFish/res/values/strings.xml
@@ -169,6 +169,7 @@ you are not actively using the program.\
Load Scid Game
Load Position
CPU Warning
+ UCI Options
Failed to read PGN data
Var:
Add Analysis
@@ -201,6 +202,7 @@ you are not actively using the program.\
Stockfish
CuckooChess
Select Engine
+ Set options
Configure Network Engine
Create Network Engine
Delete Network Engine?
diff --git a/DroidFish/src/org/petero/droidfish/DroidFish.java b/DroidFish/src/org/petero/droidfish/DroidFish.java
index d2915d8..f555a3a 100644
--- a/DroidFish/src/org/petero/droidfish/DroidFish.java
+++ b/DroidFish/src/org/petero/droidfish/DroidFish.java
@@ -30,11 +30,13 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.TreeMap;
import org.petero.droidfish.ChessBoard.SquareDecoration;
import org.petero.droidfish.activities.CPUWarning;
import org.petero.droidfish.activities.EditBoard;
+import org.petero.droidfish.activities.EditOptions;
import org.petero.droidfish.activities.EditPGNLoad;
import org.petero.droidfish.activities.EditPGNSave;
import org.petero.droidfish.activities.LoadFEN;
@@ -42,6 +44,7 @@ import org.petero.droidfish.activities.LoadScid;
import org.petero.droidfish.activities.Preferences;
import org.petero.droidfish.book.BookOptions;
import org.petero.droidfish.engine.EngineUtil;
+import org.petero.droidfish.engine.UCIOptions;
import org.petero.droidfish.gamelogic.DroidChessController;
import org.petero.droidfish.gamelogic.ChessParseError;
import org.petero.droidfish.gamelogic.Move;
@@ -155,7 +158,6 @@ public class DroidFish extends Activity implements GUIInterface {
// FIXME!!! Handle PGN non-file intents with more than one game.
// FIXME!!! Save position to fen/epd file
- // FIXME!!! Strength setting for external engines
// FIXME!!! Selection dialog for going into variation
// FIXME!!! Use two engines in engine/engine games
@@ -1275,6 +1277,7 @@ public class DroidFish extends Activity implements GUIInterface {
static private final int RESULT_OI_PGN_LOAD = 6;
static private final int RESULT_OI_FEN_LOAD = 7;
static private final int RESULT_GET_FEN = 8;
+ static private final int RESULT_EDITOPTIONS = 9;
@Override
public boolean onOptionsItemSelected(MenuItem item) {
@@ -1326,6 +1329,7 @@ public class DroidFish extends Activity implements GUIInterface {
showDialog(SELECT_BOOK_DIALOG);
return true;
case R.id.manage_engines:
+ removeDialog(MANAGE_ENGINES_DIALOG);
showDialog(MANAGE_ENGINES_DIALOG);
return true;
case R.id.set_color_theme:
@@ -1429,6 +1433,14 @@ public class DroidFish extends Activity implements GUIInterface {
setFenHelper(fen);
}
break;
+ case RESULT_EDITOPTIONS:
+ if (resultCode == RESULT_OK) {
+ @SuppressWarnings("unchecked")
+ Map uciOpts =
+ (Map)data.getSerializableExtra("org.petero.droidfish.ucioptions");
+ ctrl.setEngineUCIOptions(uciOpts);
+ }
+ break;
}
}
@@ -2726,20 +2738,48 @@ public class DroidFish extends Activity implements GUIInterface {
}
private final Dialog manageEnginesDialog() {
- final CharSequence[] items = {
- getString(R.string.select_engine),
- getString(R.string.configure_network_engine)
- };
+ final int SELECT_ENGINE = 0;
+ final int SET_ENGINE_OPTIONS = 1;
+ final int CONFIG_NET_ENGINE = 2;
+ List lst = new ArrayList();
+ List actions = new ArrayList();
+ lst.add(getString(R.string.select_engine)); actions.add(SELECT_ENGINE);
+ if (ctrl.computerIdle()) {
+ UCIOptions uciOpts = ctrl.getUCIOptions();
+ if (uciOpts != null) {
+ boolean visible = false;
+ for (String name : uciOpts.getOptionNames())
+ if (uciOpts.getOption(name).visible) {
+ visible = true;
+ break;
+ }
+ if (visible) {
+ lst.add(getString(R.string.set_engine_options));
+ actions.add(SET_ENGINE_OPTIONS);
+ }
+ }
+ }
+ lst.add(getString(R.string.configure_network_engine)); actions.add(CONFIG_NET_ENGINE);
+ final List finalActions = actions;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.option_manage_engines);
- builder.setItems(items, new DialogInterface.OnClickListener() {
+ builder.setItems(lst.toArray(new CharSequence[lst.size()]), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
- switch (item) {
- case 0:
+ switch (finalActions.get(item)) {
+ case SELECT_ENGINE:
removeDialog(SELECT_ENGINE_DIALOG);
showDialog(SELECT_ENGINE_DIALOG);
break;
- case 1:
+ case SET_ENGINE_OPTIONS: {
+ Intent i = new Intent(DroidFish.this, EditOptions.class);
+ UCIOptions uciOpts = ctrl.getUCIOptions();
+ if (uciOpts != null) {
+ i.putExtra("org.petero.droidfish.ucioptions", uciOpts);
+ startActivityForResult(i, RESULT_EDITOPTIONS);
+ }
+ break;
+ }
+ case CONFIG_NET_ENGINE:
removeDialog(NETWORK_ENGINE_DIALOG);
showDialog(NETWORK_ENGINE_DIALOG);
break;
diff --git a/DroidFish/src/org/petero/droidfish/activities/EditOptions.java b/DroidFish/src/org/petero/droidfish/activities/EditOptions.java
new file mode 100644
index 0000000..3924d51
--- /dev/null
+++ b/DroidFish/src/org/petero/droidfish/activities/EditOptions.java
@@ -0,0 +1,247 @@
+/*
+ DroidFish - An Android chess program.
+ Copyright (C) 2014 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.activities;
+
+import java.util.Locale;
+import java.util.TreeMap;
+
+import org.petero.droidfish.R;
+import org.petero.droidfish.Util;
+import org.petero.droidfish.engine.UCIOptions;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.ToggleButton;
+
+/** Edit UCI options. */
+public class EditOptions extends Activity {
+ private UCIOptions uciOpts = null;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
+ Util.setFullScreenMode(this, settings);
+
+ Intent i = getIntent();
+ uciOpts = (UCIOptions)i.getSerializableExtra("org.petero.droidfish.ucioptions");
+ if (uciOpts != null) {
+ initUI();
+ } else {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ initUI();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ sendBackResult();
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @SuppressLint("CutPasteId")
+ private final void initUI() {
+ setContentView(R.layout.editoptions);
+ Util.overrideFonts(findViewById(android.R.id.content));
+
+ LinearLayout content = (LinearLayout)findViewById(R.id.eo_content);
+ Button okButton = (Button)findViewById(R.id.eo_ok);
+ Button cancelButton = (Button)findViewById(R.id.eo_cancel);
+
+ for (String name : uciOpts.getOptionNames()) {
+ UCIOptions.OptionBase o = uciOpts.getOption(name);
+ if (!o.visible)
+ continue;
+ switch (o.type) {
+ case CHECK: {
+ View v = View.inflate(this, R.layout.uci_option_check, null);
+ CheckBox checkBox = (CheckBox)v.findViewById(R.id.eo_value);
+ checkBox.setText(o.name);
+ final UCIOptions.CheckOption co = (UCIOptions.CheckOption)o;
+ checkBox.setChecked(co.value);
+ checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ co.set(isChecked);
+ }
+ });
+ content.addView(v);
+ break;
+ }
+ case SPIN: {
+ View v = View.inflate(this, R.layout.uci_option_spin, null);
+ TextView label = (TextView)v.findViewById(R.id.eo_label);
+ EditText value = (EditText)v.findViewById(R.id.eo_value);
+ final UCIOptions.SpinOption so = (UCIOptions.SpinOption)o;
+ String labelText = String.format(Locale.US, "%s (%d\u2013%d)", so.name, so.minValue, so.maxValue);
+ label.setText(labelText);
+ value.setText(so.getStringValue());
+ if (so.minValue >= 0)
+ value.setInputType(android.text.InputType.TYPE_CLASS_NUMBER);
+ value.addTextChangedListener(new TextWatcher() {
+ public void onTextChanged(CharSequence s, int start, int before, int count) { }
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
+ @Override
+ public void afterTextChanged(Editable s) {
+ try {
+ int newVal = Integer.parseInt(s.toString());
+ if (newVal < so.minValue)
+ so.set(so.minValue);
+ else if (newVal > so.maxValue)
+ so.set(so.maxValue);
+ else
+ so.set(newVal);
+ } catch (NumberFormatException ex) {
+ }
+ }
+ });
+ content.addView(v);
+ break;
+ }
+ case COMBO: {
+ View v = View.inflate(this, R.layout.uci_option_combo, null);
+ TextView label = (TextView)v.findViewById(R.id.eo_label);
+ Spinner value = (Spinner)v.findViewById(R.id.eo_value);
+ label.setText(o.name);
+ final UCIOptions.ComboOption co = (UCIOptions.ComboOption)o;
+ ArrayAdapter adapter =
+ new ArrayAdapter(this, android.R.layout.simple_spinner_item,
+ co.allowedValues);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ value.setAdapter(adapter);
+ value.setSelection(adapter.getPosition(co.value));
+ value.setOnItemSelectedListener(new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> av, View view, int position, long id) {
+ if ((position >= 0) && (position < co.allowedValues.length))
+ co.set(co.allowedValues[position]);
+ }
+ public void onNothingSelected(AdapterView> arg0) { }
+ });
+ content.addView(v);
+ break;
+ }
+ case BUTTON: {
+ View v = View.inflate(this, R.layout.uci_option_button, null);
+ ToggleButton button = (ToggleButton)v.findViewById(R.id.eo_label);
+ final UCIOptions.ButtonOption bo = (UCIOptions.ButtonOption)o;
+ bo.trigger = false;
+ button.setText(o.name);
+ button.setTextOn(o.name);
+ button.setTextOff(o.name);
+ button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ bo.trigger = isChecked;
+ }
+ });
+ content.addView(v);
+ break;
+ }
+ case STRING: {
+ View v = View.inflate(this, R.layout.uci_option_string, null);
+ TextView label = (TextView)v.findViewById(R.id.eo_label);
+ EditText value = (EditText)v.findViewById(R.id.eo_value);
+ label.setText(o.name + " ");
+ final UCIOptions.StringOption so = (UCIOptions.StringOption)o;
+ value.setText(so.value);
+ value.addTextChangedListener(new TextWatcher() {
+ public void onTextChanged(CharSequence s, int start, int before, int count) { }
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
+ @Override
+ public void afterTextChanged(Editable s) {
+ so.set(s.toString());
+ }
+ });
+ content.addView(v);
+ break;
+ }
+ }
+ }
+
+ okButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ sendBackResult();
+ }
+ });
+ cancelButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ });
+ }
+
+ private final void sendBackResult() {
+ if (uciOpts != null) {
+ TreeMap uciMap = new TreeMap();
+ for (String name : uciOpts.getOptionNames()) {
+ UCIOptions.OptionBase o = uciOpts.getOption(name);
+ if (o != null) {
+ if (o instanceof UCIOptions.ButtonOption) {
+ UCIOptions.ButtonOption bo = (UCIOptions.ButtonOption)o;
+ if (bo.trigger)
+ uciMap.put(name, "");
+ } else {
+ uciMap.put(name, o.getStringValue());
+ }
+ }
+ }
+ Intent i = new Intent();
+ i.putExtra("org.petero.droidfish.ucioptions", uciMap);
+ setResult(RESULT_OK, i);
+ finish();
+ } else {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ }
+}
diff --git a/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java b/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java
index 661b99f..5849b6b 100644
--- a/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java
+++ b/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java
@@ -25,6 +25,7 @@ import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Locale;
+import java.util.Map;
import java.util.regex.Pattern;
import org.petero.droidfish.EngineOptions;
@@ -266,6 +267,18 @@ public class DroidComputerPlayer {
}
}
+ /** Return true if computer player is in IDLE state. */
+ public final synchronized boolean computerIdle() {
+ return engineState.state == MainState.IDLE;
+ }
+
+ public final synchronized UCIOptions getUCIOptions() {
+ UCIEngine uci = uciEngine;
+ if (uci == null)
+ return null;
+ return uci.getUCIOptions();
+ }
+
/** Return maximum number of PVs supported by engine. */
public final synchronized int getMaxPV() {
return maxPV;
@@ -280,6 +293,14 @@ public class DroidComputerPlayer {
engineOptions = options;
}
+ public synchronized void setEngineUCIOptions(Map uciOptions) {
+ if (engineState.state == MainState.IDLE) {
+ UCIEngine uci = uciEngine;
+ if (uci != null)
+ uci.setUCIOptions(uciOptions);
+ }
+ }
+
/** Return all book moves, both as a formatted string and as a list of moves. */
public final Pair> getBookHints(Position pos, boolean localized) {
return book.getAllBookMoves(pos, localized);
@@ -744,24 +765,11 @@ public class DroidComputerPlayer {
}
listener.notifyEngineName(engineName);
}
- } else if (tokens.length > 2) {
- String optName = tokens[2].toLowerCase(Locale.US);
- for (int i = 3; i < tokens.length; i++) {
- if ("type".equals(tokens[i]))
- break;
- optName += " " + tokens[i].toLowerCase(Locale.US);
- }
- uci.registerOption(optName);
- if (optName.equals("multipv")) {
- try {
- for (int i = 3; i < tokens.length; i++) {
- if (tokens[i].equals("max") && (i+1 < tokens.length)) {
- maxPV = Math.max(maxPV, Integer.parseInt(tokens[i+1]));
- break;
- }
- }
- } catch (NumberFormatException nfe) { }
- }
+ } else if (tokens[0].equals("option")) {
+ UCIOptions.OptionBase o = uci.registerOption(tokens);
+ if (o instanceof UCIOptions.SpinOption &&
+ o.name.toLowerCase(Locale.US).equals("multipv"))
+ maxPV = Math.max(maxPV, ((UCIOptions.SpinOption)o).maxValue);
}
return false;
}
diff --git a/DroidFish/src/org/petero/droidfish/engine/InternalStockFish.java b/DroidFish/src/org/petero/droidfish/engine/InternalStockFish.java
index 4b4fb97..18090bc 100644
--- a/DroidFish/src/org/petero/droidfish/engine/InternalStockFish.java
+++ b/DroidFish/src/org/petero/droidfish/engine/InternalStockFish.java
@@ -28,6 +28,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.Locale;
import java.util.zip.GZIPInputStream;
import android.content.Context;
@@ -43,11 +44,14 @@ public class InternalStockFish extends ExternalEngine {
@Override
protected File getOptionsFile() {
File extDir = Environment.getExternalStorageDirectory();
- return new File(extDir, "uci/stockfish.ini");
+ return new File(extDir, "/DroidFish/uci/stockfish.ini");
}
@Override
protected boolean configurableOption(String name) {
+ name = name.toLowerCase(Locale.US);
+ if (!super.configurableOption(name))
+ return false;
if (name.equals("skill level"))
return false;
return true;
diff --git a/DroidFish/src/org/petero/droidfish/engine/UCIEngine.java b/DroidFish/src/org/petero/droidfish/engine/UCIEngine.java
index 9bc43b9..2eb2e92 100644
--- a/DroidFish/src/org/petero/droidfish/engine/UCIEngine.java
+++ b/DroidFish/src/org/petero/droidfish/engine/UCIEngine.java
@@ -18,6 +18,8 @@
package org.petero.droidfish.engine;
+import java.util.Map;
+
import org.petero.droidfish.EngineOptions;
public interface UCIEngine {
@@ -37,6 +39,12 @@ public interface UCIEngine {
/** Read UCI options from .ini file and send them to the engine. */
public void applyIniFile();
+ /** Set engine UCI options. */
+ public void setUCIOptions(Map uciOptions);
+
+ /** Get engine UCI options. */
+ public UCIOptions getUCIOptions();
+
/** Return true if engine options have correct values.
* If false is returned, engine will be restarted. */
public boolean optionsOk(EngineOptions engineOptions);
@@ -66,14 +74,17 @@ public interface UCIEngine {
/** Set an engine boolean option. */
public void setOption(String name, boolean value);
- /** Set an engine string option. */
- public void setOption(String name, String value);
+ /** Set an engine option. If the option is not a string option,
+ * value is converted to the correct type.
+ * @return True if the option was changed. */
+ public boolean setOption(String name, String value);
/** Clear list of supported options. */
public void clearOptions();
- /** Register an option as supported by the engine. */
- public void registerOption(String optName);
+ /** Register an option as supported by the engine.
+ * @param tokens The UCI option line sent by the engine, split in words. */
+ public UCIOptions.OptionBase registerOption(String[] tokens);
/** Set number of search threads to use. */
public void setNThreads(int nThreads);
diff --git a/DroidFish/src/org/petero/droidfish/engine/UCIEngineBase.java b/DroidFish/src/org/petero/droidfish/engine/UCIEngineBase.java
index 304b7a9..5a562a1 100644
--- a/DroidFish/src/org/petero/droidfish/engine/UCIEngineBase.java
+++ b/DroidFish/src/org/petero/droidfish/engine/UCIEngineBase.java
@@ -20,9 +20,9 @@ package org.petero.droidfish.engine;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
-import java.util.HashMap;
-import java.util.HashSet;
+import java.util.ArrayList;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
@@ -35,8 +35,7 @@ import android.content.Context;
public abstract class UCIEngineBase implements UCIEngine {
private boolean processAlive;
- private HashSet allOptions;
- private HashMap currOptions;
+ UCIOptions options;
protected boolean isUCI;
public static UCIEngine getEngine(Context context, String engine,
@@ -55,8 +54,7 @@ public abstract class UCIEngineBase implements UCIEngine {
protected UCIEngineBase() {
processAlive = false;
- allOptions = new HashSet();
- currOptions = new HashMap();
+ options = new UCIOptions();
isUCI = false;
}
@@ -92,22 +90,56 @@ public abstract class UCIEngineBase implements UCIEngine {
if (ent.getKey() instanceof String && ent.getValue() instanceof String) {
String key = ((String)ent.getKey()).toLowerCase(Locale.US);
String value = (String)ent.getValue();
- if (key.startsWith("uci_") || key.equals("hash") || key.equals("ponder") ||
- key.equals("multipv") || key.equals("gaviotatbpath") ||
- key.equals("syzygypath") || key.equals("threads") || key.equals("cores"))
- continue;
- if (!configurableOption(key))
- continue;
- setOption(key, value);
+ if (configurableOption(key))
+ setOption(key, value);
}
}
}
+ @Override
+ public final void setUCIOptions(Map uciOptions) {
+ boolean modified = false;
+ for (Map.Entry ent : uciOptions.entrySet()) {
+ String key = ((String)ent.getKey()).toLowerCase(Locale.US);
+ String value = (String)ent.getValue();
+ if (configurableOption(key))
+ modified |= setOption(key, value);
+ }
+ if (modified) { // Save .ini file
+ Properties iniOptions = new Properties();
+ for (String name : options.getOptionNames()) {
+ UCIOptions.OptionBase o = options.getOption(name);
+ if (configurableOption(name) && o.modified())
+ iniOptions.put(o.name, o.getStringValue());
+ }
+ File optionsFile = getOptionsFile();
+ FileOutputStream os = null;
+ try {
+ os = new FileOutputStream(optionsFile);
+ iniOptions.store(os, null);
+ } catch (IOException ex) {
+ } finally {
+ if (os != null)
+ try { os.close(); } catch (IOException ex) {}
+ }
+ }
+ }
+
+ @Override
+ public final UCIOptions getUCIOptions() {
+ return options;
+ }
+
/** Get engine UCI options file. */
protected abstract File getOptionsFile();
/** Return true if the UCI option can be changed by the user. */
protected boolean configurableOption(String name) {
+ name = name.toLowerCase(Locale.US);
+ if (name.startsWith("uci_") || name.equals("hash") || name.equals("ponder") ||
+ name.equals("multipv") || name.equals("gaviotatbpath") ||
+ name.equals("syzygypath") || name.equals("threads") || name.equals("cores"))
+ return false;
return true;
}
@@ -120,47 +152,143 @@ public abstract class UCIEngineBase implements UCIEngine {
}
@Override
- public void clearOptions() {
- allOptions.clear();
+ public final void clearOptions() {
+ options.clear();
}
@Override
- public void registerOption(String optName) {
- allOptions.add(optName);
+ public final UCIOptions.OptionBase registerOption(String[] tokens) {
+ if (tokens.length < 5 || !tokens[1].equals("name"))
+ return null;
+ String name = tokens[2];
+ int i;
+ for (i = 3; i < tokens.length; i++) {
+ if ("type".equals(tokens[i]))
+ break;
+ name += " " + tokens[i];
+ }
+
+ if (i >= tokens.length - 1)
+ return null;
+ i++;
+ String type = tokens[i++];
+
+ String defVal = null;
+ String minVal = null;
+ String maxVal = null;
+ ArrayList var = new ArrayList();
+ try {
+ for (; i < tokens.length; i++) {
+ if (tokens[i].equals("default")) {
+ String stop = null;
+ if (type.equals("spin"))
+ stop = "min";
+ else if (type.equals("combo"))
+ stop = "var";
+ defVal = "";
+ while (i+1 < tokens.length && !tokens[i+1].equals(stop)) {
+ if (defVal.length() > 0)
+ defVal += " ";
+ defVal += tokens[i+1];
+ i++;
+ }
+ } else if (tokens[i].equals("min")) {
+ minVal = tokens[++i];
+ } else if (tokens[i].equals("max")) {
+ maxVal = tokens[++i];
+ } else if (tokens[i].equals("var")) {
+ String value = "";
+ while (i+1 < tokens.length && !tokens[i+1].equals("var")) {
+ if (value.length() > 0)
+ value += " ";
+ value += tokens[i+1];
+ i++;
+ }
+ var.add(value);
+ } else
+ return null;
+ }
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ return null;
+ }
+
+ UCIOptions.OptionBase option = null;
+ if (type.equals("check")) {
+ if (defVal != null) {
+ defVal = defVal.toLowerCase(Locale.US);
+ option = new UCIOptions.CheckOption(name, defVal.equals("true"));
+ }
+ } else if (type.equals("spin")) {
+ if (defVal != null && minVal != null && maxVal != null) {
+ try {
+ int defV = Integer.parseInt(defVal);
+ int minV = Integer.parseInt(minVal);
+ int maxV = Integer.parseInt(maxVal);
+ if (minV <= defV && defV <= maxV)
+ option = new UCIOptions.SpinOption(name, minV, maxV, defV);
+ } catch (NumberFormatException ex) {
+ }
+ }
+ } else if (type.equals("combo")) {
+ if (defVal != null && var.size() > 0) {
+ String[] allowed = var.toArray(new String[var.size()]);
+ for (String s : allowed)
+ if (s.equals(defVal)) {
+ option = new UCIOptions.ComboOption(name, allowed, defVal);
+ break;
+ }
+ }
+ } else if (type.equals("button")) {
+ option = new UCIOptions.ButtonOption(name);
+ } else if (type.equals("string")) {
+ if (defVal != null)
+ option = new UCIOptions.StringOption(name, defVal);
+ }
+
+ if (option != null) {
+ if (!configurableOption(name))
+ option.visible = false;
+ options.addOption(option);
+ }
+ return option;
}
/** Return true if engine has option optName. */
- protected boolean hasOption(String optName) {
- return allOptions.contains(optName);
+ protected final boolean hasOption(String optName) {
+ return options.contains(optName);
}
@Override
- public void setOption(String name, int value) {
+ public final void setOption(String name, int value) {
setOption(name, String.format(Locale.US, "%d", value));
}
@Override
- public void setOption(String name, boolean value) {
+ public final void setOption(String name, boolean value) {
setOption(name, value ? "true" : "false");
}
@Override
- public void setOption(String name, String value) {
- String lcName = name.toLowerCase(Locale.US);
- if (!allOptions.contains(lcName))
- return;
- String currVal = currOptions.get(lcName);
- if (value.equals(currVal))
- return;
- writeLineToEngine(String.format(Locale.US, "setoption name %s value %s", name, value));
- currOptions.put(lcName, value);
+ public final boolean setOption(String name, String value) {
+ if (!options.contains(name))
+ return false;
+ UCIOptions.OptionBase o = options.getOption(name);
+ if (o instanceof UCIOptions.ButtonOption) {
+ writeLineToEngine(String.format(Locale.US, "setoption name %s", name));
+ } else if (o.setFromString(value)) {
+ if (value.length() == 0)
+ value = "";
+ writeLineToEngine(String.format(Locale.US, "setoption name %s value %s", name, value));
+ return true;
+ }
+ return false;
}
@Override
- public void setNThreads(int nThreads) {
- if (allOptions.contains("threads"))
+ public final void setNThreads(int nThreads) {
+ if (options.contains("Threads"))
setOption("Threads", nThreads);
- else if (allOptions.contains("cores"))
+ else if (options.contains("Cores"))
setOption("Cores", nThreads);
}
}
diff --git a/DroidFish/src/org/petero/droidfish/engine/UCIOptions.java b/DroidFish/src/org/petero/droidfish/engine/UCIOptions.java
new file mode 100644
index 0000000..041d235
--- /dev/null
+++ b/DroidFish/src/org/petero/droidfish/engine/UCIOptions.java
@@ -0,0 +1,228 @@
+package org.petero.droidfish.engine;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class UCIOptions implements Serializable {
+ private static final long serialVersionUID = 1L;
+ private ArrayList names;
+ private Map options;
+
+ public static enum Type {
+ CHECK,
+ SPIN,
+ COMBO,
+ BUTTON,
+ STRING
+ }
+
+ public abstract static class OptionBase implements Serializable {
+ private static final long serialVersionUID = 1L;
+ public String name;
+ public Type type;
+ public boolean visible = true;
+
+ /** Return true if current value != default value. */
+ abstract public boolean modified();
+
+ /** Return current value as a string. */
+ abstract public String getStringValue();
+
+ /** Set option from string value. Return true if option was modified. */
+ public final boolean setFromString(String value) {
+ OptionBase o = this;
+ switch (o.type) {
+ case CHECK:
+ if (value.toLowerCase(Locale.US).equals("true"))
+ return ((CheckOption)o).set(true);
+ else if (value.toLowerCase(Locale.US).equals("false"))
+ return ((CheckOption)o).set(false);
+ return false;
+ case SPIN:
+ try {
+ int val = Integer.parseInt(value);
+ SpinOption so = (SpinOption)o;
+ return so.set(val);
+ } catch (NumberFormatException ex) {
+ }
+ return false;
+ case COMBO:
+ return ((ComboOption)o).set(value);
+ case BUTTON:
+ return false;
+ case STRING:
+ return ((StringOption)o).set(value);
+ }
+ return false;
+ }
+ }
+
+ public static final class CheckOption extends OptionBase {
+ private static final long serialVersionUID = 1L;
+ public boolean value;
+ public boolean defaultValue;
+ CheckOption(String name, boolean def) {
+ this.name = name;
+ this.type = Type.CHECK;
+ this.value = def;
+ this.defaultValue = def;
+ }
+ @Override
+ public boolean modified() {
+ return value != defaultValue;
+ }
+ @Override
+ public String getStringValue() {
+ return value ? "true" : "false";
+ }
+ public boolean set(boolean value) {
+ if (this.value != value) {
+ this.value = value;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public static final class SpinOption extends OptionBase {
+ private static final long serialVersionUID = 1L;
+ public int minValue;
+ public int maxValue;
+ public int value;
+ public int defaultValue;
+ SpinOption(String name, int minV, int maxV, int def) {
+ this.name = name;
+ this.type = Type.SPIN;
+ this.minValue = minV;
+ this.maxValue = maxV;
+ this.value = def;
+ this.defaultValue = def;
+ }
+ @Override
+ public boolean modified() {
+ return value != defaultValue;
+ }
+ @Override
+ public String getStringValue() {
+ return String.format(Locale.US, "%d", value);
+ }
+ public boolean set(int value) {
+ if ((value >= minValue) && (value <= maxValue)) {
+ if (this.value != value) {
+ this.value = value;
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ public static final class ComboOption extends OptionBase {
+ private static final long serialVersionUID = 1L;
+ public String[] allowedValues;
+ public String value;
+ public String defaultValue;
+ ComboOption(String name, String[] allowed, String def) {
+ this.name = name;
+ this.type = Type.COMBO;
+ this.allowedValues = allowed;
+ this.value = def;
+ this.defaultValue = def;
+ }
+ @Override
+ public boolean modified() {
+ return !value.equals(defaultValue);
+ }
+ @Override
+ public String getStringValue() {
+ return value;
+ }
+ public boolean set(String value) {
+ for (String allowed : allowedValues) {
+ if (allowed.toLowerCase(Locale.US).equals(value.toLowerCase(Locale.US))) {
+ if (!this.value.equals(allowed)) {
+ this.value = allowed;
+ return true;
+ }
+ break;
+ }
+ }
+ return false;
+ }
+ }
+
+ public static final class ButtonOption extends OptionBase {
+ private static final long serialVersionUID = 1L;
+ public boolean trigger;
+ ButtonOption(String name) {
+ this.name = name;
+ this.type = Type.BUTTON;
+ this.trigger = false;
+ }
+ @Override
+ public boolean modified() {
+ return false;
+ }
+ @Override
+ public String getStringValue() {
+ return "";
+ }
+ }
+
+ public static final class StringOption extends OptionBase {
+ private static final long serialVersionUID = 1L;
+ public String value;
+ public String defaultValue;
+ StringOption(String name, String def) {
+ this.name = name;
+ this.type = Type.STRING;
+ this.value = def;
+ this.defaultValue = def;
+ }
+ @Override
+ public boolean modified() {
+ return !value.equals(defaultValue);
+ }
+ @Override
+ public String getStringValue() {
+ return value;
+ }
+ public boolean set(String value) {
+ if (!this.value.equals(value)) {
+ this.value = value;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ UCIOptions() {
+ names = new ArrayList();
+ options = new TreeMap();
+ }
+
+ public void clear() {
+ options.clear();
+ }
+
+ public boolean contains(String optName) {
+ return getOption(optName) != null;
+ }
+
+ public final String[] getOptionNames() {
+ return names.toArray(new String[names.size()]);
+ }
+
+ public final OptionBase getOption(String name) {
+ return options.get(name.toLowerCase(Locale.US));
+ }
+
+ final void addOption(OptionBase p) {
+ String name = p.name.toLowerCase(Locale.US);
+ names.add(name);
+ options.put(name, p);
+ }
+}
diff --git a/DroidFish/src/org/petero/droidfish/engine/cuckoochess/CuckooChessEngine.java b/DroidFish/src/org/petero/droidfish/engine/cuckoochess/CuckooChessEngine.java
index dc5fd3f..fac5a94 100644
--- a/DroidFish/src/org/petero/droidfish/engine/cuckoochess/CuckooChessEngine.java
+++ b/DroidFish/src/org/petero/droidfish/engine/cuckoochess/CuckooChessEngine.java
@@ -78,11 +78,14 @@ public class CuckooChessEngine extends UCIEngineBase {
@Override
protected File getOptionsFile() {
File extDir = Environment.getExternalStorageDirectory();
- return new File(extDir, "uci/cuckoochess.ini");
+ return new File(extDir, "/DroidFish/uci/cuckoochess.ini");
}
@Override
protected boolean configurableOption(String name) {
+ name = name.toLowerCase(Locale.US);
+ if (!super.configurableOption(name))
+ return false;
if (name.equals("strength"))
return false;
return true;
diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java b/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java
index 75ed6f1..ea838f0 100644
--- a/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java
+++ b/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java
@@ -35,6 +35,7 @@ import org.petero.droidfish.PGNOptions;
import org.petero.droidfish.Util;
import org.petero.droidfish.book.BookOptions;
import org.petero.droidfish.engine.DroidComputerPlayer;
+import org.petero.droidfish.engine.UCIOptions;
import org.petero.droidfish.engine.DroidComputerPlayer.SearchRequest;
import org.petero.droidfish.engine.DroidComputerPlayer.SearchType;
import org.petero.droidfish.gamelogic.Game.GameState;
@@ -153,6 +154,7 @@ public class DroidChessController {
}
}
+ /** Set engine options. */
public final synchronized void setEngineOptions(EngineOptions options, boolean restart) {
if (!engineOptions.equals(options)) {
engineOptions = options;
@@ -184,6 +186,12 @@ public class DroidChessController {
}
}
+ /** Set engine UCI options. */
+ public final synchronized void setEngineUCIOptions(Map uciOptions) {
+ if (computerPlayer != null)
+ computerPlayer.setEngineUCIOptions(uciOptions);
+ }
+
/** Return current engine identifier. */
public final synchronized String getEngine() {
return engine;
@@ -287,6 +295,17 @@ public class DroidChessController {
return (computerPlayer != null) && computerPlayer.computerBusy();
}
+ /** Return true if computer player is in IDLE state. */
+ public final synchronized boolean computerIdle() {
+ return (computerPlayer != null) && computerPlayer.computerIdle();
+ }
+
+ public final synchronized UCIOptions getUCIOptions() {
+ if (!computerIdle() || computerPlayer == null)
+ return null;
+ return computerPlayer.getUCIOptions();
+ }
+
/** Make a move for a human player. */
public final synchronized void makeHumanMove(Move m) {
if (humansTurn()) {