ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Games-Go-SimpleBoard/SimpleBoard.pm
Revision: 1.1
Committed: Sun Jun 22 15:05:59 2008 UTC (15 years, 11 months ago) by root
Branch: MAIN
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     =head2 METHODS
37    
38     =over 4
39    
40     =cut
41    
42     use base Exporter;
43    
44     our $VERSION = '1.0';
45    
46     @EXPORT = qw(
47     MARK_TRIANGLE MARK_SQUARE MARK_CIRCLE MARK_SMALL_B MARK_SMALL_W MARK_B
48     MARK_W MARK_GRAYED MARK_MOVE MARK_LABEL MARK_HOSHI MARK_KO
49     MARK_REDRAW
50     COLOUR_BLACK COLOUR_WHITE
51     );
52    
53     # marker types for each board position (ORed together)
54    
55     sub MARK_TRIANGLE (){ 0x0001 }
56     sub MARK_SQUARE (){ 0x0002 }
57     sub MARK_CIRCLE (){ 0x0004 }
58     sub MARK_SMALL_B (){ 0x0008 } # small stone, used for scoring or marking
59     sub MARK_SMALL_W (){ 0x0010 } # small stone, used for scoring or marking
60     sub MARK_B (){ 0x0020 } # normal black stone
61     sub MARK_W (){ 0x0040 } # normal whit stone
62     sub MARK_GRAYED (){ 0x0080 } # in conjunction with MARK_[BW], grays the stone
63     sub MARK_LABEL (){ 0x0100 }
64     sub MARK_HOSHI (){ 0x0200 } # this is a hoshi point (not used much)
65     sub MARK_MOVE (){ 0x0400 } # this is a regular move
66     sub MARK_KO (){ 0x0800 } # this is a ko position
67     sub MARK_REDRAW (){ 0x8000 }
68    
69     sub COLOUR_BLACK (){ 0 }
70     sub COLOUR_WHITE (){ 1 }
71    
72     =item my $board = new $size
73    
74     Creates a new empty board of the given size.
75    
76     C<< $board->{max} >> stores the maximum board coordinate (size-1).
77    
78     C<< $board->{captures}[COLOUR] >> stores the number of captured stones for
79     the given colour.
80    
81     C<< $board->{last} >> stores the colour of the last move that was played.
82    
83     C<< $board->{board} >> stores a two-dimensional array with board contents.
84    
85     =cut
86    
87     sub new {
88     my $class = shift;
89     my $size = shift;
90     bless {
91     max => $size - 1,
92     board => [map [(0) x $size], 1 .. $size],
93     captures => [0, 0], # captures
94     #timer => [],
95     #score => [],
96     #last => COLOUR_...,
97     @_
98     },
99     $class;
100     }
101    
102     # inefficient and primitive, I hear you say?
103     # well... you are right :)
104     # use an extremely dumb floodfill algorithm to get rid of captured stones
105     sub capture {
106     my ($self, $mark, $x, $y) = @_;
107    
108     my %seen;
109     my @found;
110     my @nodes = ([$x,$y]);
111     my $board = $self->{board};
112    
113     my $max = $self->{max};
114    
115     while (@nodes) {
116     my ($x, $y) = @{pop @nodes};
117     unless ($seen{$x,$y}++) {
118     if ($board->[$x][$y] & $mark) {
119     push @found, [$x, $y];
120    
121     push @nodes, [$x-1, $y] unless $seen{$x-1, $y} || $x <= 0;
122     push @nodes, [$x+1, $y] unless $seen{$x+1, $y} || $x >= $max;
123     push @nodes, [$x, $y-1] unless $seen{$x, $y-1} || $y <= 0;
124     push @nodes, [$x, $y+1] unless $seen{$x, $y+1} || $y >= $max;
125     } elsif (!($board->[$x][$y] & (MARK_B | MARK_W))) {
126     return;
127     }
128     }
129     }
130    
131     @found
132     }
133    
134     =item $hint = $board->update ([update-structure...])
135    
136     Structure is
137    
138     [$x, $y, $clr, $set, $label, $hint] # update or move
139     [] # pass
140    
141     and changes the board or executes a move, by first clearing the bits
142     specified in C<$clr>, then setting bits specified in C<$set>.
143    
144     If C<$set> includes C<MARK_LABEL>, the label text must be given in
145     C<$label>.
146    
147     This function modifies the hint member of the specified path to speed up
148     repeated board generation and updates with the same update structures.
149    
150     If the hint member is a reference the scalar pointed to by the reference
151     is updated instead.
152    
153     Example, make two silly moves:
154    
155     $board->update ([[0, 18, -1, MARK_B|MARK_MOVE],
156     [0, 17, -1, MARK_W|MARK_MOVE]);
157    
158     =cut
159    
160     sub update {
161     my ($self, $path) = @_;
162    
163     my $board = $self->{board};
164    
165     for (@$path) {
166     my ($x, $y, $clr, $set, $label) = @$_;
167    
168     my $nodemask =
169     $_ == $path->[-1]
170     ? ~0
171     : ~(MARK_SQUARE | MARK_TRIANGLE | MARK_CIRCLE | MARK_LABEL | MARK_KO);
172    
173     if (defined $x) {
174     $board->[$x][$y] =
175     $board->[$x][$y]
176     & ~$clr
177     | $set
178     & $nodemask;
179    
180     $self->{label}[$x][$y] = $label if $set & MARK_LABEL;
181    
182     if ($set & MARK_MOVE) {
183     $self->{last} = $set & MARK_B ? COLOUR_BLACK : COLOUR_WHITE;
184    
185     unless (${ $_->[5] ||= \my $hint }) {
186     my ($own, $opp) =
187     $set & MARK_B
188     ? (MARK_B, MARK_W)
189     : (MARK_W, MARK_B);
190    
191     my (@capture, $suicide);
192    
193     push @capture, $self->capture ($opp, $x-1, $y) if $x > 0 && $board->[$x-1][$y] & $opp;
194     push @capture, $self->capture ($opp, $x+1, $y) if $x < $self->{max} && $board->[$x+1][$y] & $opp;
195     push @capture, $self->capture ($opp, $x, $y-1) if $y > 0 && $board->[$x][$y-1] & $opp;
196     push @capture, $self->capture ($opp, $x, $y+1) if $y < $self->{max} && $board->[$x][$y+1] & $opp;
197    
198     # keep only unique coordinates
199     @capture = do { my %seen; grep !$seen{"$_->[0],$_->[1]"}++, @capture };
200    
201     # remove captured stones
202     $self->{captures}[$self->{last}] += @capture;
203     $self->{board}[$_->[0]][$_->[1]] &= ~(MARK_B | MARK_W | MARK_MOVE)
204     for @capture;
205    
206     $suicide += $self->capture ($own, $x, $y);
207    
208     ${ $_->[5] } ||= !(@capture || $suicide);
209    
210     if (!$suicide && @capture == 1) {
211     # possible ko. now check liberties on placed stone
212    
213     my $libs;
214    
215     $libs++ if $x > 0 && !($board->[$x-1][$y] & $opp);
216     $libs++ if $x < $self->{max} && !($board->[$x+1][$y] & $opp);
217     $libs++ if $y > 0 && !($board->[$x][$y-1] & $opp);
218     $libs++ if $y < $self->{max} && !($board->[$x][$y+1] & $opp);
219    
220     if ($libs == 1) {
221     $board->[$x][$y] = $board->[$x][$y] & ~MARK_CIRCLE | (MARK_KO & $nodemask);
222     ($x, $y) = @{$capture[0]};
223     $board->[$x][$y] |= MARK_KO & $nodemask;
224     }
225     }
226     }
227     }
228     } else {
229     $self->{last} = $set & MARK_B ? COLOUR_BLACK : COLOUR_WHITE;
230     }
231     }
232     }
233    
234     =item $board->is_valid_move ($colour, $x, $y[, $may_suicide])
235    
236     Returns true if the move of the given colour on the given coordinates is
237     valid or not.
238    
239     =cut
240    
241     sub is_valid_move {
242     my ($self, $colour, $x, $y, $may_suicide) = @_;
243    
244     my $board = $self->{board};
245    
246     return if $board->[$x][$y] & (MARK_B | MARK_W | MARK_KO);
247    
248     if ($may_suicide) {
249     return 1;
250     } else {
251     my ($own, $opp) = $colour == COLOUR_BLACK
252     ? (MARK_B, MARK_W)
253     : (MARK_W, MARK_B);
254    
255     # try the move
256     local $board->[$x][$y] = $board->[$x][$y] | $own;
257    
258     return 1 if $x > 0 && $board->[$x-1][$y] & $opp && $self->capture ($opp, $x-1, $y, 1);
259     return 1 if $x < $self->{max} && $board->[$x+1][$y] & $opp && $self->capture ($opp, $x+1, $y, 1);
260     return 1 if $y > 0 && $board->[$x][$y-1] & $opp && $self->capture ($opp, $x, $y-1, 1);
261     return 1 if $y < $self->{max} && $board->[$x][$y+1] & $opp && $self->capture ($opp, $x, $y+1, 1);
262    
263     return !$self->capture ($own, $x, $y, 1);
264     }
265     }
266    
267     1;
268    
269     =back
270    
271     =head2 AUTHOR
272    
273     Marc Lehmann <schmorp@schmorp.de>
274    
275     =head2 SEE ALSO
276    
277     L<KGS::Protocol>, L<KGS::Game::Tree>, L<Gtk2::GoBoard>.
278    
279     =cut
280