gorb 0.0.1 → 0.1

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.
@@ -3,10 +3,11 @@ require 'gorb/stone'
3
3
  class Board
4
4
 
5
5
  attr_accessor :groups, :turn
6
- attr_reader :black, :white, :handicap, :komi, :size
6
+ attr_reader :black, :white, :handicap, :komi, :size, :letters, :dead_groups
7
7
 
8
- # Initialize a new Board instance. Requires two Player objects and a
9
- # handicap as arguments. The handicap should be an integer from 0 to 9.
8
+ # Initialize a new Board instance. Requires two Player objects, a handicap,
9
+ # a komi and a size as arguments. The handicap should be an integer from
10
+ # 0 to 9. Komi can be negative. Size should be either 9x9, 13x13 or 19x19.
10
11
  def initialize(black=nil, white=nil, handicap=0, komi=6.5, size="19x19")
11
12
  @black = black ||= Player.new("Black")
12
13
  @white = white ||= Player.new("White")
@@ -15,6 +16,7 @@ class Board
15
16
  @groups = []
16
17
  @hashes = []
17
18
  @turn = black
19
+ @dead_groups = []
18
20
 
19
21
  raise ArgumentError, "Incorrect handicap" if handicap < 0 or handicap > 9
20
22
  @handicap = handicap
@@ -22,10 +24,13 @@ class Board
22
24
  @turn = white if handicap > 1
23
25
 
24
26
  if size == "9x9"
27
+ @letters = %w{A B C D E F G H J}
25
28
  handicap_stones = %w{G7 C3 G3 C7 E5 C5 G5 E7 E3}
26
29
  elsif size == "13x13"
30
+ @letters = %w{A B C D E F G H J K L M N}
27
31
  handicap_stones = %w{K10 D4 K4 D10 G7 D7 K7 G10 G4}
28
32
  elsif size == "19x19"
33
+ @letters = %w{A B C D E F G H J K L M N O P Q R S T}
29
34
  handicap_stones = %w{Q16 D4 Q4 D16 K10 D10 Q10 K16 K4}
30
35
  else
31
36
  raise ArgumentError, "Incorrect board size"
@@ -71,40 +76,6 @@ class Board
71
76
  stone.board.groups.delete(stone.group) if stone.group.size == 0
72
77
  end
73
78
 
74
- # Search the Board for stones in given points.
75
- def search(points)
76
- stones = []
77
- @groups.each do |group|
78
- group.each do |stone|
79
- stones << stone if points.include? stone.point
80
- end
81
- end
82
- return stones
83
- end
84
-
85
- def stone_at?(point)
86
- @groups.any? {|group| group.include? point}
87
- end
88
-
89
- def stones_at?(points)
90
- points.all? {|point| self.stone_at? point}
91
- end
92
-
93
- # Recalculate all liberties. Removes dead groups from the table.
94
- def resolve!(added_stone)
95
- @groups.each do |group|
96
- group.liberties! if not group.include? added_stone
97
- end
98
- # The group of last added stone is checked after others to make kills by
99
- # 'suicide' (filling dame) work.
100
- added_stone.group.liberties!
101
- end
102
-
103
- # Generate a hash of a board situation. Used to enforce ko rule.
104
- def generate_hash
105
- @groups.flatten.inject([]) {|hash, stone| hash << stone.to_s}.sort.hash
106
- end
107
-
108
79
  def legal?(point, color)
109
80
  # Check if the point if already occupied.
110
81
  return false if self.stone_at? point
@@ -135,6 +106,66 @@ class Board
135
106
  return legal
136
107
  end
137
108
 
