feat: Recognize game states checkmate and stalemate

This commit is contained in:
overflowerror 2024-01-05 15:50:25 +01:00
parent 2f3d5a3340
commit e6fa6d8589
3 changed files with 86 additions and 8 deletions

View file

@ -9,6 +9,8 @@ class Game {
private Side $current; private Side $current;
private ?array $moveCache = null;
public function __construct(array $pieces, Side $current) { public function __construct(array $pieces, Side $current) {
$this->pieces = $pieces; $this->pieces = $pieces;
$this->current = $current; $this->current = $current;
@ -101,13 +103,17 @@ class Game {
public function getLegalMoves(): array { public function getLegalMoves(): array {
if ($this->moveCache) {
return $this->moveCache;
}
$ownPieces = $this->getPieces($this->current); $ownPieces = $this->getPieces($this->current);
$opponentPieces = $this->getPieces($this->current->getNext()); $opponentPieces = $this->getPieces($this->current->getNext());
$occupied = $this->getOccupied($ownPieces); $occupied = $this->getOccupied($ownPieces);
$threatened = $this->getThreatened($opponentPieces, $occupied); $threatened = $this->getThreatened($opponentPieces, $occupied);
return $this->getLegalMovesCached( $this->moveCache = $this->getLegalMovesParameterized(
$ownPieces, $ownPieces,
$opponentPieces, $opponentPieces,
$occupied, $occupied,
@ -115,6 +121,8 @@ class Game {
$this->getCaptureable($opponentPieces, true), $this->getCaptureable($opponentPieces, true),
$threatened, $threatened,
); );
return $this->moveCache;
} }
private function generatePromotionMoves(Move $candidate): array { private function generatePromotionMoves(Move $candidate): array {
@ -175,10 +183,10 @@ class Game {
private function isMoveLegal(Move $move) { private function isMoveLegal(Move $move) {
$futureGame = $this->apply($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 &$ownPieces,
array &$opponentPieces, array &$opponentPieces,
FieldBitMap $occupied, FieldBitMap $occupied,
@ -227,17 +235,29 @@ class Game {
return $game; return $game;
} }
public function getGameState(): GameState { public function getGameState(bool $onlyIsLegal = false): GameState {
$allOccupied = $this->getAllOccupied(); $allOccupied = $this->getAllOccupied();
if ($this->isIllegal($allOccupied)) { if ($this->isIllegal($allOccupied)) {
return GameState::ILLEGAL; return GameState::ILLEGAL;
} }
if ($this->isCheck($allOccupied)) { if ($onlyIsLegal) {
// TODO: check for checkmate 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; return GameState::DEFAULT;
@ -267,7 +287,8 @@ class Game {
$result .= "\033[30m"; $result .= "\033[30m";
} }
$result .= $piece->getShort() . " "; $short = $piece->getShort();
$result .= ($short ?: "p") . " ";
} else { } else {
$result .= " "; $result .= " ";
} }

View file

@ -11,4 +11,5 @@ enum GameState {
case THREEFOLD_REPETITION; case THREEFOLD_REPETITION;
case FIFTY_MOVE_RULE; case FIFTY_MOVE_RULE;
case ILLEGAL; case ILLEGAL;
case UNKNOWN_VALID;
} }

View file

@ -78,6 +78,62 @@ final class GameTest extends TestCase {
$this->assertEquals(GameState::CHECK, $subject->getGameState()); $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() { public function testLegalMoves_pawnPinnedBecauseOfCheckKingRestrictedByQueenAndPawn() {
$subject = new Game( $subject = new Game(
[ [