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

# User Rev Content
1 root 1.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 root 1.6 MARK_CROSS # cross mark
23 root 1.1 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 root 1.9 COLOUR_WHITE # guarenteed to be 0
35     COLOUR_BLACK # guarenteed to be 1
36 root 1.1
37 root 1.3 MOVE_HANDICAP # used as "x-coordinate" for handicap moves
38 root 1.9 MOVE_PASS # can be used as "x-coordinate" for pass moves
39 root 1.3
40 root 1.1 =head2 METHODS
41    
42     =over 4
43    
44     =cut
45    
46 root 1.3 no warnings;
47     use strict;
48    
49     use Carp ();
50    
51     use base Exporter::;
52 root 1.1
53     our $VERSION = '1.0';
54    
55 root 1.3 our @EXPORT = qw(
56 root 1.1 MARK_TRIANGLE MARK_SQUARE MARK_CIRCLE MARK_SMALL_B MARK_SMALL_W MARK_B
57 root 1.7 MARK_W MARK_GRAYED MARK_MOVE MARK_LABEL MARK_HOSHI MARK_KO MARK_CROSS
58 root 1.1 MARK_REDRAW
59     COLOUR_BLACK COLOUR_WHITE
60 root 1.3 MOVE_HANDICAP MOVE_PASS
61 root 1.1 );
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 root 1.8 sub MARK_CROSS (){ 0x1000 }
78 root 1.1 sub MARK_REDRAW (){ 0x8000 }
79    
80 root 1.9 sub COLOUR_WHITE (){ 0 }
81     sub COLOUR_BLACK (){ 1 }
82 root 1.1
83 root 1.3 sub MOVE_PASS (){ undef }
84     sub MOVE_HANDICAP (){ -2 }
85    
86 root 1.1 =item my $board = new $size
87    
88     Creates a new empty board of the given size.
89    
90 root 1.3 C<< $board->{size} >> stores the board size.
91    
92 root 1.1 C<< $board->{max} >> stores the maximum board coordinate (size-1).
93    
94 root 1.9 C<< $board->{captures}[COLOUR_xxx] >> stores the number of captured stones for
95 root 1.1 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 root 1.9
105 root 1.1 bless {
106 root 1.9 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 root 1.1 }
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 root 1.2 =item $hint = $board->update ([update-structures...])
149 root 1.1
150 root 1.2 Each update-structure itself is also an array-ref:
151 root 1.1
152     [$x, $y, $clr, $set, $label, $hint] # update or move
153 root 1.4 [MOVE_HANDICAP, $handicap] # black move, setup handicap
154 root 1.3 [MOVE_PASS] # pass
155 root 1.4 [] # also pass (deprecated!)
156 root 1.1
157 root 1.2 It changes the board or executes a move, by first clearing the bits
158 root 1.1 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 root 1.9 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 root 1.3
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 root 1.4 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 root 1.1
180     If the hint member is a reference the scalar pointed to by the reference
181     is updated instead.
182    
183 root 1.4 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 root 1.2
190 root 1.1 Example, make two silly moves:
191    
192 root 1.4 $board->update ([[0, 18, -1, MARK_B | MARK_MOVE],
193     [0, 17, -1, MARK_W | MARK_MOVE]]);
194 root 1.1
195     =cut
196    
197 root 1.3 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 root 1.5 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 root 1.3 );
212    
213 root 1.9 our $mark_symbols = MARK_CIRCLE | MARK_SQUARE | MARK_TRIANGLE | MARK_CROSS | MARK_KO;
214    
215 root 1.1 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 root 1.3 if (!defined $x) {
224 root 1.10 $$_ &= ~$mark_symbols for @{ delete $self->{unmark} || [] };
225 root 1.3 # pass
226    
227     } elsif ($x == MOVE_HANDICAP) {
228 root 1.10 $$_ &= ~$mark_symbols for @{ delete $self->{unmark} || [] };
229    
230 root 1.3 # $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 root 1.9 my $space = \$board->[$x][$y];
243    
244     $$space = $$space & ~$clr | $set;
245 root 1.1
246     $self->{label}[$x][$y] = $label if $set & MARK_LABEL;
247    
248     if ($set & MARK_MOVE) {
249 root 1.10 $$_ &= ~$mark_symbols for @{ $self->{unmark} || [] };
250 root 1.9 @{ $self->{unmark} } = $space;
251 root 1.1
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 root 1.9 $self->{captures}[$own == MARK_B ? COLOUR_BLACK : COLOUR_WHITE] += @capture;
270     $self->{board}[$_->[0]][$_->[1]] = 0
271 root 1.1 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 root 1.9 $$space = $$space & ~$mark_symbols | MARK_KO;
289    
290 root 1.1 ($x, $y) = @{$capture[0]};
291 root 1.9 $board->[$x][$y] |= MARK_KO;
292    
293     push @{ $self->{unmark} }, \$board->[$x][$y];
294 root 1.1 }
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 root 1.4 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 root 1.1
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 root 1.3 L<Gtk2::GoBoard>.
348 root 1.1
349     =cut
350