diff --git a/src/Game/Game.php b/src/Game/Game.php index f1a2963..7fab126 100644 --- a/src/Game/Game.php +++ b/src/Game/Game.php @@ -33,7 +33,7 @@ class Game { } public function getPieces(Side $side): array { - return array_filter($this->pieces, fn($p) => $p->getSide() == $side); + return array_values(array_filter($this->pieces, fn($p) => $p->getSide() == $side)); } private function &findPiece(Piece $needle): Piece { @@ -312,6 +312,46 @@ class Game { $this->history->add($this); } + private function isDeadPosition(): bool { + $ownPieces = $this->getPieces($this->current); + $opponentPieces = $this->getPieces($this->current->getNext()); + + $getRemaining = function (array $pieces): Piece { + return ($pieces[0] instanceof King ? $pieces[1] : $pieces[0]); + }; + + if (count($ownPieces) > 2) { + return false; + } else if (count($opponentPieces) > 2) { + return false; + } else if (count($ownPieces) == 1 && count($opponentPieces) == 1) { + return true; + } else if (count($ownPieces) == 1) { + $opponentRemaining = $getRemaining($opponentPieces); + return !( + $opponentRemaining instanceof Queen || + $opponentRemaining instanceof Rook || + $opponentRemaining instanceof Pawn + ); + } else if (count($opponentPieces) == 1) { + $ownRemaining = $getRemaining($ownPieces); + return !( + $ownRemaining instanceof Queen || + $ownRemaining instanceof Rook || + $ownRemaining instanceof Pawn + ); + } else { // both have 1 piece left besides king + $ownRemaining = $getRemaining($ownPieces); + $opponentRemaining = $getRemaining($opponentPieces); + + return ( + $ownRemaining instanceof Bishop && + $opponentRemaining instanceof Bishop && + $ownRemaining->getPosition()->getSquareColor() == $opponentRemaining->getPosition()->getSquareColor() + ); + } + } + public function getGameState(bool $onlyIsLegal = false): GameState { $allOccupied = $this->getAllOccupied(); @@ -327,6 +367,10 @@ class Game { return GameState::THREEFOLD_REPETITION; } + if ($this->isDeadPosition()) { + return GameState::DEAD_POSITION; + } + $legalMoves = $this->getLegalMoves(); if ($this->isCheck($allOccupied)) { diff --git a/src/Game/Position.php b/src/Game/Position.php index 2c97f32..64a1f6b 100644 --- a/src/Game/Position.php +++ b/src/Game/Position.php @@ -17,6 +17,10 @@ class Position { $this->rank >= 0 && $this->rank < 8; } + public function getSquareColor(): Side { + return ($this->rank % 2) ^ ($this->file % 2) ? Side::WHITE : Side::BLACK; + } + public function __toString(): string { return ["a", "b", "c", "d", "e", "f", "g", "h"][$this->file] . ($this->rank + 1); } diff --git a/tests/Game/GameTest.php b/tests/Game/GameTest.php index 6bc2509..2d9d156 100644 --- a/tests/Game/GameTest.php +++ b/tests/Game/GameTest.php @@ -140,6 +140,7 @@ final class GameTest extends TestCase { [ new King(new Position(1, 1), Side::BLACK, true), new King(new Position(7, 6), Side::WHITE, true), + new Pawn(new Position(3, 6), Side::WHITE, true), // to avoid dead position ], Side::BLACK ); @@ -257,6 +258,99 @@ final class GameTest extends TestCase { $this->assertEquals(GameState::DEFAULT, $subject->getGameState()); } + public function testGameState_deadPosition_onlyKings() { + $subject = new Game( + [ + new King(new Position(0, 7), Side::BLACK), + new King(new Position(0, 5), Side::WHITE), + ], + Side::BLACK + ); + + $this->assertEquals(GameState::DEAD_POSITION, $subject->getGameState()); + } + + public function testGameState_deadPosition_kingVsKingAndBishop() { + $subject = new Game( + [ + new King(new Position(0, 7), Side::BLACK), + new King(new Position(0, 5), Side::WHITE), + new Bishop(new Position(1, 0), Side::WHITE), + ], + Side::BLACK + ); + + $this->assertEquals(GameState::DEAD_POSITION, $subject->getGameState()); + } + + public function testGameState_deadPosition_kingVsKingAndKnight() { + $subject = new Game( + [ + new King(new Position(0, 7), Side::BLACK), + new King(new Position(0, 5), Side::WHITE), + new Knight(new Position(1, 0), Side::WHITE), + ], + Side::BLACK + ); + + $this->assertEquals(GameState::DEAD_POSITION, $subject->getGameState()); + } + + public function testGameState_deadPosition_kingAndBishopVsKingAndBishopSameColor() { + $subject = new Game( + [ + new King(new Position(0, 7), Side::BLACK), + new King(new Position(0, 5), Side::WHITE), + new Bishop(new Position(1, 0), Side::WHITE), + new Bishop(new Position(2, 1), Side::BLACK), + ], + Side::BLACK + ); + + $this->assertEquals(GameState::DEAD_POSITION, $subject->getGameState()); + } + + public function testGameState_deadPosition_kingAndBishopVsKingAndBishopDifferentColor_notDead() { + $subject = new Game( + [ + new King(new Position(0, 7), Side::BLACK), + new King(new Position(0, 5), Side::WHITE), + new Bishop(new Position(1, 0), Side::WHITE), + new Bishop(new Position(1, 1), Side::BLACK), + ], + Side::BLACK + ); + + $this->assertEquals(GameState::DEFAULT, $subject->getGameState()); + } + + public function testGameState_deadPosition_kingAndKnightVsKing_notDead() { + $subject = new Game( + [ + new King(new Position(0, 7), Side::BLACK), + new King(new Position(0, 5), Side::WHITE), + new Bishop(new Position(1, 0), Side::WHITE), + new Knight(new Position(1, 1), Side::BLACK), + ], + Side::BLACK + ); + + $this->assertEquals(GameState::DEFAULT, $subject->getGameState()); + } + + public function testGameState_deadPosition_kingAndPawnVsKing_notDead() { + $subject = new Game( + [ + new King(new Position(0, 7), Side::BLACK), + new King(new Position(0, 5), Side::WHITE), + new Pawn(new Position(1, 1), Side::BLACK, true), + ], + Side::BLACK + ); + + $this->assertEquals(GameState::DEFAULT, $subject->getGameState()); + } + public function testLegalMoves_pawnPinnedBecauseOfCheckKingRestrictedByQueenAndPawn() { $subject = new Game( [