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