UCI refactoring

+ Multiple listener support in UCIHandle
+ UCIInfo class
- Moved info and option parsing into the respective classes
- Removed unimplemented UCI callback methods from UCIPlayer
This commit is contained in:
Kai S. K. Engelbart 2019-07-31 17:47:49 +02:00
parent 6b6bda32ee
commit dd9816ce48
6 changed files with 208 additions and 116 deletions

View File

@ -1,15 +1,11 @@
package dev.kske.chess.game;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import dev.kske.chess.board.Move;
import dev.kske.chess.board.Piece.Color;
import dev.kske.chess.uci.UCIHandle;
import dev.kske.chess.uci.UCIListener;
import dev.kske.chess.uci.UCIOption;
/**
* Project: <strong>Chess</strong><br>
@ -20,11 +16,9 @@ import dev.kske.chess.uci.UCIOption;
public class UCIPlayer extends Player implements UCIListener {
private UCIHandle handle;
private List<UCIOption> options;
public UCIPlayer(Color color, String enginePath) {
super(color);
options = new ArrayList<>();
try {
handle = new UCIHandle(enginePath);
handle.setListener(this);
@ -55,16 +49,6 @@ public class UCIPlayer extends Player implements UCIListener {
this.name = name;
}
@Override
public void onUCIOk() {
System.out.println("UCI ok");
}
@Override
public void onReadyOk() {
System.out.println("Ready ok");
}
@Override
public void onBestMove(String move) {
Move moveObj = Move.fromSAN(move);
@ -72,7 +56,7 @@ public class UCIPlayer extends Player implements UCIListener {
}
@Override
public void onBestMove(String move, String ponderMove) {
public void onBestMove(String move, Move ponderMove) {
onBestMove(move);
}
@ -105,15 +89,4 @@ public class UCIPlayer extends Player implements UCIListener {
public void onRegistrationError() {
System.err.println("Registration error!");
}
@Override
public void onInfo(Map<String, String> additionalInfo) {
System.out.println("Info:");
additionalInfo.forEach((k, v) -> System.out.printf("%s: %s%n", k, v));
}
@Override
public void onOption(UCIOption option) {
options.add(option);
}
}

View File

@ -138,6 +138,6 @@ public class UCIHandle {
}
public void setListener(UCIListener listener) {
receiver.setListener(listener);
receiver.addListener(listener);
}
}

View File

@ -0,0 +1,133 @@
package dev.kske.chess.uci;
import java.util.ArrayList;
import java.util.List;
import dev.kske.chess.board.Move;
/**
* Project: <strong>Chess</strong><br>
* File: <strong>UCIInfo.java</strong><br>
* Created: <strong>28.07.2019</strong><br>
* Author: <strong>Kai S. K. Engelbart</strong>
*/
public class UCIInfo {
private int depth, seldepth, time, nodes, multipv, currmovenumber, hashfull, nps, tbhits, sbhits, cpuload,
cpunr;
private List<Move> pv, refutation, currline;
private Move currmove;
private Score score;
private String displayString;
public UCIInfo(String line) {
pv = new ArrayList<>();
refutation = new ArrayList<>();
currline = new ArrayList<>();
String[] tokens = line.split(" ");
for (int i = 0; i < tokens.length; i++)
switch (tokens[i]) {
// Single parameter info
case "depth":
case "seldepth":
case "time":
case "nodes":
case "multipv":
case "currmove":
case "currmovenumber":
case "hashfull":
case "nps":
case "tbhits":
case "sbhits":
case "cpuload":
case "string":
try {
getClass().getField(tokens[i]).set(this, tokens[++i]);
} catch (Exception ex) {}
break;
// TODO: pv
// TODO: refutation
// TODO: currline
case "pv":
// TODO: parse infos after score
case "score":
score = new Score(line.substring(tokens[i].length() + 1));
case "refutation":
case "currline":
break;
default:
System.err.printf("Unknown parameter '%s' for command 'info' found!%n", tokens[i]);
}
}
public int getDepth() { return depth; }
public int getSeldepth() { return seldepth; }
public int getTime() { return time; }
public int getNodes() { return nodes; }
public int getMultipv() { return multipv; }
public int getCurrmovenumber() { return currmovenumber; }
public int getHashfull() { return hashfull; }
public int getNps() { return nps; }
public int getTbhits() { return tbhits; }
public int getSbhits() { return sbhits; }
public int getCpuload() { return cpuload; }
public int getCpunr() { return cpunr; }
public List<Move> getPv() { return pv; }
public List<Move> getRefutation() { return refutation; }
public List<Move> getCurrline() { return currline; }
public Move getCurrmove() { return currmove; }
public Score getScore() { return score; }
public String getDisplayString() { return displayString; }
public static class Score {
private int cp, mate;
private boolean lowerbound, upperbound;
public Score(String line) {
String[] tokens = line.split(" ");
switch (tokens[0]) {
case "cp":
cp = Integer.parseInt(tokens[1]);
break;
case "mate":
mate = Integer.parseInt(tokens[1]);
break;
case "lowerbound":
lowerbound = true;
break;
case "upperbound":
upperbound = true;
break;
default:
System.err.printf("Unknown parameter '%s' for command 'score' found!%n", tokens[0]);
}
}
public int getCp() { return cp; }
public int getMate() { return mate; }
public boolean isLowerbound() { return lowerbound; }
public boolean isUpperbound() { return upperbound; }
}
}

