File Coverage

perllib/Games/Affenspiel/Board.pm
Criterion Covered Total %
statement 12 207 5.8
branch 0 124 0.0
condition 0 53 0.0
subroutine 4 25 16.0
pod 0 21 0.0
total 16 430 3.7


line stmt bran cond sub pod time code
1             # Games::Affenspiel library, Copyright (C) 2006 Mikhael Goikhman
2             #
3             # This program is free software; you can redistribute it and/or modify
4             # it under the terms of the GNU General Public License as published by
5             # the Free Software Foundation; either version 2 of the License, or
6             # (at your option) any later version.
7             #
8             # This program is distributed in the hope that it will be useful,
9             # but WITHOUT ANY WARRANTY; without even the implied warranty of
10             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11             # GNU General Public License for more details.
12             #
13             # You should have received a copy of the GNU General Public License
14             # along with this program; if not, write to the Free Software
15             # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
16              
17             package Games::Affenspiel::Board;
18              
19 1     1   1135 use strict;
  1         3  
  1         384  
20 1     1   8 use warnings;
  1         14  
  1         83  
21              
22             my $is_pipe = !-t STDOUT;
23              
24             use constant {
25 1         167 GAP => 0,
26             SQUARE1 => 1,
27             VER_BAR => 2,
28             HOR_BAR => 3,
29             SQUARE2 => 4,
30 1     1   6 };
  1         1  
31              
32             use constant {
33 1         3079 UN => 'O',
34             V1 => 'A',
35             V2 => 'V',
36             H1 => '<',
37             H2 => '>',
38             S1 => '/',
39             S2 => '\\',
40             S3 => '[',
41             S4 => ']',
42             GP => ' ',
43             IN => '?',
44 1     1   6 };
  1         1  
