更多:开源围棋…
链接: https://pan.baidu.com/s/1v0VDqjQOyBvjLIJrZbUn4w 提取码: sd4s
GitHub
https://github.com/billchiles/sgfeditor
包含C#写的围棋GUI界面。
还有python版本的sgf编辑
//// sgfparser.cs parses .sgf files. Each parse takes a file and creates a list
//// of ParsedNodes. The list of nodes may not adhere to normal game moves such
//// as alternating colors, or starting with B in an even game and W with
//// handicaps. The first node is the root node and should be game properties
//// while following nodes should represent a game, but the nodes could
//// represent setup for a problem.
////
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
namespace SgfEd {
public class ParsedGame {
public ParsedNode Nodes { get; set; }
public ParsedGame () {
// nodes is the only public member.
this.Nodes = null;
}
//// __str__ produces a strong that when printed to a file generates a valid
//// .sgf file.
////
public override string ToString () {
if (this.Nodes == null)
return ""; // Min tree is "(;)", but that implies one empty node
else
return "(" + this.NodesString(this.Nodes) + ")";
}
//// _nodes_string returns a string for a series of nodes, and the caller
//// needs to supply the open and close parens that bracket the series.
////
private string NodesString (ParsedNode nodes) {
var res = "";
while (nodes.Next != null) {
// Get one node's string with a leading newline if it is not the
// first.
res += nodes.NodeString(res != "");
if (nodes.Branches != null) {
foreach (var n in nodes.Branches)
res = res + "\n(" + this.NodesString(n) + ")";
return res;
}
nodes = nodes.Next;
}
res += nodes.NodeString(res != ""); // Test res, could be single node branch.
return res;
}
} // ParsedGame class
public class ParsedNode {
public ParsedNode Next;
public ParsedNode Previous;
public List<ParsedNode> Branches;
public Dictionary<string, List<string>> Properties;
public ParsedNode() {
this.Next = null;
this.Previous = null;
this.Branches = null;
this.Properties = new Dictionary<string, List<string>>();
}
//// node_str returns the string for one node, taking a flag for a
//// preceding newline and the dictionary of properties for the node.
//// Game uses this for error reporting.
////
public string NodeString(bool newline) {
var props = this.Properties;
string s;
if (newline)
s = "\n;";
else
s = ";";
// Print move property first for readability of .sgf file by humans.
if (props.ContainsKey("B"))
s = s + "B" + this.EscapePropertyValues("B", props["B"]);
if (props.ContainsKey("W"))
s = s + "W" + this.EscapePropertyValues("W", props["W"]);
foreach (var kv in props) {
var k = kv.Key;
if (k == "B" || k == "W") continue;
s = s + k + this.EscapePropertyValues(k, kv.Value);
}
return s;
}
//// _escaped_property_values returns a node's property value with escapes so that the .sgf
//// is valid. So, ] and \ must be preceded by a backslash.
////
private string EscapePropertyValues(string id, List<string> values) {
var res = "";
foreach (var v in values) {
res = res + "[";
if (v.Contains(']') || v.Contains('\\')) {
var sb = new StringBuilder();
foreach (var c in v) {
if (c == ']' || c == '\\')
sb.Append('\\');
sb.Append(c);
}
res = res + sb.ToString() + "]";
}
else
res = res + v + "]";
} //foreach
return res;
}
} // ParsedNode class
//// ParserAux provides stateless helpers for parsing. Public members are used throughout
//// the program.
////
public class ParserAux {
public static ParsedGame ParseFile (string name) {
var l = new Lexer(File.ReadAllText(name));
l.ScanFor("(", "Can't find game start");
var g = new ParsedGame();
g.Nodes = ParseNodes(l);
return g;
}
//// _parse_nodes returns a linked list of ParseNodes. It starts scanning for a
//// semi-colon for the start of the first node. If it encounters an open
//// paren, it recurses and creates branches that follow the current node,
//// making the next pointer of the current node point to the first node in the
//// first branch.
////
private static ParsedNode ParseNodes (Lexer lexer) {
lexer.ScanFor(";", "Must be one node in each branch");
var cur_node = ParseNode(lexer);
var first = cur_node;
var branching_yet = false;
while (lexer.HasData()) {
// Semi-colon starts another node, open paren starts a branch, close
// paren stops list of nodes. Scanning raises an exception if one of
// these chars fails to follow (ignoring whitespace).
var chr = lexer.ScanFor(";()");
if (chr == ';') {
if (branching_yet)
throw new Exception("Found node after branching started.");
cur_node.Next = ParseNode(lexer);
cur_node.Next.Previous = cur_node;
cur_node = cur_node.Next;
}
else if (chr == '(') {
if (! branching_yet) {
cur_node.Next = ParseNodes(lexer);
cur_node.Next.Previous = cur_node;
cur_node.Branches = new List<ParsedNode>() { cur_node.Next };
branching_yet = true;
}
else {
var n = ParseNodes(lexer);
n.Previous = cur_node;
cur_node.Branches.Add(n);
}
}
else if (chr == ')')
return first;
else
throw new FileFormatException("SGF file is malformed at char " + lexer.Location.ToString());
}
throw new FileFormatException("Unexpectedly hit EOF!");
} // ParseNodes
//// _parse_node returns a ParseNode with its properties filled in.
////
private static ParsedNode ParseNode (Lexer lexer) {
var node = new ParsedNode();
// Loop properties ...
while (lexer.HasData()) {
var id = lexer.GetPropertyId();
if (id == null)
// Expected to return from here due to no properties or syntax at end of properties.
return node;
if (node.Properties.ContainsKey(id))
throw new Exception(string.Format("Encountered ID, {0}, twice for node -- file location {1}.",
id, lexer.Location));
lexer.ScanFor("[", "Expected property value");
var i = -1;
var values = new List<string>();
node.Properties[id] = values;
// Loop values for one property
while (lexer.HasData()) {
// C and GC properties allow newline sequences in value.
values.Add(lexer.GetPropertyValue(id == "C" || id == "GC"));
i = lexer.PeekFor("[").Item1;
if (i == -1) break; // no new values
lexer.Location = i;
}
}
throw new FileFormatException("Unexpectedly hit EOF!");
} // ParseNode
} // ParserAux Class
//// The Lexer class provides stateless helpers for Parser or ParserAux.
////
internal class Lexer {
private string _data;
private int _data_len;
private int _index;
internal Lexer (string contents) {
this._data = contents;
this._data_len = contents.Length;
this._index = 0;
}
internal int Location { get {return this._index;}
set {this._index = value;} }
//// scan_for scans for any char in chars following whitespace. If
//// non-whitespace intervenes, this is an error. Scan_for leaves _index
//// after char and returns found char.
////
internal char ScanFor(string chars, string errmsg = null) {
var tmp = this.PeekFor(chars);
var i = tmp.Item1;
var c = tmp.Item2;
if (i == -1) {
if (errmsg != null)
errmsg = errmsg + " -- file location " + this._index.ToString();
throw new Exception(errmsg ?? string.Format("Expecting one of '{0}' while scanning -- file location {1}",
chars, this._index));
}
else {
this._index = i;
return c;
}
}
//// peek_for scans for any char in chars following whitespace. If
//// non-whitespace intervenes, this is an error. Scan_for leaves _index
//// unmodified.
////
internal Tuple<int, char> PeekFor(string chars) {
var i = this._index;
while (this.HasData()) {
var c = this._data[i];
i += 1;
if (" \t\n\r\f\v".Contains(c))
continue;
else if (chars.Contains(c))
return new Tuple<int, char>(i, c);
else
return new Tuple<int, char>(-1, ((char)0));
}
return new Tuple<int, char>(-1, ((char)0));
}
internal bool HasData() {
return this._index < this._data_len;
}
private Regex propertyIdRegexp = new Regex(@"\s*([A-Za-z]+)");
//// get_property_id skips any whitespace and expects to find alphabetic chars
//// that form a property name. .NET does not match at start location by default.
//// .NET returns extra groups, and naming the one group in the pattern does not help find group.
//// This empirically assumes groups[1] is the group we want, no documentation on group[0] in .NET,
//// but it appears to be the whole match, which lets us test if we matched at the start or not.
////
internal string GetPropertyId () {
var match = this.propertyIdRegexp.Match(this._data, this._index);
if (match.Success && match.Groups[0].Index == this._index) {
this._index += match.Length;
return match.Groups[1].ToString();
}
return null;
}
//// get_property_id skips any whitespace and expects to find alphabetic chars
//// that form a property name. This function open codes a simple regexp that
//// .NET matches incorrectly. .NET fails to match at the start index provided
//// in two ways. It finds any match anywhere in the string, which is arguably
//// appropriate, but when you use the match start syntax and provide a start
//// index for searching, .NET always fails the match.
////
//internal string GetPropertyId () {
// var i = this._index;
// // Skip any whitespace
// while (i < this._data_len) {
// var c = this._data[i];
// if (" \t\n\r\f\v".Contains(c)) {
// i += 1;
// continue;
// }
// else
// break;
// }
// // Grab alphabetic chars or fail
// var sb = new StringBuilder();
// while (i < this._data_len) {
// var c = this._data[i];
// if (('a' <= c && 'z' >= 'c') || ('A' <= c && 'Z' >= c)) {
// i += 1;
// sb.Append(c);
// }
// else
// break;
// }
// if (sb.Length > 0) {
// this._index = i;
// return sb.ToString();
// }
// else
// return null;
//}
//// get_property_value takes a flag as to whether un-escaped newlines get
//// mapped to space or kept as-is. It gobbles all the characters after a
//// '[' (which has already been consumed) up to the next ']' and returns
//// them as a string. Keep_newlines distinguishes properties like C and GC
//// that can have newlines in their values, but otherwise, newlines are
//// assumed to be purely line-length management in the .sgf file.
////
//// SGF "text" properties can have newlines, newlines following \ are removed
//// along with the \, other escaped chars are kept verbatim except whitespace
//// is converted to space.
////
//// SGF "simpletext" properties are the same as "text" but have no newlines.
////
internal string GetPropertyValue(bool keep_newlines) {
var res = new StringBuilder();
while (this.HasData()) {
var c = this._data[this._index];
this._index += 1;
if (((int)c) < ((int)' ')) { //if < space
// Map whitespace to spaces.
var tmp = this.CheckPropertyNewline(c);
var newline = tmp.Item1;
var c2 = tmp.Item2;
if (newline)
// Only map newline sequences according to keep_newlines.
if (keep_newlines) {
// Canonicalize newlines because 1) would write mixed newline sequences in different places
// in .sgf depending on comments vs. other syntax, and 2) winRT textbox converts all line
// endings with no option to preserve them. Not needed for WPF, but doesn't hurt.
res.Append('\r');
res.Append('\n');
}
else
res.Append(" "); //convert newline to space to separate words
else
res.Append(" "); //convert whatever whitespace it was to space
}
else if (c == '\\') {
// Backslash quotes chars and erases newlines.
c = this._data[this._index];
this._index += 1;
var newline = this.CheckPropertyNewline(c).Item1;
if (! newline)
res.Append(c);
}
else if (c == ']')
return res.ToString();
else
res.Append(c);
}
throw new FileFormatException("Unexpectedly hit EOF!");
} // GetPropertyValue
//// _check_property_newline check if c is part of a newline sequence. If
//// it is, then see if there's a second newline sequence char and gobble
//// it. Returns whether there was a newline sequence and what the second
//// char was if it was part of the newline sequence.
////
private Tuple<bool, char> CheckPropertyNewline (char c) {
if (c == '\n' || c == '\r') {
// Only map newline sequences according to keep_newlines.
var c2 = this._data[this._index];
if (c2 == '\n' || c2 == '\r') {
this._index += 1;
return new Tuple<bool, char>(true, c2);
}
return new Tuple<bool, char>(true, ((char)0));
}
else
return new Tuple<bool, char>(false, ((char)0));
}
} // Lexer class
} // namespace
