/*
DroidFish - An Android chess program.
Copyright (C) 2011-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.engine;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import org.petero.droidfish.EngineOptions;
import org.petero.droidfish.engine.cuckoochess.CuckooChessEngine;
public abstract class UCIEngineBase implements UCIEngine {
private boolean processAlive;
private UCIOptions options;
protected boolean isUCI;
public static UCIEngine getEngine(String engine,
EngineOptions engineOptions, Report report) {
if ("stockfish".equals(engine) && (EngineUtil.internalStockFishName() == null))
engine = "cuckoochess";
if ("cuckoochess".equals(engine))
return new CuckooChessEngine(report);
else if ("stockfish".equals(engine))
return new InternalStockFish(report);
else if (EngineUtil.isOpenExchangeEngine(engine))
return new OpenExchangeEngine(engine, report);
else if (EngineUtil.isNetEngine(engine))
return new NetworkEngine(engine, engineOptions, report);
else
return new ExternalEngine(engine, report);
}
protected UCIEngineBase() {
processAlive = false;
options = new UCIOptions();
isUCI = false;
}
protected abstract void startProcess();
@Override
public final void initialize() {
if (!processAlive) {
startProcess();
processAlive = true;
}
}
@Override
public void initOptions(EngineOptions engineOptions) {
isUCI = true;
}
@Override
public final void applyIniFile() {
File optionsFile = getOptionsFile();
Properties iniOptions = new Properties();
FileInputStream is = null;
try {
is = new FileInputStream(optionsFile);
iniOptions.load(is);
} catch (IOException ex) {
} finally {
if (is != null)
try { is.close(); } catch (IOException ex) {}
}
for (Map.Entry<Object,Object> ent : iniOptions.entrySet()) {
if (ent.getKey() instanceof String && ent.getValue() instanceof String) {
String key = ((String)ent.getKey()).toLowerCase(Locale.US);
String value = (String)ent.getValue();
if (configurableOption(key))
setOption(key, value);
}
}
}
@Override
public final boolean 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);
}
return modified;
}
@Override
public final void saveIniFile(UCIOptions options) {
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_")) {
String[] allowed = { "uci_limitstrength", "uci_elo" };
return Arrays.asList(allowed).contains(name);
} else {
String[] ignored = { "hash", "ponder", "multipv",
"gaviotatbpath", "syzygypath" };
return !Arrays.asList(ignored).contains(name);
}
}
@Override
public void shutDown() {
if (processAlive) {
writeLineToEngine("quit");
processAlive = false;
}
}
@Override
public final void clearOptions() {
options.clear();
}
@Override
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 final boolean hasOption(String optName) {
return options.contains(optName);
}
@Override
public final void setOption(String name, int value) {
setOption(name, String.format(Locale.US, "%d", value));
}
@Override
public final void setOption(String name, boolean value) {
setOption(name, value ? "true" : "false");
}
@Override
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", o.name));
} else if (o.setFromString(value)) {
if (value.length() == 0)
value = "<empty>";
writeLineToEngine(String.format(Locale.US, "setoption name %s value %s", o.name, value));
return true;
}
return false;
}
}