File Coverage

blib/lib/Chess/Game.pm
Criterion Covered Total %
statement 558 644 86.6
branch 220 324 67.9
condition 52 67 77.6
subroutine 39 41 95.1
pod 10 16 62.5
total 879 1092 80.4


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Chess::Game - a class to record and validate the moves of a game of chess
4              
5             =head1 SYNOPSIS
6              
7             use Chess::Game;
8              
9             $game = Chess::Game->new();
10             $clone = $game->clone();
11             $move = $game->make_move("e2", "e4");
12             $move_c = $clone->make_move("e2", "e4");
13             $true = ($move->get_piece() ne $move_c->get_piece());
14             $move = $game->delete_move();
15             ...
16             while (!defined($result = $game->result())) {
17             # get a move
18             $move = $game->make_move($sq1, $sq2);
19             if (!defined($move)) {
20             print $game->get_message();
21             }
22             }
23             if ($result == 1) {
24             print "White wins!\n";
25             }
26             elsif ($result == 0) {
27             print "Draw!\n"
28             }
29             else {
30             print "Black wins!\n";
31             }
32              
33             =head1 DESCRIPTION
34              
35             The Chess module provides a framework for writing chess programs with Perl.
36             This class forms part of that framework, providing move validation for all
37             moves recorded using the Chess::Game class. The Game contains a
38             L, 32 Ls and a L
39             that contains a series of Ls that record the exact
40             state of the game as it progresses. Moves can be taken back one-at-a-time to
41             allow for simple movelist manipulation.
42              
43             =head1 METHODS
44              
45             =head2 Construction
46              
47             =item new()
48              
49             Takes two optional parameters containing optional names for the players. If
50             none are provided, the player names 'white' and 'black' are used. Creates a
51             new L and places 16 L per player and initializes
52             an empty L.
53              
54             =head2 Class methods
55              
56             There are no class methods for this class.
57              
58             =head2 Object methods
59              
60             =item clone()
61              
62             Takes no parameters. Returns a new blessed Chess::Game reference in an
63             identical state to the calling object, but which can be manipulated entirely
64             separately.
65              
66             =item is_move_legal()
67              
68             Takes two parameters containing the name of the square to move from and the
69             name of the square to move to. They should be validated with
70             L prior to calling. Returns true if the
71             provided move is legal within the context of the current game.
72              
73             =item make_move()
74              
75             Takes two parameters containing the name of the square to move from and the
76             name of the square to move to. They should be validated with
77             L before calling. Optionally takes a third
78             parameter, which can be set to zero to indicate that no legality
79             checking should be done. B
80             captures or castling will not be set!> Only by entirely validating the move
81             do these flags have any meaning. The default is to validate every move. Returns
82             a L representing the move just made.
83              
84             =item get_message()
85              
86             Takes no parameters. Returns the message containing the reason L
87             or L returned false, such as "Can't castle out of check".
88              
89             =item delete_move()
90              
91             Takes no parameters. Returns a L representing the
92             last move made, and sets the state of the game to what it was prior to the
93             returned move being made.
94              
95             =item player_in_check()
96              
97             Takes a single parameter containing the name of the player to consider. Returns
98             true if the named player is in check.
99              
100             =item player_checkmated()
101              
102             Takes a single parameter containing the name of the player to consider. Returns
103             true if the named player has been checkmated.
104              
105             =item player_stalemated()
106              
107             Takes a single parameter containing the name of the player to consider. Returns
108             true if the named player has been stalemated.
109              
110             =item result()
111              
112             Takes no parameters. Returns C as long as the game is in progress. When
113             a conclusion has been reached, returns 1 if the first player checkmated the
114             second player, 0 if either player has been stalemated, or -1 if the second
115             player checkmated the first player. Is not currently able to determine if the
116             game was drawn by a three-fold repetition of positions.
117              
118             =item do_promotion()
119              
120             Takes one parameters. If the last move was a promotion (as determined by a call
121             to L, then calling this function
122             will change the newly promoted pawn to the piece specified by the provided
123             parameter. Valid values are (case-insensitive) "bishop", "knight", "queen"
124             and "rook".
125              
126             =head1 DIAGNOSTICS
127              
128             =over 4
129              
130             =item Invalid Chess::Game reference
131              
132             The program contains a reference to a Chess::Game not obtained through
133             L or L. Ensure the program only uses these methods to
134             create Chess::Game references, and the the reference refers to a defined
135             value.
136              
137             =item Invalid square 'q9'
138              
139             The program made a call to make_move() or is_move_legal() with invalid squares.
140             Ensure that all variables containing squares are validated with
141             L.
142              
143             =back
144              
145             =head1 BUGS
146              
147             The framework is not currently able to determine when a game has been drawn
148             by three-fold repetition of position. Please report any other bugs to the
149             author.
150              
151             =head1 AUTHOR
152              
153             Brian Richardson
154              
155             =head1 COPYRIGHT
156              
157             Copyright (c) 2002, 2005 Brian Richardson. All rights reserved. This module is
158             Free Software. It may be modified and redistributed under the same terms as
159             Perl itself.
160              
161             =cut
162             package Chess::Game;
163              
164 3     3   57365 use Chess::Board;
  3         8  
  3         93  
165 3     3   2079 use Chess::Piece;
  3         7  
  3         75  
166 3     3   1588 use Chess::Piece::Pawn;
  3         8  
  3         67  
167 3     3   3034 use Chess::Piece::Knight;
  3         9  
  3         78  
168 3     3   1889 use Chess::Piece::Bishop;
  3         7  
  3         75  
169 3     3   1786 use Chess::Piece::Rook;
  3         7  
  3         74  
170 3     3   1806 use Chess::Piece::Queen;
  3         8  
  3         83  
171 3     3   1930 use Chess::Piece::King;
  3         8  
  3         75  
172 3     3   1913 use Chess::Game::MoveList;
  3         11  
  3         105  
173 3     3   29 use Chess::Game::MoveListEntry;
  3         258  
  3         75  
174 3     3   18 use Carp;
  3         7  
  3         401  
175 3     3   22 use strict;
  3         6  
  3         171  
176              
177 3         207 use constant OBJECT_DATA => (
178             _player_has_moves => undef,
179             _captures => undef,
180             _kings => undef,
181             board => undef,
182             players => undef,
183             movelist => undef,
184             pieces => undef,
185             message => ''
186 3     3   19 );
  3         7  
187              
188             # same as Chess::Game::MoveListEntry
189 3     3   18 use constant MOVE_CAPTURE => 0x01;
  3         6  
  3         168  
190 3     3   18 use constant MOVE_CASTLE_SHORT => 0x02;
  3         8  
  3         134  
191 3     3   17 use constant MOVE_CASTLE_LONG => 0x04;
  3         221  
  3         141  
192 3     3   16 use constant MOVE_EN_PASSANT => 0x08;
  3         7  
  3         191  
193 3     3   17 use constant MOVE_PROMOTE => 0x10;
  3         5  
  3         41147  
194              
195             sub _add_pieces {
196 48     48   89 my ($board, $type, $squares, $player, $pieces) = @_;
197 48         97 foreach my $sq (@$squares) {
198 128         248 my $fqn = 'Chess::Piece::' . ucfirst($type);
199 128         482 my $piece = $fqn->new($sq, $player);
200 128         360 $board->set_piece_at($sq, $piece);
201 128         359 push @$pieces, $piece;
202             }
203             }
204              
205             {
206             my @_games = ( );
207              
208             sub _get_game {
209 1056     1056   1637 my ($i) = @_;
210 1056         2208 return $_games[$i];
211             }
212              
213             sub new {
214 4     4 1 41 my ($caller, $p1, $p2) = @_;
215 4   33     36 my $class = ref($caller) || $caller;
216 4   50     27 my $player1 = $p1 || "white";
217 4   50     25 my $player2 = $p2 || "black";
218 4         45 my %object_data = OBJECT_DATA;
219 4         27 my $obj_data = { %object_data };
220 4         37 my $board = Chess::Board->new();
221 4         27 my %pieces = ( $player1 => [ ], $player2 => [ ] );
222 4         13 my %captures = ( $player1 => { }, $player2 => { } );
223 4         22 my %player_has_moves = ( $player1 => { 1 => 1 }, $player2 => { 1 => 1 } );
224 4         27 _add_pieces($board, 'Rook', [ "a1", "h1" ], $player1, $pieces{$player1});
225 4         22 _add_pieces($board, 'Rook', [ "a8", "h8" ], $player2, $pieces{$player2});
226 4         22 _add_pieces($board, 'Knight', [ "b1", "g1" ], $player1, $pieces{$player1});
227 4         29 _add_pieces($board, 'Knight', [ "b8", "g8" ], $player2, $pieces{$player2});
228 4         24 _add_pieces($board, 'Bishop', [ "c1", "f1" ], $player1, $pieces{$player1});
229 4         24 _add_pieces($board, 'Bishop', [ "c8", "f8" ], $player2, $pieces{$player2});
230 4         24 _add_pieces($board, 'Queen', [ "d1" ], $player1, $pieces{$player1});
231 4         29 _add_pieces($board, 'Queen', [ "d8" ], $player2, $pieces{$player2});
232 4         23 _add_pieces($board, 'King', [ "e1" ], $player1, $pieces{$player1});
233 4         11 push @{$obj_data->{_kings}}, $pieces{$player1}[-1];
  4         27  
234 4         26 _add_pieces($board, 'King', [ "e8" ], $player2, $pieces{$player2});
235 4         10 push @{$obj_data->{_kings}}, $pieces{$player2}[-1];
  4         15  
236 4         23 my @pawn_row = Chess::Board->squares_in_line("a2", "h2");
237 4         21 _add_pieces($board, 'Pawn', \@pawn_row, $player1, $pieces{$player1});
238 4         19 @pawn_row = Chess::Board->squares_in_line("a7", "h7");
239 4         22 _add_pieces($board, 'Pawn', \@pawn_row, $player2, $pieces{$player2});
240 4         11 $obj_data->{_captures} = \%captures;
241 4         9 $obj_data->{_player_has_moves} = \%player_has_moves;
242 4         12 $obj_data->{board} = $board;
243 4         9 $obj_data->{pieces} = \%pieces;
244 4         33 $obj_data->{movelist} = Chess::Game::MoveList->new($player1, $player2);
245 4         12 $obj_data->{players} = [ $player1, $player2 ];
246 4         8 push @_games, $obj_data;
247 4         7 my $i = $#_games;
248 4         35 return bless \$i, $class;
249             }
250              
251             sub clone {
252 225     225 1 1925 my ($self) = @_;
253 225   33     755 my $class = ref($self) || croak "Invalid Chess::Game reference";
254 225         508 my $obj_data = _get_game($$self);
255 225 50       530 croak "Invalid Chess::Game reference" unless ($obj_data);
256 225         2181 my %object_data = OBJECT_DATA;
257 225         416 my $new_obj = \%object_data;
258 225         508 my $board = $obj_data->{board};
259 225         825 my $clone = $board->clone();
260 225         621 $new_obj->{board} = $clone;
261 225         527 my $p1 = $obj_data->{players}[0];
262 225         466 my $p2 = $obj_data->{players}[1];
263 225         1561 my %player_has_moves = ( $p1 => { 1 => 1 }, $p2 => { 1 => 1 } );
264 225         773 $new_obj->{players} = [ $p1, $p2 ];
265 225         829 my %pieces = ( $p1 => [ ], $p2 => [ ] );
266 225         483 $new_obj->{pieces} = \%pieces;
267 225         817 my %captures = ( $p1 => { }, $p2 => { } );
268 225         409 $new_obj->{_captures} = \%captures;
269 225         375 $new_obj->{_player_has_moves} = \%player_has_moves;
270 225         427 my %old_to_new = ( );
271 225         361 foreach my $old_piece (@{$obj_data->{pieces}{$p1}}) {
  225         772  
272 3600 100 100     26548 if ($old_piece->isa('Chess::Piece::King') or !$old_piece->captured()) {
273 3443         8769 my $old_sq = $old_piece->get_current_square();
274 3443         8976 my $new_piece = $clone->get_piece_at($old_sq);
275 3443         10715 $old_to_new{$old_piece} = $new_piece;
276 3443         3725 push @{$new_obj->{pieces}{$p1}}, $new_piece;
  3443         8507  
277 3443 100 66     31603 push @{$new_obj->{_kings}}, $new_piece if (defined($new_piece) and $new_piece->isa('Chess::Piece::King'));
  225         831  
278             }
279             else {
280 157         233 foreach my $mn (keys %{$obj_data->{_captures}{$p2}}) {
  157         666  
281 449         841 my $capture = $obj_data->{_captures}{$p2}{$mn};
282 449 100       1602 if ($capture eq $old_piece) {
283 157         399 $captures{$p2}{$mn} = $capture;
284 157         488 $old_to_new{$old_piece} = $capture;
285 157         183 push @{$new_obj->{pieces}{$p1}}, $capture
  157         761  
286             }
287             }
288             }
289             }
290 225         554 foreach my $old_piece (@{$obj_data->{pieces}{$p2}}) {
  225         710  
291 3600 100 100     25207 if ($old_piece->isa('Chess::Piece::King') or !$old_piece->captured()) {
292 3203         7926 my $old_sq = $old_piece->get_current_square();
293 3203         8283 my $new_piece = $clone->get_piece_at($old_sq);
294 3203         9443 $old_to_new{$old_piece} = $new_piece;
295 3203         3388 push @{$new_obj->{pieces}{$p2}}, $new_piece;
  3203         7946  
296 3203 100 66     27449 push @{$new_obj->{_kings}}, $new_piece if (defined($new_piece) and $new_piece->isa('Chess::Piece::King'));
  225         700  
297             }
298             else {
299 397         483 foreach my $mn (keys %{$obj_data->{_captures}{$p1}}) {
  397         1952  
300 1959         3566 my $capture = $obj_data->{_captures}{$p1}{$mn};
301 1959 100       5957 if ($capture eq $old_piece) {
302 397         891 $captures{$p1}{$mn} = $capture;
303 397         934 $old_to_new{$old_piece} = $capture;
304 397         440 push @{$new_obj->{pieces}{$p2}}, $capture;
  397         1693  
305             }
306             }
307             }
308             }
309 225         600 my $movelist = $obj_data->{movelist};
310 225         1418 my $new_ml = Chess::Game::MoveList->new($p1, $p2);
311 225         1185 my ($p1_moves, $p2_moves) = $movelist->get_all_moves();
312 225         934 for (my $i = 0; $i < @$p1_moves; $i++) {
313 1377         1979 my $p1_move = $p1_moves->[$i];
314 1377         1831 my $p2_move = $p2_moves->[$i];
315 1377         4383 my $piece = $old_to_new{$p1_move->get_piece()};
316 1377         3770 my $sq1 = $p1_move->get_start_square();
317 1377         3724 my $sq2 = $p1_move->get_dest_square();
318 1377         1738 my $flags = 0x0;
319 1377 100       3782 $flags |= MOVE_CAPTURE if ($p1_move->is_capture());
320 1377 50       4079 $flags |= MOVE_CASTLE_SHORT if ($p1_move->is_short_castle());
321 1377 50       4477 $flags |= MOVE_CASTLE_LONG if ($p1_move->is_long_castle());
322 1377 100       3508 $flags |= MOVE_EN_PASSANT if ($p1_move->is_en_passant());
323 1377         4100 $new_ml->add_move($piece, $sq1, $sq2, $flags);
324 1377 100       4034 if (defined $p2_move) {
325 1213         4410 my $p2_piece = $old_to_new{$p2_move->get_piece()};
326 1213         3257 my $p2_sq1 = $p2_move->get_start_square();
327 1213         3336 my $p2_sq2 = $p2_move->get_dest_square();
328 1213         1753 my $p2_flags = 0x0;
329 1213 100       3170 $p2_flags |= MOVE_CAPTURE if ($p2_move->is_capture());
330 1213 100       3257 $p2_flags |= MOVE_CASTLE_SHORT if ($p2_move->is_short_castle());
331 1213 50       3359 $p2_flags |= MOVE_CASTLE_LONG if ($p2_move->is_long_castle());
332 1213 50       3177 $p2_flags |= MOVE_EN_PASSANT if ($p2_move->is_en_passant());
333 1213         3393 $new_ml->add_move($p2_piece, $p2_sq1, $p2_sq2, $p2_flags);
334             }
335             }
336 225         426 foreach my $movenum (keys %{$obj_data->{_player_has_moves}{$p1}}) {
  225         1222  
337 226         1869 $player_has_moves{$p1}{$movenum} = $obj_data->{_player_has_moves}{$p1}{$movenum};
338             }
339 225         425 foreach my $movenum (keys %{$obj_data->{_player_has_moves}{$p2}}) {
  225         846  
340 239         788 $player_has_moves{$p2}{$movenum} = $obj_data->{_player_has_moves}{$p2}{$movenum};
341             }
342 225         1108 $new_obj->{movelist} = $new_ml;
343 225         628 push @_games, $new_obj;
344 225         353 my $i = $#_games;
345 225         21940 return bless \$i, $class;
346             }
347             }
348              
349             sub get_board {
350 3     3 0 1555 my ($self) = @_;
351 3 50       12 croak "Invalid Chess::Game reference" unless (ref($self));
352 3         9 my $obj_data = _get_game($$self);
353 3 50       11 croak "Invalid Chess::Game reference" unless ($obj_data);
354 3         9 return $obj_data->{board};
355             }
356              
357             sub get_pieces {
358 0     0 0 0 my ($self, $player) = @_;
359 0 0       0 croak "Invalid Chess::Game reference" unless (ref($self));
360 0         0 my $obj_data = _get_game($$self);
361 0 0       0 croak "Invalid Chess::Game reference" unless ($obj_data);
362 0 0       0 if (defined($player)) {
363 0         0 return @{$obj_data->{pieces}{$player}};
  0         0  
364             }
365             else {
366 0         0 my $player1 = $obj_data->{players}[0];
367 0         0 my $player2 = $obj_data->{players}[1];
368 0         0 return ($obj_data->{pieces}{$player1}, $obj_data->{pieces}{$player2});
369             }
370             }
371              
372             sub get_players {
373 0     0 0 0 my ($self) = @_;
374 0 0       0 croak "Invalid Chess::Game reference" unless (ref($self));
375 0         0 my $obj_data = _get_game($$self);
376 0 0       0 croak "Invalid Chess::Game reference" unless ($obj_data);
377 0         0 return @{$obj_data->{players}};
  0         0  
378             }
379              
380             sub get_movelist {
381 2     2 0 6 my ($self) = @_;
382 2 50       6 croak "Invalid Chess::Game reference" unless (ref($self));
383 2         5 my $obj_data = _get_game($$self);
384 2 50       7 croak "Invalid Chess::Game reference" unless ($obj_data);
385 2         81 return $obj_data->{movelist};
386             }
387              
388             sub get_message {
389 8     8 1 4600 my ($self) = @_;
390 8 50       29 croak "Invalid Chess::Game reference" unless (ref($self));
391 8         25 my $obj_data = _get_game($$self);
392 8 50       27 croak "Invalid Chess::Game reference" unless ($obj_data);
393 8         17 my $msg = $obj_data->{message};
394 8         15 $obj_data->{message} = '';
395 8         31 return $msg;
396             }
397              
398             sub get_capture {
399 2     2 0 18 my ($self, $player, $movenum) = @_;
400 2 50       8 croak "Invalid Chess::Game reference" unless (ref($self));
401 2         9 my $obj_data = _get_game($$self);
402 2 50       6 croak "Invalid Chess::Game reference" unless ($obj_data);
403 2         6 my $captures = $obj_data->{_captures};
404 2 50       40 return $captures->{$player}{$movenum} if exists($captures->{$player}{$movenum});
405             }
406              
407             sub _mark_threatened_kings {
408 132     132   218 my ($obj_data) = @_;
409 132         410 my $player1 = $obj_data->{players}[0];
410 132         266 my $player2 = $obj_data->{players}[1];
411 132         195 my @p1_pieces = @{$obj_data->{pieces}{$player1}};
  132         633  
412 132         251 my @p2_pieces = @{$obj_data->{pieces}{$player2}};
  132         589  
413 132         309 my $movelist = $obj_data->{movelist};
414 132         241 my $p1_king = $obj_data->{_kings}[0];
415 132         261 my $p2_king = $obj_data->{_kings}[1];
416 132         254 my $board = $obj_data->{board};
417 132         601 $p1_king->set_threatened(0);
418 132         402 $p2_king->set_threatened(0);
419 132         321 foreach my $p1_piece (@p1_pieces) {
420 2112 100 100     19571 next if ($p1_piece->isa('Chess::Piece::King') or $p1_piece->captured());
421 1821         5180 my $p1_sq = $p1_piece->get_current_square();
422 1821         4830 my $p2_sq = $p2_king->get_current_square();
423 1821 100       5331 next if (!$p1_piece->can_reach($p2_sq));
424 64 100       1426 if ($p1_piece->isa('Chess::Piece::Pawn')) {
    50          
    50          
425 1 50       5 next if (Chess::Board->horz_distance($p1_sq, $p2_sq) == 0);
426             }
427             elsif ($p1_piece->isa('Chess::Piece::King')) {
428 0 0       0 next if (Chess::Board->horz_distance($p1_sq, $p2_sq) == 2);
429             }
430             elsif (!$p1_piece->isa('Chess::Piece::Knight')) {
431 63         341 my $board_c = $board->clone();
432 63         304 $board_c->set_piece_at($p1_sq, undef);
433 63         242 $board_c->set_piece_at($p2_sq, undef);
434 63 100       236 next unless ($board_c->line_is_open($p1_sq, $p2_sq));
435             }
436 45         212 $p2_king->set_threatened(1);
437             }
438 132         360 foreach my $p2_piece (@p2_pieces) {
439 2112 100 100     16529 next if ($p2_piece->isa('Chess::Piece::King') or $p2_piece->captured());
440 1768         5129 my $p2_sq = $p2_piece->get_current_square();
441 1768         5134 my $p1_sq = $p1_king->get_current_square();
442 1768 100       4826 next if (!$p2_piece->can_reach($p1_sq));
443 11 50       234 if ($p2_piece->isa('Chess::Piece::Pawn')) {
    50          
    100          
444 0 0       0 next if (Chess::Board->horz_distance($p1_sq, $p2_sq) == 0);
445             }
446             elsif ($p2_piece->isa('Chess::Piece::King')) {
447 0 0       0 next if (Chess::Board->horz_distance($p1_sq, $p2_sq) == 2);
448             }
449             elsif (!$p2_piece->isa('Chess::Piece::Knight')) {
450 9         44 my $board_c = $board->clone();
451 9         56 $board_c->set_piece_at($p1_sq, undef);
452 9         38 $board_c->set_piece_at($p2_sq, undef);
453 9 100       33 next unless ($board_c->line_is_open($p1_sq, $p2_sq));
454             }
455 7         38 $p1_king->set_threatened(1);
456             }
457             }
458              
459             sub _is_valid_en_passant {
460 45     45   112 my ($obj_data, $piece, $sq1, $sq2) = @_;
461 45         80 my $movelist = $obj_data->{movelist};
462 45         217 my $movenum = $movelist->get_move_num();
463 45         158 my $last_moved = $movelist->get_last_moved();
464 45         233 my $move = $movelist->get_move($movenum, $last_moved);
465 45 100       124 return 0 unless $move;
466 42         221 my $piece2 = $move->get_piece();
467 42 100       398 return 0 unless ($piece2->isa('Chess::Piece::Pawn'));
468 14         32 my $player1 = $obj_data->{players}[0];
469 14         33 my $player2 = $obj_data->{players}[1];
470 14         49 my $p2_sq = $piece2->get_current_square();
471 14 100       45 if ($piece2->get_player() eq $player1) {
472 8 50       32 return 0 unless (Chess::Board->square_up_from($sq2) eq $p2_sq);
473             }
474             else {
475 6 100       28 return 0 unless (Chess::Board->square_down_from($sq2) eq $p2_sq);
476             }
477 2         13 return 1;
478             }
479              
480             sub _is_valid_short_castle {
481 7     7   23 my ($obj_data, $piece, $sq1, $sq2) = @_;
482 7         23 my $player1 = $obj_data->{players}[0];
483 7         19 my $player2 = $obj_data->{players}[1];
484 7         26 my $player = $piece->get_player();
485 7         16 my $board = $obj_data->{board};
486 7 100       26 my $tsq = $player eq $player1 ? "g1" : "g8";
487 7 100       31 return 0 unless ($sq2 eq $tsq);
488 4 50       17 unless (!$piece->moved()) {
489 0         0 $obj_data->{message} = ucfirst($player) . "'s king has already moved";
490 0         0 return 0;
491             }
492 4         8 my $rook;
493 4 100       13 if ($player eq $player1) {
494 2         22 $rook = $board->get_piece_at("h1");
495             }
496             else {
497 2         8 $rook = $board->get_piece_at("h8");
498             }
499 4 50 33     111 unless (defined($rook) and !$rook->moved()) {
500 0         0 $obj_data->{message} = ucfirst($player) . "'s kingside rook has already moved";
501 0         0 return 0;
502             }
503 4 100       17 my $rook_sq = $player eq $player1 ? "h1" : "h8";
504 4 100       13 my $king_sq = $player eq $player1 ? "e1" : "e8";
505 4         24 my $board_c = $board->clone();
506 4         22 $board_c->set_piece_at($king_sq, undef);
507 4         17 $board_c->set_piece_at($rook_sq, undef);
508 4 100       19 unless ($board_c->line_is_open($king_sq, $rook_sq)) {
509 1         7 $obj_data->{message} = "There are pieces between " . ucfirst($player) . "'s king and rook";
510 1         6 return 0;
511             }
512 3         15 return 1;
513             }
514              
515             sub _is_valid_long_castle {
516 6     6   16 my ($obj_data, $piece, $sq1, $sq2) = @_;
517 6         21 my $player1 = $obj_data->{players}[0];
518 6         14 my $player2 = $obj_data->{players}[1];
519 6         48 my $player = $piece->get_player();
520 6         16 my $board = $obj_data->{board};
521 6 100       44 my $tsq = $player eq $player1 ? "c1" : "c8";
522 6 100       30 return 0 unless ($sq2 eq $tsq);
523 3 50       14 unless (!$piece->moved()) {
524 0         0 $obj_data->{message} = ucfirst($player) . "'s king has already moved";
525 0         0 return 0;
526             }
527 3         6 my $rook;
528 3 50       11 if ($player eq $player1) {
529 3         12 $rook = $board->get_piece_at("a1");
530             }
531             else {
532 0         0 $rook = $board->get_piece_at("a8");
533             }
534 3 100 66     20 unless (defined($rook) and !$rook->moved()) {
535 1         6 $obj_data->{message} = ucfirst($player) . "'s queenside rook has already moved";
536 1         17 return 0;
537             }
538 2 50       8 my $rook_sq = $player eq $player1 ? "a1" : "a8";
539 2 50       6 my $king_sq = $player eq $player1 ? "e1" : "e8";
540 2         8 my $board_c = $board->clone();
541 2         9 $board_c->set_piece_at($king_sq, undef);
542 2         9 $board_c->set_piece_at($rook_sq, undef);
543 2 50       9 unless ($board_c->line_is_open($king_sq, $rook_sq)) {
544 0         0 $obj_data->{message} = "There are pieces between " . ucfirst($player) . "'s king and rook";
545 0         0 return 0;
546             }
547 2         9 return 1;
548             }
549              
550             sub is_move_legal {
551 371     371 1 795 my ($self, $sq1, $sq2) = @_;
552 371 50       1122 unless (Chess::Board->square_is_valid($sq1)) {
553 0         0 carp "Invalid square '$sq1'";
554 0         0 return 0;
555             }
556 371 50       1255 unless (Chess::Board->square_is_valid($sq2)) {
557 0         0 carp "Invalid square '$sq2'";
558 0         0 return 0;
559             }
560 371 50       1018 croak "Invalid Chess::Game reference" unless (ref($self));
561 371         817 my $obj_data = _get_game($$self);
562 371 50       838 croak "Invalid Chess::Game reference" unless ($obj_data);
563 371         804 my $player1 = $obj_data->{players}[0];
564 371         621 my $player2 = $obj_data->{players}[1];
565 371         604 my $board = $obj_data->{board};
566 371         1111 my $piece = $board->get_piece_at($sq1);
567 371 50       779 unless (defined($piece)) {
568 0         0 carp "No piece at '$sq1'";
569 0         0 return undef;
570             }
571 371         1292 my $player = $piece->get_player();
572 371         686 my $movelist = $obj_data->{movelist};
573 371         1425 my $last_moved = $movelist->get_last_moved();
574 371 100 100     2697 if ((defined($last_moved) and $last_moved eq $player) or
      66        
      66        
575             (!defined($last_moved) and $player ne $player1)) {
576 1         3 $obj_data->{message} = "Not your turn";
577 1         8 return 0;
578             }
579 370 50       1317 return 0 unless ($piece->can_reach($sq2));
580 370         1547 my $capture = $board->get_piece_at($sq2);
581 370 100       807 if (defined($capture)) {
582 171 100       664 unless ($capture->get_player() ne $player) {
583 127         262 $obj_data->{message} = "You can't capture your own piece";
584 127         708 return 0;
585             }
586 44 100       561 if ($piece->isa('Chess::Piece::Pawn')) {
    100          
587 6 100       30 unless (abs(Chess::Board->horz_distance($sq1, $sq2)) == 1) {
588 3         7 $obj_data->{message} = "Pawns may only capture diagonally";
589 3         16 return 0;
590             }
591             }
592             elsif ($piece->isa('Chess::Piece::King')) {
593 1 50       5 unless (abs(Chess::Board->horz_distance($sq1, $sq2)) < 2) {
594 1         3 $obj_data->{message} = "You can't capture while castling";
595 1         6 return 0;
596             }
597             }
598             }
599             else {
600 199 100       1570 if ($piece->isa('Chess::Piece::Pawn')) {
601 61         111 my $ml = $obj_data->{movelist};
602 61 100 100     226 unless (Chess::Board->horz_distance($sq1, $sq2) == 0 or
603             _is_valid_en_passant($obj_data, $piece, $sq1, $sq2)) {
604 17         45 $obj_data->{message} = "Pawns must capture on a diagonal move";
605 17         109 return 0;
606             }
607             }
608             }
609 222         433 my $valid_castle = 0;
610 222         816 my $clone = $self->clone();
611 222         664 my $r_clone = _get_game($$clone);
612 222 100       961 my $king = $r_clone->{_kings}[($player eq $player1 ? 0 : 1)];
613 222 100       2701 if ($piece->isa('Chess::Piece::King')) {
    100          
614 14         92 my $hdist = Chess::Board->horz_distance($sq1, $sq2);
615 14 100       64 if (abs($hdist) == 2) {
616 6         25 _mark_threatened_kings($r_clone);
617 6 100       37 unless (!$king->threatened()) {
618 1         4 $obj_data->{message} = "Can't castle out of check";
619 1         11 return 0;
620             }
621 5 100       17 if ($hdist > 0) {
622 3 100       14 return 0 unless (_is_valid_short_castle($obj_data, $piece, $sq1, $sq2));
623 2         5 $valid_castle = MOVE_CASTLE_SHORT;
624             }
625             else {
626 2 100       10 return 0 unless (_is_valid_long_castle($obj_data, $piece, $sq1, $sq2));
627 1         10 $valid_castle = MOVE_CASTLE_LONG;
628             }
629             }
630             }
631             elsif (!$piece->isa('Chess::Piece::Knight')) {
632 191         769 my $board_c = $board->clone();
633 191         910 $board_c->set_piece_at($sq1, undef);
634 191         753 $board_c->set_piece_at($sq2, undef);
635 191 100       715 unless ($board_c->line_is_open($sq1, $sq2)) {
636 105         449 $obj_data->{message} = "Line '$sq1' - '$sq2' is blocked";
637 105         569 return 0;
638             }
639             }
640 114 100       359 if (!$valid_castle) {
641 111         652 $clone->make_move($sq1, $sq2, 0);
642 111         558 _mark_threatened_kings($r_clone);
643 111 100       600 unless (!$king->threatened()) {
644 35         147 $obj_data->{message} = "Move leaves your king in check";
645 35         379 return 0;
646             }
647             }
648             else {
649 3 100       12 if ($valid_castle == MOVE_CASTLE_SHORT) {
650 2         7 my $tsq = Chess::Board->square_right_of($sq1);
651 2         10 $clone->make_move($sq1, $tsq, 0);
652 2         8 _mark_threatened_kings($r_clone);
653 2 100       13 unless (!$king->threatened()) {
654 1         6 $obj_data->{message} = "Can't castle through check";
655 1         16 return 0;
656             }
657 1         7 $clone->make_move($tsq, $sq2, 0);
658 1         4 _mark_threatened_kings($r_clone);
659 1 50       7 unless (!$king->threatened()) {
660 0         0 $obj_data->{message} = "Move leaves your king in check";
661 0         0 return 0;
662             }
663             }
664             else {
665 1         6 my $tsq = Chess::Board->square_left_of($sq1);
666 1         6 $clone->make_move($sq1, $tsq, 0);
667 1         5 _mark_threatened_kings($r_clone);
668 1 50       8 unless (!$king->threatened()) {
669 0         0 $obj_data->{message} = "Can't castle through check";
670 0         0 return 0;
671             }
672 1         8 $clone->make_move($tsq, $sq2, 0);
673 1         5 _mark_threatened_kings($r_clone);
674 1 50       6 unless (!$king->threatened()) {
675 0         0 $obj_data->{message} = "Move leaves your king in check";
676 0         0 return 0;
677             }
678             }
679             }
680 78         293 $obj_data->{message} = '';
681 78         627 return 1;
682             }
683              
684             sub make_move {
685 203     203 1 28117 my ($self, $sq1, $sq2, $validate) = @_;
686 203         321 my $move;
687 203 100       597 $validate = 1 unless (defined($validate));
688 203 50       801 unless (Chess::Board->square_is_valid($sq1)) {
689 0         0 carp "Invalid square '$sq1'";
690 0         0 return undef;
691             }
692 203 50       713 unless (Chess::Board->square_is_valid($sq2)) {
693 0         0 carp "Invalid square '$sq2'";
694 0         0 return undef;
695             }
696 203 100       540 if ($validate) {
697 84 100       343 return undef unless ($self->is_move_legal($sq1, $sq2));
698             }
699 195 50       636 croak "Invalid Chess::Game reference" unless (ref($self));
700 195         528 my $obj_data = _get_game($$self);
701 195 50       484 croak "Invalid Chess::Game reference" unless ($obj_data);
702 195         507 my $player1 = $obj_data->{players}[0];
703 195         385 my $player2 = $obj_data->{players}[1];
704 195         395 my $board = $obj_data->{board};
705 195         670 my $piece = $board->get_piece_at($sq1);
706 195         852 my $player = $piece->get_player();
707 195 50       611 unless (defined($piece)) {
708 0         0 carp "No piece at '$sq1'";
709 0         0 return undef;
710             }
711 195         348 my $movelist = $obj_data->{movelist};
712 195         681 my $capture = $board->get_piece_at($sq2);
713 195         359 my $flags = 0x0;
714 195 100 100     1101 if ($validate && $piece->isa('Chess::Piece::Pawn')) {
715 30 100       92 if ($player eq $player1) {
716 13 50       51 $flags |= MOVE_PROMOTE if (Chess::Board->vert_distance("d8", $sq2) == 0);
717             }
718             else {
719 17 100       76 $flags |= MOVE_PROMOTE if (Chess::Board->vert_distance("d1", $sq2) == 0);
720             }
721             }
722 195 100       466 if (defined($capture)) {
723 32         82 $flags |= MOVE_CAPTURE;
724 32         178 $capture->set_captured(1);
725 32         113 $board->set_piece_at($sq1, undef);
726 32         98 $board->set_piece_at($sq2, $piece);
727 32         150 $piece->set_current_square($sq2);
728 32         138 $piece->set_moved(1);
729 32         174 $move = $movelist->add_move($piece, $sq1, $sq2, $flags);
730 32         157 my $movenum = $move->get_move_num();
731 32         158 $obj_data->{_captures}{$player}{$movenum} = $capture;
732             }
733             else {
734 163 100 100     968 if ($validate && $piece->isa('Chess::Piece::Pawn') && _is_valid_en_passant($obj_data, $piece, $sq1, $sq2)) {
      100        
735 1         7 my $last_moved = $movelist->get_last_moved();
736 1         6 $move = $movelist->get_move($movelist->get_move_num(), $last_moved);
737 1         5 $capture = $move->get_piece();
738 1         3 $flags |= MOVE_CAPTURE;
739 1         2 $flags |= MOVE_EN_PASSANT;
740 1         11 $capture->set_captured(1);
741 1         4 $board->set_piece_at($sq1, undef);
742 1         4 $board->set_piece_at($sq2, $piece);
743 1         6 $piece->set_current_square($sq2);
744 1         6 $move = $movelist->add_move($piece, $sq1, $sq2, $flags);
745 1         3 $obj_data->{_analyzed} = 0;
746 1         6 my $movenum = $move->get_move_num();
747 1         5 $piece->_set_firstmoved($movenum);
748 1         6 $obj_data->{_captures}{$player}{$movenum} = $capture;
749             }
750             else {
751 162 100 100     859 if ($validate && $piece->isa('Chess::Piece::King')) {
752 4 100       23 $flags |= MOVE_CASTLE_SHORT if (_is_valid_short_castle($obj_data, $piece, $sq1, $sq2));
753 4 100       19 $flags |= MOVE_CASTLE_LONG if (_is_valid_long_castle($obj_data, $piece, $sq1, $sq2));
754             }
755 162 100 100     859 if (($flags & MOVE_CASTLE_SHORT) || ($flags & MOVE_CASTLE_LONG)) {
756 2         5 my ($rook_sq, $king_sq, $rook_sq_new, $king_sq_new);
757 0         0 my ($rook, $king);
758 2 100       7 if ($player eq $player1) {
759 1 50       5 $rook_sq = $flags & MOVE_CASTLE_SHORT ? "h1" : "a1";
760 1 50       5 $rook_sq_new = $flags & MOVE_CASTLE_SHORT ? "f1" : "d1";
761 1         3 $king_sq = "e1";
762 1 50       4 $king_sq_new = $flags & MOVE_CASTLE_SHORT ? "g1" : "c1";
763             }
764             else {
765 1 50       6 $rook_sq = $flags & MOVE_CASTLE_SHORT ? "h8" : "a8";
766 1 50       3 $rook_sq_new = $flags & MOVE_CASTLE_SHORT ? "f8" : "d8";
767 1         2 $king_sq = "e8";
768 1 50       5 $king_sq_new = $flags & MOVE_CASTLE_SHORT ? "g8" : "c8";
769             }
770 2         7 $king = $board->get_piece_at($king_sq);
771 2         9 $rook = $board->get_piece_at($rook_sq);
772 2         9 $board->set_piece_at($king_sq, undef);
773 2         7 $board->set_piece_at($king_sq_new, $king);
774 2         9 $king->set_current_square($king_sq_new);
775 2         8 $king->set_moved(1);
776 2         7 $board->set_piece_at($rook_sq, undef);
777 2         6 $board->set_piece_at($rook_sq_new, $rook);
778 2         17 $rook->set_current_square($rook_sq_new);
779 2         10 $rook->set_moved(1);
780 2         14 $move = $movelist->add_move($piece, $sq1, $sq2, $flags);
781 2         12 my $movenum = $move->get_move_num();
782 2         6 $obj_data->{_analyzed} = 0;
783 2         11 $king->_set_firstmoved($movenum);
784 2         9 $rook->_set_firstmoved($movenum);
785             }
786             else {
787 160         556 $board->set_piece_at($sq1, undef);
788 160         512 $board->set_piece_at($sq2, $piece);
789 160         759 $piece->set_current_square($sq2);
790 160         718 $piece->set_moved(1);
791 160         699 $move = $movelist->add_move($piece, $sq1, $sq2, $flags);
792 160         652 my $movenum = $move->get_move_num();
793 160         686 $piece->_set_firstmoved($movenum);
794             }
795             }
796             }
797 195         626 return $move;
798             }
799              
800             sub take_back_move {
801 4     4 0 541 my ($self) = @_;
802 4 50       17 croak "Invalid Chess::Game reference" unless (ref($self));
803 4         13 my $obj_data = _get_game($$self);
804 4 50       15 croak "Invalid Chess::Game reference" unless ($obj_data);
805 4         9 my $movelist = $obj_data->{movelist};
806 4         9 my $board = $obj_data->{board};
807 4         32 my $curr_player = $movelist->get_last_moved();
808 4         11 my $player1 = $obj_data->{players}[0];
809 4         19 my $move = $movelist->delete_move();
810 4 100       15 if (defined($move)) {
811 3         17 my $movenum = $move->get_move_num();
812 3         12 my $piece = $move->get_piece();
813 3         13 my $player = $piece->get_player();
814 3         13 my $ssq = $move->get_start_square();
815 3         14 my $dsq = $move->get_dest_square();
816 3 100       15 if ($move->is_promotion()) {
817 1         3 bless $piece, 'Chess::Piece::Pawn';
818             }
819 3 50       14 if ($move->is_capture()) {
    50          
    50          
820 0         0 my $capture = $obj_data->{_captures}{$player}{$movenum};
821 0 0       0 if ($move->is_en_passant()) {
822 0 0       0 if ($player eq $player1) {
823 0         0 $dsq = Chess::Board->square_down_from($dsq);
824             }
825             else {
826 0         0 $dsq = Chess::Board->square_up_from($dsq);
827             }
828             }
829 0         0 $board->set_piece_at($dsq, $capture);
830 0         0 $capture->set_current_square($dsq);
831 0         0 $capture->set_captured(0);
832 0         0 $board->set_piece_at($ssq, $piece);
833 0         0 $piece->set_current_square($ssq);
834 0 0       0 $piece->set_moved(0) if ($piece->_firstmoved() == $movenum);
835             }
836             elsif ($move->is_short_castle()) {
837 0 0       0 my $king_sq = $player eq $player1 ? "e1" : "e8";
838 0 0       0 my $rook_sq = $player eq $player1 ? "h1" : "h8";
839 0 0       0 my $king_curr_sq = $player eq $player1 ? "g1" : "g8";
840 0 0       0 my $rook_curr_sq = $player eq $player1 ? "f1" : "f8";
841 0         0 my $rook = $board->get_piece_at($rook_curr_sq);
842 0         0 $board->set_piece_at($king_curr_sq, undef);
843 0         0 $board->set_piece_at($rook_curr_sq, undef);
844 0         0 $board->set_piece_at($king_sq, $piece);
845 0         0 $board->set_piece_at($rook_sq, $rook);
846 0         0 $rook->set_current_square($rook_sq);
847 0         0 $piece->set_current_square($king_sq);
848 0         0 $rook->set_moved(0);
849 0         0 $piece->set_moved(0);
850             }
851             elsif ($move->is_long_castle()) {
852 0 0       0 my $king_sq = $player eq $player1 ? "e1" : "e8";
853 0 0       0 my $rook_sq = $player eq $player1 ? "a1" : "a8";
854 0 0       0 my $king_curr_sq = $player eq $player1 ? "c1" : "c8";
855 0 0       0 my $rook_curr_sq = $player eq $player1 ? "d1" : "d8";
856 0         0 my $rook = $board->get_piece_at($rook_curr_sq);
857 0         0 $board->set_piece_at($king_curr_sq, undef);
858 0         0 $board->set_piece_at($rook_curr_sq, undef);
859 0         0 $board->set_piece_at($king_sq, $piece);
860 0         0 $board->set_piece_at($rook_sq, $rook);
861 0         0 $rook->set_current_square($rook_sq);
862 0         0 $piece->set_current_square($king_sq);
863 0         0 $rook->set_moved(0);
864 0         0 $piece->set_moved(0);
865             }
866             else {
867 3         15 $board->set_piece_at($dsq, undef);
868 3         12 $board->set_piece_at($ssq, $piece);
869 3         14 $piece->set_current_square($ssq);
870 3 50       36 $piece->set_moved(0) if ($piece->_firstmoved() == $movenum);
871             }
872 3         15 delete $obj_data->{_player_has_moves}{$player}{$movenum};
873             }
874 4         19 return $move;
875             }
876              
877             sub _player_has_moves {
878 9     9   21 my ($self, $player) = @_;
879 9         29 my $obj_data = _get_game($$self);
880 9         23 my $movelist = $obj_data->{movelist};
881 9         47 my $movenum = $movelist->get_move_num;
882 9 100       55 if (exists($obj_data->{_player_has_moves}{$player}{$movenum})) {
883 5         27 return $obj_data->{_player_has_moves}{$player}{$movenum};
884             }
885 4         7 foreach my $piece (@{$obj_data->{pieces}{$player}}) {
  4         19  
886 43 100 100     433 next if (!$piece->isa('Chess::Piece::King') && $piece->captured());
887 37         154 my @rsqs = $piece->reachable_squares();
888 37         148 my $csq = $piece->get_current_square();
889 37         85 foreach my $sq (@rsqs) {
890 287 100       850 if ($self->is_move_legal($csq, $sq)) {
891 2         14 $obj_data->{_player_has_moves}{$player}{$movenum} = 1;
892 2         24 return 1;
893             }
894             }
895             }
896 2         11 $obj_data->{_player_has_moves}{$player}{$movenum} = 0;
897 2         11 return 0;
898             }
899              
900             sub do_promotion {
901 2     2 1 6 my ($self, $new_piece) = @_;
902 2 50       12 croak "Invalid Chess::Game reference" unless (ref($self));
903 2         7 my $obj_data = _get_game($$self);
904 2 50       8 croak "Invalid Chess::Game reference" unless ($obj_data);
905 2         5 my $board = $obj_data->{board};
906 2         4 my $movelist = $obj_data->{movelist};
907 2         7 my $movenum = $movelist->get_move_num();
908 2         8 my $last_moved = $movelist->get_last_moved();
909 2         8 my $move = $movelist->get_move($movenum, $last_moved);
910 2 50       10 return unless $move->is_promotion();
911 2         8 my $piece = $move->get_piece();
912 2         9 my $csq = $piece->get_current_square();
913 2         9 my $promoted = $piece->promote($new_piece);
914 2         8 $board->set_piece_at($csq, $promoted);
915 2         9 $move->set_promoted_to($new_piece);
916             }
917              
918             sub player_in_check {
919 10     10 1 664 my ($self, $player) = @_;
920 10 50       42 croak "Invalid Chess::Game reference" unless (ref($self));
921 10         28 my $obj_data = _get_game($$self);
922 10 50       42 croak "Invalid Chess::Game reference" unless ($obj_data);
923 10         23 my $player1 = $obj_data->{players}[0];
924 10         36 _mark_threatened_kings($obj_data);
925 10 100       65 my $king = $obj_data->{_kings}[$player eq $player1 ? 0 : 1];
926 10         45 return $king->threatened();
927             }
928              
929             sub player_checkmated {
930 5     5 1 26 my ($self, $player) = @_;
931 5 100       24 return 0 unless ($self->player_in_check($player));
932 4 100       20 if ($self->_player_has_moves($player)) {
933 1         7 return 0;
934             }
935             else {
936 3         69 return 1;
937             }
938             }
939              
940             sub player_stalemated {
941 3     3 1 11 my ($self, $player) = @_;
942 3 100       14 return 0 unless (!$self->player_in_check($player));
943 2 50       10 if ($self->_player_has_moves($player)) {
944 0         0 return 0;
945             }
946             else {
947 2         17 return 1;
948             }
949             }
950              
951             sub result {
952 3     3 1 8 my ($self) = @_;
953 3 50       16 croak "Invalid Chess::Game reference" unless (ref($self));
954 3         12 my $obj_data = _get_game($$self);
955 3 50       11 croak "Invalid Chess::Game reference" unless ($obj_data);
956 3         7 my $movelist = $obj_data->{movelist};
957 3         15 my $last_moved = $movelist->get_last_moved();
958 3         12 my $player1 = $obj_data->{players}[0];
959 3         7 my $player2 = $obj_data->{players}[1];
960 3 100       11 my $player = $last_moved eq $player1 ? $player2 : $player1;
961 3 100       14 return undef if ($self->_player_has_moves($player));
962 2 100       10 return 0 if ($self->player_stalemated($player));
963 1 50 33     6 return 1 if ($self->player_checkmated($player) && $player eq $player2);
964 0 0         return -1 if ($self->player_checkmated($player));
965             }
966              
967             1;