Class: Board

Inherits:
Object
  • Object
show all
Defined in:
lib/gorb/board.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(black = nil, white = nil, handicap = 0, komi = 6.5, size = "19x19") ⇒ Board

Initialize a new Board instance. Requires two Player objects, a handicap, a komi and a size as arguments. The handicap should be an integer from 0 to 9. Komi can be negative. Size should be either 9x9, 13x13 or 19x19.

Raises:

  • (ArgumentError)


11
12
13
14
15
16
17
18
19
20
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
# File 'lib/gorb/board.rb', line 11

def initialize(black=nil, white=nil, handicap=0, komi=6.5, size="19x19")
  @black = black ||= Player.new("Black")
  @white = white ||= Player.new("White")
  @komi = komi
  @size = size
  @groups = []
  @hashes = []
  @turn = black
  @dead_groups = []

  raise ArgumentError, "Incorrect handicap" if handicap < 0 or handicap > 9
  @handicap = handicap
  @komi = 0.5 if handicap > 0
  @turn = white if handicap > 1

  if size == "9x9"
    @letters = %w{A B C D E F G H J}
    handicap_stones = %w{G7 C3 G3 C7 E5 C5 G5 E7 E3}
  elsif size == "13x13"
    @letters = %w{A B C D E F G H J K L M N}
    handicap_stones = %w{K10 D4 K4 D10 G7 D7 K7 G10 G4}
  elsif size == "19x19"
    @letters = %w{A B C D E F G H J K L M N O P Q R S T}
    handicap_stones = %w{Q16 D4 Q4 D16 K10 D10 Q10 K16 K4}
  else
    raise ArgumentError, "Incorrect board size"
  end

  case @handicap
  when 2..5, 7, 9
    handicap_stones[0..(@handicap-1)].each {|s| self.add_stone(s, :black)}
  when 6, 8
    handicap_stones[0..@handicap].each {|s| self.add_stone(s, :black)}
    self.remove_stone(handicap_stones[4]) # Middle stone
  end
end

Instance Attribute Details

#blackObject (readonly)

Returns the value of attribute black.



6
7
8
# File 'lib/gorb/board.rb', line 6

def black
  @black
end

#dead_groupsObject (readonly)

Returns the value of attribute dead_groups.



6
7
8
# File 'lib/gorb/board.rb', line 6

def dead_groups
  @dead_groups
end

#groupsObject

Returns the value of attribute groups.



5
6
7
# File 'lib/gorb/board.rb', line 5

def groups
  @groups
end

#handicapObject (readonly)

Returns the value of attribute handicap.



6
7
8
# File 'lib/gorb/board.rb', line 6

def handicap
  @handicap
end

#komiObject (readonly)

Returns the value of attribute komi.



6
7
8
# File 'lib/gorb/board.rb', line 6

def komi
  @komi
end

#lettersObject (readonly)

Returns the value of attribute letters.



6
7
8
# File 'lib/gorb/board.rb', line 6

def letters
  @letters
end

#sizeObject (readonly)

Returns the value of attribute size.



6
7
8
# File 'lib/gorb/board.rb', line 6

def size
  @size
end

#turnObject

Returns the value of attribute turn.



5
6
7
# File 'lib/gorb/board.rb', line 5

def turn
  @turn
end

#whiteObject (readonly)

Returns the value of attribute white.



6
7
8
# File 'lib/gorb/board.rb', line 6

def white
  @white
end

Instance Method Details

#add_stone(point, color = nil) ⇒ Object

Add a Stone to the board if the move is legal. This function will also keep track of the turn. You can force the color with additional color argument – in this case the turn is not changing.

Raises:

  • (ArgumentError)


51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/gorb/board.rb', line 51

def add_stone(point, color=nil)
  # Guess the color based on turn, unless color was forced.
  unless color
    if @turn == black
      color = :black
    else
      color = :white
    end
    advance = true
  end

  # Check the legality of the move and play it if legal.
  raise ArgumentError, "Illegal move" unless legal?(point, color)
  stone = Stone.new(self, point, color)
  resolve!(stone)

  # If the color was not explicitly set, advance the turn.
  turn_over if advance
  return stone
end

#generate_hashObject

Generate a hash of a board situation. Used to enforce ko rule.



164
165
166
# File 'lib/gorb/board.rb', line 164

def generate_hash
  @groups.flatten.inject([]) {|hash, stone| hash << stone.to_s}.sort.hash
end

#legal?(point, color) ⇒ Boolean

Returns:

  • (Boolean)


79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/gorb/board.rb', line 79

def legal?(point, color)
  # Check if the point if already occupied.
  return false if self.stone_at? point

  # The method for checking legality requires placing a test stone to the
  # point and seeing what happens. This is done by doing a deep copy of the
  # board and playing the move there.
  dummy_board = Marshal.load(Marshal.dump(self))

  # Check for suicide.
  stone = Stone.new(dummy_board, point, color)
  legal = true
  if stone.group.liberties == 0
    # Normally suicide is not ok...
    legal = false
    # ...but killing with 'suicide' (filling dame) is ok.
    opposing = dummy_board.search(stone.neighbors)
    opposing.each do |opp_stone|
      if opp_stone.color != color and opp_stone.group.liberties == 0
        legal = true
      end
    end
  end

  # Check for ko.
  dummy_board.resolve!(stone)
  legal = false if @hashes.include? dummy_board.generate_hash
  return legal
end

#mark_dead_group(stone) ⇒ Object

Mark dead groups after the game has ended to ease the scoring.



179
180
181
182
# File 'lib/gorb/board.rb', line 179

