diff --git a/src/Game/Game.php b/src/Game/Game.php index c4eaa37..d79efdc 100644 --- a/src/Game/Game.php +++ b/src/Game/Game.php @@ -152,7 +152,7 @@ class Game { private function getCandidateMovesForPiece( Piece $piece, - array $opponentPieces, + array &$opponentPieces, FieldBitMap $occupied, FieldBitMap $capturableForNonPawn, FieldBitMap $captureableForPawn, @@ -186,6 +186,33 @@ class Game { return $futureGame->getGameState(true) != GameState::ILLEGAL; } + private function getCastleMoves( + array &$ownPieces, + FieldBitMap $occupied, + FieldBitMap $capturable, + FieldBitMap $threatened + ): array { + $king = $this->getKing($this->current); + return array_values( + array_map( + fn($r) => new Move( + $king, + new Position( + $king->getPosition()->file + 2 * ($r->getPosition()->file <=> $king->getPosition()->file), + $king->getPosition()->rank, + ), + null, + null, + $r, + ), + array_filter( + array_filter($ownPieces, fn($p) => $p instanceof Rook), + fn($r) => $king->canCastle($occupied, $capturable, $threatened, $r) + ) + ) + ); + } + private function getLegalMovesParameterized( array &$ownPieces, array &$opponentPieces, @@ -207,7 +234,17 @@ class Game { )); } - return array_values(array_filter($candidates, [$this, "isMoveLegal"])); + $candidates = array_values(array_filter($candidates, [$this, "isMoveLegal"])); + + // castle moves should always be legal + $candidates = array_merge($candidates, $this->getCastleMoves( + $ownPieces, + $occupied, + $capturableNonPawn, + $threatened, + )); + + return $candidates; } public function apply(Move $move): Game { diff --git a/src/Game/Move.php b/src/Game/Move.php index 0c4dd9e..2b4d39f 100644 --- a/src/Game/Move.php +++ b/src/Game/Move.php @@ -7,12 +7,20 @@ class Move { public Position $target; public ?Piece $captures = null; public ?PieceType $promoteTo = null; + public ?Piece $castleWith = null; - public function __construct(Piece $piece, Position $target, ?Piece $captures = null, ?PieceType $promoteTo = null) { + public function __construct( + Piece $piece, + Position $target, + ?Piece $captures = null, + ?PieceType $promoteTo = null, + ?Piece $castleWith = null, + ) { $this->piece = $piece; $this->target = $target; $this->captures = $captures; $this->promoteTo = $promoteTo; + $this->castleWith = $castleWith; } public function equals(Move $move): bool { @@ -22,7 +30,11 @@ class Move { ($this->captures != null && $move->captures != null && $this->captures->equals($move->captures)) || ($this->captures == null && $move->captures == null) ) && - $this->promoteTo == $move->promoteTo; + $this->promoteTo == $move->promoteTo && + ( + ($this->castleWith != null && $move->castleWith != null && $this->castleWith->equals($move->castleWith)) || + ($this->castleWith == null && $move->castleWith == null) + ); } public function __toString(): string { diff --git a/tests/Game/GameTest.php b/tests/Game/GameTest.php index 7ec259f..b5b3b67 100644 --- a/tests/Game/GameTest.php +++ b/tests/Game/GameTest.php @@ -400,4 +400,52 @@ final class GameTest extends TestCase { new Pawn(new Position(5, 3), Side::WHITE), ), $legalMoves); } + + public function testLegalMoves_castle() { + $subject = new Game( + [ + new King(new Position(0, 7), Side::BLACK), + new King(new Position(4, 0), Side::WHITE), + new Queen(new Position(2, 1), Side::BLACK), + new Rook(new Position(7, 0), Side::WHITE), + new Rook(new Position(7, 1), Side::BLACK), + ], + Side::WHITE + ); + + $legalMoves = $subject->getLegalMoves(); + + echo join("\n", $legalMoves); + + $this->assertCount(5, $legalMoves); + + $this->assertContainsEqualsOnce(new Move( + new King(new Position(4, 0), Side::BLACK), + new Position(5, 0), + ), $legalMoves); + + $this->assertContainsEqualsOnce(new Move( + new King(new Position(4, 0), Side::BLACK), + new Position(6, 0), + null, + null, + new Rook(new Position(7, 0), Side::WHITE), + ), $legalMoves); + + $this->assertContainsEqualsOnce(new Move( + new Rook(new Position(7, 0), Side::WHITE), + new Position(6, 0), + ), $legalMoves); + + $this->assertContainsEqualsOnce(new Move( + new Rook(new Position(7, 0), Side::WHITE), + new Position(5, 0), + ), $legalMoves); + + $this->assertContainsEqualsOnce(new Move( + new Rook(new Position(7, 0), Side::WHITE), + new Position(7, 1), + new Rook(new Position(7, 1), Side::BLACK), + ), $legalMoves); + } } \ No newline at end of file