SgfParser 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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](;)(;))