109
+ # Recalculate all liberties. Removes dead groups from the table.
110
+ def resolve!(added_stone)
111
+ @groups.each do |group|
112
+ if not group.include? added_stone
113
+ libs = group.liberties!
114
+ self.send(added_stone.color).captured += group.size if libs == 0
115
+ end
116
+ end
117
+ # The group of last added stone is checked after others to make kills by
118
+ # 'suicide' (filling dame) work.
119
+ added_stone.group.liberties!
120
+ end
121
+
122
+ # Search the Board for stones in given points.
123
+ def search(points)
124
+ if points.is_a?(String)
125
+ points = [points]
126
+ end
127
+ stones = []
128
+ @groups.each do |group|
129
+ group.each do |stone|
130
+ stones << stone if points.include? stone.point
131
+ end
132
+ end
133
+ return stones
134
+ end
135
+
136
+ def stone_at?(point)
137
+ @groups.any? {|group| group.include? point}
138
+ end
139
+
140
+ def stones_at?(points)
141
+ points.all? {|point| self.stone_at? point}
142
+ end
143
+
144
+ # Return the neighboring points of the point.
145
+ def neighbors(point)
146
+ x, y = @letters.index(point[0]), point[1, 2].to_i
147
+ neighbors = []
148
+ unless y == 1
149
+ neighbors << @letters[x] + (y - 1).to_s
150
+ end
151
+ unless y == self.size.split('x')[0].to_i
152
+ neighbors << @letters[x] + (y + 1).to_s
153
+ end
154
+ unless @letters[x] == @letters.first
155
+ neighbors << @letters[x-1] + y.to_s
156
+ end
157
+ unless @letters[x] == @letters.last
158
+ neighbors << @letters[x+1] + y.to_s
159
+ end
160
+ return neighbors
161
+ end
162
+
163
+ # Generate a hash of a board situation. Used to enforce ko rule.
164
+ def generate_hash
165
+ @groups.flatten.inject([]) {|hash, stone| hash << stone.to_s}.sort.hash
166
+ end
167
+
168
+ # Pass the turn and generate a hash of the board situation for checking ko.
138
169
  def turn_over
139
170
  if @turn == @black
140
171
  @turn = @white
@@ -144,4 +175,112 @@ class Board
144
175
  @hashes << generate_hash
145
176
  end
146
177
 
178
+ # Mark dead groups after the game has ended to ease the scoring.
179
+ def mark_dead_group(stone)
180
+ group = self.search(stone).first.group
181
+ @dead_groups << group
182
+ end
183
+
184
+ # Count the score.
185
+ def scoring
186
+ white, black = 0, 0
187
+
188
+ # Remove dead groups from board (or its clone).
189
+ score_board = Marshal.load(Marshal.dump(self))
190
+ score_board.dead_groups.each do |group|
191
+ score_board.groups.delete(group)
192
+ if group.first.color == :white
193
+ black += group.size
194
+ elsif group.first.color == :black
195
+ white += group.size
196
+ end
197
+ end
198
+
199
+ # Collect all empty points into a list.
200
+ empty_points = []
201
+ side = self.size.split('x')[0].to_i
202
+ for i in (0..side-1)
203
+ for j in (0..side-1)
204
+ coords = @letters[i] + (side - j).to_s
205
+ if not score_board.stone_at?(coords)
206
+ empty_points << coords
207
+ end
208
+ end
209
+ end
210
+
211
+ # Flood fill and remove from list of empty points.
212
+ areas = []
213
+ until empty_points.empty?
214
+ current_area = []
215
+ first_point = empty_points.first
216
+ remove_from_empty_points = Proc.new do |point|
217
+ if empty_points.include? point
218
+ current_area << empty_points.delete(point)
219
+ for neighbor in self.neighbors(point)
220
+ remove_from_empty_points.call(neighbor)
221
+ end
222
+ end
223
+ end
224
+ remove_from_empty_points.call(first_point)
225
+ areas << current_area
226
+ end
227
+
228
+ # Check bordering stones or groups: if uniform, award points.
229
+ areas.each do |area|
230
+ colors = []
231
+ area.each do |empty_point|
232
+ self.neighbors(empty_point).each do |neighbor|
233
+ stone = score_board.search(neighbor).first
234
+ if stone
235
+ colors << stone.color unless colors.include? stone.color
236
+ end
237
+ end
238
+ end
239
+ if colors == [:white]
240
+ white += area.size
241
+ elsif colors == [:black]
242
+ black += area.size
243
+ end
244
+ end
245
+
246
+ # Add captured stones to the total.
247
+ white += score_board.white.captured
248
+ black += score_board.black.captured
249
+
250
+ # Add komi.
251
+ white += self.komi
252
+
253
+ {:white => white, :black => black}
254
+ end
255
+
256
+ # Read a board situation from a (possibly incomplete) diagram.
257
+ def read(diagram)
258
+ # Try to read captured pieces information from gnugo output.
259
+ black_captured = /Black \(X\) has captured (\d) pieces/.match(diagram)
260
+ white_captured = /White \(O\) has captured (\d) pieces/.match(diagram)
261
+
262
+ if black_captured
263
+ self.black.captured += black_captured.captures.first.to_i
264
+ end
265
+ if white_captured
266
+ self.white.captured += white_captured.captures.first.to_i
267
+ end
268
+
269
+ diagram.gsub!(/Black.*/, '')
270
+ diagram.gsub!(/White.*/, '')
271
+ diagram.gsub!(/N O P/, '')
272
+ diagram.gsub!(/[A-NP-WYZa-z0-9]/, '')
273
+ diagram.gsub!(/[-| ():]/, '')
274
+ diagram.strip().split("\n").each_with_index do |line, i|
275
+ line.split("").each_with_index do |char, j|
276
+ coords = @letters[j] + (self.size.split('x')[0].to_i-i).to_s
277
+ if char == "X"
278
+ self.add_stone(coords, :black)
279
+ elsif char == "O"
280
+ self.add_stone(coords, :white)
281
+ end
282
+ end
283
+ end
284
+ end
285
+
147
286
  end
