ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Games-Go-SimpleBoard/SimpleBoard.pm
Revision: 1.5
Committed: Tue Jun 24 19:22:08 2008 UTC (15 years, 10 months ago) by root
Branch: MAIN
Changes since 1.4: +7 -7 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     MARK_SMALL_B # small stone, used for scoring or marking
23     MARK_SMALL_W # small stone, used for scoring or marking
24     MARK_B # normal black stone
25     MARK_W # normal whit stone
26     MARK_GRAYED # in conjunction with MARK_[BW], grays the stone
27     MARK_LABEL # a text label
28     MARK_HOSHI # this is a hoshi point (not used much)
29     MARK_MOVE # this is a regular move
30     MARK_KO # this is a ko position
31     MARK_REDRAW # ignored, can be used for your own purposes
32    
33     COLOUR_BLACK # used for $board->{last}
34     COLOUR_WHITE # to mark the colour of the last move
35    
36 root 1.3 MOVE_HANDICAP # used as "x-coordinate" for handicap moves
37     MOVE_PASS # can be used as "x-coordinate" for handicap moves
38    
39 root 1.1 =head2 METHODS
40    
41     =over 4
42    
43     =cut
44    
45 root 1.3 no warnings;
46     use strict;
47    
48     use Carp ();
49    
50     use base Exporter::;
51 root 1.1
52     our $VERSION = '1.0';
53    
54 root 1.3 our @EXPORT = qw(
55 root 1.1 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
57     MARK_REDRAW
58     COLOUR_BLACK COLOUR_WHITE
59 root 1.3 MOVE_HANDICAP MOVE_PASS
60 root 1.1 );
61    
62     # marker types for each board position (ORed together)
63    
64     sub MARK_TRIANGLE (){ 0x0001 }
65     sub MARK_SQUARE (){ 0x0002 }
66     sub MARK_CIRCLE (){ 0x0004 }
67     sub MARK_SMALL_B (){ 0x0008 } # small stone, used for scoring or marking
68     sub MARK_SMALL_W (){ 0x0010 } # small stone, used for scoring or marking
69     sub MARK_B (){ 0x0020 } # normal black stone
70     sub MARK_W (){ 0x0040 } # normal whit stone
71     sub MARK_GRAYED (){ 0x0080 } # in conjunction with MARK_[BW], grays the stone
72     sub MARK_LABEL (){ 0x0100 }
73     sub MARK_HOSHI (){ 0x0200 } # this is a hoshi point (not used much)
74     sub MARK_MOVE (){ 0x0400 } # this is a regular move
75     sub MARK_KO (){ 0x0800 } # this is a ko position
76     sub MARK_REDRAW (){ 0x8000 }
77    
78     sub COLOUR_BLACK (){ 0 }
79     sub COLOUR_WHITE (){ 1 }
80    
81 root 1.3 sub MOVE_PASS (){ undef }
82     sub MOVE_HANDICAP (){ -2 }
83    
84 root 1.1 =item my $board = new $size
85    
86     Creates a new empty board of the given size.
87    
88 root 1.3 C<< $board->{size} >> stores the board size.
89    
90 root 1.1 C<< $board->{max} >> stores the maximum board coordinate (size-1).
91    
92     C<< $board->{captures}[COLOUR] >> stores the number of captured stones for
93     the given colour.
94    
95     C<< $board->{last} >> stores the colour of the last move that was played.
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     bless {
105     max => $size - 1,
106 root 1.3 size => $size,
107 root 1.1 board => [map [(0) x $size], 1 .. $size],
108     captures => [0, 0], # captures
109     #timer => [],
110     #score => [],
111     #last => COLOUR_...,
112     @_
113     },
114     $class;
115     }
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.4 If C<$set> contains C<MARK_MOVE>, then a circle symbol will be placed
165     at this coordinate. Also, surrounded stones will be removed from the
166     board and (simple) Kos are detected and marked with square symbols and
167     C<MARK_KO>. The circle and square markings are removed with the next
168     update that uses C<MARK_MOVE>, so this flag is suited well for marking,
169     well, moves. Note that you can make invalid "moves" (such as suicide) and
170     C<update> will try to cope with it. You can use C<is_valid_move> to avoid
171     making illegal 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.1 sub update {
215     my ($self, $path) = @_;
216    
217     my $board = $self->{board};
218    
219     for (@$path) {
220     my ($x, $y, $clr, $set, $label) = @$_;
221    
222     my $nodemask =
223     $_ == $path->[-1]
224     ? ~0
225     : ~(MARK_SQUARE | MARK_TRIANGLE | MARK_CIRCLE | MARK_LABEL | MARK_KO);
226    
227 root 1.3 if (!defined $x) {
228     # pass
229     $self->{last} = $set & MARK_B ? COLOUR_BLACK : COLOUR_WHITE;
230    
231     } elsif ($x == MOVE_HANDICAP) {
232     # $y = #handicap stones
233     my $c = $HANDICAP_COORD{$self->{size}}
234     or Carp::croak "$self->{size}: illegal board size for handicap";
235     my $h = $HANDICAP_XY{$y}
236     or Carp::croak "$y: illegal number of handicap stones";
237    
238     for (@$h) {
239     my ($x, $y) = map $c->[$_], split /,/;
240     $board->[$x][$y] = MARK_B | MARK_MOVE;
241     }
242    
243     } else {
244 root 1.1 $board->[$x][$y] =
245     $board->[$x][$y]
246     & ~$clr
247     | $set
248     & $nodemask;
249    
250     $self->{label}[$x][$y] = $label if $set & MARK_LABEL;
251    
252     if ($set & MARK_MOVE) {
253     $self->{last} = $set & MARK_B ? COLOUR_BLACK : COLOUR_WHITE;
254    
255     unless (${ $_->[5] ||= \my $hint }) {
256     my ($own, $opp) =
257     $set & MARK_B
258     ? (MARK_B, MARK_W)
259     : (MARK_W, MARK_B);
260    
261     my (@capture, $suicide);
262    
263     push @capture, $self->capture ($opp, $x-1, $y) if $x > 0 && $board->[$x-1][$y] & $opp;
264     push @capture, $self->capture ($opp, $x+1, $y) if $x < $self->{max} && $board->[$x+1][$y] & $opp;
265     push @capture, $self->capture ($opp, $x, $y-1) if $y > 0 && $board->[$x][$y-1] & $opp;
266     push @capture, $self->capture ($opp, $x, $y+1) if $y < $self->{max} && $board->[$x][$y+1] & $opp;
267    
268     # keep only unique coordinates
269     @capture = do { my %seen; grep !$seen{"$_->[0],$_->[1]"}++, @capture };
270    
271     # remove captured stones
272     $self->{captures}[$self->{last}] += @capture;
273     $self->{board}[$_->[0]][$_->[1]] &= ~(MARK_B | MARK_W | MARK_MOVE)
274     for @capture;
275    
276     $suicide += $self->capture ($own, $x, $y);
277    
278     ${ $_->[5] } ||= !(@capture || $suicide);
279    
280     if (!$suicide && @capture == 1) {
281     # possible ko. now check liberties on placed stone
282    
283     my $libs;
284    
285     $libs++ if $x > 0 && !($board->[$x-1][$y] & $opp);
286     $libs++ if $x < $self->{max} && !($board->[$x+1][$y] & $opp);
287     $libs++ if $y > 0 && !($board->[$x][$y-1] & $opp);
288     $libs++ if $y < $self->{max} && !($board->[$x][$y+1] & $opp);
289    
290     if ($libs == 1) {
291     $board->[$x][$y] = $board->[$x][$y] & ~MARK_CIRCLE | (MARK_KO & $nodemask);
292     ($x, $y) = @{$capture[0]};
293     $board->[$x][$y] |= MARK_KO & $nodemask;
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 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