File Coverage

blib/lib/Games/AlphaBeta/Reversi.pm
Criterion Covered Total %
statement 207 210 98.5
branch 44 48 91.6
condition 235 266 88.3
subroutine 9 9 100.0
pod 5 5 100.0
total 500 538 92.9


line stmt bran cond sub pod time code
1             package Games::AlphaBeta::Reversi;
2 2     2   21557 use base qw(Games::AlphaBeta::Position);
  2         3  
  2         940  
3 2     2   9 use Carp;
  2         5  
  2         86  
4              
5 2     2   8 use strict;
  2         3  
  2         42  
6 2     2   8 use warnings;
  2         3  
  2         3877  
7              
8             our $VERSION = '0.1.5';
9              
10             =head1 NAME
11              
12             Games::AlphaBeta::Reversi - Reversi position class for use with
13             Games::AlphaBeta
14              
15             =head1 SYNOPSIS
16              
17             package My::Reversi;
18             use base qw(Games::AlphaBeta::Reversi);
19              
20             # implement drawing routine
21             sub draw { ... }
22              
23             package main;
24             use My::Reversi;
25             use Games::AlphaBeta;
26              
27             my ($p, $g);
28             $p = My::Reversi->new;
29             $g = Games::AlphaBeta->new($p);
30              
31             while ($p = $g->abmove) {
32             $p->draw;
33             }
34              
35             =head1 DESCRIPTION
36              
37             This module implements a position-object suitable for use with
38             L. It inherits from the
39             L base class, so be sure to read its
40             documentation. The methods implemented there will not be
41             described here.
42              
43              
44             =head1 METHODS
45              
46             =over 4
47              
48             =item init()
49              
50             Initialize the initial state. Call SUPER::init(@_) to do part of
51             the work.
52              
53             =cut
54              
55             sub init {
56 2     2 1 9 my $self = shift;
57              
58 2   50     22 my $size = shift || 8;
59 2         10 my $half = abs($size / 2);
60 2         22 my %config = (
61             player => 1,
62             size => $size,
63             board => undef,
64             );
65              
66             # Create a blank board
67 2         7 $size--;
68 2         9 for my $x (0 .. $size) {
69 16         18 for my $y (0 .. $size) {
70 128         189 $config{board}[$x][$y] = 0;
71             }
72             }
73              
74             # Put initial pieces on board
75 2         7 $config{board}[$size - $half][$size - $half] = 1;
76 2         7 $config{board}[$half][$half] = 1;
77 2         29 $config{board}[$size - $half][$half] = 2;
78 2         9 $config{board}[$half][$size - $half] = 2;
79              
80 2         28 @$self{keys %config} = values %config;
81              
82 2 50       33 $self->SUPER::init(@_) or croak "failed to call SUPER:init()";
83 2         9 return $self;
84             }
85              
86             =item as_string
87              
88             Return a plain-text representation of the current game position
89             as a string.
90              
91             =cut
92              
93             sub as_string {
94 61     61 1 363 my $self = shift;
95              
96             # Header
97 61         149 my ($c, $str) = "a";
98 61         606 $str .= " " . $c++ for (1 .. $self->{size});
99 61         424 $str = sprintf(" %s\n", $str);
100 61         397 $str .= sprintf(" +%s\n", "--" x $self->{size});
101              
102             # Actual board (with numbers down the left side)
103 61         104 my $i;
104 61         77 for (@{$self->{board}}) {
  61         147  
105 488         1411 for (join " ", @$_) {
106 488         604 tr/012/.ox/;
107 488         1586 $str .= sprintf("%2d | %s\n", ++$i, $_);
108             }
109             }
110            
111             # Footer
112 61         197 $str .= "Player " . $self->{player} . " to move.\n";
113 61         224 return $str;
114             }
115              
116              
117             =item findmoves [$own_call]
118              
119             Return an array of all legal moves at the current position for
120             the current player.
121              
122             If $own_call is true, we have been recursively called by ourself
123             to find out if the other player could move. If neither player can
124             move, return undef to denote this as an ending position.
125             Otherwise return a pass move.
126              
127             =cut
128              
129             sub findmoves {
130 5060     5060 1 7004 my ($self, $own_call) = @_;
131              
132 5060         7254 my $b = $self->{board};
133 5060         6820 my $size = $self->{size};
134 5060         4561 my @moves;
135              
136 5060         9045 for my $x (0 .. $size - 1) {
137 40480         71652 INNER: for my $y (0 .. $size - 1) {
138 323840 100       638275 unless ($b->[$x][$y]) {
139             # Define some convenient names.
140 171410         240211 my $me = $self->{player};
141 171410         193396 my $not_me = 3 - $me;
142              
143 171410         160864 my ($tx, $ty);
144              
145             # Check left
146 171410   100     759007 for ($tx = $x - 1; $tx >= 0 && $b->[$tx][$y] == $not_me; $tx--) {
147             ;
148             }
149 171410 100 100     659346 if ($tx >= 0 && $tx != $x - 1 && $b->[$tx][$y] == $me) {
      100        
150 8709         16384 push @moves, [$x, $y];
151 8709         14982 next INNER;
152             }
153              
154             # Check right
155 162701   100     683008 for ($tx = $x + 1; $tx < $size && $b->[$tx][$y] == $not_me; $tx++) {
156             ;
157             }
158 162701 100 100     597676 if ($tx < $size && $tx != $x + 1 && $b->[$tx][$y] == $me) {
      100        
159 5231         10690 push @moves, [$x, $y];
160 5231         9201 next INNER;
161             }
162              
163             # Check up
164 157470   100     617311 for ($ty = $y - 1; $ty >= 0 && $b->[$x][$ty] == $not_me; $ty--) {
165             ;
166             }
167 157470 100 100     538329 if ($ty >= 0 && $ty != $y - 1 && $b->[$x][$ty] == $me) {
      100        
168 7549         14980 push @moves, [$x, $y];
169 7549         20538 next INNER;
170             }
171              
172             # Check down
173 149921   100     621931 for ($ty = $y + 1; $ty < $size && $b->[$x][$ty] == $not_me; $ty++) {
174             ;
175             }
176 149921 100 100     576926 if ($ty < $size && $ty != $y + 1 && $b->[$x][$ty] == $me) {
      100        
177 9470         19169 push @moves, [$x, $y];
178 9470         17843 next INNER;
179             }
180              
181             # Check up/left
182 140451         139634 $tx = $x - 1;
183 140451         132737 $ty = $y - 1;
184 140451   100     666576 while ($tx >= 0 && $ty >= 0 && $b->[$tx][$ty] == $not_me) {
      100        
185 18598         18357 $tx--;
186 18598         100489 $ty--;
187             }
188 140451 100 100     676120 if ($tx >= 0 && $ty >= 0 && $tx != $x - 1 && $ty != $y - 1 &&
      100        
      66        
      100        
189             $b->[$tx][$ty] == $me) {
190 5716         11735 push @moves, [$x, $y];
191 5716         12782 next INNER;
192             }
193              
194              
195             # Check up/right
196 134735         134624 $tx = $x - 1;
197 134735         138984 $ty = $y + 1;
198 134735   100     651736 while ($tx >= 0 && $ty < $size && $b->[$tx][$ty] == $not_me) {
      100        
199 26715         24731 $tx--;
200 26715         134172 $ty++;
201             }
202 134735 100 100     654903 if ($tx >= 0 && $ty < $size && $tx != $x - 1 && $ty != $y + 1 &&
      100        
      66        
      100        
203             $b->[$tx][$ty] == $me) {
204 6505         13883 push @moves, [$x, $y];
205 6505         11968 next INNER;
206             }
207              
208             # Check down/right
209 128230         143456 $tx = $x + 1;
210 128230         124339 $ty = $y + 1;
211 128230   100     620141 while ($tx < $size && $ty < $size && $b->[$tx][$ty] == $not_me) {
      100        
212 15261         20961 $tx++;
213 15261         78879 $ty++;
214             }
215 128230 100 100     643495 if ($tx < $size && $ty < $size && $tx != $x + 1 && $ty != $y + 1 &&
      100        
      66        
      100        
216             $b->[$tx][$ty] == $me) {
217 3862         8401 push @moves, [$x, $y];
218 3862         7504 next INNER;
219             }
220              
221             # Check down/left
222 124368         112718 $tx = $x + 1;
223 124368         126023 $ty = $y - 1;
224 124368   100     582020 while ($tx < $size && $ty >= 0 && $b->[$tx][$ty] == $not_me) {
      100        
225 16798         15969 $tx++;
226 16798         88833 $ty--;
227             }
228 124368 100 100     653293 if ($tx < $size && $ty >= 0 && $tx != $x + 1 && $ty != $y - 1 &&
      100        
      66        
      100        
229             $b->[$tx][$ty] == $me) {
230 4736         10370 push @moves, [$x, $y];
231 4736         10879 next INNER;
232             }
233             }
234             }
235             }
236              
237             # If current player cannot move, check if other player can
238             # move. If she can't, the game is over. If she can, let the
239             # current player do a pass move.
240 5060 100 100     13111 unless (@moves || $own_call) {
241 25         87 $self->player(3 - $self->player);
242 25 100       74 if ($self->findmoves(1)) {
243 17         29 @moves = undef;
244             }
245 25         78 $self->player(3 - $self->player);
246             }
247              
248 5060         29053 return @moves;
249             }
250              
251              
252             =item evaluate
253              
254             Evaluate a game position and return its fitness value.
255              
256             =cut
257              
258             sub evaluate {
259 2241     2241 1 3073 my $self = shift;
260 2241         3020 my $player = $self->{player};
261 2241         2201 my ($me, $not_me);
262              
263 2241         5251 $me = scalar $self->findmoves;
264 2241         4345 $self->{player} = 3 - $player;
265 2241         8352 $not_me = scalar $self->findmoves;
266 2241         3970 $self->{player} = $player;
267              
268 2241         13561 return $me - $not_me;
269             }
270              
271              
272             =item apply $move
273              
274             Apply a move to the current position, transforming it into the
275             next position. Return reference to itself on succes, undef on
276             error.
277              
278             =cut
279              
280             sub apply ($) {
281 2792     2792 1 5423 my ($self, $move) = @_;
282              
283 2792         4569 my $me = $self->{player};
284 2792         5895 my $not_me = 3 - $self->{player};
285              
286             # null or pass move
287 2792 100       6197 unless ($move) {
288 5         11 $self->{player} = $not_me;
289 5         19 return $self;
290             }
291              
292 2787         4054 my $size = $self->{size};
293 2787         4063 my $b = $self->{board};
294 2787         4106 my ($x, $y) = @$move;
295              
296 2787         2695 my ($tx, $ty, $flipped);
297              
298             # slot must not be outside the board, or already occupied
299 2787 50 33     25965 if ($x < 0 || $x >= $size || $y < 0 || $y >= $size) {
    50 33        
      33        
300 0         0 return undef;
301             }
302             elsif ($b->[$x][$y]) {
303 0         0 return undef;
304             }
305              
306             # left
307 2787   100     18086 for ($tx = $x - 1; $tx >= 0 && $b->[$tx][$y] == $not_me; $tx--) {
308             ;
309             }
310 2787 100 100     12821 if ($tx >= 0 && $tx != $x - 1 && $b->[$tx][$y] == $me) {
      100        
311 397         547 $tx = $x - 1;
312 397   66     1954 while ($tx >= 0 && $b->[$tx][$y] == $not_me) {
313 716         895 $b->[$tx][$y] = $me;
314 716         2807 $tx--;
315             }
316 397         550 $flipped++;
317             }
318              
319             # right
320 2787   100     16616 for ($tx = $x + 1; $tx < $size && $b->[$tx][$y] == $not_me; $tx++) {
321             ;
322             }
323 2787 100 100     12683 if ($tx < $size && $tx != $x + 1 && $b->[$tx][$y] == $me) {
      100        
324 402         434 $tx = $x + 1;
325 402   66     1680 while ($tx < $size && $b->[$tx][$y] == $not_me) {
326 811         1198 $b->[$tx][$y] = $me;
327 811         2944 $tx++;
328             }
329 402         513 $flipped++;
330             }
331              
332             # up
333 2787   100     15966 for ($ty = $y - 1; $ty >= 0 && $b->[$x][$ty] == $not_me; $ty--) {
334             ;
335             }
336 2787 100 100     13540 if ($ty >= 0 && $ty != $y - 1 && $b->[$x][$ty] == $me) {
      100        
337 471         851 $ty = $y - 1;
338 471   66     2254 while ($ty >= 0 && $b->[$x][$ty] == $not_me) {
339 667         928 $b->[$x][$ty] = $me;
340 667         2495 $ty--;
341             }
342 471         653 $flipped++;
343             }
344            
345             # down
346 2787   100     17810 for ($ty = $y + 1; $ty < $size && $b->[$x][$ty] == $not_me; $ty++) {
347             ;
348             }
349 2787 100 100     12291 if ($ty < $size && $ty != $y + 1 && $b->[$x][$ty] == $me) {
      100        
350 667         1054 $ty = $y + 1;
351 667   66     3177 while ($ty < $size && $b->[$x][$ty] == $not_me) {
352 1252         1578 $b->[$x][$ty] = $me;
353 1252         4315 $ty++;
354             }
355 667         920 $flipped++;
356             }
357            
358             # up/left
359 2787         3599 $tx = $x - 1;
360 2787         2857 $ty = $y - 1;
361 2787   100     18351 while ($tx >= 0 && $ty >= 0 && $b->[$tx][$ty] == $not_me) {
      100        
362 806         823 $tx--;
363 806         4140 $ty--;
364             }
365 2787 100 100     17360 if ($tx >= 0 && $ty >= 0 && $tx != $x - 1 && $ty != $y - 1 &&
      100        
      66        
      100        
366             $b->[$tx][$ty] == $me) {
367 345         543 $tx = $x - 1;
368 345         398 $ty = $y - 1;
369 345   33     2434 while ($tx >= 0 && $ty >= 0 && $b->[$tx][$ty] == $not_me) {
      66        
370 574         839 $b->[$tx][$ty] = $me;
371 574         527 $tx--;
372 574         3079 $ty--;
373             }
374 345         513 $flipped++;
375             }
376              
377             # up/right
378 2787         3072 $tx = $x - 1;
379 2787         2871 $ty = $y + 1;
380 2787   100     15866 while ($tx >= 0 && $ty < $size && $b->[$tx][$ty] == $not_me) {
      100        
381 1164         2466 $tx--;
382 1164         6011 $ty++;
383             }
384 2787 100 100     16842 if ($tx >= 0 && $ty < $size && $tx != $x - 1 && $ty != $y + 1 &&
      100        
      66        
      100        
385             $b->[$tx][$ty] == $me) {
386 570         666 $tx = $x - 1;
387 570         583 $ty = $y + 1;
388 570   33     3305 while ($tx >= 0 && $ty < $size && $b->[$tx][$ty] == $not_me) {
      66        
389 776         1264 $b->[$tx][$ty] = $me;
390 776         784 $tx--;
391 776         3888 $ty++;
392             }
393 570         616 $flipped++;
394             }
395            
396             # down/right
397 2787         3954 $tx = $x + 1;
398 2787         3238 $ty = $y + 1;
399 2787   100     16101 while ($tx < $size && $ty < $size && $b->[$tx][$ty] == $not_me) {
      100        
400 1290         1207 $tx++;
401 1290         6636 $ty++;
402             }
403 2787 100 100     16457 if ($tx < $size && $ty < $size && $tx != $x + 1 && $ty != $y + 1 &&
      100        
      66        
      100        
404             $b->[$tx][$ty] == $me) {
405 467         610 $tx = $x + 1;
406 467         483 $ty = $y + 1;
407 467   33     2837 while ($tx < $size && $ty < $size && $b->[$tx][$ty] == $not_me) {
      66        
408 804         1017 $b->[$tx][$ty] = $me;
409 804         701 $tx++;
410 804         4228 $ty++;
411             }
412 467         558 $flipped++;
413             }
414              
415             # down/left
416 2787         3003 $tx = $x + 1;
417 2787         2968 $ty = $y - 1;
418 2787   100     16287 while ($tx < $size && $ty >= 0 && $b->[$tx][$ty] == $not_me) {
      100        
419 945         985 $tx++;
420 945         5456 $ty--;
421             }
422 2787 100 100     18377 if ($tx < $size && $ty >= 0 && $tx != $x + 1 && $ty != $y - 1 &&
      100        
      66        
      100        
423             $b->[$tx][$ty] == $me) {
424 430         649 $tx = $x + 1;
425 430         463 $ty = $y - 1;
426 430   33     2642 while ($tx < $size && $ty >= 0 && $b->[$tx][$ty] == $not_me) {
      66        
427 580         757 $b->[$tx][$ty] = $me;
428 580         561 $tx++;
429 580         3110 $ty--;
430             }
431 430         637 $flipped++;
432             }
433              
434 2787 50       6081 unless ($flipped) {
435 0         0 return undef;
436             }
437              
438 2787         4744 $b->[$x][$y] = $me;
439 2787         3907 $self->{player} = $not_me;
440              
441 2787         10713 return $self;
442             }
443              
444             =back
445              
446             =head1 BUGS
447              
448             The C method is too slow. This method is critical to
449             performance when running under Games::AlphaBeta, as more than 60%
450             of the execution time is spent there (when searching to ply 3).
451             Both the C and C routines use
452             C internally, so by speeding this routine up we
453             could gain a lot of speed.
454              
455              
456             =head1 SEE ALSO
457              
458             The author's website, describing this and other projects:
459             L
460              
461              
462             =head1 AUTHOR
463              
464             Stig Brautaset, Estig@brautaset.orgE
465              
466             =head1 COPYRIGHT AND LICENCE
467              
468             Copyright (C) 2004 by Stig Brautaset
469              
470             This library is free software; you can redistribute it and/or modify
471             it under the same terms as Perl itself, either Perl version 5.8.3 or,
472             at your option, any later version of Perl 5 you may have available.
473              
474             =cut
475              
476             # vim: shiftwidth=4 tabstop=4 softtabstop=4 expandtab