From 1d9f6f5736577f38753d1c65e5e6333ce850397a Mon Sep 17 00:00:00 2001 From: overflowerror Date: Sun, 7 Jan 2024 16:40:54 +0100 Subject: [PATCH] feat: Add fifty-move-rule --- src/Game/Game.php | 24 +++++++++ tests/Game/GameTest.php | 112 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) diff --git a/src/Game/Game.php b/src/Game/Game.php index 0bcecc0..150ffd8 100644 --- a/src/Game/Game.php +++ b/src/Game/Game.php @@ -13,6 +13,9 @@ class Game { private ?array $moveCache = null; + private int $movesSinceLastCapture = 0; + private int $movesSinceLastPawnMove = 0; + public function __construct(array $pieces, Side $current) { $this->pieces = $pieces; $this->current = $current; @@ -281,6 +284,9 @@ class Game { public function applyInPlace(Move $move): void { $this->tick(); + $this->movesSinceLastPawnMove++; + $this->movesSinceLastCapture++; + if ($move->castleWith) { $king = $this->findPiece($move->piece); $rook = $this->findPiece($move->castleWith); @@ -294,7 +300,12 @@ class Game { } else { if ($move->captures) { $this->removePiece($move->captures); + $this->movesSinceLastCapture = 0; } + if ($move->piece instanceof Pawn) { + $this->movesSinceLastPawnMove = 0; + } + if ($move->promoteTo) { $this->removePiece($move->piece); @@ -352,6 +363,15 @@ class Game { } } + public function _testFiftyMoveRule(int $movesSinceLastCapture, int $movesSinceLastPawnMove) { + $this->movesSinceLastPawnMove = $movesSinceLastPawnMove; + $this->movesSinceLastCapture = $movesSinceLastCapture; + } + + private function isFiftyMoveRule(): bool { + return $this->movesSinceLastCapture >= 50 && $this->movesSinceLastPawnMove >= 50; + } + public function getGameState(bool $onlyIsLegal = false): GameState { $allOccupied = $this->getAllOccupied(); @@ -371,6 +391,10 @@ class Game { return GameState::DEAD_POSITION; } + if ($this->isFiftyMoveRule()) { + return GameState::FIFTY_MOVE_RULE; + } + $legalMoves = $this->getLegalMoves(); if ($this->isCheck($allOccupied)) { diff --git a/tests/Game/GameTest.php b/tests/Game/GameTest.php index 9193eea..ff1241d 100644 --- a/tests/Game/GameTest.php +++ b/tests/Game/GameTest.php @@ -351,6 +351,118 @@ final class GameTest extends TestCase { $this->assertEquals(GameState::DEFAULT, $subject->getGameState()); } + public function testGameState_fiftyMoveRule() { + $subject = new Game( + [ + new King(new Position(2, 7), Side::WHITE, true), + new King(new Position(0, 0), Side::BLACK, true), + new Rook(new Position(1, 0), Side::BLACK), + ], + Side::BLACK + ); + $subject->_testFiftyMoveRule(49, 49); + $this->assertEquals(GameState::DEFAULT, $subject->getGameState()); + + $subject->applyInPlace(new Move( + new Rook(new Position(1, 0), Side::BLACK), + new Position(3, 0) + )); + + echo $subject->visualize(); + + $this->assertEquals(GameState::FIFTY_MOVE_RULE, $subject->getGameState()); + } + + public function testGameState_fiftyMoveRule_notLongEnoughSincePawnMove() { + $subject = new Game( + [ + new King(new Position(2, 7), Side::WHITE, true), + new King(new Position(0, 0), Side::BLACK, true), + new Rook(new Position(1, 0), Side::BLACK), + ], + Side::BLACK + ); + $subject->_testFiftyMoveRule(49, 48); + $this->assertEquals(GameState::DEFAULT, $subject->getGameState()); + + $subject->applyInPlace(new Move( + new Rook(new Position(1, 0), Side::BLACK), + new Position(3, 0) + )); + + echo $subject->visualize(); + + $this->assertEquals(GameState::DEFAULT, $subject->getGameState()); + } + + public function testGameState_fiftyMoveRule_notLongEnoughSinceCapture() { + $subject = new Game( + [ + new King(new Position(2, 7), Side::WHITE, true), + new King(new Position(0, 0), Side::BLACK, true), + new Rook(new Position(1, 0), Side::BLACK), + ], + Side::BLACK + ); + $subject->_testFiftyMoveRule(48, 49); + $this->assertEquals(GameState::DEFAULT, $subject->getGameState()); + + $subject->applyInPlace(new Move( + new Rook(new Position(1, 0), Side::BLACK), + new Position(3, 0) + )); + + echo $subject->visualize(); + + $this->assertEquals(GameState::DEFAULT, $subject->getGameState()); + } + + public function testGameState_fiftyMoveRule_capture() { + $subject = new Game( + [ + new King(new Position(2, 7), Side::WHITE, true), + new King(new Position(0, 0), Side::BLACK, true), + new Rook(new Position(1, 0), Side::BLACK), + new Bishop(new Position(3, 0), Side::WHITE), + ], + Side::BLACK + ); + $subject->_testFiftyMoveRule(49, 49); + $this->assertEquals(GameState::DEFAULT, $subject->getGameState()); + + $subject->applyInPlace(new Move( + new Rook(new Position(1, 0), Side::BLACK), + new Position(3, 0), + new Bishop(new Position(3, 0), Side::WHITE) + )); + + echo $subject->visualize(); + + $this->assertEquals(GameState::DEFAULT, $subject->getGameState()); + } + + public function testGameState_fiftyMoveRule_pawnMove() { + $subject = new Game( + [ + new King(new Position(2, 7), Side::WHITE, true), + new King(new Position(0, 0), Side::BLACK, true), + new Pawn(new Position(6, 5), Side::BLACK, true) + ], + Side::BLACK + ); + $subject->_testFiftyMoveRule(49, 49); + $this->assertEquals(GameState::DEFAULT, $subject->getGameState()); + + $subject->applyInPlace(new Move( + new Pawn(new Position(6, 5), Side::BLACK), + new Position(6, 4), + )); + + echo $subject->visualize(); + + $this->assertEquals(GameState::DEFAULT, $subject->getGameState()); + } + public function testLegalMoves_pawnPinnedBecauseOfCheckKingRestrictedByQueenAndPawn() { $subject = new Game( [