From e72297bebf155842a9bbdf9154ac2eda8e737fe9 Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Wed, 31 Jul 2019 17:47:49 +0200 Subject: [PATCH] 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 --- src/dev/kske/chess/game/UCIPlayer.java | 29 +----- src/dev/kske/chess/uci/UCIHandle.java | 2 +- src/dev/kske/chess/uci/UCIInfo.java | 133 ++++++++++++++++++++++++ src/dev/kske/chess/uci/UCIListener.java | 6 +- src/dev/kske/chess/uci/UCIOption.java | 51 ++++++++- src/dev/kske/chess/uci/UCIReceiver.java | 103 ++++-------------- 6 files changed, 208 insertions(+), 116 deletions(-) create mode 100644 src/dev/kske/chess/uci/UCIInfo.java diff --git a/src/dev/kske/chess/game/UCIPlayer.java b/src/dev/kske/chess/game/UCIPlayer.java index 5e57a45..79d12bc 100644 --- a/src/dev/kske/chess/game/UCIPlayer.java +++ b/src/dev/kske/chess/game/UCIPlayer.java @@ -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: Chess
@@ -20,11 +16,9 @@ import dev.kske.chess.uci.UCIOption; public class UCIPlayer extends Player implements UCIListener { private UCIHandle handle; - private List 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 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); - } } diff --git a/src/dev/kske/chess/uci/UCIHandle.java b/src/dev/kske/chess/uci/UCIHandle.java index cf13e63..d9dd77a 100644 --- a/src/dev/kske/chess/uci/UCIHandle.java +++ b/src/dev/kske/chess/uci/UCIHandle.java @@ -138,6 +138,6 @@ public class UCIHandle { } public void setListener(UCIListener listener) { - receiver.setListener(listener); + receiver.addListener(listener); } } diff --git a/src/dev/kske/chess/uci/UCIInfo.java b/src/dev/kske/chess/uci/UCIInfo.java new file mode 100644 index 0000000..4606874 --- /dev/null +++ b/src/dev/kske/chess/uci/UCIInfo.java @@ -0,0 +1,133 @@ +package dev.kske.chess.uci; + +import java.util.ArrayList; +import java.util.List; + +import dev.kske.chess.board.Move; + +/** + * Project: Chess
+ * File: UCIInfo.java
+ * Created: 28.07.2019
+ * Author: Kai S. K. Engelbart + */ +public class UCIInfo { + + private int depth, seldepth, time, nodes, multipv, currmovenumber, hashfull, nps, tbhits, sbhits, cpuload, + cpunr; + private List 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 getPv() { return pv; } + + public List getRefutation() { return refutation; } + + public List 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; } + } +} diff --git a/src/dev/kske/chess/uci/UCIListener.java b/src/dev/kske/chess/uci/UCIListener.java index 6bb84ed..75efd16 100644 --- a/src/dev/kske/chess/uci/UCIListener.java +++ b/src/dev/kske/chess/uci/UCIListener.java @@ -1,6 +1,6 @@ package dev.kske.chess.uci; -import java.util.Map; +import dev.kske.chess.board.Move; /** * Project: Chess
@@ -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 additionalInfo) {} + default void onInfo(UCIInfo info) {} /** * Tells the GUI which parameters can be changed in the engine. diff --git a/src/dev/kske/chess/uci/UCIOption.java b/src/dev/kske/chess/uci/UCIOption.java index ba6df2a..eab40bd 100644 --- a/src/dev/kske/chess/uci/UCIOption.java +++ b/src/dev/kske/chess/uci/UCIOption.java @@ -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: Chess
@@ -11,14 +13,55 @@ import java.util.List; */ public class UCIOption { - public String name, defaultVal, minVal, maxVal; - public GUIType type; - public List varList; + private String name, defaultVal, minVal, maxVal; + private GUIType type; + private List 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 getVarList() { return varList; } + public static enum GUIType { CHECK, SPIN, COMBO, BUTTON, STRING } diff --git a/src/dev/kske/chess/uci/UCIReceiver.java b/src/dev/kske/chess/uci/UCIReceiver.java index fbfb686..f9d8cdc 100644 --- a/src/dev/kske/chess/uci/UCIReceiver.java +++ b/src/dev/kske/chess/uci/UCIReceiver.java @@ -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: Chess
@@ -21,10 +19,11 @@ public class UCIReceiver implements Runnable { private final BufferedReader in; - private UCIListener listener; + private List 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 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); + } }