feat: Add game system to recognize illegal and check game states

This commit is contained in:
overflowerror 2024-01-03 14:02:52 +01:00
parent e10776dc0b
commit 2848208132
7 changed files with 190 additions and 1 deletions

View file

@ -29,6 +29,12 @@ class FieldBitMap {
}
}
public function remove(Position $position): void {
if ($position->isValid()) {
$this->map &= ~(1 << $this->getBitForPosition($position));
}
}
public function intersect(FieldBitMap $map): FieldBitMap {
return new FieldBitMap($this->map & $map->map);
}

87
src/Game/Game.php Normal file
View file

@ -0,0 +1,87 @@
<?php
namespace Game;
class Game {
private array $pieces;
private King $whiteKing;
private King $blackKing;
private Side $current;
public function __construct(array $pieces, Side $current) {
$this->pieces = $pieces;
$this->current = $current;
$this->whiteKing = current(array_filter($this->pieces, fn($p) => ($p instanceof King) && $p->getSide() == Side::WHITE));
$this->blackKing = current(array_filter($this->pieces, fn($p) => ($p instanceof King) && $p->getSide() == Side::BLACK));
}
private function getPieces(Side $side): array {
return array_filter($this->pieces, fn($p) => $p->getSide() == $side);
}
private function getKing(Side $side): King {
if ($side == Side::WHITE) {
return $this->whiteKing;
} else {
return $this->blackKing;
}
}
private function getOccupied(array $pieces): FieldBitMap {
$occupiedMap = FieldBitMap::empty();
foreach ($pieces as $piece) {
$occupiedMap->add($piece->getPosition());
}
return $occupiedMap;
}
private function getAllOccupied(): FieldBitMap {
return $this->getOccupied($this->pieces);
}
private function isInCheck(Side $side, FieldBitMap $allOccupied): bool {
$opponentPieces = $this->getPieces($side->getNext());
$king = $this->getKing($side);
$occupied = clone $allOccupied;
$occupied->remove($king->getPosition());
$captureMap = FieldBitMap::empty();
foreach ($opponentPieces as $piece) {
$captureMap = $captureMap->union($piece->getCaptureMap($occupied));
}
return $king->isInCheck($captureMap);
}
private function isCheck(FieldBitMap $allOccupied): bool {
return $this->isInCheck($this->current, $allOccupied);
}
private function isIllegal(FieldBitMap $allOccupied): bool {
return $this->isInCheck($this->current->getNext(), $allOccupied);
}
public function getGameState(): GameState {
$allOccupied = $this->getAllOccupied();
if ($this->isIllegal($allOccupied)) {
return GameState::ILLEGAL;
}
if ($this->isCheck($allOccupied)) {
// TODO: check for checkmate
return GameState::CHECK;
}
return GameState::DEFAULT;
}
public function visualize(): string {
}
}

14
src/Game/GameState.php Normal file
View file

@ -0,0 +1,14 @@
<?php
namespace Game;
enum GameState {
case DEFAULT;
case CHECK;
case CHECKMATE;
case STALEMATE;
case DEAD_POSITION;
case THREEFOLD_REPETITION;
case FIFTY_MOVE_RULE;
case ILLEGAL;
}

View file

@ -32,4 +32,8 @@ class King extends Piece {
return $result;
}
public function isInCheck(FieldBitMap $captureable): bool {
return $captureable->has($this->position);
}
}

View file

@ -25,6 +25,14 @@ abstract class Piece {
$this->wasMovedLast = true;
}
public function getSide(): Side {
return $this->side;
}
public function getPosition(): Position {
return $this->position;
}
abstract public function getName(): string;
abstract public function getShort(): string;
abstract public function getMoveCandidateMap(FieldBitMap $occupied, FieldBitMap $captureable, FieldBitMap $threatened): FieldBitMap;
@ -35,5 +43,7 @@ abstract class Piece {
return new FieldBitMap([$this->position]);
}
public function __toString() {
return $this->getShort() . $this->getPosition();
}
}

View file

@ -5,4 +5,12 @@ namespace Game;
enum Side {
case WHITE;
case BLACK;
public function getNext(): Side {
if ($this == Side::WHITE) {
return Side::BLACK;
} else {
return Side::WHITE;
}
}
}

60
tests/Game/GameTest.php Normal file
View file

@ -0,0 +1,60 @@
<?php
namespace Game;
use PHPUnit\Framework\TestCase;
final class GameTest extends TestCase {
public function testGameState_illegal_white() {
$subject = new Game(
[
new King(new Position(1, 1), Side::BLACK),
new Knight(new Position(2, 3), Side::WHITE),
new King(new Position(7, 6), Side::WHITE),
],
Side::WHITE
);
$this->assertEquals(GameState::ILLEGAL, $subject->getGameState());
}
public function testGameState_illegal_black() {
$subject = new Game(
[
new King(new Position(0, 0), Side::WHITE),
new Queen(new Position(7, 7), Side::BLACK),
new King(new Position(7, 6), Side::BLACK),
],
Side::BLACK
);
$this->assertEquals(GameState::ILLEGAL, $subject->getGameState());
}
public function testGameState_check_white() {
$subject = new Game(
[
new King(new Position(5, 4), Side::WHITE),
new Rook(new Position(2, 4), Side::BLACK),
new King(new Position(7, 6), Side::BLACK),
],
Side::WHITE
);
$this->assertEquals(GameState::CHECK, $subject->getGameState());
}
public function testGameState_check_black() {
$subject = new Game(
[
new King(new Position(5, 4), Side::BLACK),
new Pawn(new Position(4, 3), Side::WHITE),
new King(new Position(7, 6), Side::WHITE),
],
Side::BLACK
);
$this->assertEquals(GameState::CHECK, $subject->getGameState());
}
}