ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Games-Go-SimpleBoard/SimpleBoard.pm
Revision: 1.15
Committed: Thu Jul 24 09:14:12 2008 UTC (15 years, 9 months ago) by elmex
Branch: MAIN
CVS Tags: rel-1_0
Changes since 1.14: +6 -0 lines
Log Message:
remark current move and count suicides as captures

File Contents

# Content
1 package Games::Go::SimpleBoard;
2
3 =head1 NAME
4
5 Games::Go::SimpleBoard - represent a simple go board
6
7 =head1 SYNOPSIS
8
9 use Games::Go::SimpleBoard;
10
11 =head1 DESCRIPTION
12
13 Please supply a description )
14
15 =head2 EXPORTED CONSTANTS
16
17 Marker types for each board position (ORed together):
18
19 MARK_TRIANGLE # triangle mark
20 MARK_SQUARE # square mark
21 MARK_CIRCLE # circle mark
22 MARK_CROSS # cross mark
23 MARK_SMALL_B # small stone, used for scoring or marking
24 MARK_SMALL_W # small stone, used for scoring or marking
25 MARK_B # normal black stone
26 MARK_W # normal whit stone
27 MARK_GRAYED # in conjunction with MARK_[BW], grays the stone
28 MARK_LABEL # a text label
29 MARK_HOSHI # this is a hoshi point (not used much)
30 MARK_MOVE # this is a regular move
31 MARK_KO # this is a ko position
32 MARK_REDRAW # ignored, can be used for your own purposes
33
34 COLOUR_WHITE # guarenteed to be 0
35 COLOUR_BLACK # guarenteed to be 1
36
37 MOVE_HANDICAP # used as "x-coordinate" for handicap moves
38 MOVE_PASS # can be used as "x-coordinate" for pass moves
39
40 =head2 METHODS
41
42 =over 4
43
44 =cut
45
46 no warnings;
47 use strict;
48
49 use Carp ();
50
51 use base Exporter::;
52
53 our $VERSION = '1.0';
54
55 our @EXPORT = qw(
56 MARK_TRIANGLE MARK_SQUARE MARK_CIRCLE MARK_SMALL_B MARK_SMALL_W MARK_B
57 MARK_W MARK_GRAYED MARK_MOVE MARK_LABEL MARK_HOSHI MARK_KO MARK_CROSS
58 MARK_REDRAW
59 COLOUR_BLACK COLOUR_WHITE
60 MOVE_HANDICAP MOVE_PASS
61 );
62
63 # marker types for each board position (ORed together)
64
65 sub MARK_TRIANGLE (){ 0x0001 }
66 sub MARK_SQUARE (){ 0x0002 }
67 sub MARK_CIRCLE (){ 0x0004 }
68 sub MARK_SMALL_B (){ 0x0008 } # small stone, used for scoring or marking
69 sub MARK_SMALL_W (){ 0x0010 } # small stone, used for scoring or marking
70 sub MARK_B (){ 0x0020 } # normal black stone
71 sub MARK_W (){ 0x0040 } # normal whit stone
72 sub MARK_GRAYED (){ 0x0080 } # in conjunction with MARK_[BW], grays the stone
73 sub MARK_LABEL (){ 0x0100 }
74 sub MARK_HOSHI (){ 0x0200 } # this is a hoshi point (not used much)
75 sub MARK_MOVE (){ 0x0400 } # this is a regular move
76 sub MARK_KO (){ 0x0800 } # this is a ko position
77 sub MARK_CROSS (){ 0x1000 }
78 sub MARK_REDRAW (){ 0x8000 }
79
80 sub COLOUR_WHITE (){ 0 }
81 sub COLOUR_BLACK (){ 1 }
82
83 sub MOVE_PASS (){ undef }
84 sub MOVE_HANDICAP (){ -2 }
85
86 =item my $board = new $size
87
88 Creates a new empty board of the given size.
89
90 C<< $board->{size} >> stores the board size.
91
92 C<< $board->{max} >> stores the maximum board coordinate (size-1).
93
94 C<< $board->{captures}[COLOUR_xxx] >> stores the number of captured stones for
95 the given colour.
96
97 C<< $board->{board} >> stores a two-dimensional array with board contents.
98
99 =cut
100
101 sub new {
102 my $class = shift;
103 my $size = shift;
104
105 unless ($size > 0) {
106 Carp::croak ("no board size given!");
107 }
108
109 bless {
110 max => $size - 1,
111 size => $size,
112 board => [map [(0) x $size], 1 .. $size],
113 captures => [0, 0], # captures
114 #timer => [],
115 #score => [],
116 @_,
117 }, $class
118 }
119
120 # inefficient and primitive, I hear you say?
121 # well... you are right :)
122 # use an extremely dumb floodfill algorithm to get rid of captured stones
123 sub capture {
124 my ($self, $mark, $x, $y) = @_;
125
126 my %seen;
127 my @found;
128 my @nodes = ([$x,$y]);
129 my $board = $self->{board};
130
131 my $max = $self->{max};
132
133 while (@nodes) {
134 my ($x, $y) = @{pop @nodes};
135
136 unless ($seen{$x,$y}++) {
137 if ($board->[$x][$y] & $mark) {
138 push @found, [$x, $y];
139
140 push @nodes, [$x-1, $y] unless $seen{$x-1, $y} || $x <= 0;
141 push @nodes, [$x+1, $y] unless $seen{$x+1, $y} || $x >= $max;
142 push @nodes, [$x, $y-1] unless $seen{$x, $y-1} || $y <= 0;
143 push @nodes, [$x, $y+1] unless $seen{$x, $y+1} || $y >= $max;
144 } elsif (!($board->[$x][$y] & (MARK_B | MARK_W))) {
145 return;
146 }
147 }
148 }
149
150 @found
151 }
152
153 =item $hint = $board->update ([update-structures...])
154
155 Each update-structure itself is also an array-ref:
156
157 [$x, $y, $clr, $set, $label, $hint] # update or move
158 [MOVE_HANDICAP, $handicap] # black move, setup handicap
159 [MOVE_PASS] # pass
160 [] # also pass (deprecated!)
161
162 It changes the board or executes a move, by first clearing the bits
163 specified in C<$clr>, then setting bits specified in C<$set>.
164
165 If C<$set> includes C<MARK_LABEL>, the label text must be given in
166 C<$label>.
167
168 If C<$set> contains C<MARK_MOVE> then surrounded stones will be removed
169 from the board and (simple) Kos are detected and marked with square
170 symbols and C<MARK_KO>, after removing other marking symbols. The
171 markings are also removed with the next next update structure that uses
172 C<MARK_MOVE>, so this flag is suited well for marking, well, moves. Note
173 that you can make invalid "moves" (such as suicide) and C<update> will
174 try to cope with it. You can use C<is_valid_move> to avoid making illegal
175 moves.
176
177 For handicap "moves", currently only board sizes 9, 13 and 19 are
178 supported and only handicap values from 2 to 9. The placement follows the
179 IGS rules, if you want other placements, you have to set it up yourself.
180
181 This function modifies the C<$hint> member of the specified structure
182 to speed up repeated board generation and updates with the same update
183 structures.
184
185 If the hint member is a reference the scalar pointed to by the reference
186 is updated instead.
187
188 If all this hint member thing is confusing, just ignore it and specify
189 it as C<undef> or leave it out of the array entirely. Do make sure that
190 you keep your update structures around as long as previous updates don't
191 change, however, as regenerating a full board position from hinted
192 update structures is I<much> faster then recreating it from fresh update
193 structures.
194
195 Example, make two silly moves:
196
197 $board->update ([[0, 18, -1, MARK_B | MARK_MOVE],
198 [0, 17, -1, MARK_W | MARK_MOVE]]);
199
200 =cut
201
202 our %HANDICAP_COORD = (
203 9 => [2, 4, 6],
204 13 => [3, 6, 9],
205 19 => [3, 9, 15],
206 );
207 our %HANDICAP_XY = (
208 2 => [qw(0,2 2,0 )],
209 3 => [qw(0,2 2,0 0,0 )],
210 4 => [qw(0,2 2,0 0,0 2,2 )],
211 5 => [qw(0,2 2,0 0,0 2,2 1,1)],
212 6 => [qw(0,2 2,0 0,0 2,2 0,1 2,1 )],
213 7 => [qw(0,2 2,0 0,0 2,2 0,1 2,1 1,1)],
214 8 => [qw(0,2 2,0 0,0 2,2 0,1 2,1 1,0 1,2 )],
215 9 => [qw(0,2 2,0 0,0 2,2 0,1 2,1 1,0 1,2 1,1)],
216 );
217
218 our $mark_symbols = MARK_CIRCLE | MARK_SQUARE | MARK_TRIANGLE | MARK_CROSS | MARK_KO;
219
220 sub update {
221 my ($self, $path) = @_;
222
223 my $board = $self->{board};
224
225 for (@$path) {
226 my ($x, $y, $clr, $set, $label) = @$_;
227
228 if (!defined $x) {
229 $$_ &= ~$mark_symbols for @{ delete $self->{unmark} || [] };
230 # pass
231
232 } elsif ($x == MOVE_HANDICAP) {
233 $$_ &= ~$mark_symbols for @{ delete $self->{unmark} || [] };
234
235 # $y = #handicap stones
236 my $c = $HANDICAP_COORD{$self->{size}}
237 or Carp::croak "$self->{size}: illegal board size for handicap";
238 my $h = $HANDICAP_XY{$y}
239 or Carp::croak "$y: illegal number of handicap stones";
240
241 for (@$h) {
242 my ($x, $y) = map $c->[$_], split /,/;
243 $board->[$x][$y] = MARK_B | MARK_MOVE;
244 }
245
246 } else {
247 my $space = \$board->[$x][$y];
248
249 $$space = $$space & ~$clr | $set;
250
251 $self->{label}[$x][$y] = $label if $set & MARK_LABEL;
252
253 if ($set & MARK_MOVE) {
254 $$_ &= ~$mark_symbols for @{ $self->{unmark} || [] };
255 @{ $self->{unmark} } = $space;
256
257 # remark the space, in case the move was on the same spot as the
258 # old mark
259 $$space |= $set;
260
261 unless (${ $_->[5] ||= \my $hint }) {
262 my ($own, $opp) =
263 $set & MARK_B
264 ? (MARK_B, MARK_W)
265 : (MARK_W, MARK_B);
266
267 my (@capture, @suicide);
268
269 push @capture, $self->capture ($opp, $x-1, $y) if $x > 0 && $board->[$x-1][$y] & $opp;
270 push @capture, $self->capture ($opp, $x+1, $y) if $x < $self->{max} && $board->[$x+1][$y] & $opp;
271 push @capture, $self->capture ($opp, $x, $y-1) if $y > 0 && $board->[$x][$y-1] & $opp;
272 push @capture, $self->capture ($opp, $x, $y+1) if $y < $self->{max} && $board->[$x][$y+1] & $opp;
273
274 # keep only unique coordinates
275 @capture = do { my %seen; grep !$seen{"$_->[0],$_->[1]"}++, @capture };
276
277 # remove captured stones
278 $self->{captures}[$own == MARK_B ? COLOUR_BLACK : COLOUR_WHITE] += @capture;
279 $self->{board}[$_->[0]][$_->[1]] = 0
280 for @capture;
281
282 push @suicide, $self->capture ($own, $x, $y);
283
284 ${ $_->[5] } ||= !(@capture || @suicide);
285
286 if (@suicide) {
287 $self->{board}[$_->[0]][$_->[1]] = 0
288 for @suicide;
289 # count suicides as other sides stones
290 $self->{captures}[$opp == MARK_B ? COLOUR_BLACK : COLOUR_WHITE] += @suicide;
291
292 } elsif (!@suicide && @capture == 1) {
293 # possible ko. now check liberties on placed stone
294
295 my $libs;
296
297 $libs++ if $x > 0 && !($board->[$x-1][$y] & $opp);
298 $libs++ if $x < $self->{max} && !($board->[$x+1][$y] & $opp);
299 $libs++ if $y > 0 && !($board->[$x][$y-1] & $opp);
300 $libs++ if $y < $self->{max} && !($board->[$x][$y+1] & $opp);
301
302 if ($libs == 1) {
303 $$space = $$space & ~$mark_symbols | MARK_KO;
304
305 ($x, $y) = @{$capture[0]};
306 $board->[$x][$y] |= MARK_KO;
307
308 push @{ $self->{unmark} }, \$board->[$x][$y];
309 }
310 }
311 }
312 }
313 }
314 }
315 }
316
317 =item $board->is_valid_move ($colour, $x, $y[, $may_suicide])
318
319 Returns true if the move of the given colour on the given coordinates is
320 valid or not. Kos are taken into account as long as they are marked with
321 C<MARK_KO>. Suicides are invalid unless C<$may_suicide> is true (e.g. for
322 new zealand rules)
323
324 =cut
325
326 sub is_valid_move {
327 my ($self, $colour, $x, $y, $may_suicide) = @_;
328
329 my $board = $self->{board};
330
331 return if $board->[$x][$y] & (MARK_B | MARK_W | MARK_KO)
332 && !($board->[$x][$y] & MARK_GRAYED);
333
334 if ($may_suicide) {
335 return 1;
336 } else {
337 my ($own, $opp) = $colour == COLOUR_BLACK
338 ? (MARK_B, MARK_W)
339 : (MARK_W, MARK_B);
340
341 # try the move
342 local $board->[$x][$y] = $board->[$x][$y] | $own;
343
344 return 1 if $x > 0 && $board->[$x-1][$y] & $opp && $self->capture ($opp, $x-1, $y, 1);
345 return 1 if $x < $self->{max} && $board->[$x+1][$y] & $opp && $self->capture ($opp, $x+1, $y, 1);
346 return 1 if $y > 0 && $board->[$x][$y-1] & $opp && $self->capture ($opp, $x, $y-1, 1);
347 return 1 if $y < $self->{max} && $board->[$x][$y+1] & $opp && $self->capture ($opp, $x, $y+1, 1);
348
349 return !$self->capture ($own, $x, $y, 1);
350 }
351 }
352
353 1;
354
355 =back
356
357 =head2 AUTHOR
358
359 Marc Lehmann <schmorp@schmorp.de>
360
361 =head2 SEE ALSO
362
363 L<Gtk2::GoBoard>.
364
365 =cut
366