| 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"make_move()"> | 
| 87 |  |  |  |  |  |  | or L"is_move_legal()"> 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"new()"> or L"clone()">. 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; |