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 {
|
public function intersect(FieldBitMap $map): FieldBitMap {
|
||||||
return new FieldBitMap($this->map & $map->map);
|
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;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isInCheck(FieldBitMap $captureable): bool {
|
||||||
|
return $captureable->has($this->position);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -25,6 +25,14 @@ abstract class Piece {
|
||||||
$this->wasMovedLast = true;
|
$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 getName(): string;
|
||||||
abstract public function getShort(): string;
|
abstract public function getShort(): string;
|
||||||
abstract public function getMoveCandidateMap(FieldBitMap $occupied, FieldBitMap $captureable, FieldBitMap $threatened): FieldBitMap;
|
abstract public function getMoveCandidateMap(FieldBitMap $occupied, FieldBitMap $captureable, FieldBitMap $threatened): FieldBitMap;
|
||||||
|
@ -35,5 +43,7 @@ abstract class Piece {
|
||||||
return new FieldBitMap([$this->position]);
|
return new FieldBitMap([$this->position]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function __toString() {
|
||||||
|
return $this->getShort() . $this->getPosition();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -5,4 +5,12 @@ namespace Game;
|
||||||
enum Side {
|
enum Side {
|
||||||
case WHITE;
|
case WHITE;
|
||||||
case BLACK;
|
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