ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Games-Go-SimpleBoard/SimpleBoard.pm
Revision: 1.2
Committed: Mon Jun 23 00:38:35 2008 UTC (15 years, 10 months ago) by root
Branch: MAIN
Changes since 1.1: +9 -3 lines
Log Message:
*** empty log message ***

File Contents

# Content
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-structures...])
135
136 Each update-structure itself is also an array-ref:
137
138 [$x, $y, $clr, $set, $label, $hint] # update or move
139 [] # pass
140
141 It 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 If all this hint member thing is unclear, just ignore it and specify it
154 as C<undef> or leave it out of the array entirely. Do make sure that you
155 keep your update structures around, however, as regenerating a full board
156 position from hinted update structures is I<much> faster then recreating
157 it from fresh update structures.
158
159 Example, make two silly moves:
160
161 $board->update ([[0, 18, -1, MARK_B|MARK_MOVE],
162 [0, 17, -1, MARK_W|MARK_MOVE]);
163
164 =cut
165
166 sub update {
167 my ($self, $path) = @_;
168
169 my $board = $self->{board};
170
171 for (@$path) {
172 my ($x, $y, $clr, $set, $label) = @$_;
173
174 my $nodemask =
175 $_ == $path->[-1]
176 ? ~0
177 : ~(MARK_SQUARE | MARK_TRIANGLE | MARK_CIRCLE | MARK_LABEL | MARK_KO);
178
179 if (defined $x) {
180 $board->[$x][$y] =
181 $board->[$x][$y]
182 & ~$clr
183 | $set
184 & $nodemask;
185
186 $self->{label}[$x][$y] = $label if $set & MARK_LABEL;
187
188 if ($set & MARK_MOVE) {
189 $self->{last} = $set & MARK_B ? COLOUR_BLACK : COLOUR_WHITE;
190
191 unless (${ $_->[5] ||= \my $hint }) {
192 my ($own, $opp) =
193 $set & MARK_B
194 ? (MARK_B, MARK_W)
195 : (MARK_W, MARK_B);
196
197 my (@capture, $suicide);
198
199 push @capture, $self->capture ($opp, $x-1, $y) if $x > 0 && $board->[$x-1][$y] & $opp;
200 push @capture, $self->capture ($opp, $x+1, $y) if $x < $self->{max} && $board->[$x+1][$y] & $opp;
201 push @capture, $self->capture ($opp, $x, $y-1) if $y > 0 && $board->[$x][$y-1] & $opp;
202 push @capture, $self->capture ($opp, $x, $y+1) if $y < $self->{max} && $board->[$x][$y+1] & $opp;
203
204 # keep only unique coordinates
205 @capture = do { my %seen; grep !$seen{"$_->[0],$_->[1]"}++, @capture };
206
207 # remove captured stones
208 $self->{captures}[$self->{last}] += @capture;
209 $self->{board}[$_->[0]][$_->[1]] &= ~(MARK_B | MARK_W | MARK_MOVE)
210 for @capture;
211
212 $suicide += $self->capture ($own, $x, $y);
213
214 ${ $_->[5] } ||= !(@capture || $suicide);
215
216 if (!$suicide && @capture == 1) {
217 # possible ko. now check liberties on placed stone
218
219 my $libs;
220
221 $libs++ if $x > 0 && !($board->[$x-1][$y] & $opp);
222 $libs++ if $x < $self->{max} && !($board->[$x+1][$y] & $opp);
223 $libs++ if $y > 0 && !($board->[$x][$y-1] & $opp);
224 $libs++ if $y < $self->{max} && !($board->[$x][$y+1] & $opp);
225
226 if ($libs == 1) {
227 $board->[$x][$y] = $board->[$x][$y] & ~MARK_CIRCLE | (MARK_KO & $nodemask);
228 ($x, $y) = @{$capture[0]};
229 $board->[$x][$y] |= MARK_KO & $nodemask;
230 }
231 }
232 }
233 }
234 } else {
235 $self->{last} = $set & MARK_B ? COLOUR_BLACK : COLOUR_WHITE;
236 }
237 }
238 }
239
240 =item $board->is_valid_move ($colour, $x, $y[, $may_suicide])
241
242 Returns true if the move of the given colour on the given coordinates is
243 valid or not.
244
245 =cut
246
247 sub is_valid_move {
248 my ($self, $colour, $x, $y, $may_suicide) = @_;
249
250 my $board = $self->{board};
251
252 return if $board->[$x][$y] & (MARK_B | MARK_W | MARK_KO);
253
254 if ($may_suicide) {
255 return 1;
256 } else {
257 my ($own, $opp) = $colour == COLOUR_BLACK
258 ? (MARK_B, MARK_W)
259 : (MARK_W, MARK_B);
260
261 # try the move
262 local $board->[$x][$y] = $board->[$x][$y] | $own;
263
264 return 1 if $x > 0 && $board->[$x-1][$y] & $opp && $self->capture ($opp, $x-1, $y, 1);
265 return 1 if $x < $self->{max} && $board->[$x+1][$y] & $opp && $self->capture ($opp, $x+1, $y, 1);
266 return 1 if $y > 0 && $board->[$x][$y-1] & $opp && $self->capture ($opp, $x, $y-1, 1);
267 return 1 if $y < $self->{max} && $board->[$x][$y+1] & $opp && $self->capture ($opp, $x, $y+1, 1);
268
269 return !$self->capture ($own, $x, $y, 1);
270 }
271 }
272
273 1;
274
275 =back
276
277 =head2 AUTHOR
278
279 Marc Lehmann <schmorp@schmorp.de>
280
281 =head2 SEE ALSO
282
283 L<KGS::Protocol>, L<KGS::Game::Tree>, L<Gtk2::GoBoard>.
284
285 =cut
286