DroidFish: Implemented setting of engine UCI options.

This commit is contained in:
Peter Osterlund 2014-07-16 17:20:18 +00:00
parent bd011e5359
commit 4ea935944f
18 changed files with 893 additions and 67 deletions

View File

@ -118,5 +118,9 @@
<activity android:name=".activities.CPUWarning"
android:label="@string/cpu_warning_title">
</activity>
</application>
<activity android:name=".activities.EditOptions"
android:label="@string/edit_options_title"
android:configChanges="orientation">
</activity>
</application>
</manifest>

View File

@ -0,0 +1,38 @@
<?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="fill_parent"
android:baselineAligned="false">
<ScrollView
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1">
<LinearLayout
android:id="@+id/eo_content"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:text="@android:string/ok"
android:id="@+id/eo_ok"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1">
</Button>
<Button
android:text="@string/cancel"
android:id="@+id/eo_cancel"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1">
</Button>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,37 @@
<?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">
<ScrollView
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:id="@+id/eo_content"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<Button
android:text="@android:string/ok"
android:id="@+id/eo_ok"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1">
</Button>
<Button
android:text="@string/cancel"
android:id="@+id/eo_cancel"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1">
</Button>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,5 @@
<ToggleButton xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/eo_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
</ToggleButton>

View File

@ -0,0 +1,5 @@
<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/eo_value"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
</CheckBox>

View File

@ -0,0 +1,15 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/eo_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
<Spinner
android:id="@+id/eo_value"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
</Spinner>
</LinearLayout>

View File

@ -0,0 +1,16 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="@+id/eo_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
<EditText
android:id="@+id/eo_value"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:inputType="numberSigned">
</EditText>
</LinearLayout>

View File

@ -0,0 +1,16 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="@+id/eo_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
<EditText
android:id="@+id/eo_value"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:inputType="text">
</EditText>
</LinearLayout>

View File

@ -169,6 +169,7 @@ you are not actively using the program.\
<string name="load_scid_game_title">Load Scid Game</string>
<string name="load_fen_title">Load Position</string>
<string name="cpu_warning_title">CPU Warning</string>
<string name="edit_options_title">UCI Options</string>
<string name="failed_to_read_pgn_data">Failed to read PGN data</string>
<string name="variation">Var:</string>
<string name="add_analysis">Add Analysis</string>
@ -201,6 +202,7 @@ you are not actively using the program.\
<string name="stockfish_engine">Stockfish</string>
<string name="cuckoochess_engine">CuckooChess</string>
<string name="select_engine">Select Engine</string>
<string name="set_engine_options">Set options</string>
<string name="configure_network_engine">Configure Network Engine</string>
<string name="create_network_engine">Create Network Engine</string>
<string name="delete_network_engine">Delete Network Engine?</string>

View File

@ -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<String,String> uciOpts =
(Map<String,String>)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<CharSequence> lst = new ArrayList<CharSequence>();
List<Integer> actions = new ArrayList<Integer>();
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<Integer> 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;

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<CharSequence> adapter =
new ArrayAdapter<CharSequence>(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<String, String> uciMap = new TreeMap<String,String>();
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();
}
}
}

View File

@ -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<String,String> 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<String, ArrayList<Move>> 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;
}

View File

@ -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;

View File

@ -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<String,String> 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);

View File

@ -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<String> allOptions;
private HashMap<String, String> 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<String>();
currOptions = new HashMap<String, String>();
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<String,String> uciOptions) {
boolean modified = false;
for (Map.Entry<String,String> 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<String> var = new ArrayList<String>();
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 = "<empty>";
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);
}
}

View File

@ -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<String> names;
private Map<String, OptionBase> 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<String>();
options = new TreeMap<String, OptionBase>();
}
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);
}
}

View File

@ -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;

View File

@ -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<String,String> 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()) {