// SgfWriter.java package net.sf.gogui.sgf; import java.io.OutputStream; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.util.Map; import net.sf.gogui.game.ConstGameInfo; import net.sf.gogui.game.ConstGameTree; import net.sf.gogui.game.ConstNode; import net.sf.gogui.game.ConstSgfProperties; import net.sf.gogui.game.MarkType; import net.sf.gogui.game.NodeUtil; import net.sf.gogui.game.StringInfo; import net.sf.gogui.game.StringInfoColor; import net.sf.gogui.game.TimeSettings; import net.sf.gogui.go.ConstBoard; import net.sf.gogui.go.ConstPointList; 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_EMPTY; import net.sf.gogui.go.Komi; import net.sf.gogui.go.Move; import net.sf.gogui.go.GoPoint; import net.sf.gogui.go.PointList; import net.sf.gogui.util.StringUtil; /** Write in SGF format. */ public class SgfWriter { public static final String ENCODING = "UTF-8"; /** Write game tree in SGF format. @param out Output stream. @param tree Game tree to write. @param application Application name for AP property. @param version If not null, version appended to application name in AP property. */ public SgfWriter(OutputStream out, ConstGameTree tree, String application, String version) { try { m_out = new PrintStream(out, false, ENCODING); } catch (UnsupportedEncodingException e) { // UTF-8 should be supported by every Java implementation assert false; } print("("); m_size = tree.getBoardSize(); printHeader(application, version); printNewLine(); printNode(tree.getRootConst(), true); print(")"); m_out.println(m_buffer.toString()); m_out.close(); } /** Write position in SGF format. @param out Output stream. @param board Position to write. @param application Application name for AP property. @param version If not null, version appended to application name in AP property. */ public SgfWriter(OutputStream out, ConstBoard board, String application, String version) { m_size = board.getSize(); m_out = new PrintStream(out); print("("); printHeader(application, version); printNewLine(); printPosition(board); print(")"); m_out.println(m_buffer.toString()); m_out.close(); } private static final int STRINGBUF_CAPACITY = 128; private static final int MAX_CHARS_PER_LINE = 78; private final StringBuilder m_buffer = new StringBuilder(STRINGBUF_CAPACITY); private final int m_size; private PrintStream m_out; private String getEscaped(String text) { return getEscaped(text, false); } private String getEscaped(String text, boolean escapeColon) { StringBuilder result = new StringBuilder(2 * text.length()); for (int i = 0; i < text.length(); ++i) { char c = text.charAt(i); String specialCharacters; if (escapeColon) specialCharacters = "]:\\"; else specialCharacters = "]\\"; if (specialCharacters.indexOf(c) >= 0) { result.append('\\'); result.append(c); } else if (c != '\n' && Character.isWhitespace(c)) result.append(' '); else result.append(c); } return result.toString(); } private static int getMoveNumberInVariation(ConstNode node) { int moveNumber = 0; while (node != null) { if (node.getMove() != null) ++moveNumber; node = node.getFatherConst(); if (node != null && node.getNumberChildren() > 1) break; } return moveNumber; } private String getPoint(GoPoint p) { if (p == null) return ""; int x = 'a' + p.getX(); int y = 'a' + (m_size - p.getY() - 1); return "" + (char)x + (char)y; } private String getPointValue(GoPoint point) { return "[" + getPoint(point) + "]"; } private String getPointList(ConstPointList v) { StringBuilder buffer = new StringBuilder(STRINGBUF_CAPACITY); for (int i = 0; i < v.size(); ++i) buffer.append(getPointValue(v.get(i))); return buffer.toString(); } private boolean hasByoyomiInformation(ConstNode node) { ConstGameInfo info = node.getGameInfoConst(); if (info == null) return false; TimeSettings settings = info.getTimeSettings(); return (settings != null && settings.getUseByoyomi()); } private void print(String text) { if (text.indexOf('\n') > 0) { printNewLine(); m_buffer.append(text); printNewLine(); return; } if (m_buffer.length() + text.length() > MAX_CHARS_PER_LINE) printNewLine(); m_buffer.append(text); } private void printNewLine() { if (m_buffer.length() > 0) { m_out.println(m_buffer.toString()); m_buffer.replace(0, m_buffer.length(), ""); } } private void printHeader(String application, String version) { StringBuilder header = new StringBuilder(128); header.append(";FF[4]CA["); header.append(getEscaped(ENCODING)); header.append(']'); if (application != null && ! application.equals("")) { String appName = application; if (version != null && ! version.equals("")) appName = appName + ":" + version; header.append("AP["); header.append(getEscaped(appName)); header.append(']'); } if (m_size != 19) { header.append("SZ["); header.append(m_size); header.append(']'); } print(header.toString()); } private void printGameInfo(ConstGameInfo info) { int handicap = info.getHandicap(); Komi komi = info.getKomi(); if (handicap > 0) print("HA[" + handicap + "]"); if (komi != null && ! (handicap > 0 && komi.equals(new Komi(0)))) print("KM[" + komi + "]"); TimeSettings timeSettings = info.getTimeSettings(); if (timeSettings != null) { print("TM[" + timeSettings.getPreByoyomi() / 1000 + "]"); String overtime = SgfUtil.getOvertime(timeSettings); if (overtime != null) print("OT[" + overtime + "]"); } printInfo("PB", info.get(StringInfoColor.NAME, BLACK)); printInfo("PW", info.get(StringInfoColor.NAME, WHITE)); printInfo("BR", info.get(StringInfoColor.RANK, BLACK)); printInfo("WR", info.get(StringInfoColor.RANK, WHITE)); printInfo("BT", info.get(StringInfoColor.TEAM, BLACK)); printInfo("WT", info.get(StringInfoColor.TEAM, WHITE)); printInfo("DT", info.get(StringInfo.DATE)); printInfo("RE", info.get(StringInfo.RESULT)); printInfo("RU", info.get(StringInfo.RULES)); printInfo("US", info.get(StringInfo.USER)); printInfo("CP", info.get(StringInfo.COPYRIGHT)); printInfo("AN", info.get(StringInfo.ANNOTATION)); printInfo("RO", info.get(StringInfo.ROUND)); printInfo("SO", info.get(StringInfo.SOURCE)); printNewLine(); } private void printInfo(String label, String value) { if (value == null || value.equals("")) return; print(label + "[" + getEscaped(value) + "]"); } private void printLabels(ConstNode node) { Map<GoPoint,String> labels = node.getLabelsUnmodifiable(); if (labels == null) return; StringBuilder buffer = new StringBuilder(STRINGBUF_CAPACITY); buffer.append("LB"); for (Map.Entry<GoPoint,String> entry : labels.entrySet()) { GoPoint point = entry.getKey(); String value = entry.getValue(); buffer.append('['); buffer.append(getPoint(point)); buffer.append(':'); buffer.append(getEscaped(value, true)); buffer.append(']'); } print(buffer.toString()); } private void printMarked(ConstNode node, String property, MarkType type) { ConstPointList marked = node.getMarkedConst(type); if (marked != null) print(property + getPointList(marked)); } private void printNode(ConstNode node, boolean isRoot) { Move move = node.getMove(); if (! isRoot) { if (move != null) { int moveNumber = getMoveNumberInVariation(node); if (moveNumber != 1 && moveNumber % 10 == 1) printNewLine(); } print(";"); } ConstGameInfo info = node.getGameInfoConst(); if (info != null) printGameInfo(info); if (move != null) { String point = getPointValue(move.getPoint()); if (move.getColor() == BLACK) print("B" + point); else print("W" + point); } for (GoColor c : BLACK_WHITE_EMPTY) { ConstPointList points = node.getSetup(c); if (points.size() == 0) continue; StringBuilder buffer = new StringBuilder(STRINGBUF_CAPACITY); if (c == BLACK) buffer.append("AB"); else if (c == WHITE) buffer.append("AW"); else buffer.append("AE"); for (GoPoint p : points) buffer.append(getPointValue(p)); print(buffer.toString()); } String comment = node.getComment(); if (! StringUtil.isEmpty(comment)) print("C[" + getEscaped(comment) + "]"); if (! Double.isNaN(node.getTimeLeft(BLACK))) print("BL[" + node.getTimeLeft(BLACK) + "]"); if (node.getMovesLeft(BLACK) >= 0) print("OB[" + node.getMovesLeft(BLACK) + "]"); if (! Double.isNaN(node.getTimeLeft(WHITE))) print("WL[" + node.getTimeLeft(WHITE) + "]"); if (node.getMovesLeft(WHITE) >= 0) print("OW[" + node.getMovesLeft(WHITE) + "]"); if (node.getPlayer() != null) printToPlay(node.getPlayer()); printMarked(node, "MA", MarkType.MARK); printMarked(node, "CR", MarkType.CIRCLE); printMarked(node, "SQ", MarkType.SQUARE); printMarked(node, "TR", MarkType.TRIANGLE); printMarked(node, "SL", MarkType.SELECT); printMarked(node, "TB", MarkType.TERRITORY_BLACK); printMarked(node, "TW", MarkType.TERRITORY_WHITE); printLabels(node); if (! Double.isNaN(node.getValue())) print("V[" + node.getValue() + "]"); ConstSgfProperties sgfProps = NodeUtil.cleanSgfProps(node); if (sgfProps != null) for (String key : sgfProps.getKeys()) { if (key.equals("OT") && hasByoyomiInformation(node)) continue; print(key); for (int i = 0; i < sgfProps.getNumberValues(key); ++i) print("[" + sgfProps.getValue(key, i) + "]"); } int numberChildren = node.getNumberChildren(); if (numberChildren == 0) return; if (numberChildren == 1) { printNode(node.getChildConst(), false); return; } for (int i = 0; i < numberChildren; ++i) { printNewLine(); print("("); printNode(node.getChildConst(i), false); print(")"); } } private void printPosition(ConstBoard board) { PointList black = new PointList(); PointList white = new PointList(); for (GoPoint p : board) { GoColor c = board.getColor(p); if (c == BLACK) black.add(p); else if (c == WHITE) white.add(p); } printSetup(black, white); printNewLine(); printToPlay(board.getToMove()); } private void printSetup(ConstPointList black, ConstPointList white) { if (black.size() > 0 || white.size() > 0) { if (black.size() > 0) print("AB" + getPointList(black)); printNewLine(); if (white.size() > 0) print("AW" + getPointList(white)); } } private void printToPlay(GoColor color) { if (color == BLACK) print("PL[B]"); else print("PL[W]"); } }