def mark_dead_group(stone)
  group = self.search(stone).first.group
  @dead_groups << group
end

#neighbors(point) ⇒ Object

Return the neighboring points of the point.



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/gorb/board.rb', line 145

def neighbors(point)
  x, y = @letters.index(point[0]), point[1, 2].to_i
  neighbors = []
  unless y == 1
    neighbors << @letters[x] + (y - 1).to_s
  end
  unless y == self.size.split('x')[0].to_i
    neighbors << @letters[x] + (y + 1).to_s
  end
  unless @letters[x] == @letters.first
    neighbors << @letters[x-1] + y.to_s
  end
  unless @letters[x] == @letters.last
    neighbors << @letters[x+1] + y.to_s
  end
  return neighbors
end

#read(diagram) ⇒ Object

Read a board situation from a (possibly incomplete) diagram.



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/gorb/board.rb', line 257

def read(diagram)
  # Try to read captured pieces information from gnugo output.
  black_captured = /Black \(X\) has captured (\d) pieces/.match(diagram)
  white_captured = /White \(O\) has captured (\d) pieces/.match(diagram)

  if black_captured
    self.black.captured += black_captured.captures.first.to_i
  end
  if white_captured
    self.white.captured += white_captured.captures.first.to_i
  end

  diagram.gsub!(/Black.*/, '')
  diagram.gsub!(/White.*/, '')
  diagram.gsub!(/N O P/, '')
  diagram.gsub!(/[A-NP-WYZa-z0-9]/, '')
  diagram.gsub!(/[-| ():]/, '')
  diagram.strip().split("\n").each_with_index do |line, i|
    line.split("").each_with_index do |char, j|
      coords = @letters[j] + (self.size.split('x')[0].to_i-i).to_s
      if char == "X"
        self.add_stone(coords, :black)
      elsif char == "O"
        self.add_stone(coords, :white)
      end
    end
  end
end

#remove_stone(point) ⇒ Object

Raises:

  • (ArgumentError)


72
73
74
75
76
77
# File 'lib/gorb/board.rb', line 72

def remove_stone(point)
  stone = self.search(point).first
  raise ArgumentError, "No such stone" unless stone
  stone.group.delete(stone)
  stone.board.groups.delete(stone.group) if stone.group.size == 0
end

#resolve!(added_stone) ⇒ Object

Recalculate all liberties. Removes dead groups from the table.



110
111
112
113
114
115
116
117
118
119
120
# File 'lib/gorb/board.rb', line 110

def resolve!(added_stone)
  @groups.each do |group|
    if not group.include? added_stone
      libs = group.liberties!
      self.send(added_stone.color).captured += group.size if libs == 0
    end
  end
  # The group of last added stone is checked after others to make kills by
  # 'suicide' (filling dame) work.
  added_stone.group.liberties!
end

#scoringObject

Count the score.



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/gorb/board.rb', line 185

def scoring
  white, black = 0, 0

  # Remove dead groups from board (or its clone).
  score_board = Marshal.load(Marshal.dump(self))
  score_board.dead_groups.each do |group|
    score_board.groups.delete(group)
    if group.first.color == :white
      black += group.size
    elsif group.first.color == :black
      white += group.size
    end
  end

  # Collect all empty points into a list.
  empty_points = []
  side = self.size.split('x')[0].to_i
  for i in (0..side-1)
    for j in (0..side-1)
      coords = @letters[i] + (side - j).to_s
      if not score_board.stone_at?(coords)
        empty_points << coords
      end
    end
  end

  # Flood fill and remove from list of empty points.
  areas = []
  until empty_points.empty?
    current_area = []
    first_point = empty_points.first
    remove_from_empty_points = Proc.new do |point|
      if empty_points.include? point
        current_area << empty_points.delete(point)
        for neighbor in self.neighbors(point)
          remove_from_empty_points.call(neighbor)
        end
      end
    end
    remove_from_empty_points.call(first_point)
    areas << current_area
  end

  # Check bordering stones or groups: if uniform, award points.
  areas.each do |area|
    colors = []
    area.each do |empty_point|
      self.neighbors(empty_point).each do |neighbor|
        stone = score_board.search(neighbor).first
        if stone
          colors << stone.color unless colors.include? stone.color
        end
      end
    end
    if colors == [:white]
      white += area.size
    elsif colors == [:black]
      black += area.size
    end
  end

  # Add captured stones to the total.
  white += score_board.white.captured
  black += score_board.black.captured

  # Add komi.
  white += self.komi

  {:white => white, :black => black}
end

#search(points) ⇒ Object

Search the Board for stones in given points.



123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/gorb/board.rb', line 123

def search(points)
  if points.is_a?(String)
    points = [points]
  end
  stones = []
  @groups.each do |group|
    group.each do |stone|
      stones << stone if points.include? stone.point
    end
  end
  return stones
end

#stone_at?(point) ⇒ Boolean

Returns:

  • (Boolean)


136
137
138
# File 'lib/gorb/board.rb', line 136

def stone_at?(point)
  @groups.any? {|group| group.include? point}
end

#stones_at?(points) ⇒ Boolean

Returns:

  • (Boolean)


140
141
142
# File 'lib/gorb/board.rb', line 140

def stones_at?(points)
  points.all? {|point| self.stone_at? point}
end

#turn_overObject

Pass the turn and generate a hash of the board situation for checking ko.



169
170
171
172
173
174
175
176
# File 'lib/gorb/board.rb', line 169

def turn_over
  if @turn == @black
    @turn = @white
  else
    @turn = @black
  end
  @hashes << generate_hash
end