SgfParser 0.9.0 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -5
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +27 -0
- data/Rakefile +11 -26
- data/SgfParser.gemspec +71 -67
- data/VERSION +1 -1
- data/lib/sgf/parser/node.rb +0 -12
- data/lib/sgf/parser/tree.rb +9 -20
- data/lib/sgf/parser/tree_parse.rb +23 -9
- data/lib/sgf/sgfindent.rb +118 -0
- data/sample_sgf/ff4_ex_saved.sgf +45 -0
- data/sample_sgf/simple.sgf +11 -0
- data/sample_sgf/simple_saved.sgf +7 -0
- data/spec/spec_helper.rb +2 -5
- data/spec/tree_parser_spec.rb +13 -2
- data/spec/tree_spec.rb +28 -9
- metadata +57 -19
- data/.gitignore +0 -25
- data/spec/spec.opts +0 -1
- data/tryme.rb +0 -3
data/.document
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
README.rdoc
|
2
|
-
lib/**/*.rb
|
3
|
-
bin/*
|
4
|
-
features/**/*.feature
|
5
|
-
LICENSE
|
1
|
+
README.rdoc
|
2
|
+
lib/**/*.rb
|
3
|
+
bin/*
|
4
|
+
features/**/*.feature
|
5
|
+
LICENSE
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color --format doc
|
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm 1.9.2@jrun
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
diff-lcs (1.1.2)
|
5
|
+
git (1.2.5)
|
6
|
+
jeweler (1.6.4)
|
7
|
+
bundler (~> 1.0)
|
8
|
+
git (>= 1.2.5)
|
9
|
+
rake
|
10
|
+
rake (0.9.2)
|
11
|
+
rcov (0.9.9)
|
12
|
+
rspec (2.6.0)
|
13
|
+
rspec-core (~> 2.6.0)
|
14
|
+
rspec-expectations (~> 2.6.0)
|
15
|
+
rspec-mocks (~> 2.6.0)
|
16
|
+
rspec-core (2.6.4)
|
17
|
+
rspec-expectations (2.6.0)
|
18
|
+
diff-lcs (~> 1.1.2)
|
19
|
+
rspec-mocks (2.6.0)
|
20
|
+
|
21
|
+
PLATFORMS
|
22
|
+
ruby
|
23
|
+
|
24
|
+
DEPENDENCIES
|
25
|
+
jeweler
|
26
|
+
rcov
|
27
|
+
rspec
|
data/Rakefile
CHANGED
@@ -18,38 +18,23 @@ rescue LoadError
|
|
18
18
|
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
19
|
end
|
20
20
|
|
21
|
-
require '
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
require 'rspec/core/rake_task'
|
22
|
+
desc 'Default: run specs.'
|
23
|
+
task :default => :spec
|
24
|
+
|
25
|
+
desc "Run specs"
|
26
|
+
RSpec::Core::RakeTask.new do |spec|
|
27
|
+
spec.pattern = "./spec/**/*_spec.rb"
|
25
28
|
end
|
26
29
|
|
27
|
-
|
28
|
-
spec.libs << 'lib' << 'spec'
|
30
|
+
RSpec::Core::RakeTask.new(:coverage) do |spec|
|
29
31
|
spec.pattern = 'spec/**/*_spec.rb'
|
30
32
|
spec.rcov = true
|
33
|
+
spec.rcov_opts = ['--exclude', 'spec']
|
31
34
|
end
|
32
35
|
|
33
|
-
task
|
34
|
-
|
35
|
-
# In case I ever want to go back to cucumber.
|
36
|
-
#begin
|
37
|
-
# require 'cucumber/rake/task'
|
38
|
-
# Cucumber::Rake::Task.new(:features)
|
39
|
-
#
|
40
|
-
# task :features => :check_dependencies
|
41
|
-
#rescue LoadError
|
42
|
-
# task :features do
|
43
|
-
# abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
|
44
|
-
# end
|
45
|
-
#end
|
46
|
-
|
47
|
-
#task :default => [:spec, :features]
|
48
|
-
|
49
|
-
task :default => [:spec]
|
50
|
-
|
51
|
-
require 'rake/rdoctask'
|
52
|
-
Rake::RDocTask.new do |rdoc|
|
36
|
+
require 'rdoc/task'
|
37
|
+
RDoc::Task.new do |rdoc|
|
53
38
|
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
54
39
|
|
55
40
|
rdoc.rdoc_dir = 'rdoc'
|
data/SgfParser.gemspec
CHANGED
@@ -1,67 +1,71 @@
|
|
1
|
-
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in
|
4
|
-
# -*- encoding: utf-8 -*-
|
5
|
-
|
6
|
-
Gem::Specification.new do |s|
|
7
|
-
s.name = %q{SgfParser}
|
8
|
-
s.version = "0.9.
|
9
|
-
|
10
|
-
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
-
s.authors = ["Aldric Giacomoni"]
|
12
|
-
s.date = %q{
|
13
|
-
s.description = %q{SGFParser is a library that parses and saves SGF (Smart Game Format) files.}
|
14
|
-
s.email = %q{aldric@trevoke.net}
|
15
|
-
s.extra_rdoc_files = [
|
16
|
-
"LICENSE",
|
17
|
-
|
18
|
-
]
|
19
|
-
s.files = [
|
20
|
-
".document",
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
60
|
-
else
|
61
|
-
s.add_dependency(%q<
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{SgfParser}
|
8
|
+
s.version = "0.9.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Aldric Giacomoni"]
|
12
|
+
s.date = %q{2011-07-29}
|
13
|
+
s.description = %q{SGFParser is a library that parses and saves SGF (Smart Game Format) files.}
|
14
|
+
s.email = %q{aldric@trevoke.net}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".rspec",
|
22
|
+
".rvmrc",
|
23
|
+
"Gemfile",
|
24
|
+
"Gemfile.lock",
|
25
|
+
"LICENSE",
|
26
|
+
"README.rdoc",
|
27
|
+
"Rakefile",
|
28
|
+
"SgfParser.gemspec",
|
29
|
+
"VERSION",
|
30
|
+
"lib/sgf/parser/node.rb",
|
31
|
+
"lib/sgf/parser/properties.rb",
|
32
|
+
"lib/sgf/parser/tree.rb",
|
33
|
+
"lib/sgf/parser/tree_parse.rb",
|
34
|
+
"lib/sgf/sgf_indent.rb",
|
35
|
+
"lib/sgf/sgfindent.rb",
|
36
|
+
"lib/sgf_parser.rb",
|
37
|
+
"sample_sgf/ff4_ex.sgf",
|
38
|
+
"sample_sgf/ff4_ex_saved.sgf",
|
39
|
+
"sample_sgf/redrose-tartrate.sgf",
|
40
|
+
"sample_sgf/simple.sgf",
|
41
|
+
"sample_sgf/simple_saved.sgf",
|
42
|
+
"sample_usage/parsing_files.rb",
|
43
|
+
"spec/node_spec.rb",
|
44
|
+
"spec/spec_helper.rb",
|
45
|
+
"spec/tree_parser_spec.rb",
|
46
|
+
"spec/tree_spec.rb"
|
47
|
+
]
|
48
|
+
s.homepage = %q{http://github.com/Trevoke/SGFParser}
|
49
|
+
s.require_paths = ["lib"]
|
50
|
+
s.rubygems_version = %q{1.6.2}
|
51
|
+
s.summary = %q{A library for working with SGF files.}
|
52
|
+
|
53
|
+
if s.respond_to? :specification_version then
|
54
|
+
s.specification_version = 3
|
55
|
+
|
56
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
57
|
+
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
58
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
59
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
60
|
+
else
|
61
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
62
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
63
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
64
|
+
end
|
65
|
+
else
|
66
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
67
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
68
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.9.
|
1
|
+
0.9.1
|
data/lib/sgf/parser/node.rb
CHANGED
@@ -1,8 +1,5 @@
|
|
1
1
|
module SgfParser
|
2
2
|
|
3
|
-
# Each part of the SGF Tree is a node. This holds links to the parent node,
|
4
|
-
# to the next node(s) in the tree, and of course, the properties of said node.
|
5
|
-
# Accessors : node.parent, node.children, node.properties
|
6
3
|
class Node
|
7
4
|
|
8
5
|
attr_accessor :parent, :children, :properties
|
@@ -19,9 +16,6 @@ module SgfParser
|
|
19
16
|
@properties.merge! args[:properties] if !args[:properties].nil?
|
20
17
|
end
|
21
18
|
|
22
|
-
# Adds one child or several children. Can be passed in as a comma-separated
|
23
|
-
# list or an array of node children. Will raise an error if one of the
|
24
|
-
# arguments is not of class Node.
|
25
19
|
def add_children *nodes
|
26
20
|
nodes.flatten!
|
27
21
|
raise "Non-node child given!" if nodes.find { |node| node.class != Node }
|
@@ -30,8 +24,6 @@ module SgfParser
|
|
30
24
|
@children.concat nodes
|
31
25
|
end
|
32
26
|
|
33
|
-
# Adds one or more properties to the node.
|
34
|
-
# Argument: a hash {'property' => 'value'}
|
35
27
|
def add_properties hash
|
36
28
|
hash.each do |key, value|
|
37
29
|
@properties[key] ||= ""
|
@@ -39,18 +31,14 @@ module SgfParser
|
|
39
31
|
end
|
40
32
|
end
|
41
33
|
|
42
|
-
# Iterates over each child of the given node
|
43
|
-
# node.each_child { |child| puts child.properties }
|
44
34
|
def each_child
|
45
35
|
@children.each { |child| yield child }
|
46
36
|
end
|
47
37
|
|
48
|
-
# Compares one node's properties to another node's properties
|
49
38
|
def == other_node
|
50
39
|
@properties == other_node.properties
|
51
40
|
end
|
52
41
|
|
53
|
-
# Making comments easier to access.
|
54
42
|
def comments
|
55
43
|
@properties["C"]
|
56
44
|
end
|
data/lib/sgf/parser/tree.rb
CHANGED
@@ -1,19 +1,10 @@
|
|
1
1
|
module SgfParser
|
2
2
|
|
3
|
-
# This is a placeholder for the root of the gametree(s). It's an abstraction,
|
4
|
-
# but it allows an easy way to save, iterate over, and compare other trees.
|
5
|
-
# Accessors: tree.root
|
6
3
|
class Tree
|
7
4
|
include Enumerable
|
8
5
|
|
9
6
|
attr_accessor :root
|
10
7
|
|
11
|
-
# Create a new tree. Can also be used to load a tree from either a file or
|
12
|
-
# a string. Raises an error if both are provided.
|
13
|
-
# options: \n
|
14
|
-
# :filename => filename \n
|
15
|
-
# !!! OR !!! \n
|
16
|
-
# :string => string \n
|
17
8
|
def initialize args={}
|
18
9
|
@root = Node.new
|
19
10
|
@sgf = ""
|
@@ -27,10 +18,9 @@ module SgfParser
|
|
27
18
|
parse unless @sgf.empty?
|
28
19
|
end
|
29
20
|
|
30
|
-
# Iterates over the tree, node by node, in preorder fashion.
|
31
|
-
# Does not support other types of iteration, but may in the future.
|
32
|
-
# tree.each { |node| puts "I am node. Hear me #{node.properties} !"}
|
33
21
|
def each order=:preorder, &block
|
22
|
+
# I know, I know. SGF is only preorder. Well, it's implemented, ain't it?
|
23
|
+
# Stop complaining.
|
34
24
|
case order
|
35
25
|
when :preorder
|
36
26
|
preorder @root, &block
|
@@ -51,10 +41,8 @@ module SgfParser
|
|
51
41
|
# tree.save :filename => file_name
|
52
42
|
def save args={}
|
53
43
|
raise ArgumentError, "No file name provided" if args[:filename].nil?
|
54
|
-
# SGF files are trees stored in pre-order traversal.
|
55
44
|
@savable_sgf = "("
|
56
45
|
@root.children.each { |child| write_node child }
|
57
|
-
# write_node @root
|
58
46
|
@savable_sgf << ")"
|
59
47
|
|
60
48
|
File.open(args[:filename], 'w') { |f| f << @savable_sgf }
|
@@ -67,11 +55,12 @@ module SgfParser
|
|
67
55
|
@savable_sgf << ";"
|
68
56
|
unless node.properties.empty?
|
69
57
|
properties = ""
|
70
|
-
node.properties.each do |
|
71
|
-
|
72
|
-
|
58
|
+
node.properties.each do |identity, property|
|
59
|
+
new_property = property[1...-1]
|
60
|
+
new_property.gsub!("]", "\\]") if identity == "C"
|
61
|
+
properties += "#{identity.to_s}[#{new_property}]"
|
73
62
|
end
|
74
|
-
@savable_sgf <<
|
63
|
+
@savable_sgf << properties
|
75
64
|
end
|
76
65
|
|
77
66
|
case node.children.size
|
@@ -104,13 +93,13 @@ module SgfParser
|
|
104
93
|
preorder(child, &block)
|
105
94
|
end
|
106
95
|
end
|
107
|
-
end
|
96
|
+
end
|
108
97
|
|
109
98
|
def method_missing method_name, *args
|
110
99
|
output = @root.children[0].properties[method_name]
|
111
100
|
super(method_name, args) if output.nil?
|
112
101
|
output
|
113
|
-
end
|
102
|
+
end
|
114
103
|
|
115
104
|
end
|
116
105
|
|
@@ -3,9 +3,8 @@ require 'stringio'
|
|
3
3
|
module SgfParser
|
4
4
|
class Tree
|
5
5
|
|
6
|
-
private
|
6
|
+
#private
|
7
7
|
|
8
|
-
# Creates a tree (truly, a linked list) from @sgf.
|
9
8
|
def parse
|
10
9
|
while char = next_character
|
11
10
|
case char
|
@@ -65,14 +64,29 @@ module SgfParser
|
|
65
64
|
|
66
65
|
def get_property
|
67
66
|
buffer = ""
|
68
|
-
while
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
67
|
+
while char = next_character
|
68
|
+
case char
|
69
|
+
when "]" then break unless multiple_properties?
|
70
|
+
when "\\" then
|
71
|
+
char << next_character
|
72
|
+
char = "]" if char == "\\]"
|
73
|
+
end
|
74
|
+
|
75
|
+
buffer << char
|
76
|
+
end
|
77
|
+
"[#{buffer}]"
|
78
|
+
end
|
79
|
+
|
80
|
+
def multiple_properties?
|
81
|
+
multiple_properties = false
|
82
|
+
if char = next_character
|
83
|
+
char = next_character if char == "\n"
|
84
|
+
if char == "["
|
85
|
+
multiple_properties = true
|
86
|
+
end
|
87
|
+
@stream.pos -= 1
|
88
|
+
multiple_properties
|
74
89
|
end
|
75
|
-
buffer
|
76
90
|
end
|
77
91
|
|
78
92
|
def store_character(char)
|
@@ -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,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]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](;)(;))
|
@@ -0,0 +1,11 @@
|
|
1
|
+
(;GM[1]FF[4]CA[UTF-8]
|
2
|
+
SZ[19]RU[Japanese]KM[0.50]TM[1290]OT[3x30 byo-yomi]
|
3
|
+
PB[tartrate]PW[redrose]BR[7d]WR[1p]DT[2003-06-18]PC[The Kiseido Go Server (KGS) at http://kgs.kiseido.com/]C[tartrate [7d?\]: hi
|
4
|
+
tartrate [7d?\]: thx
|
5
|
+
]RE[B+Resign]
|
6
|
+
;B[jj]BL[1280.503]CR[jj]C[redrose [1p\]: hi
|
7
|
+
Jerk [7d?\]: wow
|
8
|
+
Anark [11k\]: game of the day :)
|
9
|
+
Jerk [7d?\]: really big match
|
10
|
+
]
|
11
|
+
;W[ih]WL[1277.190]CR[ih];AB[aa][bb][cc])
|
data/spec/spec_helper.rb
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
2
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
3
|
require 'sgf_parser'
|
4
|
-
require '
|
5
|
-
require '
|
4
|
+
require 'rspec'
|
5
|
+
require 'rspec/autorun'
|
6
6
|
|
7
|
-
Spec::Runner.configure do |config|
|
8
|
-
|
9
|
-
end
|
data/spec/tree_parser_spec.rb
CHANGED
@@ -6,8 +6,19 @@ describe "SgfParser::Tree.parse" do
|
|
6
6
|
@tree = SgfParser::Tree.new :filename => 'sample_sgf/ff4_ex.sgf'
|
7
7
|
end
|
8
8
|
|
9
|
-
it "should
|
10
|
-
|
9
|
+
it "should have FF in the first node" do
|
10
|
+
@tree.root.children[0].properties.keys.should include("FF")
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should give an error if FF is missing from the first node" do
|
14
|
+
pending "To be coded later"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should parse properly the AW property" do
|
18
|
+
input = StringIO.new "dd][de][ef]"
|
19
|
+
tree = SgfParser::Tree.new
|
20
|
+
tree.instance_variable_set "@stream", input
|
21
|
+
tree.get_property.should == "[dd][de][ef]"
|
11
22
|
end
|
12
23
|
|
13
24
|
end
|
data/spec/tree_spec.rb
CHANGED
@@ -3,20 +3,39 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
3
3
|
describe "SgfParser::Tree" do
|
4
4
|
|
5
5
|
before :each do
|
6
|
-
|
6
|
+
|
7
7
|
end
|
8
8
|
|
9
9
|
it "should parse properly" do
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
tree = SgfParser::Tree.new :filename => 'sample_sgf/ff4_ex.sgf'
|
11
|
+
tree.class.should == SgfParser::Tree
|
12
|
+
tree.root.children.size.should == 2
|
13
|
+
tree.root.children[0].children.size.should == 5
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should save a simple tree properly" do
|
17
|
+
tree = SgfParser::Tree.new :filename => 'sample_sgf/simple.sgf'
|
18
|
+
new = 'sample_sgf/simple_saved.sgf'
|
19
|
+
tree.save :filename => new
|
20
|
+
tree2 = SgfParser::Tree.new :filename => new
|
21
|
+
tree.should == tree2
|
13
22
|
end
|
14
23
|
|
15
|
-
it "should save properly" do
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
24
|
+
it "should save the sample SGF properly" do
|
25
|
+
tree = SgfParser::Tree.new :filename => 'sample_sgf/ff4_ex.sgf'
|
26
|
+
new = 'sample_sgf/ff4_ex_saved.sgf'
|
27
|
+
tree.save :filename => new
|
28
|
+
tree2 = SgfParser::Tree.new :filename => new
|
29
|
+
tree_children = []
|
30
|
+
tree2_children = []
|
31
|
+
tree.each { |node| tree_children << node }
|
32
|
+
tree2.each { |node| tree2_children << node }
|
33
|
+
tree_children.size.should == tree2_children.size
|
34
|
+
tree_children.each_with_index do |node, index|
|
35
|
+
node.properties.should == tree2_children[index].properties
|
36
|
+
end
|
37
|
+
tree_children.should == tree2_children
|
38
|
+
tree2.should == tree
|
20
39
|
end
|
21
40
|
|
22
41
|
end
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: SgfParser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 57
|
5
|
+
prerelease:
|
5
6
|
segments:
|
6
7
|
- 0
|
7
8
|
- 9
|
8
|
-
-
|
9
|
-
version: 0.9.
|
9
|
+
- 1
|
10
|
+
version: 0.9.1
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Aldric Giacomoni
|
@@ -14,23 +15,53 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date:
|
18
|
+
date: 2011-07-29 00:00:00 -04:00
|
18
19
|
default_executable:
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
21
|
-
|
22
|
-
prerelease: false
|
22
|
+
type: :development
|
23
23
|
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
name: jeweler
|
33
|
+
version_requirements: *id001
|
34
|
+
prerelease: false
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
type: :development
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
24
39
|
requirements:
|
25
40
|
- - ">="
|
26
41
|
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
name: rcov
|
47
|
+
version_requirements: *id002
|
48
|
+
prerelease: false
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
type: :development
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 13
|
27
57
|
segments:
|
28
58
|
- 1
|
29
59
|
- 2
|
30
60
|
- 9
|
31
61
|
version: 1.2.9
|
32
|
-
|
33
|
-
version_requirements: *
|
62
|
+
name: rspec
|
63
|
+
version_requirements: *id003
|
64
|
+
prerelease: false
|
34
65
|
description: SGFParser is a library that parses and saves SGF (Smart Game Format) files.
|
35
66
|
email: aldric@trevoke.net
|
36
67
|
executables: []
|
@@ -42,7 +73,10 @@ extra_rdoc_files:
|
|
42
73
|
- README.rdoc
|
43
74
|
files:
|
44
75
|
- .document
|
45
|
-
- .
|
76
|
+
- .rspec
|
77
|
+
- .rvmrc
|
78
|
+
- Gemfile
|
79
|
+
- Gemfile.lock
|
46
80
|
- LICENSE
|
47
81
|
- README.rdoc
|
48
82
|
- Rakefile
|
@@ -53,47 +87,51 @@ files:
|
|
53
87
|
- lib/sgf/parser/tree.rb
|
54
88
|
- lib/sgf/parser/tree_parse.rb
|
55
89
|
- lib/sgf/sgf_indent.rb
|
90
|
+
- lib/sgf/sgfindent.rb
|
56
91
|
- lib/sgf_parser.rb
|
57
92
|
- sample_sgf/ff4_ex.sgf
|
93
|
+
- sample_sgf/ff4_ex_saved.sgf
|
58
94
|
- sample_sgf/redrose-tartrate.sgf
|
95
|
+
- sample_sgf/simple.sgf
|
96
|
+
- sample_sgf/simple_saved.sgf
|
59
97
|
- sample_usage/parsing_files.rb
|
60
98
|
- spec/node_spec.rb
|
61
|
-
- spec/spec.opts
|
62
99
|
- spec/spec_helper.rb
|
100
|
+
- spec/tree_parser_spec.rb
|
63
101
|
- spec/tree_spec.rb
|
64
|
-
- tryme.rb
|
65
102
|
has_rdoc: true
|
66
103
|
homepage: http://github.com/Trevoke/SGFParser
|
67
104
|
licenses: []
|
68
105
|
|
69
106
|
post_install_message:
|
70
|
-
rdoc_options:
|
71
|
-
|
107
|
+
rdoc_options: []
|
108
|
+
|
72
109
|
require_paths:
|
73
110
|
- lib
|
74
111
|
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
75
113
|
requirements:
|
76
114
|
- - ">="
|
77
115
|
- !ruby/object:Gem::Version
|
116
|
+
hash: 3
|
78
117
|
segments:
|
79
118
|
- 0
|
80
119
|
version: "0"
|
81
120
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
82
122
|
requirements:
|
83
123
|
- - ">="
|
84
124
|
- !ruby/object:Gem::Version
|
125
|
+
hash: 3
|
85
126
|
segments:
|
86
127
|
- 0
|
87
128
|
version: "0"
|
88
129
|
requirements: []
|
89
130
|
|
90
131
|
rubyforge_project:
|
91
|
-
rubygems_version: 1.
|
132
|
+
rubygems_version: 1.6.2
|
92
133
|
signing_key:
|
93
134
|
specification_version: 3
|
94
135
|
summary: A library for working with SGF files.
|
95
|
-
test_files:
|
96
|
-
|
97
|
-
- spec/spec_helper.rb
|
98
|
-
- spec/tree_parser_spec.rb
|
99
|
-
- spec/tree_spec.rb
|
136
|
+
test_files: []
|
137
|
+
|
data/.gitignore
DELETED
data/spec/spec.opts
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
--color
|
data/tryme.rb
DELETED