📄 sgf.java
字号:
package ergo.logic;
// $Id: SGF.java,v 1.2 1999/08/13 01:17:49 sigue Exp $
/*
* Copyright (C) 1999 Carl L. Gay and Antranig M. Basman.
* See the file copyright.txt, distributed with this software,
* for further information.
*/
// The +++s indicate things that need improvement.
// To do list:
//
// - This class is a total hack. Rewrite it.
// - Store unrecognized properties in the game and write them back out.
// - Do something intelligent with some of the markups, like, uh, display 'em.
// - Verify that the move number corresponds with move numbers I generate,
// and warn if a difference.
// - Optionally write out extended property names. e.g., SiZe rather than SZ
// - Deal with non-move nodes (by creating a MarkupMove class? by adding
// their props to the previous move?)
// - notePropertySeen() is called at the wrong level. It should only be
// done after a move for the node has been created.
// - Skip junk at beginning of file, i.e. anything before "(;", but
// allowing whitespace in between the ( and the ;.
import ergo.Ergo; // remove this
import ergo.util.*;
import ergo.ui.*;
import ergo.logic.*;
import ergo.server.*;
import java.io.*;
import java.util.Vector;
class SGFParseException extends Exception {
public SGFParseException (String msg) {
super(msg);
}
}
public final class SGF {
private static final int SNAMEINDEX = 0; // short name index
private static final int PTYPEINDEX = 1;
private static final int VTYPEINDEX = 2;
private static final int LNAMEINDEX = 3; // long name index
private static final int SGF3_MAXGAMESIZE = 26;
public static final int nodesPerLine = 4;
// {{property, type, valuetype}, ...}
// type: m = move, "" = none, s = setup, r = root, g = game-info
// valuetype: p = point, r = real, d = double, "" = none, n = number,
// s = simple text, t = text,
// c = color, lx = list of x, ex = empty list of x
// :xy = composed x and y
// Ordered alphabetically by short property name.
private static final String[][] PROPERTIES = {
{"AB", "s", "lp", "AddBlack"},
{"AE", "s", "lp", "AddEmpty"},
{"AN", "g", "s", "ANnotation"},
{"AP", "r", ":sn", "APlication"},
{"AR", "", ":lp", "ARrow"}, // (!)
{"AW", "s", "lp", "AddWhite"},
{"B", "m", "p", "Black"},
{"BL", "m", "r", "BlackLeft"},
{"BM", "m", "d", "BadMove"},
{"BR", "g", "s", "BlackRank"},
{"BT", "g", "s", "BlackTeam"},
{"C", "", "t", "Comment"},
{"CA", "r", "s", "ChAracterset"},
{"CP", "g", "s", "CoPyright"},
{"CR", "", "lp", "CiRcle"},
{"DD", "", "ep", "DD"}, // dim points
{"DM", "", "d", "DM"}, // even position
{"DO", "m", "", "DOubtful"},
{"DT", "g", "s", "DaTe"},
{"EV", "g", "s", "EVent"},
{"FF", "r", "n", "FileFormat"}, // 1-4
{"FG", "", ":ns", "FiGure"}, // (!)
{"GB", "", "d", "GoodforBlack"},
{"GC", "g", "t", "GameComment"},
{"GM", "r", "n", "GaMe"}, // game (1-5,7-17)
{"GN", "g", "s", "GameName"},
{"GW", "", "d", "GoodforWhite"},
{"HA", "g", "n", "HAndicap"}, // >= 2
{"HO", "", "d", "HOtspot"},
{"IT", "m", "", "InTeresting"},
{"KM", "g", "r", "KoMi"},
{"KO", "m", "", "KO"},
{"LB", "", "", "LaBel"}, // (!)
{"LN", "", "", "LiNe"}, // (!)
{"MA", "", "lp", "MArk"},
{"MN", "m", "n", "MoveNumber"},
{"N", "", "s", "Nodename"},
{"OB", "m", "n", "OvertimeBlack"}, // overtime stones black
{"ON", "g", "t", "OpeNing"},
{"OT", "g", "s", "OverTime"},
{"OW", "m", "n", "OvertimeWhite"}, // overtime stones white
{"PB", "g", "s", "PlayerBlack"},
{"PC", "g", "s", "PlaCe"},
{"PL", "s", "c", "PLayer"}, // player to play
{"PM", "", "n", "PrintMove"}, // print move mode
{"PW", "g", "s", "PlayerWhite"},
{"RE", "g", "s", "REsult"},
{"RO", "g", "s", "ROund"},
{"RU", "g", "s", "RUles"},
{"SL", "", "lp", "SeLected"},
{"SO", "g", "s", "SOurce"},
{"SQ", "", "lp", "SQuare"},
{"ST", "r", "n", "STyle"}, // 0-3
{"SZ", "r", ":nn", "SiZe"}, // (!)
{"TB", "", "ep", "TerritoryBlack"},
{"TE", "m", "d", "TEsuji"},
{"TM", "g", "r", "TiMelimit"},
{"TR", "", "lp", "TRiangle"},
{"TW", "", "ep", "TerritoryWhite"},
{"UC", "", "d", "UnClear"}, // unclear position
{"US", "g", "s", "USer"},
{"V", "", "r", "Value"},
{"VW", "", "ep", "VieW"},
{"W", "m", "p", "White"},
{"WL", "m", "r", "WhitetimeLeft"},
{"WR", "g", "s", "WhiteRank"},
{"WT", "g", "s", "WhiteTeam"},
};
private char prevChar = ' '; // previous char, if we've backed up
private int filepos = 0; // for error reporting
private int lineNum = 0; // for error reporting
private int maxfilepos = 0;
private static final int BUFFSIZE = 500;
private char[] buffer = new char[BUFFSIZE];
private int bufferIndex = 0;
private int bufferEnd = 0; // May be < BUFFSIZE on final read().
private TerminalWindow term = null;
// Issue pedantic warnings about SGF format problems?
// (Not used much yet.)
public boolean pedantic = true;
public SGF () {}
public SGF (TerminalWindow w) {
term = w;
}
// This is the only public interface to the SGF parser.
// Returns a Vector of Game objects, or null if error.
public Vector readSGFFile (String filename) throws Exception {
FileReader in = new FileReader(filename);
return parseCollection(in);
}
char nextChar (Reader in) throws IOException {
if (bufferIndex >= bufferEnd) {
bufferEnd = in.read(buffer);
bufferIndex = 0;
if (bufferEnd == -1)
throw new EOFException();
}
char c = (char) buffer[bufferIndex++];
if (c == '\n' && filepos == maxfilepos) // i.e., not backed up
++lineNum;
++filepos;
maxfilepos = Math.max(filepos, maxfilepos);
return c;
}
void backup () throws SGFParseException {
if (bufferIndex < 0)
error("Internal error: Attempt to backup past beginning of input.");
else {
--bufferIndex;
--filepos;
}
}
char skipWhite (Reader in) throws IOException {
char c;
while (Character.isWhitespace(c = nextChar(in)))
;
return c;
}
void error (String msg) throws SGFParseException {
String s = "Error at line " + (lineNum + 1) + ": " + msg;
Debug.backtrace();
throw new SGFParseException(s);
}
void warn (String msg) {
String warning = "Warning at line " + (lineNum + 1) + ": " + msg;
if (term != null)
term.displayString(warning);
else
Debug.println(warning);
}
static final boolean isUCAlpha (char c) {
return (c >= 'A' && c <= 'Z');
}
static final boolean isAlpha (char c) {
return (c >= 'a' && c <= 'z') || isUCAlpha(c);
}
// \n = ASCII 10 and \r = ASCII 13
static final boolean isLineBreakChar(char c) {
return (c == '\n' || c == '\r');
}
// Returns a Vector of Game objects
Vector parseCollection (Reader in) throws SGFParseException, IOException {
char c;
Vector games = new Vector();
// loop adding game trees to games vector.
// loop ends with EOF error or return
while (true) {
// Skip any junk before the beginning of each game tree (i.e., before
// the first '('. This is just a convenience for the user, not part
// of the SGF spec, AFAIK.
try {
while (true) {
c = nextChar(in);
//System.out.print((int)c);
if (c == '(') {
backup();
break;
}
}
}
catch (EOFException eof) {
if (games.size() > 0)
return games;
else
error("End of file reached while searching for game tree.");
}
Game game = parseGameTree(in, true);
games.addElement(game);
}
}
// Parse a game tree. parent is non-null if called recursively.
// isRootNode is only true for gametrees that are immediate descendents
// of a collection.
Game parseGameTree (Reader in, boolean isRootNode) throws SGFParseException, IOException {
Game game = new Game();
parseGameTreeInternal(in, game, isRootNode);
game.goToBeginning();
return game;
}
void parseGameTreeInternal (Reader in, Game game, boolean isRootNode) throws SGFParseException, IOException {
char c = skipWhite(in);
if (c != '(')
error("Expected '('. Found '" + c + "' instead.");
else {
while (true) {
Move parent = game.getCurrentMove();
char next = skipWhite(in);
if (next == ';') {
backup();
Node node = new Node(in, game);
if (node.isMove(game)) {
Move m = node.toMove(game);
if (m == null)
warn("Ignoring null move.");
else if (! game.placeMove(m, null, false, false))
// +++ This is bogus. SGF doesn't require that nodes
// be valid moves.
error("Illegal move node (" + node + ") encountered.");
else
parent = m;
}
else
// +++ Should either create some sort of MarkupMove here or
// just add the node props to the previous move.
warn("Ignoring non-move node: " + node);
}
else if (next == '(') {
backup();
parseGameTreeInternal(in, game, false);
game.goBackward(parent);
}
else if (next == ')')
return;
else
error("Expected ';', '(', or ')'. Found '" + next + "' instead.");
}
}
}
// Called for each property read in a game record. Properties that aren't
// handled specially are stored in the current move of the game so they
// can be written out with the move when the game is resaved. +++ This is
// not strictly correct since SGF doesn't allow mixing of certain kinds of
// properties within a node.
// values is a Vector of String property values.
void notePropertySeen (String property, Vector values, Game game) throws SGFParseException {
String first = (String) values.elementAt(0);
//Debug.println("property seen: " + property + " = " + first);
if ("SZ".equals(property)) {
if (property.indexOf(':') != -1)
error("Non-square board sizes not yet supported.");
try { game.setSize(Integer.parseInt(first)); }
catch (NumberFormatException nfe) {
warn("Invalid game size (SZ) property: " + first);
}
}
else if ("KM".equals(property))
game.setKomi(new Double(first).doubleValue());
else if ("PC".equals(property))
game.SGFlocation = first;
else if ("PB".equals(property))
game.blackName = first;
else if ("PW".equals(property))
game.whiteName = first;
else if ("BR".equals(property))
game.blackRank = first;
else if ("WR".equals(property))
game.whiteRank = first;
else if ("RE".equals(property))
game.result = first;
else if ("FF".equals(property)) {
try {
int version = Integer.parseInt(first);
if (version != 4)
warn("SGF version " + version + " is not fully supported."
+ " This file may not load correctly.");
}
catch (NumberFormatException nfe2) {
warn("Invalid File Format (FF) property: " + first);
}
}
else {
// Unhandled property. Add it to the move unless it's a root prop,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -