ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Games-Go-SimpleBoard/SimpleBoard.pm
Revision: 1.10
Committed: Wed Jun 25 20:49:38 2008 UTC (15 years, 10 months ago) by root
Branch: MAIN
Changes since 1.9: +4 -2 lines
Log Message:
*** empty log message ***

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