ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Games-Go-SimpleBoard/SimpleBoard.pm
Revision: 1.12
Committed: Wed Jul 23 19:19:31 2008 UTC (15 years, 9 months ago) by elmex
Branch: MAIN
Changes since 1.11: +2 -1 lines
Log Message:
is_valid_move checks for MARK_GRAYED now. assumption: MARK_GRAYED is never an occupied position on the board.

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 unless ($seen{$x,$y}++) {
136 if ($board->[$x][$y] & $mark) {
137 push @found, [$x, $y];
138
139 push @nodes, [$x-1, $y] unless $seen{$x-1, $y} || $x <= 0;
140 push @nodes, [$x+1, $y] unless $seen{$x+1, $y} || $x >= $max;
141 push @nodes, [$x, $y-1] unless $seen{$x, $y-1} || $y <= 0;
142 push @nodes, [$x, $y+1] unless $seen{$x, $y+1} || $y >= $max;
143 } elsif (!($board->[$x][$y] & (MARK_B | MARK_W))) {
144 return;
145 }
146 }
147 }
148
149 @found
150 }
151
152 =item $hint = $board->update ([update-structures...])
153
154 Each update-structure itself is also an array-ref:
155
156 [$x, $y, $clr, $set, $label, $hint] # update or move
157 [MOVE_HANDICAP, $handicap] # black move, setup handicap
158 [MOVE_PASS] # pass
159 [] # also pass (deprecated!)
160
161 It changes the board or executes a move, by first clearing the bits
162 specified in C<$clr>, then setting bits specified in C<$set>.
163
164 If C<$set> includes C<MARK_LABEL>, the label text must be given in
165 C<$label>.
166
167 If C<$set> contains C<MARK_MOVE> then surrounded stones will be removed
168 from the board and (simple) Kos are detected and marked with square
169 symbols and C<MARK_KO>, after removing other marking symbols. The
170 markings are also removed with the next next update structure that uses
171 C<MARK_MOVE>, so this flag is suited well for marking, well, moves. Note
172 that you can make invalid "moves" (such as suicide) and C<update> will
173 try to cope with it. You can use C<is_valid_move> to avoid making illegal
174 moves.
175
176 For handicap "moves", currently only board sizes 9, 13 and 19 are
177 supported and only handicap values from 2 to 9. The placement follows the
178 IGS rules, if you want other placements, you have to set it up yourself.
179
180 This function modifies the C<$hint> member of the specified structure
181 to speed up repeated board generation and updates with the same update
182 structures.
183
184 If the hint member is a reference the scalar pointed to by the reference
185 is updated instead.
186
187 If all this hint member thing is confusing, just ignore it and specify
188 it as C<undef> or leave it out of the array entirely. Do make sure that
189 you keep your update structures around as long as previous updates don't
190 change, however, as regenerating a full board position from hinted
191 update structures is I<much> faster then recreating it from fresh update
192 structures.
193
194 Example, make two silly moves:
195
196 $board->update ([[0, 18, -1, MARK_B | MARK_MOVE],
197 [0, 17, -1, MARK_W | MARK_MOVE]]);
198
199 =cut
200
201 our %HANDICAP_COORD = (
202 9 => [2, 4, 6],
203 13 => [3, 6, 9],
204 19 => [3, 9, 15],
205 );
206 our %HANDICAP_XY = (
207 2 => [qw(0,2 2,0 )],
208 3 => [qw(0,2 2,0 0,0 )],
209 4 => [qw(0,2 2,0 0,0 2,2 )],
210 5 => [qw(0,2 2,0 0,0 2,2 1,1)],
211 6 => [qw(0,2 2,0 0,0 2,2 0,1 2,1 )],
212 7 => [qw(0,2 2,0 0,0 2,2 0,1 2,1 1,1)],
213 8 => [qw(0,2 2,0 0,0 2,2 0,1 2,1 1,0 1,2 )],
214 9 => [qw(0,2 2,0 0,0 2,2 0,1 2,1 1,0 1,2 1,1)],
215 );
216
217 our $mark_symbols = MARK_CIRCLE | MARK_SQUARE | MARK_TRIANGLE | MARK_CROSS | MARK_KO;
218
219 sub update {
220 my ($self, $path) = @_;
221
222 my $board = $self->{board};
223
224 for (@$path) {
225 my ($x, $y, $clr, $set, $label) = @$_;
226
227 if (!defined $x) {
228 $$_ &= ~$mark_symbols for @{ delete $self->{unmark} || [] };
229 # pass
230
231 } elsif ($x == MOVE_HANDICAP) {
232 $$_ &= ~$mark_symbols for @{ delete $self->{unmark} || [] };
233
234 # $y = #handicap stones
235 my $c = $HANDICAP_COORD{$self->{size}}
236 or Carp::croak "$self->{size}: illegal board size for handicap";
237 my $h = $HANDICAP_XY{$y}
238 or Carp::croak "$y: illegal number of handicap stones";
239
240 for (@$h) {
241 my ($x, $y) = map $c->[$_], split /,/;
242 $board->[$x][$y] = MARK_B | MARK_MOVE;
243 }
244
245 } else {
246 my $space = \$board->[$x][$y];
247
248 $$space = $$space & ~$clr | $set;
249
250 $self->{label}[$x][$y] = $label if $set & MARK_LABEL;
251
252 if ($set & MARK_MOVE) {
253 $$_ &= ~$mark_symbols for @{ $self->{unmark} || [] };
254 @{ $self->{unmark} } = $space;
255
256 unless (${ $_->[5] ||= \my $hint }) {
257 my ($own, $opp) =
258 $set & MARK_B
259 ? (MARK_B, MARK_W)
260 : (MARK_W, MARK_B);
261
262 my (@capture, $suicide);
263
264 push @capture, $self->capture ($opp, $x-1, $y) if $x > 0 && $board->[$x-1][$y] & $opp;
265 push @capture, $self->capture ($opp, $x+1, $y) if $x < $self->{max} && $board->[$x+1][$y] & $opp;
266 push @capture, $self->capture ($opp, $x, $y-1) if $y > 0 && $board->[$x][$y-1] & $opp;
267 push @capture, $self->capture ($opp, $x, $y+1) if $y < $self->{max} && $board->[$x][$y+1] & $opp;
268
269 # keep only unique coordinates
270 @capture = do { my %seen; grep !$seen{"$_->[0],$_->[1]"}++, @capture };
271
272 # remove captured stones
273 $self->{captures}[$own == MARK_B ? COLOUR_BLACK : COLOUR_WHITE] += @capture;
274 $self->{board}[$_->[0]][$_->[1]] = 0
275 for @capture;
276
277 $suicide += $self->capture ($own, $x, $y);
278
279 ${ $_->[5] } ||= !(@capture || $suicide);
280
281 if (!$suicide && @capture == 1) {
282 # possible ko. now check liberties on placed stone
283
284 my $libs;
285
286 $libs++ if $x > 0 && !($board->[$x-1][$y] & $opp);
287 $libs++ if $x < $self->{max} && !($board->[$x+1][$y] & $opp);
288 $libs++ if $y > 0 && !($board->[$x][$y-1] & $opp);
289 $libs++ if $y < $self->{max} && !($board->[$x][$y+1] & $opp);
290
291 if ($libs == 1) {
292 $$space = $$space & ~$mark_symbols | MARK_KO;
293
294 ($x, $y) = @{$capture[0]};
295 $board->[$x][$y] |= MARK_KO;
296
297 push @{ $self->{unmark} }, \$board->[$x][$y];
298 }
299 }
300 }
301 }
302 }
303 }
304 }
305
306 =item $board->is_valid_move ($colour, $x, $y[, $may_suicide])
307
308 Returns true if the move of the given colour on the given coordinates is
309 valid or not. Kos are taken into account as long as they are marked with
310 C<MARK_KO>. Suicides are invalid unless C<$may_suicide> is true (e.g. for
311 new zealand rules)
312
313 =cut
314
315 sub is_valid_move {
316 my ($self, $colour, $x, $y, $may_suicide) = @_;
317
318 my $board = $self->{board};
319
320 return if $board->[$x][$y] & (MARK_B | MARK_W | MARK_KO)
321 && !($board->[$x][$y] & MARK_GRAYED);
322
323 if ($may_suicide) {
324 return 1;
325 } else {
326 my ($own, $opp) = $colour == COLOUR_BLACK
327 ? (MARK_B, MARK_W)
328 : (MARK_W, MARK_B);
329
330 # try the move
331 local $board->[$x][$y] = $board->[$x][$y] | $own;
332
333 return 1 if $x > 0 && $board->[$x-1][$y] & $opp && $self->capture ($opp, $x-1, $y, 1);
334 return 1 if $x < $self->{max} && $board->[$x+1][$y] & $opp && $self->capture ($opp, $x+1, $y, 1);
335 return 1 if $y > 0 && $board->[$x][$y-1] & $opp && $self->capture ($opp, $x, $y-1, 1);
336 return 1 if $y < $self->{max} && $board->[$x][$y+1] & $opp && $self->capture ($opp, $x, $y+1, 1);
337
338 return !$self->capture ($own, $x, $y, 1);
339 }
340 }
341
342 1;
343
344 =back
345
346 =head2 AUTHOR
347
348 Marc Lehmann <schmorp@schmorp.de>
349
350 =head2 SEE ALSO
351
352 L<Gtk2::GoBoard>.
353
354 =cut
355