sgf_parser 0.1.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/.irbrc +3 -0
- data/.rvmrc +4 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +17 -0
- data/LICENSE +20 -0
- data/README.textile +27 -0
- data/Rakefile +20 -0
- data/TODO +0 -0
- data/VERSION +1 -0
- data/bin/sgf +28 -0
- data/bin/stm2dot +18 -0
- data/doc/sgf_state_machine.dot +58 -0
- data/doc/sgf_state_machine.svg +269 -0
- data/lib/sgf.rb +15 -0
- data/lib/sgf/binary_file_error.rb +4 -0
- data/lib/sgf/debugger.rb +15 -0
- data/lib/sgf/default_event_listener.rb +37 -0
- data/lib/sgf/model/constants.rb +19 -0
- data/lib/sgf/model/event_listener.rb +72 -0
- data/lib/sgf/model/game.rb +52 -0
- data/lib/sgf/model/label.rb +12 -0
- data/lib/sgf/model/node.rb +115 -0
- data/lib/sgf/model/property_handler.rb +51 -0
- data/lib/sgf/more/state_machine_presenter.rb +43 -0
- data/lib/sgf/more/stm_dot_converter.rb +139 -0
- data/lib/sgf/parse_error.rb +25 -0
- data/lib/sgf/parser.rb +56 -0
- data/lib/sgf/renderer.rb +25 -0
- data/lib/sgf/sgf_helper.rb +55 -0
- data/lib/sgf/sgf_state_machine.rb +174 -0
- data/lib/sgf/state_machine.rb +76 -0
- data/sgf_parser.gemspec +95 -0
- data/spec/fixtures/2009-11-01-1.sgf +24 -0
- data/spec/fixtures/2009-11-01-2.sgf +23 -0
- data/spec/fixtures/chinese_gb.sgf +9 -0
- data/spec/fixtures/chinese_utf.sgf +9 -0
- data/spec/fixtures/example.sgf +18 -0
- data/spec/fixtures/good.sgf +55 -0
- data/spec/fixtures/good1.sgf +167 -0
- data/spec/fixtures/kgs.sgf +723 -0
- data/spec/fixtures/test.png +0 -0
- data/spec/sgf/model/event_listener_spec.rb +97 -0
- data/spec/sgf/model/game_spec.rb +29 -0
- data/spec/sgf/model/node_spec.rb +84 -0
- data/spec/sgf/more/state_machine_presenter_spec.rb +29 -0
- data/spec/sgf/parse_error_spec.rb +10 -0
- data/spec/sgf/parser_spec.rb +210 -0
- data/spec/sgf/sgf_helper_spec.rb +68 -0
- data/spec/sgf/sgf_state_machine_spec.rb +166 -0
- data/spec/sgf/state_machine_spec.rb +137 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +47 -0
- metadata +150 -0
data/lib/sgf.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/sgf/debugger')
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/sgf/state_machine')
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '/sgf/sgf_state_machine')
|
4
|
+
require File.expand_path(File.dirname(__FILE__) + '/sgf/default_event_listener')
|
5
|
+
require File.expand_path(File.dirname(__FILE__) + '/sgf/parser')
|
6
|
+
require File.expand_path(File.dirname(__FILE__) + '/sgf/parse_error')
|
7
|
+
require File.expand_path(File.dirname(__FILE__) + '/sgf/binary_file_error')
|
8
|
+
require File.expand_path(File.dirname(__FILE__) + '/sgf/sgf_helper')
|
9
|
+
|
10
|
+
require File.expand_path(File.dirname(__FILE__) + '/sgf/model/constants')
|
11
|
+
require File.expand_path(File.dirname(__FILE__) + '/sgf/model/property_handler')
|
12
|
+
require File.expand_path(File.dirname(__FILE__) + '/sgf/model/event_listener')
|
13
|
+
require File.expand_path(File.dirname(__FILE__) + '/sgf/model/game')
|
14
|
+
require File.expand_path(File.dirname(__FILE__) + '/sgf/model/node')
|
15
|
+
require File.expand_path(File.dirname(__FILE__) + '/sgf/model/label')
|
data/lib/sgf/debugger.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module SGF
|
2
|
+
class DefaultEventListener
|
3
|
+
include Debugger
|
4
|
+
|
5
|
+
def initialize debug_mode = false
|
6
|
+
enable_debug_mode if debug_mode
|
7
|
+
end
|
8
|
+
|
9
|
+
def start_game
|
10
|
+
debug 'start_game'
|
11
|
+
end
|
12
|
+
|
13
|
+
def start_node
|
14
|
+
debug 'start_node'
|
15
|
+
end
|
16
|
+
|
17
|
+
def property_name= name
|
18
|
+
debug "property_name = '#{name}'"
|
19
|
+
end
|
20
|
+
|
21
|
+
def property_value= value
|
22
|
+
debug "property_value = '#{value}'"
|
23
|
+
end
|
24
|
+
|
25
|
+
def start_variation
|
26
|
+
debug "start_variation"
|
27
|
+
end
|
28
|
+
|
29
|
+
def end_variation
|
30
|
+
debug "end_variation"
|
31
|
+
end
|
32
|
+
|
33
|
+
def end_game
|
34
|
+
debug "end_game"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SGF
|
2
|
+
module Model
|
3
|
+
module Constants
|
4
|
+
WEIQI = 1
|
5
|
+
|
6
|
+
DEFAULT_BOARD_SIZE = 19
|
7
|
+
DEFAULT_KOMI = 7.5
|
8
|
+
|
9
|
+
BLACK = 1
|
10
|
+
WHITE = 2
|
11
|
+
|
12
|
+
NODE_SETUP = 0
|
13
|
+
NODE_MOVE = 1
|
14
|
+
NODE_PASS = 2
|
15
|
+
|
16
|
+
POSITIONS = "abcdefghijklmnopqrs"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module SGF
|
2
|
+
module Model
|
3
|
+
class EventListener < SGF::DefaultEventListener
|
4
|
+
include SGF::SGFHelper
|
5
|
+
|
6
|
+
attr_reader :game, :node
|
7
|
+
|
8
|
+
def initialize debug_mode = false
|
9
|
+
super(debug_mode)
|
10
|
+
end
|
11
|
+
|
12
|
+
def start_game
|
13
|
+
super
|
14
|
+
|
15
|
+
@game = Game.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def start_variation
|
19
|
+
super
|
20
|
+
|
21
|
+
@node = Node.new(@node)
|
22
|
+
@node.variation_root = true
|
23
|
+
end
|
24
|
+
|
25
|
+
def end_variation
|
26
|
+
super
|
27
|
+
|
28
|
+
@node = find_variation_root(@node).parent
|
29
|
+
end
|
30
|
+
|
31
|
+
def start_node
|
32
|
+
super
|
33
|
+
|
34
|
+
@node = @node.nil? ? game.root_node : Node.new(@node)
|
35
|
+
end
|
36
|
+
|
37
|
+
def property_name= name
|
38
|
+
super name
|
39
|
+
|
40
|
+
@property_name = name
|
41
|
+
end
|
42
|
+
|
43
|
+
def property_value= value
|
44
|
+
super value
|
45
|
+
|
46
|
+
set_property @property_name, value
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def find_variation_root node
|
52
|
+
while not node.variation_root?
|
53
|
+
return node if node.parent.nil?
|
54
|
+
|
55
|
+
node = node.parent
|
56
|
+
end
|
57
|
+
node
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_property name, value
|
61
|
+
return unless name
|
62
|
+
name = name.strip.upcase
|
63
|
+
value.strip! unless value.nil?
|
64
|
+
|
65
|
+
return if GAME_PROPERTY_HANDLER.handle(game, name, value)
|
66
|
+
return if NODE_PROPERTY_HANDLER.handle(node, name, value)
|
67
|
+
|
68
|
+
puts "WARNING: SGF property is not recognized(name=#{name}, value=#{value})"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module SGF
|
2
|
+
module Model
|
3
|
+
class Game
|
4
|
+
include Constants
|
5
|
+
include SGF::SGFHelper
|
6
|
+
|
7
|
+
attr_accessor :game_type, :name, :rule, :board_size, :handicap, :komi,
|
8
|
+
:black_player, :black_rank, :white_player, :white_rank,
|
9
|
+
:black_team, :white_team,
|
10
|
+
:time_rule, :overtime_rule, :result,
|
11
|
+
:played_on, :program, :place, :event, :round, :source,
|
12
|
+
:annotation, :comment
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@game_type = WEIQI
|
16
|
+
@board_size = DEFAULT_BOARD_SIZE
|
17
|
+
@handicap = 0
|
18
|
+
@komi = DEFAULT_KOMI
|
19
|
+
end
|
20
|
+
|
21
|
+
def misc_properties
|
22
|
+
@misc_properties ||= {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def game_type=(value); @game_type = value.to_i; end
|
26
|
+
def board_size=(value); @board_size = value.to_i; end
|
27
|
+
def handicap=(value); @handicap = value.to_i; end
|
28
|
+
def komi=(value); @komi = value.to_f; end
|
29
|
+
|
30
|
+
def root_node
|
31
|
+
@root_node ||= Node.new(nil)
|
32
|
+
end
|
33
|
+
|
34
|
+
def time_rule
|
35
|
+
@time_rule.to_s + (overtime_rule ? " (#{overtime_rule})" : "")
|
36
|
+
end
|
37
|
+
|
38
|
+
def moves
|
39
|
+
moves = 0
|
40
|
+
|
41
|
+
node = root_node
|
42
|
+
while node
|
43
|
+
moves = node.move_no
|
44
|
+
node = node.children[0]
|
45
|
+
end
|
46
|
+
|
47
|
+
moves
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module SGF
|
2
|
+
module Model
|
3
|
+
class Node
|
4
|
+
include Constants
|
5
|
+
include SGF::SGFHelper
|
6
|
+
|
7
|
+
attr_reader :node_type, :color, :move, :black_moves, :white_moves, :labels
|
8
|
+
attr_reader :parent, :children
|
9
|
+
attr_accessor :comment, :whose_turn
|
10
|
+
|
11
|
+
def initialize parent = nil
|
12
|
+
@parent = parent
|
13
|
+
@node_type = NODE_SETUP
|
14
|
+
@labels = []
|
15
|
+
@whose_turn = BLACK
|
16
|
+
@trunk = true
|
17
|
+
if parent
|
18
|
+
@trunk = parent.trunk?
|
19
|
+
@parent.children << self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def move_no
|
24
|
+
parent_move_no = parent.nil? ? 0 : parent.move_no
|
25
|
+
self.node_type == NODE_MOVE ? parent_move_no + 1 : parent_move_no
|
26
|
+
end
|
27
|
+
|
28
|
+
def misc_properties
|
29
|
+
@misc_properties ||= {}
|
30
|
+
end
|
31
|
+
|
32
|
+
def whose_turn= input
|
33
|
+
@whose_turn = input.to_i
|
34
|
+
end
|
35
|
+
|
36
|
+
def black_moves
|
37
|
+
@black_moves ||= []
|
38
|
+
end
|
39
|
+
|
40
|
+
def white_moves
|
41
|
+
@white_moves ||= []
|
42
|
+
end
|
43
|
+
|
44
|
+
def clear_moves
|
45
|
+
@white_moves ||= []
|
46
|
+
end
|
47
|
+
|
48
|
+
def children
|
49
|
+
@children ||= []
|
50
|
+
end
|
51
|
+
|
52
|
+
def child
|
53
|
+
@children.first
|
54
|
+
end
|
55
|
+
|
56
|
+
def root?
|
57
|
+
@parent.nil?
|
58
|
+
end
|
59
|
+
|
60
|
+
def trunk?
|
61
|
+
@trunk
|
62
|
+
end
|
63
|
+
|
64
|
+
def last?
|
65
|
+
@children.nil? or @children.empty?
|
66
|
+
end
|
67
|
+
|
68
|
+
def variation_root= value
|
69
|
+
@variation_root = value
|
70
|
+
@trunk = false if value
|
71
|
+
end
|
72
|
+
|
73
|
+
def variation_root?
|
74
|
+
@variation_root
|
75
|
+
end
|
76
|
+
|
77
|
+
def sgf_setup_black input
|
78
|
+
to_position_array(input).each {|position| self.black_moves << position}
|
79
|
+
end
|
80
|
+
|
81
|
+
def sgf_setup_white input
|
82
|
+
to_position_array(input).each {|position| self.white_moves << position}
|
83
|
+
end
|
84
|
+
|
85
|
+
def sgf_setup_clear input
|
86
|
+
to_position_array(input).each {|position| self.clear_moves << position}
|
87
|
+
end
|
88
|
+
|
89
|
+
def sgf_play_black input
|
90
|
+
@color = BLACK
|
91
|
+
set_move input
|
92
|
+
end
|
93
|
+
|
94
|
+
def sgf_play_white input
|
95
|
+
@color = WHITE
|
96
|
+
set_move input
|
97
|
+
end
|
98
|
+
|
99
|
+
def sgf_label input
|
100
|
+
@labels << to_label(input)
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def set_move input
|
106
|
+
if input.nil? or input.strip.size == 0
|
107
|
+
@node_type = NODE_PASS
|
108
|
+
else
|
109
|
+
@node_type = NODE_MOVE
|
110
|
+
@move = to_position(input)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module SGF
|
2
|
+
module Model
|
3
|
+
class PropertyHandler
|
4
|
+
def initialize method_mappings, misc_properties
|
5
|
+
@method_mappings = method_mappings
|
6
|
+
@misc_properties = misc_properties
|
7
|
+
end
|
8
|
+
|
9
|
+
def handle model, name, value
|
10
|
+
if @method_mappings.include?(name)
|
11
|
+
model.send(@method_mappings[name], value)
|
12
|
+
|
13
|
+
true
|
14
|
+
elsif @misc_properties.include?(name)
|
15
|
+
if model.misc_properties[name]
|
16
|
+
model.misc_properties[name] = [model.misc_properties[name], value].flatten
|
17
|
+
else
|
18
|
+
model.misc_properties[name] = value
|
19
|
+
end
|
20
|
+
|
21
|
+
true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# See http://www.red-bean.com/sgf/properties.html for property definitions
|
27
|
+
GAME_PROPERTY_MAPPINGS = {
|
28
|
+
'GM' => :game_type=, 'GN' => :name=, 'RU' => :rule=, 'SZ' => :board_size=, 'HA' => :handicap=, 'KM' => :komi=,
|
29
|
+
'PW' => :white_player=, 'BR' => :black_rank=, 'PB' => :black_player=, 'WR' => :white_rank=,
|
30
|
+
'BT' => :black_team=, 'WT' => :white_team=,
|
31
|
+
'DT' => :played_on=, 'TM' => :time_rule=, 'OT' => :overtime_rule=,
|
32
|
+
'SY' => :program=, 'RE' => :result=, 'AP' => :program=, 'PC' => :place=, 'EV' => :event=, 'RO' => :round=,
|
33
|
+
'SO' => :source=, 'AN' => :annotation=, 'GC' => :comment=
|
34
|
+
}
|
35
|
+
|
36
|
+
GAME_MISC_PROPERTIES = %w(FF US CA ST)
|
37
|
+
|
38
|
+
NODE_PROPERTY_MAPPINGS = {
|
39
|
+
"B" => :sgf_play_black, "W" => :sgf_play_white, "C" => :comment=,
|
40
|
+
"AB" => :sgf_setup_black, "AW" => :sgf_setup_white, "AE" => :sgf_setup_clear, "LB" => :sgf_label,
|
41
|
+
"PL" => :whose_turn=
|
42
|
+
}
|
43
|
+
|
44
|
+
NODE_MARK_PROPERTIES = %w(AR CR DD LB LN MA SL SQ TR)
|
45
|
+
NODE_TIME_PROPERTIES = %w(BL WL OB OW TB TW)
|
46
|
+
NODE_MISC_PROPERTIES = %w(N GW GB DM UC TE BM DO IT MN) + NODE_MARK_PROPERTIES + NODE_TIME_PROPERTIES
|
47
|
+
|
48
|
+
GAME_PROPERTY_HANDLER = PropertyHandler.new(GAME_PROPERTY_MAPPINGS, GAME_MISC_PROPERTIES)
|
49
|
+
NODE_PROPERTY_HANDLER = PropertyHandler.new(NODE_PROPERTY_MAPPINGS, NODE_MISC_PROPERTIES)
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# This class is created for illustrative purpose only. It should not be used as
|
2
|
+
# the base class for state machine visualizer
|
3
|
+
|
4
|
+
module SGF
|
5
|
+
module More
|
6
|
+
class StateMachinePresenter
|
7
|
+
def nodes
|
8
|
+
nodes_hash.values
|
9
|
+
end
|
10
|
+
|
11
|
+
def edges
|
12
|
+
@edges ||= []
|
13
|
+
end
|
14
|
+
|
15
|
+
def process state_machine
|
16
|
+
nodes_hash[state_machine.start_state.to_s] = create_node(state_machine.start_state.to_s)
|
17
|
+
state_machine.transitions.each do |from_state, transitions|
|
18
|
+
transitions.each do |transition|
|
19
|
+
from_node = nodes_hash[from_state.to_s] ||= create_node(from_state.to_s)
|
20
|
+
to_state = transition.after_state || from_state
|
21
|
+
to_node = nodes_hash[to_state.to_s] ||= create_node(to_state.to_s)
|
22
|
+
transition_desc = transition.description || transition.event_pattern.inspect
|
23
|
+
edges << create_edge(from_node, to_node, transition_desc)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def nodes_hash
|
31
|
+
@nodes ||= {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_node name
|
35
|
+
name
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_edge from_node, to_node, description
|
39
|
+
[from_node, to_node, description]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|