45              
46             my $policy = 0;
47              
48             sub set_policy ($) {
49 0   0 0 0   $policy = shift || 0;
50             }
51              
52             sub new ($;$) {
53 0     0 0   my $class = shift;
54 0           my $num = shift;
55              
56 0           return bless([], $class)->reset($num);
57             }
58              
59             sub clone ($) {
60 0     0 0   my $self = shift;
61              
62 0           my $new_board = ref($self)->new;
63 0           $new_board->[$_] = [ @{$self->[$_]} ] for 0 .. 4;
  0            
64              
65 0           return $new_board;
66             }
67              
68             sub reset ($;$) {
69 0     0 0   my $self = shift;
70 0   0       my $num = shift || 0;
71              
72 0 0         if ($num == 1) {
    0          
73 0           $self->[0] = [ GP, S1, S2, GP, ];
74 0           $self->[1] = [ GP, S3, S4, GP, ];
75 0           $self->[2] = [ GP, GP, GP, GP, ];
76 0           $self->[3] = [ GP, GP, GP, GP, ];
77 0           $self->[4] = [ GP, GP, GP, GP, ];
78             }
79             elsif ($num == 2) {
80 0           $self->[0] = [ V1, S1, S2, V1, ];
81 0           $self->[1] = [ V2, S3, S4, V2, ];
82 0           $self->[2] = [ GP, H1, H2, GP, ];
83 0           $self->[3] = [ UN, H1, H2, UN, ];
84 0           $self->[4] = [ UN, H1, H2, UN, ];
85             }
86             else {
87 0           $self->[0] = [ V1, S1, S2, V1, ];
88 0           $self->[1] = [ V2, S3, S4, V2, ];
89 0           $self->[2] = [ GP, H1, H2, GP, ];
90 0           $self->[3] = [ V1, UN, UN, V1, ];
91 0           $self->[4] = [ V2, UN, UN, V2, ];
92             }
93              
94 0           return $self;
95             }
96              
97             sub is_final ($) {
98 0     0 0   my $self = shift;
99              
100             return
101 0   0       $self->get_cell_at([4, 1]) eq S3 &&
102             $self->get_cell_at([4, 2]) eq S4;
103             }
104              
105             sub show ($) {
106 0     0 0   my $self = shift;
107              
108 0   0       my $plain_ascii = $is_pipe || $ENV{DUMB_CHARS} || !$ENV{TERM};
109              
110 0 0         my $v = $plain_ascii ? '|' : "\cNx\cO";
111 0 0         my $h = $plain_ascii ? '-' : "\cNq\cO";
112 0 0         my $ul = $plain_ascii ? '+' : "\cNl\cO";
113 0 0         my $ur = $plain_ascii ? '+' : "\cNk\cO";
114 0 0         my $dl = $plain_ascii ? '+' : "\cNm\cO";
115 0 0         my $dr = $plain_ascii ? '+' : "\cNj\cO";
116              
117 0           print "$ul$h$h$h$h$ur\n";
118 0           foreach my $row (@$self) {
119 0           print "$v";
120 0           print $_ for @$row;
121 0           print "$v\n";
122             }
123 0           print "$dl$h$h$h$h$dr\n";
124              
125 0           return $self;
126             }
127              
128             sub hash ($) {
129 0     0 0   my $self = shift;
130              
131 0 0         return join('', map { map { my $v = $self->get_bar_by_first_cell($_); defined $v ? $v : '' } @$_ } @$self);
  0            
  0            
  0            
132             }
133              
134             sub hash2 ($) {
135 0     0 0   my $self = shift;
136              
137 0           return join('', map { map { $self->get_bar_by_cell($_) } @$_ } @$self);
  0            
  0            
138             }
139              
140             sub stringify_position ($) {
141 0     0 0   my $self = shift;
142 0           my $position = shift;
143              
144 0           return '[' . join(', ', @$position) . ']';
145             }
146              
147             sub get_cell_at ($$) {
148 0     0 0   my $self = shift;
149 0           my $position = shift;
150              
151 0 0 0       return IN
      0        
152             if $position->[0] < 0 || $position->[1] < 0
153             || !$self->[$position->[0]];
154 0   0       return $self->[$position->[0]]->[$position->[1]] || IN;
155             }
156              
157             sub set_cell_at ($$$) {
158 0     0 0   my $self = shift;
159 0           my $position = shift;
160 0           my $value = shift;
161              
162 0 0         die "Incorrect setting out of board at position "
163             . $self->stringify_position($position) . "\n"
164             unless $self->[$position->[0]]->[$position->[1]];
165              
166 0           return $self->[$position->[0]]->[$position->[1]] = $value;
167             }
168              
169             sub get_gap_positions ($) {
170 0     0 0   my $self = shift;
171              
172 0           my @gap_positions;
173 0           for my $y (0 .. 4) {
174 0           for my $x (0 .. 3) {
175 0 0         push @gap_positions, [ $y, $x ] if $self->[$y][$x] eq GP;
176             }
177             }
178              
179 0           return @gap_positions;
180             }
181              
182             sub is_adjacent_positions ($$$) {
183 0     0 0   my $self = shift;
184 0           my $position1 = shift;
185 0           my $position2 = shift;
186              
187 0           my ($y1, $x1) = @$position1;
188 0           my ($y2, $x2) = @$position2;
189              
190             return
191 0 0 0       $x1 == $x2 && abs($y1 - $y2) == 1 ? 'v' :
    0 0        
192             $y1 == $y2 && abs($x1 - $x2) == 1 ? 'h' :
193             undef;
194             }
195              
196             sub is_ver ($) {
197 0     0 0   my $self = shift;
198 0           my $direction = shift;
199              
200 0   0       return $direction eq 'u' || $direction eq 'd';
201             }
202              
203             sub is_hor ($) {
204 0     0 0   my $self = shift;
205 0           my $direction = shift;
206              
207 0   0       return $direction eq 'l' || $direction eq 'r';
208             }
209              
210             sub apply_direction ($$$;$) {
211 0     0 0   my $self = shift;
212 0           my $position = shift;
213 0           my $direction = shift;
214 0   0       my $reverse = shift || 0;
215              
216 0           my $position2 = [ @$position ];
217              
218 0 0         $position2->[0]-- if $direction eq ($reverse ? 'd' : 'u');
    0          
219 0 0         $position2->[0]++ if $direction eq ($reverse ? 'u' : 'd');
    0          
220 0 0         $position2->[1]-- if $direction eq ($reverse ? 'r' : 'l');
    0          
221 0 0         $position2->[1]++ if $direction eq ($reverse ? 'l' : 'r');
    0          
222              
223 0           return $position2;
224             }
225              
226             sub get_bar_by_cell ($$) {
227 0     0 0   my $self = shift;
228 0           my $cell = shift;
229              
230 0 0         return SQUARE1 if $cell eq UN;
231 0 0 0       return VER_BAR if $cell eq V1 || $cell eq V2;
232 0 0 0       return HOR_BAR if $cell eq H1 || $cell eq H2;
233 0 0 0       return SQUARE2 if $cell eq S1 || $cell eq S2 || $cell eq S3 || $cell eq S4;
      0        
      0        
234 0 0         return GAP if $cell eq GP;
235 0           return undef;
236             }
237              
238             sub get_bar_by_first_cell ($$) {
239 0     0 0   my $self = shift;
240 0           my $cell = shift;
241              
242 0 0         return SQUARE1 if $cell eq UN;
243 0 0         return VER_BAR if $cell eq V1;
244 0 0         return HOR_BAR if $cell eq H1;
245 0 0         return SQUARE2 if $cell eq S1;
246 0 0         return GAP if $cell eq GP;
247 0           return undef;
248             }
249              
250             sub move ($$$) {
251 0     0 0   my $self = shift;
252 0           my $gap1_position = shift;
253 0           my $direction = shift;
254              
255 0 0         return undef unless $self->get_cell_at($gap1_position) eq GP;
256              
257 0           my $bar1_position = $self->apply_direction($gap1_position, $direction, 1);
258 0           my $bar1_cell = $self->get_cell_at($bar1_position);
259 0           my $bar = $self->get_bar_by_cell($bar1_cell);
260 0 0         return undef unless $bar;
261              
262 0 0         if ($bar == SQUARE1) {
    0          
    0          
    0          
263 0           $self->set_cell_at($gap1_position, UN);
264 0           $self->set_cell_at($bar1_position, GP);
265             }
266             elsif ($bar == VER_BAR) {
267 0 0         if ($self->is_hor($direction)) {
268 0 0         my $alt_direction = $bar1_cell eq V1 ? 'd' : 'u';
269 0           my $gap2_position = $self->apply_direction($gap1_position, $alt_direction);
270 0           my $bar2_position = $self->apply_direction($bar1_position, $alt_direction);
271 0 0         return undef unless $self->get_cell_at($gap2_position) eq GP;
272 0           my $bar2_cell = $self->get_cell_at($bar2_position);
273 0 0         return undef unless $self->get_bar_by_cell($bar2_cell) eq VER_BAR;
274 0           $self->set_cell_at($gap1_position, $bar1_cell);
275 0           $self->set_cell_at($gap2_position, $bar2_cell);
276 0           $self->set_cell_at($bar1_position, GP);
277 0           $self->set_cell_at($bar2_position, GP);
278             } else {
279 0           my $bar2_position = $self->apply_direction($bar1_position, $direction, 1);
280 0           my $bar2_cell = $self->get_cell_at($bar2_position);
281 0           $self->set_cell_at($gap1_position, $bar1_cell);
282 0           $self->set_cell_at($bar1_position, $bar2_cell);
283 0           $self->set_cell_at($bar2_position, GP);
284             }
285             }
286             elsif ($bar == HOR_BAR) {
287 0 0         if ($self->is_ver($direction)) {
288 0 0         my $alt_direction = $bar1_cell eq H1 ? 'r' : 'l';
289 0           my $gap2_position = $self->apply_direction($gap1_position, $alt_direction);
290 0           my $bar2_position = $self->apply_direction($bar1_position, $alt_direction);
291 0 0         return undef unless $self->get_cell_at($gap2_position) eq GP;
292 0           my $bar2_cell = $self->get_cell_at($bar2_position);
293 0 0         return undef unless $self->get_bar_by_cell($bar2_cell) eq HOR_BAR;
294 0           $self->set_cell_at($gap1_position, $bar1_cell);
295 0           $self->set_cell_at($gap2_position, $bar2_cell);
296 0           $self->set_cell_at($bar1_position, GP);
297 0           $self->set_cell_at($bar2_position, GP);
298             } else {
299 0           my $bar2_position = $self->apply_direction($bar1_position, $direction, 1);
300 0           my $bar2_cell = $self->get_cell_at($bar2_position);
301 0           $self->set_cell_at($gap1_position, $bar1_cell);
302 0           $self->set_cell_at($bar1_position, $bar2_cell);
303 0           $self->set_cell_at($bar2_position, GP);
304             }
305             }
306             elsif ($bar == SQUARE2) {
307 0 0         my $alt_direction = $self->is_ver($direction)
    0          
    0          
    0          
    0          
    0          
    0          
308             ? $bar1_cell eq S1 ? 'r' : $bar1_cell eq S2 ? 'l' : $bar1_cell eq S3 ? 'r' : 'l'
309             : $bar1_cell eq S1 ? 'd' : $bar1_cell eq S2 ? 'd' : $bar1_cell eq S3 ? 'u' : 'u';
310 0           my $gap2_position = $self->apply_direction($gap1_position, $alt_direction);
311 0           my $bar2_position = $self->apply_direction($bar1_position, $alt_direction);
312 0           my $bar3_position = $self->apply_direction($bar1_position, $direction, 1);
313 0           my $bar4_position = $self->apply_direction($bar2_position, $direction, 1);
314 0 0         return undef unless $self->get_cell_at($gap2_position) eq GP;
315 0           my $bar2_cell = $self->get_cell_at($bar2_position);
316 0           my $bar3_cell = $self->get_cell_at($bar3_position);
317 0           my $bar4_cell = $self->get_cell_at($bar4_position);
318 0 0         return undef unless $self->get_bar_by_cell($bar2_cell) eq SQUARE2;
319 0           $self->set_cell_at($gap1_position, $bar1_cell);
320 0           $self->set_cell_at($gap2_position, $bar2_cell);
321 0           $self->set_cell_at($bar1_position, $bar3_cell);
322 0           $self->set_cell_at($bar2_position, $bar4_cell);
323 0           $self->set_cell_at($bar3_position, GP);
324 0           $self->set_cell_at($bar4_position, GP);
325             }
326              
327 0 0         print "$direction -> ", $self->stringify_position($gap1_position), "\n"
328             if $ENV{DEBUG_MOVES};
329              
330 0           return $bar;
331             }
332              
333             sub choose_random_move ($) {
334 0     0 0   my $self = shift;
335              
336 0           my @gap_positions = $self->get_gap_positions;
337 0           my ($bar, $gap_position, $direction);
338              
339 0           until (defined($bar = $self->move(
340             $gap_position = $gap_positions[int(rand(scalar @gap_positions))],
341             $direction = ['u', 'd', 'l', 'r']->[int(rand(4))]
342             ))) {}
343              
344 0           return ($bar, $gap_position, $direction);
345             }
346              
347             sub expand_valid_moves ($) {
348 0     0 0   my $self = shift;
349              
350 0           my @gap_positions = $self->get_gap_positions;
351 0           my @move_infos = ();
352 0           my $included_boards = {};
353              
354 0           for my $gap_position (@gap_positions) {
355 0           for my $direction ('u', 'd', 'l', 'r') {
356 0           my $board = $self->clone;
357 0           my $bar = $board->move($gap_position, $direction);
358 0 0         next unless $bar;
359 0           my $hash = $board->hash;
360 0 0         next if $included_boards->{$hash};
361 0           $included_boards->{$hash} = 1;
362 0           push @move_infos, [ $bar, $gap_position, $direction, $board ];
363             }
364             }
365              
366 0 0 0       @move_infos = sort { $b->[0] <=> $a->[0] } @move_infos
  0            
367             if $policy == 2 || $policy == 3;
368 0 0 0       @move_infos = reverse @move_infos
369             if $policy == 1 || $policy == 3;
370 0 0         @move_infos = sort { rand(2) < 1 ? 1 : -1 } @move_infos
  0 0          
371             if $policy == -1;
372              
373 0           return \@move_infos;
374             }
375              
376             1;