diff --git a/Makefile b/Makefile index f5a4c28..8b191dc 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ install: ## install vendors docker compose exec php composer install stan: ## static analysis via phpstan - docker compose exec php php vendor/bin/phpstan analyse -v + docker compose exec php php vendor/bin/phpstan analyse -v --memory-limit=-1 start: ## start docker image docker compose up -d diff --git a/docs/board.md b/docs/board.md index 4dd010b..508d1bd 100644 --- a/docs/board.md +++ b/docs/board.md @@ -16,8 +16,13 @@ foreach ($board as $offset => $piece) { } ``` -The only method that can be directly used is `$board->reverse()`, useful to toggle sides (e.g. to see -the board from the black point of view, and then from the white again). +The following methods can be used directly: + +* `reverse()`: toggle sides (e.g. to see the board from the black point of view, and then from the white again) +* `isAttacked(string $color, int $square): bool`: check if a square is attacked by any piece of the given color + (e.g. `$chess->board->isAttacked(Piece::WHITE, Board::SQUARES['e4'])`) +* `hasInsufficientMaterial(): bool`: check if material is insufficient to continue the game. + Same as `Chess::insufficientMaterial()` The following static methods can be used if you need to deal with [SAN](https://en.wikipedia.org/wiki/Algebraic_notation_(chess)): diff --git a/src/Board.php b/src/Board.php index 61609db..9b07474 100644 --- a/src/Board.php +++ b/src/Board.php @@ -142,6 +142,113 @@ public function isReversed(): bool return $this->reversed; } + public function isAttacked(string $color, int $square): bool + { + for ($i = self::SQUARES['a8']; $i <= self::SQUARES['h1']; ++$i) { + if (($i & 0x88) > 0) { + $i += 7; + continue; + } + $piece = $this->squares[$i] ?? null; + // check empty square and color + if (null === $piece || $piece->getColor() !== $color) { + continue; + } + + $difference = $i - $square; + $index = $difference + 119; + + if ((self::ATTACKS[$index] & (1 << Piece::SHIFTS[$piece->getType()])) > 0) { + if ($piece->isPawn()) { + if ($difference > 0) { + if ($piece->getColor() === Piece::WHITE) { + return true; + } + } elseif ($piece->getColor() === Piece::BLACK) { + return true; + } + continue; + } + + if ($piece->isKnight() || $piece->isKing()) { + return true; + } + + $offset = self::RAYS[$index]; + $j = $i + $offset; + $blocked = false; + while ($j !== $square) { + if (($this->squares[$j] ?? null) !== null) { + $blocked = true; + break; + } + $j += $offset; + } + + if (!$blocked) { + return true; + } + } + } + + return false; + } + + public function hasInsufficientMaterial(): bool + { + $pieces = [ + Piece::PAWN => 0, + Piece::KNIGHT => 0, + Piece::BISHOP => 0, + Piece::ROOK => 0, + Piece::QUEEN => 0, + Piece::KING => 0, + ]; + $bishops = []; + $numPieces = 0; + $sqColor = 0; + + for ($i = self::SQUARES['a8']; $i <= self::SQUARES['h1']; ++$i) { + $sqColor = ($sqColor + 1) % 2; + if (($i & 0x88) > 0) { + $i += 7; + continue; + } + + $piece = $this->squares[$i] ?? null; + if ($piece !== null) { + ++$pieces[$piece->getType()]; + if ($piece->isBishop()) { + $bishops[] = $sqColor; + } + ++$numPieces; + } + } + + // k vs k + if ($numPieces === 2) { + return true; + } + + // k vs kn / k vs kb + if ($numPieces === 3 && ($pieces[Piece::BISHOP] === 1 || $pieces[Piece::KNIGHT] === 1)) { + return true; + } + + // k(b){0,} vs k(b){0,} , because maybe you are a programmer we talk in regex (preg) :-p + if ($numPieces === $pieces[Piece::BISHOP] + 2) { + $sum = 0; + foreach ($bishops as $bishop) { + $sum += $bishop; + } + if ($sum === 0 || $sum === \count($bishops)) { + return true; + } + } + + return false; + } + public static function rank(int $i): int { return $i >> 4; diff --git a/src/Chess.php b/src/Chess.php index bc4eec8..ec3e12b 100644 --- a/src/Chess.php +++ b/src/Chess.php @@ -526,9 +526,9 @@ protected function generateMoves(?int $square = null, bool $legal = true): array if ( $this->board[$castlingFrom + 1] === null && $this->board[$castlingTo] === null && - !$this->attacked($them, $this->kings[$us]) && - !$this->attacked($them, $castlingFrom + 1) && - !$this->attacked($them, $castlingTo) + !$this->board->isAttacked($them, $this->kings[$us]) && + !$this->board->isAttacked($them, $castlingFrom + 1) && + !$this->board->isAttacked($them, $castlingTo) ) { self::addMove($us, $this->board, $moves, $this->kings[$us], $castlingTo, Move::BITS['KSIDE_CASTLE']); } @@ -543,9 +543,9 @@ protected function generateMoves(?int $square = null, bool $legal = true): array $this->board[$castlingFrom - 1] === null && $this->board[$castlingFrom - 2] === null && // $castlingTo $this->board[$castlingFrom - 3] === null && // col "b", next to rock - !$this->attacked($them, $this->kings[$us]) && - !$this->attacked($them, $castlingFrom - 1) && - !$this->attacked($them, $castlingTo) + !$this->board->isAttacked($them, $this->kings[$us]) && + !$this->board->isAttacked($them, $castlingFrom - 1) && + !$this->board->isAttacked($them, $castlingTo) ) { self::addMove($us, $this->board, $moves, $this->kings[$us], $castlingTo, Move::BITS['QSIDE_CASTLE']); } @@ -638,59 +638,12 @@ public function moves(?int $square = null): array protected function attacked(string $color, int $square): bool { - for ($i = Board::SQUARES['a8']; $i <= Board::SQUARES['h1']; ++$i) { - if (($i & 0x88) > 0) { - $i += 7; - continue; - } - $piece = $this->board[$i]; - // check empty square and color - if (null === $piece || $piece->getColor() !== $color) { - continue; - } - - $difference = $i - $square; - $index = $difference + 119; - - if ((Board::ATTACKS[$index] & (1 << Piece::SHIFTS[$piece->getType()])) > 0) { - if ($piece->isPawn()) { - if ($difference > 0) { - if ($piece->getColor() === Piece::WHITE) { - return true; - } - } elseif ($piece->getColor() === Piece::BLACK) { - return true; - } - continue; - } - - if ($piece->isKnight() || $piece->isKing()) { - return true; - } - - $offset = Board::RAYS[$index]; - $j = $i + $offset; - $blocked = false; - while ($j !== $square) { - if ($this->board[$j] !== null) { - $blocked = true; - break; - } - $j += $offset; - } - - if (!$blocked) { - return true; - } - } - } - - return false; + return $this->board->isAttacked($color, $square); } protected function kingAttacked(string $color): bool { - return null !== $this->kings[$color] && $this->attacked(self::swapColor($color), $this->kings[$color]); + return null !== $this->kings[$color] && $this->board->isAttacked(self::swapColor($color), $this->kings[$color]); } public function inCheck(): bool @@ -710,57 +663,7 @@ public function inStalemate(): bool public function insufficientMaterial(): bool { - $pieces = [ - Piece::PAWN => 0, - Piece::KNIGHT => 0, - Piece::BISHOP => 0, - Piece::ROOK => 0, - Piece::QUEEN => 0, - Piece::KING => 0, - ]; - $bishops = []; - $numPieces = 0; - $sqColor = 0; - - for ($i = Board::SQUARES['a8']; $i <= Board::SQUARES['h1']; ++$i) { - $sqColor = ($sqColor + 1) % 2; - if (($i & 0x88) > 0) { - $i += 7; - continue; - } - - $piece = $this->board[$i]; - if ($piece !== null) { - ++$pieces[$piece->getType()]; - if ($piece->isBishop()) { - $bishops[] = $sqColor; - } - ++$numPieces; - } - } - - // k vs k - if ($numPieces === 2) { - return true; - } - - // k vs kn / k vs kb - if ($numPieces === 3 && ($pieces[Piece::BISHOP] === 1 || $pieces[Piece::KNIGHT] === 1)) { - return true; - } - - // k(b){0,} vs k(b){0,} , because maybe you are a programmer we talk in regex (preg) :-p - if ($numPieces === $pieces[Piece::BISHOP] + 2) { - $sum = 0; - foreach ($bishops as $bishop) { - $sum += $bishop; - } - if ($sum === 0 || $sum === \count($bishops)) { - return true; - } - } - - return false; + return $this->board->hasInsufficientMaterial(); } /* TODO: while this function is fine for casual use, a better diff --git a/tests/AttackTest.php b/tests/AttackTest.php index 5a4ada1..84c0118 100644 --- a/tests/AttackTest.php +++ b/tests/AttackTest.php @@ -5,6 +5,7 @@ namespace PChess\Chess\Test; use PChess\Chess\Board; +use PChess\Chess\Chess; use PChess\Chess\Piece; use PHPUnit\Framework\TestCase; @@ -12,110 +13,110 @@ final class AttackTest extends TestCase { public function testAttackedPawn(): void { - $chess = new ChessPublicator(Board::EMPTY); + $chess = new Chess(Board::EMPTY); $chess->put(new Piece(Piece::PAWN, Piece::WHITE), 'e4'); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d3'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['e3'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f3'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d3'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['e3'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f3'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d4'])); - //~ self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['e4'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f4'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d4'])); + //~ self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['e4'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f4'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d5'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['e5'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f5'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d5'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['e5'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f5'])); } public function testAttackedKnight(): void { - $chess = new ChessPublicator(Board::EMPTY); + $chess = new Chess(Board::EMPTY); $chess->put(new Piece(Piece::KNIGHT, Piece::WHITE), 'e4'); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d2'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d6'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f2'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f6'])); - - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d1'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d3'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d4'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d5'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d7'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d8'])); - - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f1'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f3'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f4'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f5'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f7'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f8'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d2'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d6'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f2'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f6'])); + + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d1'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d3'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d4'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d5'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d7'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d8'])); + + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f1'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f3'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f4'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f5'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f7'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f8'])); } public function testAttackedBishop(): void { - $chess = new ChessPublicator(Board::EMPTY); + $chess = new Chess(Board::EMPTY); $chess->put(new Piece(Piece::BISHOP, Piece::WHITE), 'e4'); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d3'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d5'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f3'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f5'])); - - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d4'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['e3'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['e5'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f4'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d3'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d5'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f3'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f5'])); + + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d4'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['e3'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['e5'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f4'])); } public function testAttackedRook(): void { - $chess = new ChessPublicator(Board::EMPTY); + $chess = new Chess(Board::EMPTY); $chess->put(new Piece(Piece::ROOK, Piece::WHITE), 'e4'); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d3'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d5'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f3'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f5'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f7'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f8'])); - - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d4'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['e3'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['e5'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f4'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d3'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d5'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f3'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f5'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f7'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f8'])); + + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d4'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['e3'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['e5'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f4'])); } public function testAttackedQueen(): void { - $chess = new ChessPublicator(Board::EMPTY); + $chess = new Chess(Board::EMPTY); $chess->put(new Piece(Piece::QUEEN, Piece::WHITE), 'e4'); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d3'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d4'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d5'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['e3'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['e4'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['e5'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f3'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f4'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f5'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d3'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d4'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d5'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['e3'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['e4'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['e5'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f3'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f4'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f5'])); } public function testAttackedKing(): void { - $chess = new ChessPublicator(Board::EMPTY); + $chess = new Chess(Board::EMPTY); $chess->put(new Piece(Piece::KING, Piece::WHITE), 'e4'); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d3'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d4'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['d5'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['e3'])); - self::assertFalse($chess->attackedPublic(Piece::WHITE, Board::SQUARES['e4'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['e5'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f3'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f4'])); - self::assertTrue($chess->attackedPublic(Piece::WHITE, Board::SQUARES['f5'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d3'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d4'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['d5'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['e3'])); + self::assertFalse($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['e4'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['e5'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f3'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f4'])); + self::assertTrue($chess->board->isAttacked(Piece::WHITE, Board::SQUARES['f5'])); } public function testInCheck(): void { - $chess = new ChessPublicator(Board::EMPTY); + $chess = new Chess(Board::EMPTY); $chess->put(new Piece(Piece::KING, Piece::WHITE), 'e7'); $chess->put(new Piece(Piece::QUEEN, Piece::BLACK), 'e4'); self::assertSame(Piece::WHITE, $chess->turn); @@ -128,7 +129,7 @@ public function testInCheck(): void public function testInCheckmate(): void { - $chess = new ChessPublicator('r1bqk1nr/pppp1Qpp/2n5/2b1p3/2B1P3/8/PPPP1PPP/RNB1K1NR b KQkq - 0 4'); + $chess = new Chess('r1bqk1nr/pppp1Qpp/2n5/2b1p3/2B1P3/8/PPPP1PPP/RNB1K1NR b KQkq - 0 4'); self::assertTrue($chess->inCheckmate()); } @@ -136,34 +137,34 @@ public function testInStalemate(): void { // fen source: https://www.redhotpawn.com/forum/only-chess/interesting-stalemate-position.152109 // start fen : 3b3k/p6p/1p5P/3q4/8/n7/PP6/K4Q2 w - - 0 1 - $chess = new ChessPublicator('7k/p6p/1p3b1P/3q4/8/n7/PP6/K7 w - - 0 2'); + $chess = new Chess('7k/p6p/1p3b1P/3q4/8/n7/PP6/K7 w - - 0 2'); self::assertTrue($chess->inStalemate()); } public function testInsufficientMaterial(): void { // k vs k - $chess = new ChessPublicator(Board::EMPTY); + $chess = new Chess(Board::EMPTY); $chess->put(new Piece(Piece::KING, Piece::WHITE), 'e1'); $chess->put(new Piece(Piece::KING, Piece::BLACK), 'e8'); self::assertTrue($chess->insufficientMaterial()); // k vs kn - $chess = new ChessPublicator(Board::EMPTY); + $chess = new Chess(Board::EMPTY); $chess->put(new Piece(Piece::KING, Piece::WHITE), 'e1'); $chess->put(new Piece(Piece::KING, Piece::BLACK), 'e8'); $chess->put(new Piece(Piece::KNIGHT, Piece::WHITE), 'e4'); self::assertTrue($chess->insufficientMaterial()); // k vs kb - $chess = new ChessPublicator(Board::EMPTY); + $chess = new Chess(Board::EMPTY); $chess->put(new Piece(Piece::KING, Piece::WHITE), 'e1'); $chess->put(new Piece(Piece::KING, Piece::BLACK), 'e8'); $chess->put(new Piece(Piece::BISHOP, Piece::WHITE), 'e4'); self::assertTrue($chess->insufficientMaterial()); // k vs k(b){0,} << bishop(s) in same color - $chess = new ChessPublicator(Board::EMPTY); + $chess = new Chess(Board::EMPTY); $chess->put(new Piece(Piece::KING, Piece::WHITE), 'e1'); $chess->put(new Piece(Piece::KING, Piece::BLACK), 'e8'); $chess->put(new Piece(Piece::BISHOP, Piece::BLACK), 'e5'); @@ -174,11 +175,28 @@ public function testInsufficientMaterial(): void self::assertTrue($chess->insufficientMaterial()); $chess->put(new Piece(Piece::BISHOP, Piece::BLACK), 'b8'); self::assertTrue($chess->insufficientMaterial()); + + // k vs kbb << bishops on opposite colours + $chess = new Chess(Board::EMPTY); + $chess->put(new Piece(Piece::KING, Piece::WHITE), 'e1'); + $chess->put(new Piece(Piece::KING, Piece::BLACK), 'e8'); + $chess->put(new Piece(Piece::BISHOP, Piece::BLACK), 'e5'); + $chess->put(new Piece(Piece::BISHOP, Piece::BLACK), 'd5'); + self::assertFalse($chess->insufficientMaterial()); + + // k vs kr + $chess = new Chess(Board::EMPTY); + $chess->put(new Piece(Piece::KING, Piece::WHITE), 'e1'); + $chess->put(new Piece(Piece::KING, Piece::BLACK), 'e8'); + $chess->put(new Piece(Piece::ROOK, Piece::WHITE), 'a1'); + self::assertFalse($chess->insufficientMaterial()); + + self::assertFalse((new Chess())->board->hasInsufficientMaterial()); } public function testInThreefoldRepetition(): void { - $chess = new ChessPublicator(); + $chess = new Chess(); /* * [Event "Fischer - Petrosian Candidates Final"] diff --git a/tests/ChessPublicator.php b/tests/ChessPublicator.php index 832e053..729cdcd 100644 --- a/tests/ChessPublicator.php +++ b/tests/ChessPublicator.php @@ -27,11 +27,6 @@ public function getLastHistory(): Entry return $this->history->get(\count($this->history->getEntries()) - 1); } - public function attackedPublic(string $color, int $square): bool - { - return $this->attacked($color, $square); - } - /** * @return array */