Skip to content

Commit

Permalink
Merge pull request #8 from onspli/7-error-losing-the-castling-for-no-…
Browse files Browse the repository at this point in the history
…apparent-reason

7 error losing the castling for no apparent reason
  • Loading branch information
onspli authored May 27, 2022
2 parents 79309ab + 8a5edc9 commit a874916
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 52 deletions.
56 changes: 22 additions & 34 deletions src/FEN.php
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,15 @@ public function set_castling(string $castling) : void
$rooks_file = strtolower($castling_right);
if ($rooks_file < $new_kings_file) {
$castling_type = ctype_upper($castling_right)? 'Q' : 'q';
if ($new_queens_rook_file && $new_queens_rook_file != $rooks_file) {
throw new ParseException("Multiple queen's rook castling rights in string '$castling'.");
}
$new_queens_rook_file = $rooks_file;
} else {
$castling_type = ctype_upper($castling_right)? 'K' : 'k';
if ($new_kings_rook_file && $new_kings_rook_file != $rooks_file) {
throw new ParseException("Multiple king's rook castling rights in string '$castling'.");
}
$new_kings_rook_file = $rooks_file;
}
}
Expand All @@ -293,10 +299,6 @@ public function set_castling(string $castling) : void

private function validate_castling(array $castling_rights, $kings_file, $kings_rook_file, $queens_rook_file) : void
{
if ($kings_file === null || $kings_rook_file === null || $queens_rook_file === null)
{
throw new ParseException("Invalid castling string.");
}

if ($castling_rights['K'] || $castling_rights['Q'])
{
Expand Down Expand Up @@ -353,7 +355,6 @@ private function validate_castling(array $castling_rights, $kings_file, $kings_r
*/
private function get_castling_availability(string $type) : bool
{
self::validate_castling_type($type);
return $this->castling_rights[$type];
}

Expand All @@ -370,17 +371,9 @@ private function get_castling_availability(string $type) : bool
*/
private function set_castling_availability(string $type, bool $avalability) : void
{
self::validate_castling_type($type);
$this->castling_rights[$type] = $avalability;
}

private static function validate_castling_type(string $type) : void
{
if (!in_array($type, ['K', 'Q', 'k', 'q'])) {
throw new ParseException("Invalid castling type '$type'.");
}
}

/**
* Get En Passant target square.
*
Expand Down Expand Up @@ -844,7 +837,7 @@ private function standard_move(Move &$move) : void
$this->validate_move_target_square($move);
$origin = $this->get_move_origin($move);
$new_board = $this->get_new_board($move, $origin);
$this->set_new_board($new_board);
$this->set_board($new_board);
$move->set_origin($origin);
}

Expand All @@ -870,15 +863,6 @@ private function get_new_board(Move $move, Square $origin) : Board
return $new_board;
}

private function set_new_board(Board $new_board) : void
{
$active_color = $this->get_active_color();
if ($new_board->is_check($active_color)) {
throw new RulesException('King is in check. ' . $new_board->export() . ' ' . $active_color);
}
$this->set_board($new_board);
}

/**
* Perform a move.
*/
Expand Down Expand Up @@ -908,27 +892,31 @@ public function move(string $move) : void

private function after_move_update_castling_availability(string $piece_type, Square $origin_square, Square $target_square) : void
{
$active_color = $this->get_active_color();
if ($active_color == 'w') {
$active_first_rank = '1';
$opponents_first_rank = '8';
} else {
$active_first_rank = '8';
$opponents_first_rank = '1';
}

// moving king or rooks prevents castling
$origin_file = $origin_square->get_file();
if ($piece_type == 'R' && $origin_file == $this->queens_rook_file) {
$origin_square_str = $origin_square->export();
if ($piece_type == 'R' && $origin_square_str == $this->queens_rook_file.$active_first_rank) {
$this->set_castling_availability($this->get_active_piece('Q'), false);
} else if ($piece_type == 'R' && $origin_file == $this->kings_rook_file) {
} else if ($piece_type == 'R' && $origin_square_str == $this->kings_rook_file.$active_first_rank) {
$this->set_castling_availability($this->get_active_piece('K'), false);
} else if ($piece_type == 'K' && $origin_file == $this->kings_file) {
} else if ($piece_type == 'K' && $origin_square_str == $this->kings_file.$active_first_rank) {
$this->set_castling_availability($this->get_active_piece('Q'), false);
$this->set_castling_availability($this->get_active_piece('K'), false);
}

// capturing opponents rooks prevents castling
$active_color = $this->get_active_color();
$target_square_str = $target_square->export();
if ($active_color == 'b' && $target_square_str == $this->queens_rook_file.'1') {
$this->set_castling_availability($this->get_opponents_piece('Q'), false);
} else if ($active_color == 'b' && $target_square_str == $this->kings_rook_file.'1') {
$this->set_castling_availability($this->get_opponents_piece('K'), false);
} else if ($active_color == 'w' && $target_square_str == $this->queens_rook_file.'8') {
if ($target_square_str == $this->queens_rook_file.$opponents_first_rank) {
$this->set_castling_availability($this->get_opponents_piece('Q'), false);
} else if ($active_color == 'w' && $target_square_str == $this->kings_rook_file.'8') {
} else if ($target_square_str == $this->kings_rook_file.$opponents_first_rank) {
$this->set_castling_availability($this->get_opponents_piece('K'), false);
}

Expand Down
132 changes: 114 additions & 18 deletions tests/unit/FENTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,66 +139,138 @@ public function testCastling() : void
$this->assertEquals('-', $fen->get_castling());
}

public function testSetCastlingInvalid1() : void
public function testSetCastlingInvalidString() : void
{
$fen = new FEN;
$this->expectException(ParseException::class);
$this->expectExceptionMessage("Invalid castling right 'K' in string 'KQxq'.");
// this is Shredder FEN and the first invalid castling right is 'K'
$fen->set_castling('KQxq');
}

public function testSetCastlingInvalid2() : void
public function testSetCastlingInvalidStringMixShredder() : void
{
$fen = new FEN;
$this->expectException(ParseException::class);
$fen->set_castling('KQah');
$this->expectExceptionMessage("Invalid castling right 'k' in string 'AHkq'.");
// this is Shredder FEN and the first invalid castling right is 'k'
$fen->set_castling('AHkq');
}

public function testSetCastlingInvalid3() : void
public function testSetCastlingInvalidStringMixFiles1() : void
{
$fen = new FEN;
$this->expectException(ParseException::class);
$this->expectExceptionMessage("Multiple queen's rook castling rights in string 'AHbh'.");
$fen->set_castling('AHbh');
}

public function testSetCastlingInvalidStringMixFiles2() : void
{
$fen = new FEN;
$this->expectException(ParseException::class);
$fen = new FEN('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w AHbh - 0 1');
$this->expectExceptionMessage("Multiple queen's rook castling rights in string 'BHah'.");
$fen->set_castling('BHah');
}

public function testSetCastlingInvalid4() : void
public function testSetCastlingInvalidStringMixFiles3() : void
{
$fen = new FEN;
$this->expectException(ParseException::class);
$this->expectExceptionMessage("Multiple king's rook castling rights in string 'AHag'.");
$fen->set_castling('AHag');
}

public function testSetCastlingInvalidStringMixFiles4() : void
{
$fen = new FEN;
$this->expectException(ParseException::class);
$fen = new FEN('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w AHag - 0 1');
$this->expectExceptionMessage("Multiple king's rook castling rights in string 'AGah'.");
$fen->set_castling('AGah');
}

public function testSetCastlingInvalid5() : void
public function testSetCastlingInvalidBlackQueenRookPosition() : void
{
$this->expectException(ParseException::class);
$fen = new FEN('rnbkqbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBKqBNR w KQkq - 0 1');
$this->expectExceptionMessage("Invalid castling string. Black queen's rook not in initial position.");
$fen = new FEN('nrbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w AHah - 0 1');
}

public function testSetCastlingInvalid6() : void
public function testSetCastlingInvalidWhiteQueenRookPosition() : void
{
$this->expectException(ParseException::class);
$fen = new FEN('nrbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/NRBQKBNR w KQkq - 0 1');
$this->expectExceptionMessage("Invalid castling string. White queen's rook not in initial position.");
$fen = new FEN('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/NRBQKBNR w AHah - 0 1');
}

public function testSetCastlingInvalid7() : void
public function testSetCastlingInvalidInvalidBlackKingRookPosition() : void
{
$this->expectException(ParseException::class);
$fen = new FEN('rnbqkbrn/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBRN w KQkq - 0 1');
$this->expectExceptionMessage("Invalid castling string. Black king's rook not in initial position.");
$fen = new FEN('rnbqkbrn/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w AHah - 0 1');
}

public function testSetCastlingInvalid8() : void
public function testSetCastlingInvalidInvalidWhiteKingRookPosition() : void
{
$this->expectException(ParseException::class);
$fen = new FEN('rnbqkbrn/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBRN w AHah - 0 1');
$this->expectExceptionMessage("Invalid castling string. White king's rook not in initial position.");
$fen = new FEN('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBRN w AHah - 0 1');
}

public function testSetCastlingInvalid9() : void
public function testSetCastlingInvalidBlackQueenRookPosition2() : void
{
$this->expectException(ParseException::class);
$this->expectExceptionMessage("Invalid castling string. Black queen's rook not in initial position.");
$fen = new FEN('nrbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1');
}

public function testSetCastlingInvalidWhiteQueenRookPosition2() : void
{
$this->expectException(ParseException::class);
$this->expectExceptionMessage("Invalid castling string. White queen's rook not in initial position.");
$fen = new FEN('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/NRBQKBNR w KQkq - 0 1');
}

public function testSetCastlingInvalidInvalidBlackKingRookPosition2() : void
{
$this->expectException(ParseException::class);
$this->expectExceptionMessage("Invalid castling string. Black king's rook not in initial position.");
$fen = new FEN('rnbqkbrn/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1');
}

public function testSetCastlingInvalidInvalidWhiteKingRookPosition2() : void
{
$this->expectException(ParseException::class);
$this->expectExceptionMessage("Invalid castling string. White king's rook not in initial position.");
$fen = new FEN('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBRN w KQkq - 0 1');
}

public function testSetCastlingInvalidWhiteKingPosition() : void
{
$this->expectException(ParseException::class);
$this->expectExceptionMessage("Invalid castling string. White king not in initial position.");
$fen = new FEN('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBKQBNR w KQkq - 0 1');
}

public function testSetCastlingInvalidBlackKingPosition() : void
{
$this->expectException(ParseException::class);
$this->expectExceptionMessage("Invalid castling string. Black king not in initial position.");
$fen = new FEN('rnbkqbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1');
}

public function testSetCastlingInvalidKingsNotBetweenRooks() : void
{
$this->expectException(ParseException::class);
$this->expectExceptionMessage("Multiple queen's rook castling rights in string 'ADad'.");
$fen = new FEN('rnbrkbqn/pppppppp/8/8/8/8/PPPPPPPP/RNBRKBQN w ADad - 0 1');
}

public function testSetCastlingInvalid10() : void
public function testSetCastlingInvalidKingsNotBetweenRooks2() : void
{
$this->expectException(ParseException::class);
$fen = new FEN('qnbnkbrr/pppppppp/8/8/8/8/PPPPPPPP/qNBNKBRR w GHgh - 0 1');
$this->expectExceptionMessage("Multiple king's rook castling rights in string 'GHgh'.");
$fen = new FEN('qnbnkbrr/pppppppp/8/8/8/8/PPPPPPPP/QNBNKBRR w GHgh - 0 1');
}

public function testFiftyMove() : void
Expand Down Expand Up @@ -712,6 +784,30 @@ public function testCastlingQueensideCheckOnTargetWhite() : void
$fen->move('O-O-O');
}

// Issue #7: Losing the castling for no apparent reason
public function testCastlingMoveOtherRookOnRookFile() : void
{
// queenside
$fen = new FEN('rnbqkbn1/ppppppp1/r7/7p/7P/R7/PPPPPPP1/RNBQKBN1 w Qq - 4 4');
$fen->move('Ra4');
$this->assertEquals('Qq', $fen->get_castling());
$fen->move('Ra5');
$this->assertEquals('Qq', $fen->get_castling());

// kingside
$fen = new FEN('1nbqkbnr/1ppppppp/7r/p7/P7/7R/1PPPPPPP/1NBQKBNR w Kk - 4 4');
$fen->move('Rh4');
$this->assertEquals('Kk', $fen->get_castling());
$fen->move('Rh5');
$this->assertEquals('Kk', $fen->get_castling());

// example from Github Issue
$fen = new FEN('2b1k2r/1nq1ppbp/2pp2p1/7r/2P5/1N2P2P/P1Q1BPP1/5RK1 b k - 1 19');
$fen->move('Rh6');
$this->assertEquals('2b1k2r/1nq1ppbp/2pp2pr/8/2P5/1N2P2P/P1Q1BPP1/5RK1', $fen->get_board());
$this->assertEquals('k', $fen->get_castling());
}

public function testPossibleMoves() : void
{
$fen = new FEN;
Expand Down

0 comments on commit a874916

Please sign in to comment.