File Coverage

blib/lib/AI/Evolve/Befunge/Physics/othello.pm
Criterion Covered Total %
statement 104 104 100.0
branch 56 56 100.0
condition 6 6 100.0
subroutine 16 16 100.0
pod 9 9 100.0
total 191 191 100.0


line stmt bran cond sub pod time code
1             package AI::Evolve::Befunge::Physics::othello;
2 1     1   7 use strict;
  1         2  
  1         47  
3 1     1   6 use warnings;
  1         2  
  1         47  
4 1     1   6 use Carp;
  1         2  
  1         85  
5 1     1   7 use Language::Befunge::Vector;
  1         2  
  1         14  
6              
7 1     1   23 use AI::Evolve::Befunge::Util;
  1         1  
  1         11  
8 1     1   187 use AI::Evolve::Befunge::Physics qw(register_physics);
  1         1  
  1         6  
9 1     1   105 use base 'AI::Evolve::Befunge::Physics';
  1         2  
  1         1452  
10              
11             my @valid_dirs = (v(-1,-1),v(0,-1),v(1,-1),v(-1,0),v(1,0),v(-1,1),v(0,1),v(1,1));
12              
13              
14             =head1 NAME
15             AI::Evolve::Befunge::Physics::othello - an othello game
16              
17              
18             =head1 SYNOPSIS
19              
20             my $physics = AI::Evolve::Befunge::Physics->new('othello');
21              
22              
23             =head1 DESCRIPTION
24              
25             This is an implementation of the "othello" board game ruleset. This
26             game is also known to some as "reversi". It is implemented as a
27             plugin for the AI::Evolve::Befunge Physics system; essentially an AI
28             creature exists in an "othello" universe, and plays by its rules.
29              
30              
31             =head1 CONSTRUCTOR
32              
33             Use AI::Evolve::Befunge::Physics->new() to get an othello object;
34             there is no constructor in this module for you to call directly.
35              
36              
37             =head1 METHODS
38              
39             =head2 setup_board
40              
41             $othello->setup_board($board);
42              
43             Initialize the board to its default state. For othello, this looks
44             like:
45              
46             ........
47             ........
48             ........
49             ...xo...
50             ...ox...
51             ........
52             ........
53             ........
54              
55             =cut
56              
57             sub setup_board {
58 1     1 1 5 my ($self, $board) = @_;
59 1         6 $board->clear();
60 1         5 $board->set_value(v(3, 3), 1);
61 1         21 $board->set_value(v(3, 4), 2);
62 1         10 $board->set_value(v(4, 3), 2);
63 1         5 $board->set_value(v(4, 4), 1);
64             }
65              
66              
67             =head2 in_bounds
68              
69             die("out of bounds") unless $othello->in_bounds($vec);
70              
71             Returns 1 if the vector is within the playspace, and 0 otherwise.
72              
73             =cut
74              
75             sub in_bounds {
76 12311     12311 1 28840 my($self, $vec) = @_;
77 12311 100       34762 confess("vec undefined") unless defined $vec;
78 12310         23089 foreach my $d (0..1) {
79 23359 100       84295 return 0 unless $vec->get_component($d) >= 0;
80 21937 100       92511 return 0 unless $vec->get_component($d) <= 7;
81             }
82 9777         33863 return 1;
83             }
84              
85              
86             =head2 try_move_vector
87              
88             my $score = $othello->try_move_vector($board, $player, $pos, $dir);
89              
90             Determines how many flippable enemy pieces exist in the given
91             direction. This is a lowlevel routine, meant to be called by
92             the valid_move() and make_move() methods, below.
93              
94             =cut
95              
96             sub try_move_vector {
97 11465     11465 1 20921 my ($self, $board, $player, $pos, $vec) = @_;
98 11465 100       37484 return 0 if $board->fetch_value($pos);
99 11464         17853 my $rv = 0;
100 11464         34132 $pos += $vec;
101 11464         199249 while($self->in_bounds($pos)) {
102 9773         34430 my $val = $board->fetch_value($pos);
103 9773 100       43248 return 0 unless $val;
104 1342 100       4587 return $rv if $val == $player;
105 841         1304 $rv++;
106 841         2305 $pos += $vec;
107             }
108 2532         8056 return 0;
109             }
110              
111              
112             =head2 valid_move
113              
114             $next_player = $othello->make_move($board, $player, $pos)
115             if $othello->valid_move($board, $player, $pos);
116              
117             If the move is valid, returns the number of pieces which would be
118             flipped by moving in the given position. Returns 0 otherwise.
119              
120             =cut
121              
122             sub valid_move {
123 1658     1658 1 3429 my ($self, $board, $player, $v) = @_;
124 1658 100       4504 confess "board is not a ref!" unless ref $board;
125 1657 100 100     8364 confess "Usage: valid_move(self,board,player,v)"
126             unless defined($player) && defined($v);
127 1655 100       4438 confess("$v is not a vector argument") unless ref($v) eq 'Language::Befunge::Vector';
128 1654 100       5538 return 0 if $board->fetch_value($v);
129 1429         2329 my $rv = 0;
130 1429         2649 foreach my $vec (@valid_dirs) {
131 11432         28632 $rv += $self->try_move_vector($board,$player,$v,$vec);
132             }
133 1429         5381 return $rv;
134             }
135              
136              
137             =head2 won
138              
139             my $winner = $othello->won($board);
140              
141             If the game has been won, returns the player who won. Returns 0
142             otherwise.
143              
144             =cut
145              
146             sub won {
147 10     10 1 31 my ($self, $board) = @_;
148 10         36 my ($p1, $p2) = (0,0);
149 10         35 foreach my $y (0..7) {
150 60         143 foreach my $x (0..7) {
151 459         1601 my $v = v($x, $y);
152 459 100       1377 return 0 if $self->valid_move($board,1,$v);
153 458 100       1258 return 0 if $self->valid_move($board,2,$v);
154 455 100       1645 if($board->fetch_value($v) == 1) {
    100          
155 27         83 $p1++;
156             } elsif($board->fetch_value($v)) {
157 27         81 $p2++;
158             }
159             }
160             }
161 6 100       43 unless($p1) {
162 1         21 return 2;
163             }
164 5 100       25 unless($p2) {
165 2         26 return 1;
166             }
167 3 100       16 return 0 if $p1 == $p2;
168 2 100       26 return $p2 < $p1 ? 1 : 2;
169             }
170              
171              
172             =head2 over
173              
174             my $over = $othello->over($board);
175              
176             Returns 1 if no more moves are valid from either player, and returns
177             0 otherwise.
178              
179             =cut
180              
181             sub over {
182 3     3 1 11 my ($self, $board) = @_;
183 3         8 my ($p1, $p2) = (0,0);
184 3         12 foreach my $y (0..7) {
185 14         37 foreach my $x (0..7) {
186 101 100       389 return 0 if $self->valid_move($board,1,v($x,$y));
187 100 100       428 return 0 if $self->valid_move($board,2,v($x,$y));
188             }
189             }
190 1         11 return 1;
191             }
192              
193              
194             =head2 score
195              
196             my $score = $othello->score($board, $player, $number_of_moves);
197              
198             Returns the number of pieces on the board owned by the given player.
199              
200             =cut
201              
202             sub score {
203 4     4 1 14 my ($self, $board, $player, $moves) = @_;
204 4         8 my $mine = 0;
205 4         13 foreach my $y (0..7) {
206 32         70 foreach my $x (0..7) {
207 256 100       807 if($board->fetch_value(v($x, $y)) == $player) {
208 11         31 $mine++;
209             }
210             }
211             }
212 4         27 return $mine;
213             }
214              
215              
216             =head2 can_pass
217              
218             my $can_pass = $othello->can_pass($board, $player);
219              
220             Returns 1 if the player can pass, and 0 otherwise. For the othello
221             rule set, passing is only allowed if no valid moves are available.
222              
223             =cut
224              
225             sub can_pass {
226 6     6 1 18 my ($self,$board,$player) = @_;
227 6         14 my $possible_points = 0;
228 6         16 foreach my $y (0..7) {
229 48         97 foreach my $x (0..7) {
230 384         1426 $possible_points += valid_move($self,$board,$player,v($x,$y));
231             }
232             }
233 6 100       80 return $possible_points ? 0 : 1;
234             }
235              
236              
237             =head2 make_move
238              
239             $othello->make_move($board, $player, $pos);
240              
241             Makes the indicated move, updates the board with the new piece and
242             flips enemy pieces as necessary.
243              
244             =cut
245              
246             sub make_move {
247 8     8 1 46 my ($self, $board, $player, $pos) = @_;
248 8 100 100     80 confess "make_move: player value '$player' out of range!" if $player < 1 or $player > 2;
249 6 100       40 confess "make_move: vector is undef!" unless defined $pos;
250 5 100       19 confess "make_move: vector '$pos' out of range!" unless $self->in_bounds($pos);
251 4         10 foreach my $vec (@valid_dirs) {
252 32         121 my $num = $self->try_move_vector($board,$player,$pos,$vec);
253 32         101 my $cur = $pos + $vec;
254 32         459 for(1..$num) {
255 4         23 $board->set_value($cur, $player);
256 4         14 $cur += $vec;
257             }
258             }
259 4         20 $board->set_value($pos, $player);
260 4 100       23 return 0 if $self->won($board); # game over, one of the players won
261 3 100       21 return 3-$player unless $self->can_pass($board,3-$player); # normal case, other player's turn
262 2 100       11 return $player unless $self->can_pass($board,$player); # player moves again
263 1         13 return 0; # game over, tie game
264             }
265              
266              
267             register_physics(
268             name => "othello",
269             token => ord('O'),
270             decorate => 1,
271             board_size => v(8, 8),
272             commands => {
273             M => \&AI::Evolve::Befunge::Physics::op_make_board_move,
274             T => \&AI::Evolve::Befunge::Physics::op_query_tokens
275             },
276             );
277              
278             1;