View File

@ -1,6 +1,6 @@
package dev.kske.chess.uci;
import java.util.Map;
import dev.kske.chess.board.Move;
/**
* Project: <strong>Chess</strong><br>
@ -47,7 +47,7 @@ public interface UCIListener {
* @param move The best move the engine has found
* @param ponderMove The move the engine likes to ponder on
*/
default void onBestMove(String move, String ponderMove) {}
default void onBestMove(String move, Move ponderMove) {}
/**
* The engine will check the copy protection now.
@ -83,7 +83,7 @@ public interface UCIListener {
*
* @param additionalInfo Contains all pieces of information to be sent
*/
default void onInfo(Map<String, String> additionalInfo) {}
default void onInfo(UCIInfo info) {}
/**
* Tells the GUI which parameters can be changed in the engine.

View File

@ -1,7 +1,9 @@
package dev.kske.chess.uci;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringJoiner;
/**
* Project: <strong>Chess</strong><br>
@ -11,14 +13,55 @@ import java.util.List;
*/
public class UCIOption {
public String name, defaultVal, minVal, maxVal;
public GUIType type;
public List<String> varList;
private String name, defaultVal, minVal, maxVal;
private GUIType type;
private List<String> varList;
public UCIOption() {
public UCIOption(String line) {
varList = new ArrayList<>();
String[] tokens = line.split(" ");
for (int i = 0; i < tokens.length; i++)
switch (tokens[i]) {
case "name":
StringJoiner nameJoiner = new StringJoiner(" ");
while (!Arrays.asList("type", "default", "min", "max", "var").contains(tokens[i + 1]))
nameJoiner.add(tokens[++i]);
name = nameJoiner.toString();
break;
case "type":
type = GUIType.valueOf(tokens[++i].toUpperCase());
break;
case "default":
// Default string may be empty
defaultVal = i == tokens.length - 1 ? "" : tokens[++i];
break;
case "min":
minVal = tokens[++i];
break;
case "max":
maxVal = tokens[++i];
break;
case "var":
varList.add(tokens[++i]);
break;
default:
System.err.printf("Unknown parameter '%s' for command 'option' found!%n", tokens[i]);
}
}
public String getName() { return name; }
public String getDefaultVal() { return defaultVal; }
public String getMinVal() { return minVal; }
public String getMaxVal() { return maxVal; }
public GUIType getType() { return type; }
public List<String> getVarList() { return varList; }
public static enum GUIType {
CHECK, SPIN, COMBO, BUTTON, STRING
}

View File

@ -4,12 +4,10 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.StringJoiner;
import java.util.ArrayList;
import java.util.List;
import dev.kske.chess.uci.UCIOption.GUIType;
import dev.kske.chess.board.Move;
/**
* Project: <strong>Chess</strong><br>
@ -21,10 +19,11 @@ public class UCIReceiver implements Runnable {
private final BufferedReader in;
private UCIListener listener;
private List<UCIListener> listeners;
public UCIReceiver(InputStream in) {
this.in = new BufferedReader(new InputStreamReader(in));
this.in = new BufferedReader(new InputStreamReader(in));
listeners = new ArrayList<>();
}
@Override
@ -49,10 +48,10 @@ public class UCIReceiver implements Runnable {
parseId(line.substring(command.length() + 1));
break;
case "uciok":
listener.onUCIOk();
listeners.forEach(UCIListener::onUCIOk);
break;
case "readyok":
listener.onReadyOk();
listeners.forEach(UCIListener::onReadyOk);
break;
case "bestmove":
parseBestMove(line.substring(command.length() + 1));
@ -79,10 +78,10 @@ public class UCIReceiver implements Runnable {
String arg = line.substring(param.length() + 1);
switch (param) {
case "name":
listener.onIdName(arg);
listeners.forEach(l -> l.onIdName(arg));
break;
case "author":
listener.onIdAuthor(arg);
listeners.forEach(l -> l.onIdAuthor(arg));
break;
default:
System.err.printf("Unknown parameter '%s' for command 'id' found!%n", param);
@ -94,20 +93,20 @@ public class UCIReceiver implements Runnable {
String move = tokens[0];
// Ponder move
if (tokens.length == 3) listener.onBestMove(move, tokens[2]);
else listener.onBestMove(move);
if (tokens.length == 3) listeners.forEach(l -> l.onBestMove(move, Move.fromSAN(tokens[2])));
else listeners.forEach(l -> l.onBestMove(move));
}
private void parseCopyProtection(String line) {
switch (line) {
case "checking":
listener.onCopyProtectionChecking();
listeners.forEach(UCIListener::onCopyProtectionChecking);
break;
case "ok":
listener.onCopyProtectionOk();
listeners.forEach(UCIListener::onCopyProtectionOk);
break;
case "error":
listener.onCopyProtectionError();
listeners.forEach(UCIListener::onCopyProtectionError);
break;
default:
System.err.printf("Unknown parameter '%s' for command 'copyprotection' found!%n", line);
@ -117,13 +116,13 @@ public class UCIReceiver implements Runnable {
private void parseRegistration(String line) {
switch (line) {
case "checking":
listener.onRegistrationChecking();
listeners.forEach(UCIListener::onRegistrationChecking);
break;
case "ok":
listener.onRegistrationOk();
listeners.forEach(UCIListener::onRegistrationOk);
break;
case "error":
listener.onRegistrationError();
listeners.forEach(UCIListener::onRegistrationError);
break;
default:
System.err.printf("Unknown parameter '%s' for command 'registration' found!%n", line);
@ -131,70 +130,14 @@ public class UCIReceiver implements Runnable {
}
private void parseInfo(String line) {
String[] tokens = line.split(" ");
Map<String, String> additionalInfo = new HashMap<>();
for (int i = 0; i < tokens.length; i++)
switch (tokens[i]) {
// Single parameter info
case "depth":
case "seldepth":
case "time":
case "nodes":
case "multipv":
case "currmove":
case "currmovenumber":
case "hashfull":
case "nps":
case "tbhits":
case "sbhits":
case "cpuload":
case "string":
additionalInfo.put(tokens[i], tokens[++i]);
break;
// TODO: pv
// TODO: score
// TODO: refutation
// TODO: currline
default:
System.err.printf("Unknown parameter '%s' for command 'info' found!%n", tokens[i]);
}
listener.onInfo(additionalInfo);
listeners.forEach(l -> l.onInfo(new UCIInfo(line)));
}
private void parseOption(String line) {
String[] tokens = line.split(" ");
UCIOption option = new UCIOption();
for (int i = 0; i < tokens.length; i++)
switch (tokens[i]) {
case "name":
StringJoiner name = new StringJoiner(" ");
while (!Arrays.asList("type", "default", "min", "max", "var").contains(tokens[i + 1]))
name.add(tokens[++i]);
option.name = name.toString();
break;
case "type":
option.type = GUIType.valueOf(tokens[++i].toUpperCase());
break;
case "default":
// Default string may be empty
option.defaultVal = i == tokens.length - 1 ? "" : tokens[++i];
break;
case "min":
option.minVal = tokens[++i];
break;
case "max":
option.maxVal = tokens[++i];
break;
case "var":
option.varList.add(tokens[++i]);
break;
default:
System.err.printf("Unknown parameter '%s' for command 'option' found!%n", tokens[i]);
}
listener.onOption(option);
listeners.forEach(l -> l.onOption(new UCIOption((line))));
}
public void setListener(UCIListener listener) { this.listener = listener; }
public void addListener(UCIListener listener) {
listeners.add(listener);
}
}