feat: Add dead positions to game system

This commit is contained in:
overflowerror 2024-01-07 15:59:39 +01:00
parent b15e5fc922
commit 51d01b0953
3 changed files with 143 additions and 1 deletions

View file

@ -33,7 +33,7 @@ class Game {
} }
public function getPieces(Side $side): array { 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 { private function &findPiece(Piece $needle): Piece {
@ -312,6 +312,46 @@ class Game {
$this->history->add($this); $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 { public function getGameState(bool $onlyIsLegal = false): GameState {
$allOccupied = $this->getAllOccupied(); $allOccupied = $this->getAllOccupied();
@ -327,6 +367,10 @@ class Game {
return GameState::THREEFOLD_REPETITION; return GameState::THREEFOLD_REPETITION;
} }
if ($this->isDeadPosition()) {
return GameState::DEAD_POSITION;
}
$legalMoves = $this->getLegalMoves(); $legalMoves = $this->getLegalMoves();
if ($this->isCheck($allOccupied)) { if ($this->isCheck($allOccupied)) {

View file

@ -17,6 +17,10 @@ class Position {
$this->rank >= 0 && $this->rank < 8; $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 { public function __toString(): string {
return ["a", "b", "c", "d", "e", "f", "g", "h"][$this->file] . ($this->rank + 1); return ["a", "b", "c", "d", "e", "f", "g", "h"][$this->file] . ($this->rank + 1);
} }

View file

@ -140,6 +140,7 @@ final class GameTest extends TestCase {
[ [
new King(new Position(1, 1), Side::BLACK, true), new King(new Position(1, 1), Side::BLACK, true),
new King(new Position(7, 6), Side::WHITE, 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 Side::BLACK
); );
@ -257,6 +258,99 @@ final class GameTest extends TestCase {
$this->assertEquals(GameState::DEFAULT, $subject->getGameState()); $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() { public function testLegalMoves_pawnPinnedBecauseOfCheckKingRestrictedByQueenAndPawn() {
$subject = new Game( $subject = new Game(
[ [