SgfParser 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,25 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+
23
+
24
+ ## Rubymine
25
+ .idea/*
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Aldric Giacomoni
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,28 @@
1
+ SGF: all formats (but untested with FF < 4)
2
+ Ruby: >1.8.7 (may work with 1.8.6)
3
+
4
+ Example:
5
+ require 'sgf_parser'
6
+ tree = SgfParser::Tree.new :filename => File
7
+ tree = SgfParser::Tree.new :sgf_string => String
8
+
9
+ All trees begin with an empty node ( @root) which allows a simple support of multiple gametrees.
10
+ Most games will just care about, say,
11
+ tree.root.children[0] which is the first node of the first gametree.
12
+
13
+ For any node, one can summon the properties as such:
14
+ node.properties # => returns a hash of the properties.
15
+ A single property can be called, like the comments, for instance, like so:
16
+ node.C # => returns the comments for this node.
17
+
18
+ The library currently uses method_missing to painlessly return the data. I must admit that this is both clever coding and laziness on my part.
19
+
20
+ The 'SGF Indenter', the purpose of which is to make the actual SGF file more
21
+ human readable, is working.
22
+
23
+ ___
24
+
25
+ TODO
26
+ ? Create a "Game" class, and if a whole set of () exists, then I have a game?
27
+ That way maybe I can easily go to multiple games stored in a single SGF file?
28
+ Mostly syntactic sugar, but may be worth implementing.
@@ -0,0 +1,59 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "SgfParser"
8
+ gem.summary = %Q{A library for working with SGF files.}
9
+ gem.description = %Q{SGFParser is a library that parses and saves SGF (Smart Game Format) files.}
10
+ gem.email = "aldric@trevoke.net"
11
+ gem.homepage = "http://github.com/Trevoke/SGFParser"
12
+ gem.authors = ["Aldric Giacomoni"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'spec/rake/spectask'
22
+ Spec::Rake::SpecTask.new(:spec) do |spec|
23
+ spec.libs << 'lib' << 'spec'
24
+ spec.spec_files = FileList['spec/**/*_spec.rb']
25
+ end
26
+
27
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.pattern = 'spec/**/*_spec.rb'
30
+ spec.rcov = true
31
+ end
32
+
33
+ task :spec => :check_dependencies
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|
53
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
54
+
55
+ rdoc.rdoc_dir = 'rdoc'
56
+ rdoc.title = "SgfParser #{version}"
57
+ rdoc.rdoc_files.include('README*')
58
+ rdoc.rdoc_files.include('lib/**/*.rb')
59
+ end
@@ -0,0 +1,66 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{SgfParser}
8
+ s.version = "0.8.0"
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{2010-01-05}
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
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "SgfParser.gemspec",
26
+ "VERSION",
27
+ "lib/sgf/parser/node.rb",
28
+ "lib/sgf/parser/properties.rb",
29
+ "lib/sgf/parser/tree.rb",
30
+ "lib/sgf/parser/tree_parse.rb",
31
+ "lib/sgf/sgfindent.rb",
32
+ "lib/sgf_parser.rb",
33
+ "sample_sgf/ff4_ex.sgf",
34
+ "sample_sgf/ff4_ex_saved.sgf",
35
+ "sample_sgf/redrose-tartrate.sgf",
36
+ "sample_usage/parsing_files.rb",
37
+ "spec/node_spec.rb",
38
+ "spec/spec.opts",
39
+ "spec/spec_helper.rb",
40
+ "spec/tree_spec.rb"
41
+ ]
42
+ s.homepage = %q{http://github.com/Trevoke/SGFParser}
43
+ s.rdoc_options = ["--charset=UTF-8"]
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = %q{1.3.5}
46
+ s.summary = %q{A library for working with SGF files.}
47
+ s.test_files = [
48
+ "spec/node_spec.rb",
49
+ "spec/spec_helper.rb",
50
+ "spec/tree_spec.rb"
51
+ ]
52
+
53
+ if s.respond_to? :specification_version then
54
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
55
+ s.specification_version = 3
56
+
57
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
58
+ s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
59
+ else
60
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
61
+ end
62
+ else
63
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
64
+ end
65
+ end
66
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.8.0
@@ -0,0 +1,67 @@
1
+ module SgfParser
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
+ class Node
7
+
8
+ attr_accessor :parent, :children, :properties
9
+
10
+ # Creates a new node. Options which can be passed in are:
11
+ # :parent => parent_node (nil by default)
12
+ # : children => [list, of, children] (empty array by default)
13
+ # :properties => {hash_of => properties} (empty hash by default)
14
+ def initialize args={}
15
+ @parent = args[:parent]
16
+ @children = []
17
+ add_children args[:children] if !args[:children].nil?
18
+ @properties = Hash.new
19
+ @properties.merge! args[:properties] if !args[:properties].nil?
20
+ end
21
+
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
+ def add_children *nodes
26
+ raise "Non-node child given!" if nodes.find { |node| node.class != Node }
27
+ @children.concat nodes.flatten
28
+ end
29
+
30
+ # Adds one or more properties to the node.
31
+ # Argument: a hash {'property' => 'value'}
32
+ def add_properties hash
33
+ hash.each do |key, value|
34
+ @properties[key] ||= ""
35
+ @properties[key].concat value
36
+ end
37
+ end
38
+
39
+ # Iterates over each child of the given node
40
+ # node.each_child { |child| puts child.properties }
41
+ def each_child
42
+ @children.each { |child| yield child }
43
+ end
44
+
45
+ # Compares one node's properties to another node's properties
46
+ def == other_node
47
+ @properties == other_node.properties
48
+ end
49
+
50
+ # Making comments easier to access.
51
+ def comments
52
+ @properties["C"]
53
+ end
54
+
55
+ alias :comment :comments
56
+
57
+ private
58
+
59
+ def method_missing method_name, *args
60
+ output = @properties[method_name.to_s.upcase]
61
+ super(method_name, args) if output.nil?
62
+ output
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,97 @@
1
+ # A parser for SGF Files. Main usage: SGF::Tree.new :filename => file_name
2
+ module SgfParser
3
+
4
+ # http://www.red-bean.com/sgf/proplist.html
5
+
6
+ # Here we define SGF::Properties, so we can figure out what each property
7
+ # is and does.
8
+
9
+ property_string = %Q{AB Add Black setup list of stone
10
+ AE Add Empty setup list of point
11
+ AN Annotation game-info simpletext
12
+ AP Application root composed simpletext ':' simpletext
13
+ AR Arrow - list of composed point ':' point
14
+ AS Who adds stones - (LOA) simpletext
15
+ AW Add White setup list of stone
16
+ B Black move move
17
+ BL Black time left move real
18
+ BM Bad move move double
19
+ BR Black rank game-info simpletext
20
+ BT Black team game-info simpletext
21
+ C Comment - text
22
+ CA Charset root simpletext
23
+ CP Copyright game-info simpletext
24
+ CR Circle - list of point
25
+ DD Dim points - (inherit) elist of point
26
+ DM Even position - double
27
+ DO Doubtful move none
28
+ DT Date game-info simpletext
29
+ EV Event game-info simpletext
30
+ FF Fileformat root number (range: 1-4)
31
+ FG Figure - none | composed number ":" simpletext
32
+ GB Good for Black - double
33
+ GC Game comment game-info text
34
+ GM Game root number (range: 1-5,7-16)
35
+ GN Game name game-info simpletext
36
+ GW Good for White - double
37
+ HA Handicap game-info (Go) number
38
+ HO Hotspot - double
39
+ IP Initial pos. game-info (LOA) simpletext
40
+ IT Interesting move none
41
+ IY Invert Y-axis game-info (LOA) simpletext
42
+ KM Komi game-info (Go) real
43
+ KO Ko move none
44
+ LB Label - list of composed point ':' simpletext
45
+ LN Line - list of composed point ':' point
46
+ MA Mark - list of point
47
+ MN set move number move number
48
+ N Nodename - simpletext
49
+ OB OtStones Black move number
50
+ ON Opening game-info simpletext
51
+ OT Overtime game-info simpletext
52
+ OW OtStones White move number
53
+ PB Player Black game-info simpletext
54
+ PC Place game-info simpletext
55
+ PL Player to play setup color
56
+ PM Print move mode - (inherit) number
57
+ PW Player White game-info simpletext
58
+ RE Result game-info simpletext
59
+ RO Round game-info simpletext
60
+ RU Rules game-info simpletext
61
+ SE Markup - (LOA) point
62
+ SL Selected - list of point
63
+ SO Source game-info simpletext
64
+ SQ Square - list of point
65
+ ST Style root number (range: 0-3)
66
+ SU Setup type game-info (LOA) simpletext
67
+ SZ Size root (number | composed number ':' number)
68
+ TB Territory Black - (Go) elist of point
69
+ TE Tesuji move double
70
+ TM Timelimit game-info real
71
+ TR Triangle - list of point
72
+ TW Territory White - (Go) elist of point
73
+ UC Unclear pos - double
74
+ US User game-info simpletext
75
+ V Value - real
76
+ VW View - (inherit) elist of point
77
+ W White move move
78
+ WL White time left move real
79
+ WR White rank game-info simpletext
80
+ WT White team game-info simpletext }
81
+
82
+ property_array = property_string.split("\n")
83
+ hash = {}
84
+ property_array.each do |set|
85
+ temp = set.gsub("\t", " ")
86
+ id = temp[0..3].strip
87
+ desc = temp[4..19].strip
88
+ property_type = temp[20..35].strip
89
+ property_value = temp[37..-1].strip
90
+ hash[id] = [desc, property_type, property_value]
91
+ end
92
+
93
+ # All this work for this minuscule line!
94
+ PROPERTIES = hash
95
+
96
+ end
97
+
@@ -0,0 +1,124 @@
1
+ module SgfParser
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
+ class Tree
7
+ include Enumerable
8
+
9
+ attr_accessor :root
10
+
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
+ # :sgf_string => string \n
17
+ def initialize args={}
18
+ @root = Node.new
19
+ @sgf = ""
20
+ raise ArgumentError, "Both file and string provided" if args[:filename] &&
21
+ args[:sgf_string]
22
+ if !args[:filename].nil?
23
+ load_file args[:filename]
24
+ elsif !args[:sgf_string].nil?
25
+ load_string args[:sgf_string]
26
+ end
27
+
28
+ end # initialize
29
+
30
+
31
+ # Iterates over the tree, node by node, in preorder fashion.
32
+ # Does not support other types of iteration, but may in the future.
33
+ # tree.each { |node| puts "I am node. Hear me #{node.properties} !"}
34
+ def each order=:preorder, &block
35
+ case order
36
+ when :preorder
37
+ preorder @root, &block
38
+ end
39
+ end # each
40
+
41
+ # Compares a tree to another tree, node by node.
42
+ # Nodes must by the same (same properties, parents and children).
43
+ def == other_tree
44
+ one = []
45
+ two = []
46
+ each { |node| one << node }
47
+ other_tree.each { |node| two << node }
48
+ one == two
49
+ end # ==
50
+
51
+ # Saves the tree as an SGF file. raises an error if a filename is not given.
52
+ # tree.save :filename => file_name
53
+ def save args={}
54
+ raise ArgumentError, "No file name provided" if args[:filename].nil?
55
+ # SGF files are trees stored in pre-order traversal.
56
+ @sgf_string = "("
57
+ @root.children.each { |child| write_node child }
58
+ # write_node @root
59
+ @sgf_string << ")"
60
+
61
+ File.open(args[:filename], 'w') { |f| f << @sgf_string }
62
+ end #save
63
+
64
+ private
65
+
66
+ # Adds a stringified node to the variable @sgf_string - for saving purposes.
67
+ def write_node node=@root
68
+ @sgf_string << ";"
69
+ unless node.properties.empty?
70
+ properties = ""
71
+ node.properties.each do |k, v|
72
+ v_escaped = v.gsub("]", "\\]")
73
+ properties += "#{k.to_s}[#{v_escaped}]"
74
+ end
75
+ @sgf_string << "#{properties}"
76
+ end
77
+
78
+ case node.children.size
79
+ when 0
80
+ @sgf_string << ")"
81
+ when 1
82
+ write_node node.children[0]
83
+ else
84
+ node.each_child do |child|
85
+ @sgf_string << "("
86
+ write_node child
87
+ end
88
+ end
89
+ end
90
+
91
+ # Used to load and parse a string if a string was given.
92
+ def load_string string
93
+ @sgf = string
94
+ parse unless @sgf.empty?
95
+ end # load_string
96
+
97
+ # Used to load and parse a file if a file was given.
98
+ def load_file filename
99
+ @sgf = ""
100
+ File.open(filename, 'r') { |f| @sgf = f.read }
101
+ parse unless @sgf.empty?
102
+ end # load_file
103
+
104
+ # Traverse the tree in preorder fashion, starting with the @root node if
105
+ # no node is given, and activating the passed block on each.
106
+ def preorder node=@root, &block
107
+ # stop processing if the block returns false
108
+ if yield node then
109
+ node.each_child do |child|
110
+ preorder(child, &block)
111
+ end
112
+ end
113
+ end # preorder
114
+
115
+ def method_missing method_name, *args
116
+ output = @root.children[0].properties[method_name]
117
+ super(method_name, args) if output.nil?
118
+ output
119
+ end # method_missing
120
+
121
+ end
122
+
123
+ end
124
+