package dev.kske.chess.uci; import java.io.IOException; import java.io.PrintWriter; import java.util.List; import java.util.StringJoiner; import dev.kske.chess.board.Move; /** * Project: Chess
* File: UCIHandle.java
* Created: 18.07.2019
* * @since Chess v0.3-alpha * @author Kai S. K. Engelbart */ public class UCIHandle { private final Process process; private final PrintWriter out; private final UCIReceiver receiver; /** * Creates an instance of {@link UCIHandle}. The engine process is started * and * passed to a new {@link UCIReceiver}. * * @param enginePath the path to the engine executable * @throws IOException if the engine process could not be started */ public UCIHandle(String enginePath) throws IOException { process = new ProcessBuilder(enginePath).start(); out = new PrintWriter(process.getOutputStream(), true); receiver = new UCIReceiver(process.getInputStream()); } /** * Starts the {@link UCIReceiver} used to gather engine output. */ public void start() { new Thread(receiver, "UCI Receiver").start(); uci(); } /** * Tells the engine to use UCI. */ public void uci() { out.println("uci"); } /** * Switches the debug mode of the engine on or off. * * @param debug Enables debugging if set to {@code true}, disables it * otherwise */ public void debug(boolean debug) { out.println("debug " + (debug ? "on" : "off")); } /** * Synchronized the engine with the GUI */ public void isready() { out.println("isready"); } /** * Signifies a button press to the engine. * * @param name The name of the button */ public void setOption(String name) { out.println("setoption name " + name); } /** * Changes an internal parameter of the engine. * * @param name The name of the parameter * @param value The value of the parameter */ public void setOption(String name, String value) { out.printf("setoption name %s value %s%n", name, value); } /** * Registers the engine * * @param name The name the engine should be registered with * @param code The code the engine should be registered with */ public void register(String name, String code) { out.printf("register %s %s%n", name, code); } /** * Tells the engine to postpone the registration. */ public void registerLater() { out.println("register later"); } /** * Tells the engine that the next search will be from a different game. */ public void uciNewGame() { out.println("ucinewgame"); } /** * Sets up the position in its initial state. */ public void positionStartpos() { out.println("position startpos"); } /** * Sets up the position described in the FEN string. * * @param fen FEN representation of the current board */ public void positionFEN(String fen) { out.println("position fen " + fen); } /** * Sets up the position described by a list of moves. * * @param moves the moves to execute from the starting position to reach the * desired position */ public void positionMoves(List moves) { StringJoiner joiner = new StringJoiner(" "); moves.forEach(m -> joiner.add(m.toLAN())); out.println("position moves " + joiner); } /** * Starts calculating on the current position. */ public void go() { out.println("go"); } /** * Starts calculating on the current position. * This command has multiple optional parameters which will only be included * in * the call if they are not {@code null}, greater than zero or {@code true} * for * {@code searchMoves}, all integer parameters and all boolean parameters * respectively. * * @param searchMoves restrict the search to these moves only * @param ponder start the search in ponder mode * @param wTime the amount of milliseconds left on white's clock * @param bTime the amount of milliseconds left on black's clocks * @param wInc white's increment per move in milliseconds * @param bInc black's increment per move in milliseconds * @param movesToGo the number of moves left until the next time control * @param depth the maximal amount of plies to search * @param nodes the maximal amount of nodes to search * @param mate the amount of moves in which to search for a mate * @param moveTime the exact search time * @param infinite search until the {@code stop} command */ public void go( List searchMoves, boolean ponder, int wTime, int bTime, int wInc, int bInc, int movesToGo, int depth, int nodes, int mate, int moveTime, boolean infinite ) { StringJoiner joiner = new StringJoiner(" "); joiner.add("go"); if (searchMoves != null && !searchMoves.isEmpty()) { joiner.add("searchmoves"); searchMoves.forEach(m -> joiner.add(m.toLAN())); } if (ponder) joiner.add("ponder"); if (wTime > 0) { joiner.add("wtime"); joiner.add(String.valueOf(wTime)); } if (bTime > 0) { joiner.add("btime"); joiner.add(String.valueOf(bTime)); } if (wInc > 0) { joiner.add("winc"); joiner.add(String.valueOf(wInc)); } if (bInc > 0) { joiner.add("bind"); joiner.add(String.valueOf(bInc)); } if (movesToGo > 0) { joiner.add("movestogo"); joiner.add(String.valueOf(movesToGo)); } if (depth > 0) { joiner.add("depth"); joiner.add(String.valueOf(depth)); } if (nodes > 0) { joiner.add("nodes"); joiner.add(String.valueOf(nodes)); } if (mate > 0) { joiner.add("mate"); joiner.add(String.valueOf(mate)); } if (moveTime > 0) { joiner.add("movetime"); joiner.add(String.valueOf(moveTime)); } if (infinite) joiner.add("infinite"); out.println(joiner); } /** * Stops calculation as soon as possible. */ public void stop() { out.println("stop"); } /** * Tells the engine that the user has played the expected move. */ public void ponderHit() { out.println("ponderhit"); } /** * Quits the engine process as soon as possible. */ public void quit() { out.println("quit"); } /** * Registers a UCI listener. * * @param listener the UCI listener to register */ public void registerListener(UCIListener listener) { receiver.registerListener(listener); } }