SgfParser 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,111 @@
1
+ module SgfParser
2
+ class Tree
3
+
4
+ private
5
+
6
+ # This function parses a SGF string into a linked list, or tree.
7
+ def parse
8
+ @sgf.gsub! "\\\\n\\\\r", ""
9
+ @sgf.gsub! "\\\\r\\\\n", ""
10
+ @sgf.gsub! "\\\\r", ""
11
+ @sgf.gsub! "\\\\n", ""
12
+ #@sgf.gsub! "\n", ""
13
+ branches = [] # This stores where new branches are open
14
+ current_node = @root # Let's start at the beginning, shall we?
15
+ identprop = false # We are not in the middle of an identprop value.
16
+ # An identprop is an identity property - a value.
17
+ content = Hash.new # Hash holding all the properties
18
+ param, property = "", "" # Variables holding the idents and props
19
+ end_of_a_series = false # To keep track of params with multiple properties
20
+
21
+ sgf_array = @sgf.split(//)
22
+ iterator = 0
23
+ array_length = sgf_array.size
24
+
25
+ while iterator < array_length
26
+ char = sgf_array[iterator]
27
+ case char
28
+ =begin
29
+ Basically, if we're inside an identprop, it's a normal character (or a closing
30
+ character).
31
+ If we're not, it's either a property or a special SGF character so we have
32
+ to handle that properly.
33
+ =end
34
+ when '(' # Opening a new branch
35
+ if identprop
36
+ property << char
37
+ else
38
+ branches.unshift current_node
39
+ end
40
+ when ')' # Closing a branch
41
+ if identprop
42
+ property << char
43
+ else
44
+ current_node = branches.shift
45
+ param, property = "", ""
46
+ content.clear
47
+ end
48
+ when ';' # Opening a new node
49
+ if identprop
50
+ property << char
51
+ else
52
+ # Make the current node the old node, make new node, store data
53
+ parent = current_node
54
+ current_node = Node.new :parent => parent
55
+ parent.add_properties content
56
+ parent.add_children current_node
57
+ param, property = "", ""
58
+ content.clear
59
+ end
60
+ when '[' # Open identprop?
61
+ if identprop
62
+ property << char
63
+ else # If we're not inside an identprop, then now we are.
64
+ identprop = true
65
+ end_of_a_series = false
66
+ end
67
+ when ']' # Close identprop
68
+ # Cleverness : checking for this first, then for the backslash.
69
+ # This means that if we encounter this, it must be closing an identprop.
70
+ # Because the "\\" code handles the logic to see if we're inside an identprop,
71
+ # And for skipping the bracket if necessary.
72
+ end_of_a_series = true # Maybe end of a series of identprop.
73
+ identprop = false # That's our cue to close an identprop.
74
+ content[param] = property
75
+ property = ""
76
+ when "\\"
77
+ # If we're inside a comment, then maybe we're about to escape a ].
78
+ # This is the whole reason we need this ugly loop.
79
+ if identprop
80
+ # If the next character is a closing bracket, then it's just
81
+ # escaped and the identprop continues.
82
+ if sgf_array[iterator + 1] == "]"
83
+ property << "]"
84
+ # On the next pass through, we will skip that bracket.
85
+ iterator += 1
86
+ else
87
+ property << "\\"
88
+ end
89
+ else
90
+ #This should never happen - a backslash outside an identprop ?!
91
+ #But let's not have it be told that I'm not prepared.
92
+ param << "\\"
93
+ end
94
+ when "\n"
95
+ property << "\n" if identprop
96
+
97
+ else
98
+ # Well, I guess it's "just" a character after all.
99
+ if end_of_a_series
100
+ end_of_a_series = false
101
+ param, property = "", ""
102
+ end
103
+ identprop ? (property << char) : param << char
104
+ end
105
+ iterator += 1
106
+ end
107
+
108
+ end
109
+
110
+ end
111
+ end
@@ -0,0 +1,118 @@
1
+ module SgfParser
2
+
3
+ # This indents an SGF file to make it more readable. It outputs to the screen
4
+ # by default, but can be given a file as output.
5
+ # Usage: SgfParser::Indenter.new infile, outfile
6
+ class Indenter
7
+
8
+ def initialize file, out=$stdout
9
+ sgf = ""
10
+ File.open(file) { |f| sgf = f.read }
11
+ @new_string = ""
12
+ sgf.gsub! "\\\\n\\\\r", ""
13
+ sgf.gsub! "\\\\r\\\\n", ""
14
+ sgf.gsub! "\\\\r", ""
15
+ sgf.gsub! "\\\\n", ""
16
+ #sgf.gsub! "\n", ""
17
+
18
+ end_of_a_series = false
19
+ identprop = false # We are not in the middle of an identprop value.
20
+ # An identprop is an identity property - a value.
21
+
22
+ sgf_array = sgf.split(//)
23
+ iterator = 0
24
+ array_length = sgf_array.size
25
+ indent = 0
26
+
27
+ while iterator < array_length - 1
28
+ char = sgf_array[iterator]
29
+
30
+ case char
31
+ when '(' # Opening a new branch
32
+ if !identprop
33
+ @new_string << "\n"
34
+ indent += 2
35
+ #tabulate indent
36
+ @new_string << " " * indent
37
+ end
38
+ @new_string << char
39
+
40
+ when ')' # Closing a branch
41
+ @new_string << char
42
+ if !identprop
43
+ @new_string << "\n"
44
+ indent -= 2
45
+ @new_string << " " * indent
46
+ #tabulate indent
47
+ end
48
+
49
+ when ';' # Opening a new node
50
+ if !identprop
51
+ @new_string << "\n"
52
+ @new_string << " " * indent
53
+ #tabulate indent
54
+ end
55
+ @new_string << char
56
+
57
+ when '[' # Open comment?
58
+ if !identprop #If we're not inside a comment, then now we are.
59
+ identprop = true
60
+ end_of_a_series = false
61
+ end
62
+ @new_string << char
63
+
64
+ when ']' # Close comment
65
+ end_of_a_series = true # Maybe end of a series of comments.
66
+ identprop = false # That's our cue to close a comment.
67
+ @new_string << char
68
+
69
+ when "\\" # If we're inside a comment, then maybe we're about to escape a ].
70
+ # This is the whole reason we need this ugly charade of a loop.
71
+ if identprop
72
+ if sgf_array[iterator + 1] == "]"
73
+ @new_string << "\\]"
74
+ iterator += 1
75
+ else
76
+ @new_string << "\\"
77
+ end
78
+ else
79
+ #This should never happen - a backslash outside a comment ?!
80
+ #But let's not have it be told that I'm not prepared.
81
+ @new_string << "\\"
82
+ end
83
+
84
+ when "\n"
85
+ @new_string << "\n"
86
+ @new_string << " " * indent
87
+ #tabulate indent
88
+
89
+ else
90
+ # Well, I guess it's "just" a character after all.
91
+ if end_of_a_series
92
+ end_of_a_series = false
93
+ end
94
+ @new_string << char
95
+ end
96
+
97
+ iterator += 1
98
+ end
99
+
100
+ if out == $stdout
101
+ $stdout << @new_string
102
+ else
103
+ File.open(out, 'w') { |file| file << @new_string }
104
+ end
105
+
106
+ end
107
+
108
+ private
109
+
110
+ def tabulate indent
111
+ indent.times { print " " }
112
+ end
113
+ end
114
+ end
115
+
116
+
117
+
118
+
@@ -0,0 +1,8 @@
1
+ $: << File.dirname(__FILE__)
2
+
3
+ require 'yaml'
4
+ require 'sgf/parser/properties'
5
+ require 'sgf/parser/node'
6
+ require 'sgf/parser/tree'
7
+ require 'sgf/parser/tree_parse'
8
+ require 'sgf/sgfindent'
@@ -0,0 +1,165 @@
1
+ (;FF[4]AP[Primiview:3.1]GM[1]SZ[19]GN[Gametree 1: properties]US[Arno Hollosi]
2
+
3
+ (;B[pd]N[Moves, comments, annotations]
4
+ C[Nodename set to: "Moves, comments, annotations"];W[dp]GW[1]
5
+ C[Marked as "Good for White"];B[pp]GB[2]
6
+ C[Marked as "Very good for Black"];W[dc]GW[2]
7
+ C[Marked as "Very good for White"];B[pj]DM[1]
8
+ C[Marked as "Even position"];W[ci]UC[1]
9
+ C[Marked as "Unclear position"];B[jd]TE[1]
10
+ C[Marked as "Tesuji" or "Good move"];W[jp]BM[2]
11
+ C[Marked as "Very bad move"];B[gd]DO[]
12
+ C[Marked as "Doubtful move"];W[de]IT[]
13
+ C[Marked as "Interesting move"];B[jj];
14
+ C[White "Pass" move]W[];
15
+ C[Black "Pass" move]B[tt])
16
+
17
+ (;AB[dd][de][df][dg][do:gq]
18
+ AW[jd][je][jf][jg][kn:lq][pn:pq]
19
+ N[Setup]C[Black & white stones at the top are added as single stones.
20
+
21
+ Black & white stones at the bottom are added using compressed point lists.]
22
+ ;AE[ep][fp][kn][lo][lq][pn:pq]
23
+ C[AddEmpty
24
+
25
+ Black stones & stones of left white group are erased in FF[3\] way.
26
+
27
+ White stones at bottom right were erased using compressed point list.]
28
+ ;AB[pd]AW[pp]PL[B]C[Added two stones.
29
+
30
+ Node marked with "Black to play".];PL[W]
31
+ C[Node marked with "White to play"])
32
+
33
+ (;AB[dd][de][df][dg][dh][di][dj][nj][ni][nh][nf][ne][nd][ij][ii][ih][hq]
34
+ [gq][fq][eq][dr][ds][dq][dp][cp][bp][ap][iq][ir][is][bo][bn][an][ms][mr]
35
+ AW[pd][pe][pf][pg][ph][pi][pj][fd][fe][ff][fh][fi][fj][kh][ki][kj][os][or]
36
+ [oq][op][pp][qp][rp][sp][ro][rn][sn][nq][mq][lq][kq][kr][ks][fs][gs][gr]
37
+ [er]N[Markup]C[Position set up without compressed point lists.]
38
+
39
+ ;TR[dd][de][df][ed][ee][ef][fd:ff]
40
+ MA[dh][di][dj][ej][ei][eh][fh:fj]
41
+ CR[nd][ne][nf][od][oe][of][pd:pf]
42
+ SQ[nh][ni][nj][oh][oi][oj][ph:pj]
43
+ SL[ih][ii][ij][jj][ji][jh][kh:kj]
44
+ TW[pq:ss][so][lr:ns]
45
+ TB[aq:cs][er:hs][ao]
46
+ C[Markup at top partially using compressed point lists (for markup on white stones); listed clockwise, starting at upper left:
47
+ - TR (triangle)
48
+ - CR (circle)
49
+ - SQ (square)
50
+ - SL (selected points)
51
+ - MA ('X')
52
+
53
+ Markup at bottom: black & white territory (using compressed point lists)]
54
+ ;LB[dc:1][fc:2][nc:3][pc:4][dj:a][fj:b][nj:c]
55
+ [pj:d][gs:ABCDEFGH][gr:ABCDEFG][gq:ABCDEF][gp:ABCDE][go:ABCD][gn:ABC][gm:AB]
56
+ [mm:12][mn:123][mo:1234][mp:12345][mq:123456][mr:1234567][ms:12345678]
57
+ C[Label (LB property)
58
+
59
+ Top: 8 single char labels (1-4, a-d)
60
+
61
+ Bottom: Labels up to 8 char length.]
62
+
63
+ ;DD[kq:os][dq:hs]
64
+ AR[aa:sc][sa:ac][aa:sa][aa:ac][cd:cj]
65
+ [gd:md][fh:ij][kj:nh]
66
+ LN[pj:pd][nf:ff][ih:fj][kh:nj]
67
+ C[Arrows, lines and dimmed points.])
68
+
69
+ (;B[qd]N[Style & text type]
70
+ C[There are hard linebreaks & soft linebreaks.
71
+ Soft linebreaks are linebreaks preceeded by '\\' like this one >o\
72
+ k<. Hard line breaks are all other linebreaks.
73
+ Soft linebreaks are converted to >nothing<, i.e. removed.
74
+
75
+ Note that linebreaks are coded differently on different systems.
76
+
77
+ Examples (>ok< shouldn't be split):
78
+
79
+ linebreak 1 "\\n": >o\
80
+ k<
81
+ linebreak 2 "\\n\\r": >o\
82
+
83
+ linebreak 3 "\\r\\n": >o\
84
+ k<
85
+ linebreak 4 "\\r": >o\
86
+
87
+ (;W[dd]N[W d16]C[Variation C is better.](;B[pp]N[B q4])
88
+ (;B[dp]N[B d4])
89
+ (;B[pq]N[B q3])
90
+ (;B[oq]N[B p3])
91
+ )
92
+ (;W[dp]N[W d4])
93
+ (;W[pp]N[W q4])
94
+ (;W[cc]N[W c17])
95
+ (;W[cq]N[W c3])
96
+ (;W[qq]N[W r3])
97
+ )
98
+
99
+ (;B[qr]N[Time limits, captures & move numbers]
100
+ BL[120.0]C[Black time left: 120 sec];W[rr]
101
+ WL[300]C[White time left: 300 sec];B[rq]
102
+ BL[105.6]OB[10]C[Black time left: 105.6 sec
103
+ Black stones left (in this byo-yomi period): 10];W[qq]
104
+ WL[200]OW[2]C[White time left: 200 sec
105
+ White stones left: 2];B[sr]
106
+ BL[87.00]OB[9]C[Black time left: 87 sec
107
+ Black stones left: 9];W[qs]
108
+ WL[13.20]OW[1]C[White time left: 13.2 sec
109
+ White stones left: 1];B[rs]
110
+ C[One white stone at s2 captured];W[ps];B[pr];W[or]
111
+ MN[2]C[Set move number to 2];B[os]
112
+ C[Two white stones captured
113
+ (at q1 & r1)]
114
+ ;MN[112]W[pq]C[Set move number to 112];B[sq];W[rp];B[ps]
115
+ ;W[ns];B[ss];W[nr]
116
+ ;B[rr];W[sp];B[qs]C[Suicide move
117
+ (all B stones get captured)])
118
+ )
119
+
120
+ (;FF[4]AP[Primiview:3.1]GM[1]SZ[19]C[Gametree 2: game-info
121
+
122
+ Game-info properties are usually stored in the root node.
123
+ If games are merged into a single game-tree, they are stored in the node\
124
+ where the game first becomes distinguishable from all other games in\
125
+ the tree.]
126
+ ;B[pd]
127
+ (;PW[W. Hite]WR[6d]RO[2]RE[W+3.5]
128
+ PB[B. Lack]BR[5d]PC[London]EV[Go Congress]W[dp]
129
+ C[Game-info:
130
+ Black: B. Lack, 5d
131
+ White: W. Hite, 6d
132
+ Place: London
133
+ Event: Go Congress
134
+ Round: 2
135
+ Result: White wins by 3.5])
136
+ (;PW[T. Suji]WR[7d]RO[1]RE[W+Resign]
137
+ PB[B. Lack]BR[5d]PC[London]EV[Go Congress]W[cp]
138
+ C[Game-info:
139
+ Black: B. Lack, 5d
140
+ White: T. Suji, 7d
141
+ Place: London
142
+ Event: Go Congress
143
+ Round: 1
144
+ Result: White wins by resignation])
145
+ (;W[ep];B[pp]
146
+ (;PW[S. Abaki]WR[1d]RO[3]RE[B+63.5]
147
+ PB[B. Lack]BR[5d]PC[London]EV[Go Congress]W[ed]
148
+ C[Game-info:
149
+ Black: B. Lack, 5d
150
+ White: S. Abaki, 1d
151
+ Place: London
152
+ Event: Go Congress
153
+ Round: 3
154
+ Result: Balck wins by 63.5])
155
+ (;PW[A. Tari]WR[12k]KM[-59.5]RO[4]RE[B+R]
156
+ PB[B. Lack]BR[5d]PC[London]EV[Go Congress]W[cd]
157
+ C[Game-info:
158
+ Black: B. Lack, 5d
159
+ White: A. Tari, 12k
160
+ Place: London
161
+ Event: Go Congress
162
+ Round: 4
163
+ Komi: -59.5 points
164
+ Result: Black wins by resignation])
165
+ ))
@@ -0,0 +1,45 @@
1
+ (;FF[4]AP[Primiview:3.1]GM[1]SZ[19]GN[Gametree 1: properties]US[Arno Hollosi](;B[pd]N[Moves, comments, annotations]C[Nodename set to: "Moves, comments, annotations"];W[dp]C[Marked as "Good for White"]GW[1];B[pp]C[Marked as "Very good for Black"]GB[2];W[dc]C[Marked as "Very good for White"]GW[2];B[pj]C[Marked as "Even position"]DM[1];W[ci]UC[1]C[Marked as "Unclear position"];B[jd]C[Marked as "Tesuji" or "Good move"]TE[1];BM[2]W[jp]C[Marked as "Very bad move"];DO[]B[gd]C[Marked as "Doubtful move"];IT[]W[de]C[Marked as "Interesting move"];B[jj];W[]C[White "Pass" move];)(;AB[do:gq]N[Setup]C[Black & white stones at the top are added as single stones.
2
+
3
+ Black & white stones at the bottom are added using compressed point lists.] AW[pn:pq];C[AddEmpty
4
+
5
+ Black stones & stones of left white group are erased in FF[3\] way.
6
+
7
+ White stones at bottom right were erased using compressed point list.]AE[pn:pq];AW[pp]AB[pd]C[Added two stones.
8
+
9
+ Node marked with "Black to play".]PL[B];)(;AW[er]AB[mr]N[Markup]C[Position set up without compressed point lists.]; SL[kh:kj] TW[lr:ns] TB[ao]C[Markup at top partially using compressed point lists (for markup on white stones); listed clockwise, starting at upper left:
10
+ - TR (triangle)
11
+ - CR (circle)
12
+ - SQ (square)
13
+ - SL (selected points)
14
+ - MA ('X')
15
+
16
+ Markup at bottom: black & white territory (using compressed point lists)] CR[pd:pf]TR[fd:ff] SQ[ph:pj] MA[fh:fj];LB[ms:12345678]C[Label (LB property)
17
+
18
+ Top: 8 single char labels (1-4, a-d)
19
+
20
+ Bottom: Labels up to 8 char length.];)(;B[qd]N[Style & text type]C[There are hard linebreaks & soft linebreaks.
21
+ Soft linebreaks are linebreaks preceeded by '\\' like this one >o\
22
+ k<. Hard line breaks are all other linebreaks.
23
+ Soft linebreaks are converted to >nothing<, i.e. removed.
24
+
25
+ Note that linebreaks are coded differently on different systems.
26
+
27
+ Examples (>ok< shouldn't be split):
28
+
29
+ linebreak 1 "": >o\
30
+ k<
31
+ linebreak 2 "": >o\
32
+
33
+ linebreak 3 "": >o\
34
+ k<
35
+ linebreak 4 "": >o\
36
+ Black stones left (in this byo-yomi period): 10]OB[10]BL[105.6];W[qq]C[White time left: 200 sec
37
+ White stones left: 2]OW[2]WL[200];B[sr]C[Black time left: 87 sec
38
+ Black stones left: 9]OB[9]BL[87.00];W[qs]C[White time left: 13.2 sec
39
+ White stones left: 1]OW[1]WL[13.20];B[rs]C[One white stone at s2 captured];W[ps];B[pr];W[or]MN[2]C[Set move number to 2];B[os]C[Two white stones captured
40
+ (at q1 & r1)];MN[112]W[pq]C[Set move number to 112];B[sq];W[rp];B[ps];W[ns];B[ss];W[nr];B[rr];W[sp];);FF[4]C[Gametree 2: game-info
41
+
42
+ Game-info properties are usually stored in the root node.
43
+ If games are merged into a single game-tree, they are stored in the node\
44
+ where the game first becomes distinguishable from all other games in\
45
+ the tree.]AP[Primiview:3.1]GM[1]SZ[19];B[pd](;)(;)(;W[ep];B[pp](;)(;))