📄 sgf.java
字号:
// in which case add it to the game.
// +++ Why bother? Might as well add them all to the move, some
// will just get stored in the root move.
String[] info = getPropInfo(property);
if (info == null)
warn(property + " is not a recognized SGF4 property name.");
if (info != null && "r".equals(info[PTYPEINDEX]))
game.addSGFproperty(property, values);
else
game.getCurrentMove().addSGFproperty(property, values);
}
}
public static String[] getPropInfo (String prop) {
for (int i = 0; i < PROPERTIES.length; i++)
if (PROPERTIES[i][SNAMEINDEX].equals(prop))
return PROPERTIES[i];
//Debug.println("Unknown property seen: " + prop);
return null;
}
// Return true if the given property has propvalue type SimpleText.
boolean isSimpleText (String prop) {
String[] info = getPropInfo(prop);
return (info == null) ? false : "s".equals(info[VTYPEINDEX]);
}
// Return true if the given property has propvalue type Text.
// Unrecognized properties are assumed to be of type Text.
private boolean isText (String prop) {
String[] info = getPropInfo(prop);
return (info == null) ? true : "t".equals(info[VTYPEINDEX]);
}
// Write a string to the output stream, escaping ], \, and :
public static void writeText (PrintWriter out, String text) {
StringBuffer buff = new StringBuffer();
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (c == ']' || c == '\\' || c == ':')
buff.append('\\');
buff.append(c);
}
out.print(buff.toString());
}
class Node {
private Vector nodeProperties = new Vector();
private Vector nodeValues = new Vector();
// When this is called nextChar(in) = ';'
Node (Reader in, Game game) throws SGFParseException, IOException {
//Debug.println("parsing a node");
char c = skipWhite(in);
if (c != ';')
error("';' expected at beginning of node. '" + c + "' found.");
while (true) {
c = skipWhite(in);
if (c == ';' || c == '(' || c == ')') { // Node "follow set"
backup();
break;
}
else if (isAlpha(c)) {
// Reading a PropIdent string, such as FF, SZ, GaMe, etc.
// c contains the first alphabetic char of the propIdent string.
StringBuffer buff = new StringBuffer();
buff.append(c);
while (true) {
c = skipWhite(in);
if (isAlpha(c)) {
if (isUCAlpha(c)) // ignore lowercase chars in propidents
buff.append(c);
}
else if (c == '[') {
backup();
break;
}
else
error("Alphabetic character or '[' expected, '"
+ c + "' found.");
}
String property = buff.toString();
//Debug.println("property = " + property);
nodeProperties.addElement(property);
// Reading a PropValue list such as "[foo]" or "[he][ll]"
Vector pvals = new Vector(1, 10); // common case is 1 propval
while (skipWhite(in) == '[') {
buff.setLength(0);
if (isSimpleText(property))
readSimpleTextProperty(in, buff);
else if (isText(property))
readTextProperty(in, buff);
else // No escaping...just copy until ']' reached.
while ((c = skipWhite(in)) != ']')
buff.append(c);
pvals.addElement(buff.toString());
}
backup();
//Debug.println("propvalue = " + value);
nodeValues.addElement(pvals);
notePropertySeen(property, pvals, game);
}
else
error("Expected ;, (, ), or an alphabetic character at end of node. '"
+ c + "' found.");
} // end while (true)
} // end Node constructor
void readTextProperty (Reader in, StringBuffer buff) throws IOException {
boolean escaped = false;
char prev = prevChar;
while (true) {
char c = nextChar(in);
if (escaped) {
if (!isLineBreakChar(c)) // escaped line breaks removed
buff.append(c);
escaped = false;
}
else {
if (c == '\\') // if we get here, escaped == false
escaped = true;
else if (Character.isWhitespace(c)) {
if (isLineBreakChar(c)) { // deal with CRLF, LFCR, etc.
if ((c == prev) || !isLineBreakChar(prev)) {
buff.append('\n');
}
}
else {
c = ' ';
buff.append(c);
}
}
else if (c == ']')
break;
else
buff.append(c);
}
prev = c;
}
}
void readSimpleTextProperty (Reader in, StringBuffer buff) throws IOException {
boolean escaped = false;
while (true) {
char c = nextChar(in);
if (escaped)
escaped = false;
else {
if (c == '\\') {
escaped = true;
continue;
}
else if (Character.isWhitespace(c))
c = ' ';
else if (c == ']')
break;
}
buff.append(c);
}
}
// Returns a vector of string property values. Normally there will
// only be one element in the vector, but this way it can handle
// list property values (most notably "list of point").
private Vector getPropertyValues (String prop) {
for (int i = 0; i < nodeProperties.size(); i++)
if (prop.equals((String) nodeProperties.elementAt(i)))
return (Vector) nodeValues.elementAt(i);
return null;
}
// This is used for properties that are known not to allow list values.
private String getPropertyValue (String prop) {
if (prop == null)
return null;
Vector values = getPropertyValues(prop);
if (values != null && values.size() > 0)
return (String) values.elementAt(0);
else
return null;
}
private String getColor () {
// Black stone move or HAndicap move.
if (getPropertyValue("B") != null
|| (getPropertyValue("HA") != null))
return "B";
else if (getPropertyValue("W") != null)
return "W";
else
return null;
}
// Returns true if this node can be converted into an Ergo Move.
// This effectively means that it should only return true for
// nodes which contain a B or W property. The root move is treated
// specially here and in toMove() so that kibitzes and such can
// be added to it.
private boolean isMove (Game game) {
return (game.getCurrentMove() instanceof RootMove
|| getColor() != null);
}
// Returns null to signify an invalid position.
private Position SGFtoPosition (String sgf, int gameSize) throws SGFParseException {
if (sgf == null || sgf.length() < 2)
error("Invalid move: " + sgf);
char rowc = sgf.charAt(1);
char colc = sgf.charAt(0);
int col;
int row;
if (colc >= 'a' && colc <= 'z')
col = (int) colc - (int) 'a';
else
col = (int) colc - (int) 'A' + 26;
if (rowc >= 'a' && rowc <= 'z')
row = (int) rowc - (int) 'a';
else
row = (int) rowc - (int) 'A' + 26;
row = (gameSize - 1) - row; // invert y axis
if (col < 0 || row < 0)
error("Invalid move: " + sgf);
else if (sgf.length() > 2)
warn("Ignoring extra characters in move: " + sgf);
return new Position(row, col);
}
Move toMove (Game game) throws SGFParseException {
Move parent = game.getCurrentMove();
int gameSize = game.size();
int moveNum = parent.moveNumber() + 1;
String colorv = getColor();
int color = ("B".equals(colorv) ? Move.BLACK : Move.WHITE);
String timelefts = colorv + "L";
String timeleftv = getPropertyValue(timelefts); // BL or WL
int timeleft = 0;
String sgfpos = getPropertyValue(colorv);
String handicapv = getPropertyValue("HA");
Move m = null;
if (timeleftv != null) {
try {
timeleft = Integer.parseInt(timeleftv);
}
catch (NumberFormatException e1) {
// +++ This warning may have wrong filepos
warn("Time left (" + timelefts
+ ") property invalid, using 0 instead.");
}
}
//Debug.println("toMove: parent = " + parent + ", new moveNum = " + moveNum);
if ("".equals(sgfpos) || (gameSize <= SGF3_MAXGAMESIZE
&& "tt".equals(sgfpos))) {
//Debug.println("making pass move");
m = new PassMove(parent, timeleft, moveNum, color, true);
}
else if (sgfpos != null) {
//Debug.println("making stone move");
// +++ Should probably warn here if haven't seen SZ prop yet.
Position pos = SGFtoPosition(sgfpos, gameSize);
m = new StoneMove(parent, timeleft, moveNum, color, true, pos);
}
else if (handicapv != null) {
Vector pvals = getPropertyValues("AB");
if (pvals != null) {
// Use the AddBlack property in this node to place the handicap.
PositionVector positions = new PositionVector(9);
for (int i = 0; i < pvals.size(); i++) {
String spos = (String) pvals.elementAt(i);
// +++ Should probably warn here if haven't seen SZ prop yet.
positions.addElement(SGFtoPosition(spos, game.size()));
}
m = new HandicapMove(parent, timeleft, moveNum, true, positions);
}
else {
// There's no AddBlack prop, so place the standard handicap.
try {
int numStones = Integer.parseInt(handicapv);
if (pedantic && numStones < 2)
warn("Invalid HAndicap property. Value must be >= 2.");
if (numStones < 0 || numStones > 9) // Ergo can deal with 1
error("Invalid HAndicap property. "
+ "Value must be between 1 and 9 inclusive.");
// Many apps seem to write HA[0] even though that's illegal,
// so deal with it here.
if (numStones != 0)
m = new HandicapMove(parent, timeleft, moveNum, true,
numStones, gameSize);
}
catch (NumberFormatException queRollo) {
error("Invalid HAndicap property. "
+ "Value must be an integer.");
}
}
}
// This doesn't seem to be any other kind of move, so add the
// properties to the root move.
else if (game.getCurrentMove() instanceof RootMove)
m = game.getCurrentMove();
if (m != null) {
// Add other properties to the move here.
for (int i = 0; i < nodeProperties.size(); i++) {
String prop = (String) nodeProperties.elementAt(i);
// Don't add the properties that are part of the move's type
if (prop != null
&& !("HA".equals(prop) || prop.equals(colorv)
|| prop.equals(timelefts) || "AB".equals(prop))) {
Vector pvals = getPropertyValues(prop);
if (pvals != null) {
if ("C".equals(prop)) // Kibitzes handled specially.
for (int k = 0; k < pvals.size(); k++)
m.addKibitz((String) pvals.elementAt(k));
else
m.addSGFproperty(prop, pvals);
}
}
}
}
return m;
}
public String toString () {
StringBuffer b = new StringBuffer();
b.append("Node[");
for (int i = 0; i < nodeProperties.size(); i++) {
b.append(nodeProperties.elementAt(i));
b.append("=");
b.append(nodeValues.elementAt(i));
if (i < nodeProperties.size() - 1)
b.append(",");
}
b.append(']');
return b.toString();
}
} // end inner class Node
} // end class SGF
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -