// Terminal.java package net.sf.gogui.tools.terminal; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileNotFoundException; import java.io.InputStreamReader; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import net.sf.gogui.game.ConstNode; import net.sf.gogui.game.GameInfo; import net.sf.gogui.game.GameTree; import net.sf.gogui.game.Node; import net.sf.gogui.go.Board; import net.sf.gogui.go.BoardUtil; import net.sf.gogui.go.GoColor; import static net.sf.gogui.go.GoColor.BLACK_WHITE; import net.sf.gogui.go.Move; import net.sf.gogui.go.GoPoint; import net.sf.gogui.gtp.GtpClient; import net.sf.gogui.gtp.GtpError; import net.sf.gogui.gtp.GtpResponseFormatError; import net.sf.gogui.gtp.GtpUtil; import net.sf.gogui.sgf.SgfError; import net.sf.gogui.sgf.SgfReader; import net.sf.gogui.sgf.SgfWriter; import net.sf.gogui.util.StringUtil; import net.sf.gogui.version.Version; /** Simple text based interface to Go programs supporting GTP. */ public class Terminal implements GtpClient.IOCallback { public Terminal(String program, int size, boolean verbose) throws Exception { if (program.equals("")) throw new Exception("No program set"); m_verbose = verbose; m_gtp = new GtpClient(program, null, verbose, this); m_gtp.queryProtocolVersion(); m_gtp.querySupportedCommands(); initGame(size); } public void close() { m_gtp.close(); m_gtp.waitForExit(); } public void mainLoop() throws IOException { newGame(m_board.getSize()); printBoard(); BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); try { while (true) { System.out.print("> "); String line = reader.readLine(); if (line == null) break; line = line.trim(); if (line.equals("")) continue; if (handleCommand(line)) break; } } finally { System.out.println(); } } public void receivedInvalidResponse(String s) { System.out.print(s); } public void receivedResponse(boolean error, String s) { } public void receivedStdErr(String s) { // If m_verbose, logging is already done by Gtp if (! m_verbose) System.err.print(s); } public void sentCommand(String s) { } /** Colorize go board text output. @see Argument color in net.sf.gogui.go.Board.toString(ConstBoard,boolean,boolean) */ public void setColor(boolean enable) { m_color = enable; } private final boolean m_verbose; private boolean m_color; private Board m_board; private GameTree m_tree; private final GtpClient m_gtp; private Node m_currentNode; private void cmdPlay(GoPoint point) { if (! cmdPlay(m_board.getToMove(), point)) return; printBoard(); genmove(); } private boolean cmdPlay(GoColor color, GoPoint point) { String command = m_gtp.getCommandPlay(Move.get(color, point)); StringBuilder response = new StringBuilder(); if (! send(command, response)) { System.out.println(response); return false; } play(Move.get(color, point)); return true; } private void genmove() { GoColor toMove = m_board.getToMove(); String command = m_gtp.getCommandGenmove(toMove); StringBuilder response = new StringBuilder(); if (! send(command, response)) { System.out.println(response); return; } try { GoPoint point = GtpUtil.parsePoint(response.toString(), m_board.getSize()); System.out.println("Computer move: " + GoPoint.toString(point)); play(Move.get(toMove, point)); printBoard(); } catch (GtpResponseFormatError e) { System.out.println(response); } } /** Handle command line from user. @return true if quit command received. */ private boolean handleCommand(String cmdLine) { String[] cmdArray = StringUtil.splitArguments(cmdLine); String cmd = cmdArray[0]; if (cmd.equals("quit")) { send("quit"); return true; } if (cmd.equals("genmove")) genmove(); else if (cmd.equals("help")) help(); else if (cmd.equals("list")) listCommands(); else if (cmd.equals("load")) load(cmdArray); else if (cmd.equals("newgame")) newGame(cmdArray); else if (cmd.equals("save")) save(cmdArray); else if (cmd.equals("undo")) undo(cmdArray); else if (GtpUtil.isStateChangingCommand(cmd)) System.out.println("Command not allowed"); else { try { GoPoint point = GtpUtil.parsePoint(cmdLine, m_board.getSize()); cmdPlay(point); } catch (GtpResponseFormatError e) { StringBuilder response = new StringBuilder(); send(cmdLine, response); System.out.println(response); } } return false; } private void help() { String text = "Enter a move or one of the following commands:\n" + "genmove, help, load, list, newgame, save, undo, quit.\n" + "GTP commands that change the board state are not allowed.\n" + "Other GTP commands are forwarded to the program.\n"; System.out.print(text); } private void initGame(int size) { m_board = new Board(size); m_tree = new GameTree(size, null, null, null, null); setCurrentNode(m_tree.getRoot()); } private void listCommands() { try { m_gtp.querySupportedCommands(); } catch (GtpError error) { System.out.println(error.getMessage()); return; } ArrayList<String> commands = m_gtp.getSupportedCommands(); for (int i = 0; i < commands.size(); ++i) System.out.println(commands.get(i)); } private void load(String[] cmdArray) { if (cmdArray.length < 2) { System.out.println("Need filename argument"); return; } File file = new File(cmdArray[1]); try { FileInputStream fileStream = new FileInputStream(file); SgfReader reader = new SgfReader(fileStream, file, null, 0); String warnings = reader.getWarnings(); if (warnings != null) System.out.print(warnings); GameTree tree = reader.getTree(); GameInfo info = tree.getGameInfo(tree.getRoot()); if (info.getHandicap() > 0) { System.out.println("Handicap games not supported"); return; } if (! newGame(tree.getBoardSize())) return; send("komi " + info.getKomi()); ConstNode node = tree.getRoot(); while (node != null) { for (GoColor c : BLACK_WHITE) { for (GoPoint stone : node.getSetup(c)) if (! cmdPlay(c, stone)) return; } Move move = node.getMove(); if (move != null && ! cmdPlay(move.getColor(), move.getPoint())) return; node = node.getChildConst(); } printBoard(); } catch (FileNotFoundException e) { System.out.println("File not found: " + file); } catch (SgfError e) { System.out.println("Could not read file " + file + ": " + e.getMessage()); } } private boolean newGame(int size) { String command; StringBuilder response = new StringBuilder(); command = m_gtp.getCommandBoardsize(size); if (command != null && ! send(command, response)) { System.out.println(response); return false; } command = m_gtp.getCommandClearBoard(size); if (! send(command, response)) { System.out.println(response); return false; } initGame(size); return true; } private void newGame(String[] cmdArray) { int size = m_board.getSize(); if (cmdArray.length > 1) { try { size = Integer.parseInt(cmdArray[1]); } catch (NumberFormatException exception) { } } newGame(size); printBoard(); } private void play(Move move) { m_board.play(move); Node node = new Node(move); m_currentNode.append(node); m_currentNode = node; } private void printBoard() { System.out.print(BoardUtil.toString(m_board, true, m_color)); } private void save(String[] cmdArray) { if (cmdArray.length < 2) { System.out.println("Need filename argument"); return; } File file = new File(cmdArray[1]); try { OutputStream out = new FileOutputStream(file); new SgfWriter(out, m_tree, "gogui-terminal", Version.get()); } catch (FileNotFoundException e) { System.out.println("Write error"); return; } } private String send(String cmd) { StringBuilder response = new StringBuilder(); send(cmd, response); return response.toString(); } private boolean send(String cmd, StringBuilder response) { try { response.append(m_gtp.send(cmd)); return true; } catch (GtpError error) { response.append(error.getMessage()); return false; } } private void setCurrentNode(ConstNode node) { m_currentNode = m_tree.getNode(node); } private void undo(String[] cmdArray) { if (cmdArray.length > 1) { System.out.println("undo command takes no arguments"); return; } StringBuilder response = new StringBuilder(); if (! send("undo", response)) { System.out.println(response); return; } m_board.undo(); m_currentNode = m_currentNode.getFather(); printBoard(); } }