SgfParser 1.0.0 → 2.0.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.
- data/.gitignore +26 -0
- data/.rvmrc +1 -1
- data/Gemfile +1 -11
- data/Gemfile.lock +20 -17
- data/README.rdoc +9 -35
- data/Rakefile +105 -44
- data/SgfParser.gemspec +14 -59
- data/bin/sgf_indent.rb +13 -0
- data/examples/simple_iteration_of_games_in_a_directory.rb +11 -0
- data/lib/sgf.rb +4 -2
- data/lib/sgf/error.rb +13 -0
- data/lib/sgf/game.rb +64 -0
- data/lib/sgf/node.rb +58 -11
- data/lib/sgf/parser.rb +121 -74
- data/lib/sgf/properties.rb +81 -88
- data/lib/sgf/tree.rb +39 -53
- data/lib/sgf/version.rb +3 -0
- data/lib/sgf/writer.rb +54 -0
- data/spec/data/example1.sgf +10 -0
- data/spec/game_spec.rb +70 -0
- data/spec/node_spec.rb +28 -2
- data/spec/parser_spec.rb +107 -10
- data/spec/spec_helper.rb +25 -1
- data/spec/tree_spec.rb +25 -18
- data/spec/writer_spec.rb +97 -0
- metadata +43 -28
- data/VERSION +0 -1
- data/lib/sgf/indenter.rb +0 -108
- data/sample_usage/parsing_files.rb +0 -19
- data/spec/data/ff4_ex_saved.sgf +0 -45
- data/spec/data/simple_saved.sgf +0 -7
data/spec/tree_spec.rb
CHANGED
@@ -1,29 +1,36 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
|
3
|
-
describe "
|
3
|
+
describe "SGF::Tree" do
|
4
4
|
|
5
|
+
before :each do
|
6
|
+
@tree = get_tree_from 'spec/data/ff4_ex.sgf'
|
7
|
+
end
|
5
8
|
|
6
|
-
it "should
|
7
|
-
tree
|
8
|
-
tree.class.should == SGF::Tree
|
9
|
-
tree.root.children.size.should == 2
|
10
|
-
tree.root.children[0].children.size.should == 5
|
9
|
+
it "should have two gametrees" do
|
10
|
+
@tree.games.size.should == 2
|
11
11
|
end
|
12
12
|
|
13
|
-
it "should
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
it "should have a decent inspect" do
|
14
|
+
inspect = @tree.inspect
|
15
|
+
inspect.should match /SGF::Tree/
|
16
|
+
inspect.should match /#{@tree.object_id}/
|
17
|
+
inspect.should match /2 Games/
|
18
|
+
inspect.should match /62 Nodes/
|
19
19
|
end
|
20
20
|
|
21
|
-
it "should
|
22
|
-
tree =
|
23
|
-
|
24
|
-
tree.
|
25
|
-
|
26
|
-
|
21
|
+
it "should use preorder traversal for each" do
|
22
|
+
@tree = get_tree_from 'spec/data/example1.sgf'
|
23
|
+
array = []
|
24
|
+
@tree.each {|node| array << node}
|
25
|
+
array[0].c.should == "root"
|
26
|
+
array[1].c.should == "a"
|
27
|
+
array[2].c.should == "b"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should load a file properly" do
|
31
|
+
@tree.class.should == SGF::Tree
|
32
|
+
@tree.root.children.size.should == 2
|
33
|
+
@tree.root.children[0].children.size.should == 5
|
27
34
|
end
|
28
35
|
|
29
36
|
end
|
data/spec/writer_spec.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "SGF::Writer" do
|
4
|
+
|
5
|
+
TEMP_FILE = 'spec/data/temp.sgf'
|
6
|
+
|
7
|
+
after :each do
|
8
|
+
FileUtils.rm_f TEMP_FILE
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should save a simple tree properly" do
|
12
|
+
sgf = File.read('spec/data/simple.sgf')
|
13
|
+
parse_save_load_and_compare_to_saved sgf
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should save an SGF with two gametrees properly" do
|
17
|
+
parse_save_load_and_compare_to_saved "(;FF[4])(;FF[4])"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should save the one-line simplified sample" do
|
21
|
+
parse_save_load_and_compare_to_saved ONE_LINE_SIMPLE_SAMPLE_SGF
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should save the simplified SGF properly" do
|
25
|
+
parse_save_load_and_compare_to_saved SIMPLIFIED_SAMPLE_SGF
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should save the sample SGF properly" do
|
29
|
+
sgf = File.read('spec/data/ff4_ex.sgf')
|
30
|
+
parse_save_load_and_compare_to_saved sgf
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should indent a simple SGF nicely" do
|
34
|
+
sgf = save_to_temp_file_and_read '(;FF[4])'
|
35
|
+
sgf.should == "\n(\n ;FF[4]\n)"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should indent a one-node SGF with two properties" do
|
39
|
+
sgf = save_to_temp_file_and_read '(;FF[4]PW[Cho Chikun])'
|
40
|
+
sgf.should == "\n(\n ;FF[4]\n PW[Cho Chikun]\n)"
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should indent two nodes on same column" do
|
44
|
+
sgf = save_to_temp_file_and_read '(;FF[4];PB[qq])'
|
45
|
+
sgf.should == "\n(\n ;FF[4]\n ;PB[qq]\n)"
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should indent branches further" do
|
49
|
+
string = '(;FF[4](;PB[qq])(;PB[qa]))'
|
50
|
+
sgf = save_to_temp_file_and_read string
|
51
|
+
expected = %Q{
|
52
|
+
(
|
53
|
+
;FF[4]
|
54
|
+
(
|
55
|
+
;PB[qq]
|
56
|
+
)
|
57
|
+
(
|
58
|
+
;PB[qa]
|
59
|
+
)
|
60
|
+
)}
|
61
|
+
sgf.should == expected
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should indent two gametrees" do
|
65
|
+
string = '(;FF[4];PB[qq])(;FF[4];PB[dd])'
|
66
|
+
sgf = save_to_temp_file_and_read string
|
67
|
+
expected = %Q{
|
68
|
+
(
|
69
|
+
;FF[4]
|
70
|
+
;PB[qq]
|
71
|
+
)
|
72
|
+
(
|
73
|
+
;FF[4]
|
74
|
+
;PB[dd]
|
75
|
+
)}
|
76
|
+
sgf.should == expected
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def parse_save_load_and_compare_to_saved string
|
82
|
+
parser =SGF::Parser.new
|
83
|
+
tree = parser.parse string
|
84
|
+
tree.save TEMP_FILE
|
85
|
+
tree2 = get_tree_from TEMP_FILE
|
86
|
+
tree2.should == tree
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
def save_to_temp_file_and_read sgf_string
|
91
|
+
tree = SGF::Parser.new.parse sgf_string
|
92
|
+
tree.save TEMP_FILE
|
93
|
+
sgf = File.read TEMP_FILE
|
94
|
+
sgf
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: SgfParser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,12 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-08-01 00:00:00.000000000
|
13
|
-
default_executable:
|
12
|
+
date: 2011-08-01 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
|
-
name:
|
17
|
-
requirement: &
|
15
|
+
name: rspec
|
16
|
+
requirement: &84169490 !ruby/object:Gem::Requirement
|
18
17
|
none: false
|
19
18
|
requirements:
|
20
19
|
- - ! '>='
|
@@ -22,10 +21,10 @@ dependencies:
|
|
22
21
|
version: '0'
|
23
22
|
type: :development
|
24
23
|
prerelease: false
|
25
|
-
version_requirements: *
|
24
|
+
version_requirements: *84169490
|
26
25
|
- !ruby/object:Gem::Dependency
|
27
26
|
name: rcov
|
28
|
-
requirement: &
|
27
|
+
requirement: &84169230 !ruby/object:Gem::Requirement
|
29
28
|
none: false
|
30
29
|
requirements:
|
31
30
|
- - ! '>='
|
@@ -33,10 +32,10 @@ dependencies:
|
|
33
32
|
version: '0'
|
34
33
|
type: :development
|
35
34
|
prerelease: false
|
36
|
-
version_requirements: *
|
35
|
+
version_requirements: *84169230
|
37
36
|
- !ruby/object:Gem::Dependency
|
38
|
-
name:
|
39
|
-
requirement: &
|
37
|
+
name: rake
|
38
|
+
requirement: &84168780 !ruby/object:Gem::Requirement
|
40
39
|
none: false
|
41
40
|
requirements:
|
42
41
|
- - ! '>='
|
@@ -44,28 +43,31 @@ dependencies:
|
|
44
43
|
version: '0'
|
45
44
|
type: :development
|
46
45
|
prerelease: false
|
47
|
-
version_requirements: *
|
46
|
+
version_requirements: *84168780
|
48
47
|
- !ruby/object:Gem::Dependency
|
49
|
-
name:
|
50
|
-
requirement: &
|
48
|
+
name: rdoc
|
49
|
+
requirement: &84168450 !ruby/object:Gem::Requirement
|
51
50
|
none: false
|
52
51
|
requirements:
|
53
52
|
- - ! '>='
|
54
53
|
- !ruby/object:Gem::Version
|
55
|
-
version:
|
54
|
+
version: '0'
|
56
55
|
type: :development
|
57
56
|
prerelease: false
|
58
|
-
version_requirements: *
|
59
|
-
description:
|
60
|
-
|
61
|
-
|
62
|
-
|
57
|
+
version_requirements: *84168450
|
58
|
+
description: SGF::Parser does standard stream parsing of the SGF file, instead of
|
59
|
+
using an AG or some other auto-generated newfangled parser stuff. It is therefore
|
60
|
+
faster to use, and hopefully will also be easier to use. Feedback helps :)
|
61
|
+
email: trevoke@gmail.com
|
62
|
+
executables:
|
63
|
+
- sgf_indent.rb
|
63
64
|
extensions: []
|
64
65
|
extra_rdoc_files:
|
65
66
|
- LICENSE
|
66
67
|
- README.rdoc
|
67
68
|
files:
|
68
69
|
- .document
|
70
|
+
- .gitignore
|
69
71
|
- .rspec
|
70
72
|
- .rvmrc
|
71
73
|
- Gemfile
|
@@ -74,24 +76,27 @@ files:
|
|
74
76
|
- README.rdoc
|
75
77
|
- Rakefile
|
76
78
|
- SgfParser.gemspec
|
77
|
-
-
|
79
|
+
- bin/sgf_indent.rb
|
80
|
+
- examples/simple_iteration_of_games_in_a_directory.rb
|
78
81
|
- lib/sgf.rb
|
79
|
-
- lib/sgf/
|
82
|
+
- lib/sgf/error.rb
|
83
|
+
- lib/sgf/game.rb
|
80
84
|
- lib/sgf/node.rb
|
81
85
|
- lib/sgf/parser.rb
|
82
86
|
- lib/sgf/properties.rb
|
83
87
|
- lib/sgf/tree.rb
|
84
|
-
-
|
88
|
+
- lib/sgf/version.rb
|
89
|
+
- lib/sgf/writer.rb
|
90
|
+
- spec/data/example1.sgf
|
85
91
|
- spec/data/ff4_ex.sgf
|
86
|
-
- spec/data/ff4_ex_saved.sgf
|
87
92
|
- spec/data/redrose-tartrate.sgf
|
88
93
|
- spec/data/simple.sgf
|
89
|
-
- spec/
|
94
|
+
- spec/game_spec.rb
|
90
95
|
- spec/node_spec.rb
|
91
96
|
- spec/parser_spec.rb
|
92
97
|
- spec/spec_helper.rb
|
93
98
|
- spec/tree_spec.rb
|
94
|
-
|
99
|
+
- spec/writer_spec.rb
|
95
100
|
homepage: http://github.com/Trevoke/SGFParser
|
96
101
|
licenses: []
|
97
102
|
post_install_message:
|
@@ -112,8 +117,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
112
117
|
version: '0'
|
113
118
|
requirements: []
|
114
119
|
rubyforge_project:
|
115
|
-
rubygems_version: 1.
|
120
|
+
rubygems_version: 1.8.10
|
116
121
|
signing_key:
|
117
122
|
specification_version: 3
|
118
|
-
summary: A library
|
119
|
-
test_files:
|
123
|
+
summary: A library that parses and saves SGF (Smart Game Format) files.
|
124
|
+
test_files:
|
125
|
+
- spec/data/example1.sgf
|
126
|
+
- spec/data/ff4_ex.sgf
|
127
|
+
- spec/data/redrose-tartrate.sgf
|
128
|
+
- spec/data/simple.sgf
|
129
|
+
- spec/game_spec.rb
|
130
|
+
- spec/node_spec.rb
|
131
|
+
- spec/parser_spec.rb
|
132
|
+
- spec/spec_helper.rb
|
133
|
+
- spec/tree_spec.rb
|
134
|
+
- spec/writer_spec.rb
|
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
1.0.0
|
data/lib/sgf/indenter.rb
DELETED
@@ -1,108 +0,0 @@
|
|
1
|
-
require 'stringio'
|
2
|
-
|
3
|
-
module SGF
|
4
|
-
|
5
|
-
# This indents an SGF file to make it more readable. It outputs to the screen
|
6
|
-
# by default, but can be given a file as output.
|
7
|
-
# Usage: SgfParser::Indenter.new infile, outfile
|
8
|
-
class Indenter
|
9
|
-
|
10
|
-
def initialize file, out=$stdout
|
11
|
-
@stream = load_file_to_stream(file)
|
12
|
-
@new_string = ""
|
13
|
-
@indentation = 0
|
14
|
-
@out = (out == $stdout) ? $stdout : File.open(out, 'w')
|
15
|
-
parse
|
16
|
-
end
|
17
|
-
|
18
|
-
def next_character
|
19
|
-
!@stream.eof? && @stream.sysread(1)
|
20
|
-
end
|
21
|
-
|
22
|
-
def parse
|
23
|
-
while char = next_character
|
24
|
-
case char
|
25
|
-
when '(' then open_branch
|
26
|
-
when ')' then close_branch
|
27
|
-
when ';' then new_node
|
28
|
-
when '[' then add_property
|
29
|
-
else add_identity(char)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
@out << @new_string
|
34
|
-
@out.close unless @out == $stdout
|
35
|
-
#if out == $stdout
|
36
|
-
# $stdout << @new_string
|
37
|
-
#else
|
38
|
-
# File.open(out, 'w') { |file| file << @new_string }
|
39
|
-
#end
|
40
|
-
end
|
41
|
-
|
42
|
-
def open_branch
|
43
|
-
next_line
|
44
|
-
@indentation += 2
|
45
|
-
@new_string << " " * @indentation
|
46
|
-
@new_string << "("
|
47
|
-
end
|
48
|
-
|
49
|
-
def close_branch
|
50
|
-
@new_string << ")"
|
51
|
-
next_line
|
52
|
-
@indentation -= 2
|
53
|
-
@indentation = 0 if @indentation < 0
|
54
|
-
@new_string << " " * @indentation
|
55
|
-
end
|
56
|
-
|
57
|
-
def new_node
|
58
|
-
next_line
|
59
|
-
@new_string << " " * @indentation
|
60
|
-
@new_string << ";"
|
61
|
-
end
|
62
|
-
|
63
|
-
def next_line
|
64
|
-
@new_string << "\n"
|
65
|
-
end
|
66
|
-
|
67
|
-
#TODO Fix it more. Add _ONE_ set of indentation if there are newlines,
|
68
|
-
#TODO Not one set for every newline.
|
69
|
-
def add_property
|
70
|
-
buffer = "["
|
71
|
-
while true
|
72
|
-
next_bit = @stream.sysread(1)
|
73
|
-
next_bit << @stream.sysread(1) if next_bit == "\\"
|
74
|
-
buffer << next_bit
|
75
|
-
buffer << " " * @indentation if next_bit == "\n"
|
76
|
-
break if next_bit == "]"
|
77
|
-
end
|
78
|
-
buffer << "\n"
|
79
|
-
buffer << " " * @indentation
|
80
|
-
@new_string << buffer
|
81
|
-
end
|
82
|
-
|
83
|
-
def add_identity(char)
|
84
|
-
@new_string << char unless char == "\n"
|
85
|
-
end
|
86
|
-
|
87
|
-
private
|
88
|
-
|
89
|
-
def load_file_to_stream(file)
|
90
|
-
sgf = ""
|
91
|
-
File.open(file) { |f| sgf = f.read }
|
92
|
-
clean_string(sgf)
|
93
|
-
return StringIO.new(sgf, 'r')
|
94
|
-
end
|
95
|
-
|
96
|
-
def clean_string(sgf)
|
97
|
-
sgf.gsub! "\\\\n\\\\r", ""
|
98
|
-
sgf.gsub! "\\\\r\\\\n", ""
|
99
|
-
sgf.gsub! "\\\\r", ""
|
100
|
-
sgf.gsub! "\\\\n", ""
|
101
|
-
end
|
102
|
-
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# With this code, we will display info on all files within a directory:
|
2
|
-
# "Black_player-White_player.sgf"
|
3
|
-
|
4
|
-
require '../lib/sgf_parser'
|
5
|
-
|
6
|
-
|
7
|
-
Dir.glob('../data/*').each do |file|
|
8
|
-
next if File.directory? file # Let's not touch directories.
|
9
|
-
next if File.extname(file) != ".sgf" # If it's not an SGF file, let's not touch it.
|
10
|
-
sgf = SgfParser::Tree.new :filename => file
|
11
|
-
# The "root" is an empty node, always, so we can support SGF collections painlessly:
|
12
|
-
# Whole trees simply become branches from the root.
|
13
|
-
|
14
|
-
black = sgf.root.children[0].PB rescue "Black" # Alright, so this isn't particularly elegant.
|
15
|
-
white = sgf.root.children[0].PW rescue "White" # It's a work in progress !
|
16
|
-
|
17
|
-
puts "#{black}-#{white}.sgf"
|
18
|
-
|
19
|
-
end
|
data/spec/data/ff4_ex_saved.sgf
DELETED
@@ -1,45 +0,0 @@
|
|
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]GW[1]C[Marked as "Good for White"];B[pp]GB[2]C[Marked as "Very good for Black"];W[dc]GW[2]C[Marked as "Very good for White"];B[pj]DM[1]C[Marked as "Even position"];W[ci]UC[1]C[Marked as "Unclear position"];B[jd]TE[1]C[Marked as "Tesuji" or "Good move"];W[jp]BM[2]C[Marked as "Very bad move"];B[gd]DO[]C[Marked as "Doubtful move"];W[de]IT[]C[Marked as "Interesting move"];B[jj];C[White "Pass" move]W[];)(;AB[dd][de][df][dg][do:gq] AW[jd][je][jf][jg][kn:lq][pn:pq]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.];AE[ep][fp][kn][lo][lq][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.];AB[pd]AW[pp]PL[B]C[Added two stones.
|
8
|
-
|
9
|
-
Node marked with "Black to play".];)(;AB[dd][de][df][dg][dh][di][dj][nj][ni][nh][nf][ne][nd][ij][ii][ih][hq][gq][fq][eq][dr][ds][dq][dp][cp][bp][ap][iq][ir][is][bo][bn][an][ms][mr]AW[pd][pe][pf][pg][ph][pi][pj][fd][fe][ff][fh][fi][fj][kh][ki][kj][os][or][oq][op][pp][qp][rp][sp][ro][rn][sn][nq][mq][lq][kq][kr][ks][fs][gs][gr][er]N[Markup]C[Position set up without compressed point lists.];TR[dd][de][df][ed][ee][ef][fd:ff] MA[dh][di][dj][ej][ei][eh][fh:fj] CR[nd][ne][nf][od][oe][of][pd:pf] SQ[nh][ni][nj][oh][oi][oj][ph:pj] SL[ih][ii][ij][jj][ji][jh][kh:kj] TW[pq:ss][so][lr:ns] TB[aq:cs][er:hs][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)];LB[dc:1][fc:2][nc:3][pc:4][dj:a][fj:b][nj:c][pj:d][gs:ABCDEFGH][gr:ABCDEFG][gq:ABCDEF][gp:ABCDE][go:ABCD][gn:ABC][gm:AB][mm:12][mn:123][mo:1234][mp:12345][mq:123456][mr:1234567][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];W[qq]WL[200]OW[2]C[White time left: 200 sec
|
37
|
-
White stones left: 2];B[sr]BL[87.00]OB[9]C[Black time left: 87 sec
|
38
|
-
Black stones left: 9];W[qs]WL[13.20]OW[1]C[White time left: 13.2 sec
|
39
|
-
White stones left: 1];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]AP[Primiview:3.1]GM[1]SZ[19]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.];B[pd](;)(;)(;W[ep];B[pp](;)(;))
|