mirror of
https://github.com/sigmasternchen/php-chess
synced 2025-03-15 07:58:54 +00:00
feat: Add game system to recognize illegal and check game states
This commit is contained in:
parent
e10776dc0b
commit
2848208132
7 changed files with 190 additions and 1 deletions
|
@ -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
87
src/Game/Game.php
Normal 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
14
src/Game/GameState.php
Normal 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;
|
||||
}
|
|
@ -32,4 +32,8 @@ class King extends Piece {
|
|||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function isInCheck(FieldBitMap $captureable): bool {
|
||||
return $captureable->has($this->position);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
60
tests/Game/GameTest.php
Normal 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());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue