// GtpUtil.java package net.sf.gogui.gtp; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sf.gogui.game.ConstClock; import net.sf.gogui.game.TimeSettings; import net.sf.gogui.go.GoColor; import static net.sf.gogui.go.GoColor.BLACK; import static net.sf.gogui.go.GoColor.WHITE; import static net.sf.gogui.go.GoColor.BLACK_WHITE; import net.sf.gogui.go.GoPoint; import net.sf.gogui.go.InvalidPointException; import net.sf.gogui.go.Move; import net.sf.gogui.go.PointList; import net.sf.gogui.util.StringUtil; /** Utility functions used in package gtp. */ public final class GtpUtil { /** Get GTP time settings command . @param settings The time settings. If null, this function will return a GTP command for "no time limit" ("time_settings 0 1 0" with zero byoyomi stones), which could confuse some programs, so it should be only sent if necessary (when changing from a state with time settings to a state with no time settings). */ public static String getTimeSettingsCommand(TimeSettings settings) { if (settings == null) return "time_settings 0 1 0"; long preByoyomi = settings.getPreByoyomi() / 1000; long byoyomi = 0; long byoyomiMoves = 0; if (settings.getUseByoyomi()) { byoyomi = settings.getByoyomi() / 1000; byoyomiMoves = settings.getByoyomiMoves(); } return "time_settings " + preByoyomi + " " + byoyomi + " " + byoyomiMoves; } /** Check if command line contains a command. @return false if command line contains only whitespaces or only a comment */ public static boolean isCommand(String line) { line = line.trim(); return (! line.equals("") && ! line.startsWith("#")); } /** Check if command changes the board state. Compares command to known GTP commands that change the board state, such that a controller that keeps track of the board state cannot blindly forward them to a GTP engine without updating its internal board state too. Includes all such commands from GTP protocol version 1 and 2, the <code>quit</code> command, and some known GTP extension commands (e.g. <code>gg-undo</code> from GNU Go and <code>gogui-play_sequence</code> from GoGui). Does not include non-criticlal state changing commands like <code>komi</code>. @param line The command or complete command line @return <code>true</code> if command is a state-changing command */ public static boolean isStateChangingCommand(String line) { GtpCommand cmd = new GtpCommand(line); String c = cmd.getCommand(); return (c.equals("boardsize") || c.equals("black") || c.equals("clear_board") || c.equals("fixed_handicap") || c.equals("genmove") || c.equals("genmove_black") || c.equals("genmove_cleanup") || c.equals("genmove_white") || c.equals("gg-undo") || c.equals("gogui-play_sequence") || c.equals("kgs-genmove_cleanup") || c.equals("loadsgf") || c.equals("place_free_handicap") || c.equals("play") || c.equals("play_sequence") || c.equals("quit") || c.equals("set_free_handicap") || c.equals("undo") || c.equals("white")); } public static double[][] parseDoubleBoard(String response, int boardSize) throws GtpResponseFormatError { try { double result[][] = new double[boardSize][boardSize]; String s[][] = parseStringBoard(response, boardSize); for (int x = 0; x < boardSize; ++x) for (int y = 0; y < boardSize; ++y) result[x][y] = Double.parseDouble(s[x][y]); return result; } catch (NumberFormatException e) { throw new GtpResponseFormatError("Floating point number expected"); } } public static GoPoint parsePoint(String s, int boardSize) throws GtpResponseFormatError { try { return GoPoint.parsePoint(s, boardSize); } catch (InvalidPointException e) { throw new GtpResponseFormatError("Invalid point " + s + " (size " + boardSize + ")"); } } public static PointList parsePointList(String s, int boardSize) throws GtpResponseFormatError { try { return GoPoint.parsePointList(s, boardSize); } catch (InvalidPointException e) { throw new GtpResponseFormatError(e.getMessage()); } } /** Find all points contained in string. */ public static PointList parsePointString(String text) { return parsePointString(text, GoPoint.MAX_SIZE); } /** Find all points contained in string. */ public static PointList parsePointString(String text, int boardSize) { String regex = "\\b([Pp][Aa][Ss][Ss]|[A-Ta-t](1\\d|[1-9]))\\b"; Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE); Matcher matcher = pattern.matcher(text); PointList list = new PointList(32); while (matcher.find()) { int start = matcher.start(); int end = matcher.end(); GoPoint point; try { point = parsePoint(text.substring(start, end), boardSize); } catch (GtpResponseFormatError e) { assert false; continue; } list.add(point); } return list; } public static void parsePointStringList(String s, PointList pointList, ArrayList<String> stringList, int boardsize) throws GtpResponseFormatError { pointList.clear(); stringList.clear(); String array[] = StringUtil.splitArguments(s); boolean nextIsPoint = true; GoPoint point = null; for (int i = 0; i < array.length; ++i) if (! array[i].equals("")) { if (nextIsPoint) { point = parsePoint(array[i], boardsize); nextIsPoint = false; } else { nextIsPoint = true; pointList.add(point); stringList.add(array[i]); } } if (! nextIsPoint) throw new GtpResponseFormatError("Missing string"); } public static String[][] parseStringBoard(String s, int boardSize) throws GtpResponseFormatError { String result[][] = new String[boardSize][boardSize]; try { BufferedReader reader = new BufferedReader(new StringReader(s)); for (int y = boardSize - 1; y >= 0; --y) { String line = reader.readLine(); if (line == null) throw new GtpResponseFormatError("Incomplete string board"); if (line.trim().equals("")) { ++y; continue; } String[] args = StringUtil.splitArguments(line); if (args.length < boardSize) throw new GtpResponseFormatError("Incomplete string board"); for (int x = 0; x < boardSize; ++x) result[x][y] = args[x]; } } catch (IOException e) { throw new GtpResponseFormatError("I/O error"); } return result; } /** Find all moves contained in string. */ public static Move[] parseVariation(String s, GoColor toMove, int boardSize) { ArrayList<Move> list = new ArrayList<Move>(32); String token[] = StringUtil.splitArguments(s); boolean isColorSet = true; for (int i = 0; i < token.length; ++i) { String t = token[i].toLowerCase(Locale.ENGLISH); if (t.equals("b") || t.equals("black")) { toMove = BLACK; isColorSet = true; } else if (t.equals("w") || t.equals("white")) { toMove = WHITE; isColorSet = true; } else { GoPoint point; try { point = parsePoint(t, boardSize); } catch (GtpResponseFormatError e) { continue; } if (! isColorSet) toMove = toMove.otherColor(); list.add(Move.get(toMove, point)); isColorSet = false; } } Move result[] = new Move[list.size()]; for (int i = 0; i < result.length; ++i) result[i] = (Move)list.get(i); return result; } /** Inform a GTP engine about the clock state. Sends the clock state to the engine, if the clock is initialized and the engine supports the time_left standard GTP command. Otherwise, does nothing. */ public static void sendTimeLeft(GtpClientBase gtp, ConstClock clock, GoColor c) { if (! clock.isInitialized() || ! gtp.isSupported("time_left")) return; String color = (c == BLACK ? "b" : "w"); long timeLeft = clock.getTimeLeft(c) / 1000; long movesLeft = 0; if (clock.getTimeSettings().getUseByoyomi() && clock.isInByoyomi(c)) movesLeft = clock.getMovesLeft(c); try { gtp.send("time_left " + color + " " + timeLeft + " " + movesLeft); } catch (GtpError e) { } } /** Make constructor unavailable; class is for namespace only. */ private GtpUtil() { } }