@@ -11,6 +11,10 @@ class Group < Array
11
11
  group.each {|stone| self << stone}
12
12
  @board.groups.delete group
13
13
  end
14
+ # Group references need to be updated for each stone after a merge.
15
+ self.each do |stone|
16
+ stone.group = self
17
+ end
14
18
  end
15
19
 
16
20
  def include?(point)
@@ -1,8 +1,12 @@
1
1
  class Player
2
2
 
3
+ attr_accessor :captured
4
+ attr_reader :name, :rank
5
+
3
6
  def initialize(name="Anonymous", rank=nil)
4
7
  @name = name
5
8
  @rank = rank
9
+ @captured = 0
6
10
  end
7
11
 
8
12
  def to_str
@@ -10,16 +10,8 @@ class Stone
10
10
  @point = point
11
11
  @color = color
12
12
 
13
- if @board.size == "9x9"
14
- @letters = %w{A B C D E F G H J}
15
- elsif @board.size == "13x13"
16
- @letters = %w{A B C D E F G H J K L M N}
17
- elsif @board.size == "19x19"
18
- @letters = %w{A B C D E F G H J K L M N O P Q R S T}
19
- end
20
-
21
13
  if (@point[1, 2].to_i > @board.size.split('x')[0].to_i or
22
- not @letters.index(@point[0]))
14
+ not @board.letters.index(@point[0]))
23
15
  raise ArgumentError, "Invalid point"
24
16
  end
25
17
 
@@ -28,21 +20,7 @@ class Stone
28
20
 
29
21
  # Return the neighboring points of the Stone.
30
22
  def neighbors
31
- x, y = @letters.index(@point[0]), @point[1, 2].to_i
32
- neighbors = []
33
- unless y == 1
34
- neighbors << @letters[x] + (y - 1).to_s
35
- end
36
- unless y == @board.size.split('x')[0].to_i
37
- neighbors << @letters[x] + (y + 1).to_s
38
- end
39
- unless @letters[x] == @letters.first
40
- neighbors << @letters[x-1] + y.to_s
41
- end
42
- unless @letters[x] == @letters.last
43
- neighbors << @letters[x+1] + y.to_s
44
- end
45
- return neighbors
23
+ @board.neighbors(@point)
46
24
  end
47
25
 
48
26
  # Return the liberties of the Stone.
metadata CHANGED
@@ -4,9 +4,8 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 0
8
7
  - 1
9
- version: 0.0.1
8
+ version: "0.1"
10
9
  platform: ruby
11
10
  authors:
12
11
  - Aku Kotkavuo
@@ -14,7 +13,7 @@ autorequire:
14
13
  bindir: bin
15
14
  cert_chain: []
16
15
 
17
- date: 2010-09-06 00:00:00 +03:00
16
+ date: 2010-09-13 00:00:00 +03:00
18
17
  default_executable:
19
18
  dependencies: []
20
19