ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Games-Go-SimpleBoard/SimpleBoard.pm
(Generate patch)

Comparing Games-Go-SimpleBoard/SimpleBoard.pm (file contents):
Revision 1.3 by root, Mon Jun 23 20:41:16 2008 UTC vs.
Revision 1.16 by elmex, Tue Jul 29 10:03:52 2008 UTC

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

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines