From e6fa6d8589619a4d289ea1d9e03071af1b18a342 Mon Sep 17 00:00:00 2001 From: overflowerror Date: Fri, 5 Jan 2024 15:50:25 +0100 Subject: [PATCH] feat: Recognize game states checkmate and stalemate --- src/Game/Game.php | 37 +++++++++++++++++++++------ src/Game/GameState.php | 1 + tests/Game/GameTest.php | 56 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 8 deletions(-) diff --git a/src/Game/Game.php b/src/Game/Game.php index 724002e..cdabce7 100644 --- a/src/Game/Game.php +++ b/src/Game/Game.php @@ -9,6 +9,8 @@ class Game { private Side $current; + private ?array $moveCache = null; + public function __construct(array $pieces, Side $current) { $this->pieces = $pieces; $this->current = $current; @@ -101,13 +103,17 @@ class Game { public function getLegalMoves(): array { + if ($this->moveCache) { + return $this->moveCache; + } + $ownPieces = $this->getPieces($this->current); $opponentPieces = $this->getPieces($this->current->getNext()); $occupied = $this->getOccupied($ownPieces); $threatened = $this->getThreatened($opponentPieces, $occupied); - return $this->getLegalMovesCached( + $this->moveCache = $this->getLegalMovesParameterized( $ownPieces, $opponentPieces, $occupied, @@ -115,6 +121,8 @@ class Game { $this->getCaptureable($opponentPieces, true), $threatened, ); + + return $this->moveCache; } private function generatePromotionMoves(Move $candidate): array { @@ -175,10 +183,10 @@ class Game { private function isMoveLegal(Move $move) { $futureGame = $this->apply($move); - return $futureGame->getGameState() != GameState::ILLEGAL; + return $futureGame->getGameState(true) != GameState::ILLEGAL; } - private function getLegalMovesCached( + private function getLegalMovesParameterized( array &$ownPieces, array &$opponentPieces, FieldBitMap $occupied, @@ -227,17 +235,29 @@ class Game { return $game; } - public function getGameState(): GameState { + public function getGameState(bool $onlyIsLegal = false): GameState { $allOccupied = $this->getAllOccupied(); if ($this->isIllegal($allOccupied)) { return GameState::ILLEGAL; } - if ($this->isCheck($allOccupied)) { - // TODO: check for checkmate + if ($onlyIsLegal) { + return GameState::UNKNOWN_VALID; + } - return GameState::CHECK; + $legalMoves = $this->getLegalMoves(); + + if ($this->isCheck($allOccupied)) { + if (!$legalMoves) { + return GameState::CHECKMATE; + } else { + return GameState::CHECK; + } + } + + if (!$legalMoves) { + return GameState::STALEMATE; } return GameState::DEFAULT; @@ -267,7 +287,8 @@ class Game { $result .= "\033[30m"; } - $result .= $piece->getShort() . " "; + $short = $piece->getShort(); + $result .= ($short ?: "p") . " "; } else { $result .= " "; } diff --git a/src/Game/GameState.php b/src/Game/GameState.php index 2f7e0f3..ac61d34 100644 --- a/src/Game/GameState.php +++ b/src/Game/GameState.php @@ -11,4 +11,5 @@ enum GameState { case THREEFOLD_REPETITION; case FIFTY_MOVE_RULE; case ILLEGAL; + case UNKNOWN_VALID; } \ No newline at end of file diff --git a/tests/Game/GameTest.php b/tests/Game/GameTest.php index d898ac1..642d9ff 100644 --- a/tests/Game/GameTest.php +++ b/tests/Game/GameTest.php @@ -78,6 +78,62 @@ final class GameTest extends TestCase { $this->assertEquals(GameState::CHECK, $subject->getGameState()); } + public function testGameState_checkmate_white() { + $subject = new Game( + [ + new King(new Position(0, 0), Side::WHITE), + new Queen(new Position(1, 1), Side::BLACK), + new King(new Position(2, 2), Side::BLACK), + ], + Side::WHITE + ); + + $this->assertEquals(GameState::CHECKMATE, $subject->getGameState()); + } + + public function testGameState_checkmate_black() { + $subject = new Game( + [ + new King(new Position(0, 4), Side::BLACK), + new Rook(new Position(0, 1), Side::WHITE), + new King(new Position(2, 4), Side::WHITE), + ], + Side::BLACK + ); + + $this->assertEquals(GameState::CHECKMATE, $subject->getGameState()); + } + + public function testGameState_stalemate_white() { + $subject = new Game( + [ + new King(new Position(4, 3), Side::WHITE), + new Pawn(new Position(3, 3), Side::WHITE), + new Rook(new Position(2, 3), Side::BLACK), + new King(new Position(6, 3), Side::BLACK), + new Queen(new Position(7, 4), Side::BLACK), + new Knight(new Position(2, 0), Side::BLACK), + new Bishop(new Position(3, 1), Side::BLACK), + ], + Side::WHITE + ); + + $this->assertEquals(GameState::STALEMATE, $subject->getGameState()); + } + + public function testGameState_stalemate_black() { + $subject = new Game( + [ + new King(new Position(0, 7), Side::BLACK), + new Rook(new Position(1, 1), Side::WHITE), + new King(new Position(0, 5), Side::WHITE), + ], + Side::BLACK + ); + + $this->assertEquals(GameState::STALEMATE, $subject->getGameState()); + } + public function testLegalMoves_pawnPinnedBecauseOfCheckKingRestrictedByQueenAndPawn() { $subject = new Game( [