ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Games-Go-SimpleBoard/SimpleBoard.pm
Revision: 1.3
Committed: Mon Jun 23 20:41:16 2008 UTC (15 years, 10 months ago) by root
Branch: MAIN
Changes since 1.2: +63 -8 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.3 [MOVE_HANDICAP, $handicap] # black move, set handicap
155     [MOVE_PASS] # pass
156     [] # also pass
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.3 If C<$set> contains C<MARK_MOVE>, then a circle symbol will be placed on
165     this coordinate only if this is the last move done (which is useful for a
166     move marker).
167    
168     For handicap "moves", currently only board sizes 9, 13 and 19 are
169     supported and only handicap values from 2 to 9. The placement follows the
170     IGS rules, if you want other placements, you have to set it up yourself.
171    
172 root 1.1 This function modifies the hint member of the specified path to speed up
173     repeated board generation and updates with the same update structures.
174    
175     If the hint member is a reference the scalar pointed to by the reference
176     is updated instead.
177    
178 root 1.2 If all this hint member thing is unclear, just ignore it and specify it
179     as C<undef> or leave it out of the array entirely. Do make sure that you
180     keep your update structures around, however, as regenerating a full board
181     position from hinted update structures is I<much> faster then recreating
182     it from fresh update structures.
183    
184 root 1.1 Example, make two silly moves:
185    
186     $board->update ([[0, 18, -1, MARK_B|MARK_MOVE],
187 root 1.3 [0, 17, -1, MARK_W|MARK_MOVE]]);
188 root 1.1
189     =cut
190    
191 root 1.3 our %HANDICAP_COORD = (
192     9 => [2, 4, 6],
193     13 => [3, 6, 9],
194     19 => [3, 9, 15],
195     );
196     our %HANDICAP_XY = (
197     2 => [qw(0,2 2,0 )],
198     3 => [qw(0,2 2,0 2,2 )],
199     4 => [qw(0,2 2,0 2,2 0,0 )],
200     5 => [qw(0,2 2,0 2,2 0,0 1,1)],
201     6 => [qw(0,2 2,0 2,2 0,0 0,1 2,1 )],
202     7 => [qw(0,2 2,0 2,2 0,0 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 )],
204     9 => [qw(0,2 2,0 2,2 0,0 0,1 2,1 1,0 1,2 1,1)],
205     );
206    
207 root 1.1 sub update {
208     my ($self, $path) = @_;
209    
210     my $board = $self->{board};
211    
212     for (@$path) {
213     my ($x, $y, $clr, $set, $label) = @$_;
214    
215     my $nodemask =
216     $_ == $path->[-1]
217     ? ~0
218     : ~(MARK_SQUARE | MARK_TRIANGLE | MARK_CIRCLE | MARK_LABEL | MARK_KO);
219    
220 root 1.3 if (!defined $x) {
221     # pass
222     $self->{last} = $set & MARK_B ? COLOUR_BLACK : COLOUR_WHITE;
223    
224     } elsif ($x == MOVE_HANDICAP) {
225     # $y = #handicap stones
226     my $c = $HANDICAP_COORD{$self->{size}}
227     or Carp::croak "$self->{size}: illegal board size for handicap";
228     my $h = $HANDICAP_XY{$y}
229     or Carp::croak "$y: illegal number of handicap stones";
230    
231     for (@$h) {
232     my ($x, $y) = map $c->[$_], split /,/;
233     $board->[$x][$y] = MARK_B | MARK_MOVE;
234     }
235    
236     } else {
237 root 1.1 $board->[$x][$y] =
238     $board->[$x][$y]
239     & ~$clr
240     | $set
241     & $nodemask;
242    
243     $self->{label}[$x][$y] = $label if $set & MARK_LABEL;
244    
245     if ($set & MARK_MOVE) {
246     $self->{last} = $set & MARK_B ? COLOUR_BLACK : COLOUR_WHITE;
247    
248     unless (${ $_->[5] ||= \my $hint }) {
249     my ($own, $opp) =
250     $set & MARK_B
251     ? (MARK_B, MARK_W)
252     : (MARK_W, MARK_B);
253    
254     my (@capture, $suicide);
255    
256     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;
258     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;
260    
261     # keep only unique coordinates
262     @capture = do { my %seen; grep !$seen{"$_->[0],$_->[1]"}++, @capture };
263    
264     # remove captured stones
265     $self->{captures}[$self->{last}] += @capture;
266     $self->{board}[$_->[0]][$_->[1]] &= ~(MARK_B | MARK_W | MARK_MOVE)
267     for @capture;
268    
269     $suicide += $self->capture ($own, $x, $y);
270    
271     ${ $_->[5] } ||= !(@capture || $suicide);
272    
273     if (!$suicide && @capture == 1) {
274     # possible ko. now check liberties on placed stone
275    
276     my $libs;
277    
278     $libs++ if $x > 0 && !($board->[$x-1][$y] & $opp);
279     $libs++ if $x < $self->{max} && !($board->[$x+1][$y] & $opp);
280     $libs++ if $y > 0 && !($board->[$x][$y-1] & $opp);
281     $libs++ if $y < $self->{max} && !($board->[$x][$y+1] & $opp);
282    
283     if ($libs == 1) {
284     $board->[$x][$y] = $board->[$x][$y] & ~MARK_CIRCLE | (MARK_KO & $nodemask);
285     ($x, $y) = @{$capture[0]};
286     $board->[$x][$y] |= MARK_KO & $nodemask;
287     }
288     }
289     }
290     }
291     }
292     }
293     }
294    
295     =item $board->is_valid_move ($colour, $x, $y[, $may_suicide])
296    
297     Returns true if the move of the given colour on the given coordinates is
298     valid or not.
299    
300     =cut
301    
302     sub is_valid_move {
303     my ($self, $colour, $x, $y, $may_suicide) = @_;
304    
305     my $board = $self->{board};
306    
307     return if $board->[$x][$y] & (MARK_B | MARK_W | MARK_KO);
308    
309     if ($may_suicide) {
310     return 1;
311     } else {
312     my ($own, $opp) = $colour == COLOUR_BLACK
313     ? (MARK_B, MARK_W)
314     : (MARK_W, MARK_B);
315    
316     # try the move
317     local $board->[$x][$y] = $board->[$x][$y] | $own;
318    
319     return 1 if $x > 0 && $board->[$x-1][$y] & $opp && $self->capture ($opp, $x-1, $y, 1);
320     return 1 if $x < $self->{max} && $board->[$x+1][$y] & $opp && $self->capture ($opp, $x+1, $y, 1);
321     return 1 if $y > 0 && $board->[$x][$y-1] & $opp && $self->capture ($opp, $x, $y-1, 1);
322     return 1 if $y < $self->{max} && $board->[$x][$y+1] & $opp && $self->capture ($opp, $x, $y+1, 1);
323    
324     return !$self->capture ($own, $x, $y, 1);
325     }
326     }
327    
328     1;
329    
330     =back
331    
332     =head2 AUTHOR
333    
334     Marc Lehmann <schmorp@schmorp.de>
335    
336     =head2 SEE ALSO
337    
338 root 1.3 L<Gtk2::GoBoard>.
339 root 1.1
340     =cut
341