ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Games-Go-SimpleBoard/SimpleBoard.pm
Revision: 1.9
Committed: Wed Jun 25 20:11:33 2008 UTC (15 years, 10 months ago) by root
Branch: MAIN
Changes since 1.8: +38 -42 lines
Log Message:
completely rewrote mvoe logic

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