⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 sgf.java

📁 ErGo是一个很早的Java通用围棋服务器(IGS/NNGS)客户端程序。有全部源码和文档
💻 JAVA
📖 第 1 页 / 共 2 页
字号:
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 + -