mirror of
https://github.com/sigmasternchen/php-chess
synced 2025-03-15 07:58:54 +00:00
feat: Add game system function for fetching legal moves in position
This commit is contained in:
parent
2848208132
commit
ba9fc502c3
7 changed files with 461 additions and 1 deletions
|
@ -21,6 +21,22 @@ class Game {
|
||||||
return array_filter($this->pieces, fn($p) => $p->getSide() == $side);
|
return array_filter($this->pieces, fn($p) => $p->getSide() == $side);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function &findPiece(Piece $needle): Piece {
|
||||||
|
foreach ($this->pieces as &$piece) {
|
||||||
|
if ($piece->equals($needle)) {
|
||||||
|
return $piece;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \RuntimeException("piece not found: " . $piece);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function removePiece(Piece $needle): void {
|
||||||
|
$this->pieces = array_values(
|
||||||
|
array_filter($this->pieces, fn($p) => !($p->equals($needle)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private function getKing(Side $side): King {
|
private function getKing(Side $side): King {
|
||||||
if ($side == Side::WHITE) {
|
if ($side == Side::WHITE) {
|
||||||
return $this->whiteKing;
|
return $this->whiteKing;
|
||||||
|
@ -42,6 +58,24 @@ class Game {
|
||||||
return $this->getOccupied($this->pieces);
|
return $this->getOccupied($this->pieces);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getCaptureable(array $pieces, bool $forPawn): FieldBitMap {
|
||||||
|
$captureableMap = FieldBitMap::empty();
|
||||||
|
foreach ($pieces as $piece) {
|
||||||
|
$captureableMap = $captureableMap->union($piece->getCaptureableMap($forPawn));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $captureableMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getThreatened(array $pieces, FieldBitMap $occupied): FieldBitMap {
|
||||||
|
$threatenedMap = FieldBitMap::empty();
|
||||||
|
foreach ($pieces as $piece) {
|
||||||
|
$threatenedMap = $threatenedMap->union($piece->getCaptureMap($occupied));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $threatenedMap;
|
||||||
|
}
|
||||||
|
|
||||||
private function isInCheck(Side $side, FieldBitMap $allOccupied): bool {
|
private function isInCheck(Side $side, FieldBitMap $allOccupied): bool {
|
||||||
$opponentPieces = $this->getPieces($side->getNext());
|
$opponentPieces = $this->getPieces($side->getNext());
|
||||||
$king = $this->getKing($side);
|
$king = $this->getKing($side);
|
||||||
|
@ -65,6 +99,131 @@ class Game {
|
||||||
return $this->isInCheck($this->current->getNext(), $allOccupied);
|
return $this->isInCheck($this->current->getNext(), $allOccupied);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getLegalMoves(): array {
|
||||||
|
$ownPieces = $this->getPieces($this->current);
|
||||||
|
$opponentPieces = $this->getPieces($this->current->getNext());
|
||||||
|
|
||||||
|
$occupied = $this->getOccupied($ownPieces);
|
||||||
|
$threatened = $this->getThreatened($opponentPieces, $occupied);
|
||||||
|
|
||||||
|
return $this->getLegalMovesCached(
|
||||||
|
$ownPieces,
|
||||||
|
$opponentPieces,
|
||||||
|
$occupied,
|
||||||
|
$this->getCaptureable($opponentPieces, false),
|
||||||
|
$this->getCaptureable($opponentPieces, true),
|
||||||
|
$threatened,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generatePromotionMoves(Move $candidate): array {
|
||||||
|
$candidates = [];
|
||||||
|
|
||||||
|
foreach (PieceType::getPromotionPieces() as $type) {
|
||||||
|
$candidate->promoteTo = $type;
|
||||||
|
$candidates[] = clone $candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function findCapturedPiece(Piece $piece, array $opponentPieces, Position $target): ?Piece {
|
||||||
|
foreach ($opponentPieces as $capture) {
|
||||||
|
if ($capture->getCaptureableMap($piece instanceof Pawn)->has($target)) {
|
||||||
|
return $capture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isCapture(Position $target, FieldBitMap $captureableForPawn): bool {
|
||||||
|
return $captureableForPawn->has($target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCandidateMovesForPiece(
|
||||||
|
Piece $piece,
|
||||||
|
array $opponentPieces,
|
||||||
|
FieldBitMap $occupied,
|
||||||
|
FieldBitMap $capturableForNonPawn,
|
||||||
|
FieldBitMap $captureableForPawn,
|
||||||
|
FieldBitMap $threatened,
|
||||||
|
): array {
|
||||||
|
$candidates = [];
|
||||||
|
|
||||||
|
$candidateMap = $piece->getMoveCandidateMap(
|
||||||
|
$occupied,
|
||||||
|
($piece instanceof Pawn) ? $captureableForPawn : $capturableForNonPawn,
|
||||||
|
$threatened
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($candidateMap->getPositions() as $target) {
|
||||||
|
$candidate = new Move($piece, $target);
|
||||||
|
if ($this->isCapture($target, $captureableForPawn)) {
|
||||||
|
$candidate->captures = $this->findCapturedPiece($piece, $opponentPieces, $target);
|
||||||
|
}
|
||||||
|
if ($piece->canPromote($target)) {
|
||||||
|
$candidates = array_merge($candidates, $this->generatePromotionMoves($candidate));
|
||||||
|
} else {
|
||||||
|
$candidates[] = $candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isMoveLegal(Move $move) {
|
||||||
|
$futureGame = $this->apply($move);
|
||||||
|
return $futureGame->getGameState() != GameState::ILLEGAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getLegalMovesCached(
|
||||||
|
array &$ownPieces,
|
||||||
|
array &$opponentPieces,
|
||||||
|
FieldBitMap $occupied,
|
||||||
|
FieldBitMap $capturableNonPawn,
|
||||||
|
FieldBitMap $captureablePawn,
|
||||||
|
FieldBitMap $threatened
|
||||||
|
): array {
|
||||||
|
$candidates = [];
|
||||||
|
|
||||||
|
foreach ($ownPieces as $piece) {
|
||||||
|
$candidates = array_merge($candidates, $this->getCandidateMovesForPiece(
|
||||||
|
$piece,
|
||||||
|
$opponentPieces,
|
||||||
|
$occupied,
|
||||||
|
$capturableNonPawn,
|
||||||
|
$captureablePawn,
|
||||||
|
$threatened,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_filter($candidates, [$this, "isMoveLegal"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function apply(Move $move): Game {
|
||||||
|
$game = clone $this;
|
||||||
|
|
||||||
|
if ($move->captures) {
|
||||||
|
$game->removePiece($move->captures);
|
||||||
|
}
|
||||||
|
if ($move->promoteTo) {
|
||||||
|
$game->removePiece($move->piece);
|
||||||
|
|
||||||
|
$promoted = $move->piece->promote($move->promoteTo);
|
||||||
|
$promoted->move($move->target);
|
||||||
|
$game->pieces[] = $promoted;
|
||||||
|
} else {
|
||||||
|
$piece = $game->findPiece($move->piece);
|
||||||
|
$piece->move($move->target);
|
||||||
|
}
|
||||||
|
|
||||||
|
$game->current = $game->current->getNext();
|
||||||
|
|
||||||
|
return $game;
|
||||||
|
}
|
||||||
|
|
||||||
public function getGameState(): GameState {
|
public function getGameState(): GameState {
|
||||||
$allOccupied = $this->getAllOccupied();
|
$allOccupied = $this->getAllOccupied();
|
||||||
|
|
||||||
|
@ -81,6 +240,10 @@ class Game {
|
||||||
return GameState::DEFAULT;
|
return GameState::DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function __clone(): void {
|
||||||
|
$this->pieces = array_map(fn($p) => clone $p, $this->pieces);
|
||||||
|
}
|
||||||
|
|
||||||
public function visualize(): string {
|
public function visualize(): string {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
33
src/Game/Move.php
Normal file
33
src/Game/Move.php
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Game;
|
||||||
|
|
||||||
|
class Move {
|
||||||
|
public Piece $piece;
|
||||||
|
public Position $target;
|
||||||
|
public ?Piece $captures = null;
|
||||||
|
public ?PieceType $promoteTo = null;
|
||||||
|
|
||||||
|
public function __construct(Piece $piece, Position $target, ?Piece $captures = null, ?PieceType $promoteTo = null) {
|
||||||
|
$this->piece = $piece;
|
||||||
|
$this->target = $target;
|
||||||
|
$this->captures = $captures;
|
||||||
|
$this->promoteTo = $promoteTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function equals(Move $move): bool {
|
||||||
|
return $this->piece->equals($move->piece) &&
|
||||||
|
$this->target->equals($move->target) &&
|
||||||
|
(
|
||||||
|
($this->captures != null && $move->captures != null && $this->captures->equals($move->captures)) ||
|
||||||
|
($this->captures == null && $move->captures == null)
|
||||||
|
) &&
|
||||||
|
$this->promoteTo == $move->promoteTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString(): string {
|
||||||
|
return $this->piece . " " .
|
||||||
|
$this->piece->getShort() . ($this->captures ? "x" : "") . $this->target .
|
||||||
|
($this->promoteTo ? $this->promoteTo->getShort() : "");
|
||||||
|
}
|
||||||
|
}
|
|
@ -68,4 +68,8 @@ class Pawn extends Piece {
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function canPromote(Position $position): bool {
|
||||||
|
return ($this->side == Side::WHITE) ? ($position->rank == 7) : ($position->rank == 0);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -9,9 +9,10 @@ abstract class Piece {
|
||||||
protected bool $wasMovedLast = false;
|
protected bool $wasMovedLast = false;
|
||||||
protected ?Position $oldPosition = null;
|
protected ?Position $oldPosition = null;
|
||||||
|
|
||||||
public function __construct(Position $position, Side $side) {
|
public function __construct(Position $position, Side $side, bool $hasMoved = false) {
|
||||||
$this->position = $position;
|
$this->position = $position;
|
||||||
$this->side = $side;
|
$this->side = $side;
|
||||||
|
$this->hasMoved = $hasMoved;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tick() {
|
public function tick() {
|
||||||
|
@ -46,4 +47,44 @@ abstract class Piece {
|
||||||
public function __toString() {
|
public function __toString() {
|
||||||
return $this->getShort() . $this->getPosition();
|
return $this->getShort() . $this->getPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function getClassForType(PieceType $type): string {
|
||||||
|
switch ($type) {
|
||||||
|
case PieceType::PAWN:
|
||||||
|
return Pawn::class;
|
||||||
|
case PieceType::BISHOP:
|
||||||
|
return Bishop::class;
|
||||||
|
case PieceType::KNIGHT:
|
||||||
|
return Knight::class;
|
||||||
|
case PieceType::ROOK:
|
||||||
|
return Rook::class;
|
||||||
|
case PieceType::QUEEN:
|
||||||
|
return Queen::class;
|
||||||
|
case PieceType::KING:
|
||||||
|
return King::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \RuntimeException("unknown piecetype " . $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function ofType(PieceType $type, Position $position, Side $side): Piece {
|
||||||
|
return new (self::getClassForType($type))($position, $side);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function promote(PieceType $type): Piece {
|
||||||
|
$result = self::ofType($type, $this->position, $this->side);
|
||||||
|
$result->hasMoved = $this->hasMoved;
|
||||||
|
$result->wasMovedLast = $this->wasMovedLast;
|
||||||
|
$result->oldPosition = $this->oldPosition;
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function equals(Piece $piece): bool {
|
||||||
|
return get_class($this) == get_class($piece) &&
|
||||||
|
$this->position->equals($piece->position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canPromote(Position $position): bool {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
27
src/Game/PieceType.php
Normal file
27
src/Game/PieceType.php
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Game;
|
||||||
|
|
||||||
|
enum PieceType {
|
||||||
|
case PAWN;
|
||||||
|
case BISHOP;
|
||||||
|
case KNIGHT;
|
||||||
|
case ROOK;
|
||||||
|
case QUEEN;
|
||||||
|
case KING;
|
||||||
|
|
||||||
|
public static function getPromotionPieces() {
|
||||||
|
return [PieceType::BISHOP, PieceType::KNIGHT, PieceType::ROOK, PieceType::QUEEN];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getShort(): string {
|
||||||
|
return match ($this) {
|
||||||
|
self::PAWN => "",
|
||||||
|
self::BISHOP => "B",
|
||||||
|
self::KNIGHT => "N",
|
||||||
|
self::ROOK => "R",
|
||||||
|
self::QUEEN => "Q",
|
||||||
|
self::KING => "K",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,4 +20,8 @@ class Position {
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function equals(Position $position): bool {
|
||||||
|
return $this->file == $position->file && $this->rank == $position->rank;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -6,6 +6,26 @@ use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
final class GameTest extends TestCase {
|
final class GameTest extends TestCase {
|
||||||
|
|
||||||
|
protected function assertContainsEqualsOnce(object $needle, array $haystack) {
|
||||||
|
if (!method_exists($needle, "equals")) {
|
||||||
|
$this->assertFalse("equals() missing on needle");
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = false;
|
||||||
|
|
||||||
|
foreach ($haystack as $item) {
|
||||||
|
if ($needle->equals($item)) {
|
||||||
|
if ($result) {
|
||||||
|
$this->assertFalse("element duplication");
|
||||||
|
} else {
|
||||||
|
$result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertTrue($result, "no such element");
|
||||||
|
}
|
||||||
|
|
||||||
public function testGameState_illegal_white() {
|
public function testGameState_illegal_white() {
|
||||||
$subject = new Game(
|
$subject = new Game(
|
||||||
[
|
[
|
||||||
|
@ -57,4 +77,172 @@ final class GameTest extends TestCase {
|
||||||
|
|
||||||
$this->assertEquals(GameState::CHECK, $subject->getGameState());
|
$this->assertEquals(GameState::CHECK, $subject->getGameState());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testLegalMoves_pawnPinnedBecauseOfCheckKingRestrictedByQueenAndPawn() {
|
||||||
|
$subject = new Game(
|
||||||
|
[
|
||||||
|
new King(new Position(7, 6), Side::BLACK),
|
||||||
|
new Queen(new Position(1, 6), Side::BLACK),
|
||||||
|
new Pawn(new Position(2, 6), Side::WHITE),
|
||||||
|
new King(new Position(3, 6), Side::WHITE),
|
||||||
|
],
|
||||||
|
Side::WHITE
|
||||||
|
);
|
||||||
|
|
||||||
|
$legalMoves = $subject->getLegalMoves();
|
||||||
|
|
||||||
|
$this->assertCount(5, $legalMoves);
|
||||||
|
|
||||||
|
$this->assertContainsEqualsOnce(new Move(
|
||||||
|
new King(new Position(3, 6), Side::WHITE),
|
||||||
|
new Position(3, 7),
|
||||||
|
null, null,
|
||||||
|
), $legalMoves);
|
||||||
|
|
||||||
|
$this->assertContainsEqualsOnce(new Move(
|
||||||
|
new King(new Position(3, 6), Side::WHITE),
|
||||||
|
new Position(4, 7),
|
||||||
|
null, null,
|
||||||
|
), $legalMoves);
|
||||||
|
|
||||||
|
$this->assertContainsEqualsOnce(new Move(
|
||||||
|
new King(new Position(3, 6), Side::WHITE),
|
||||||
|
new Position(4, 6),
|
||||||
|
null, null,
|
||||||
|
), $legalMoves);
|
||||||
|
|
||||||
|
$this->assertContainsEqualsOnce(new Move(
|
||||||
|
new King(new Position(3, 6), Side::WHITE),
|
||||||
|
new Position(4, 5),
|
||||||
|
null, null,
|
||||||
|
), $legalMoves);
|
||||||
|
|
||||||
|
$this->assertContainsEqualsOnce(new Move(
|
||||||
|
new King(new Position(3, 6), Side::WHITE),
|
||||||
|
new Position(3, 5),
|
||||||
|
null, null,
|
||||||
|
), $legalMoves);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLegalMoves_kingIsBlockedPawnCanPromote() {
|
||||||
|
$subject = new Game(
|
||||||
|
[
|
||||||
|
new King(new Position(0, 0), Side::BLACK),
|
||||||
|
new King(new Position(3, 6), Side::WHITE),
|
||||||
|
new Queen(new Position(1, 2), Side::WHITE),
|
||||||
|
new Pawn(new Position(7, 1), Side::BLACK, true),
|
||||||
|
],
|
||||||
|
Side::BLACK
|
||||||
|
);
|
||||||
|
|
||||||
|
$legalMoves = $subject->getLegalMoves();
|
||||||
|
|
||||||
|
$this->assertCount(4, $legalMoves);
|
||||||
|
|
||||||
|
$this->assertContainsEqualsOnce(new Move(
|
||||||
|
new Pawn(new Position(7, 1), Side::BLACK),
|
||||||
|
new Position(7, 0),
|
||||||
|
null, PieceType::BISHOP,
|
||||||
|
), $legalMoves);
|
||||||
|
|
||||||
|
$this->assertContainsEqualsOnce(new Move(
|
||||||
|
new Pawn(new Position(7, 1), Side::BLACK),
|
||||||
|
new Position(7, 0),
|
||||||
|
null, PieceType::KNIGHT,
|
||||||
|
), $legalMoves);
|
||||||
|
|
||||||
|
$this->assertContainsEqualsOnce(new Move(
|
||||||
|
new Pawn(new Position(7, 1), Side::BLACK),
|
||||||
|
new Position(7, 0),
|
||||||
|
null, PieceType::ROOK,
|
||||||
|
), $legalMoves);
|
||||||
|
|
||||||
|
$this->assertContainsEqualsOnce(new Move(
|
||||||
|
new Pawn(new Position(7, 1), Side::BLACK),
|
||||||
|
new Position(7, 0),
|
||||||
|
null, PieceType::QUEEN,
|
||||||
|
), $legalMoves);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLegalMoves_kingIsBlockedInitialPawnMove() {
|
||||||
|
$subject = new Game(
|
||||||
|
[
|
||||||
|
new King(new Position(0, 0), Side::BLACK),
|
||||||
|
new King(new Position(3, 6), Side::WHITE),
|
||||||
|
new Queen(new Position(1, 2), Side::WHITE),
|
||||||
|
new Pawn(new Position(1, 6), Side::BLACK),
|
||||||
|
],
|
||||||
|
Side::BLACK
|
||||||
|
);
|
||||||
|
|
||||||
|
$legalMoves = $subject->getLegalMoves();
|
||||||
|
|
||||||
|
$this->assertCount(2, $legalMoves);
|
||||||
|
|
||||||
|
$this->assertContainsEqualsOnce(new Move(
|
||||||
|
new Pawn(new Position(1, 6), Side::BLACK),
|
||||||
|
new Position(1, 5),
|
||||||
|
null, null,
|
||||||
|
), $legalMoves);
|
||||||
|
|
||||||
|
$this->assertContainsEqualsOnce(new Move(
|
||||||
|
new Pawn(new Position(1, 6), Side::BLACK),
|
||||||
|
new Position(1, 4),
|
||||||
|
null, null,
|
||||||
|
), $legalMoves);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLegalMoves_kingIsBlockedEnPassant() {
|
||||||
|
$opponentPawn = new Pawn(new Position(3, 1), Side::WHITE);
|
||||||
|
$opponentPawn->move(new Position(3, 3));
|
||||||
|
|
||||||
|
$subject = new Game(
|
||||||
|
[
|
||||||
|
new King(new Position(0, 0), Side::BLACK),
|
||||||
|
new King(new Position(7, 6), Side::WHITE),
|
||||||
|
new Queen(new Position(1, 2), Side::WHITE),
|
||||||
|
$opponentPawn,
|
||||||
|
new Pawn(new Position(4, 3), Side::BLACK, true),
|
||||||
|
],
|
||||||
|
Side::BLACK
|
||||||
|
);
|
||||||
|
|
||||||
|
$legalMoves = $subject->getLegalMoves();
|
||||||
|
|
||||||
|
$this->assertCount(2, $legalMoves);
|
||||||
|
|
||||||
|
$this->assertContainsEqualsOnce(new Move(
|
||||||
|
new Pawn(new Position(4, 3), Side::BLACK),
|
||||||
|
new Position(4, 2),
|
||||||
|
null, null,
|
||||||
|
), $legalMoves);
|
||||||
|
|
||||||
|
$this->assertContainsEqualsOnce(new Move(
|
||||||
|
new Pawn(new Position(4, 3), Side::BLACK),
|
||||||
|
new Position(3, 2),
|
||||||
|
$opponentPawn, null,
|
||||||
|
), $legalMoves);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLegalMoves_kingIsInCheckAttackerCanBeTaken() {
|
||||||
|
$subject = new Game(
|
||||||
|
[
|
||||||
|
new King(new Position(0, 0), Side::BLACK),
|
||||||
|
new King(new Position(3, 6), Side::WHITE),
|
||||||
|
new Queen(new Position(1, 1), Side::WHITE),
|
||||||
|
new Queen(new Position(5, 1), Side::BLACK),
|
||||||
|
],
|
||||||
|
Side::BLACK
|
||||||
|
);
|
||||||
|
|
||||||
|
$legalMoves = $subject->getLegalMoves();
|
||||||
|
|
||||||
|
$this->assertCount(2, $legalMoves);
|
||||||
|
|
||||||
|
$this->assertContainsEqualsOnce(new Move(
|
||||||
|
new King(new Position(0, 0), Side::BLACK),
|
||||||
|
new Position(1, 1),
|
||||||
|
new Queen(new Position(1, 1), Side::WHITE), null,
|
||||||
|
), $legalMoves);
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue