mirror of
https://github.com/sigmasternchen/php-chess
synced 2025-03-15 07:58:54 +00:00
feat: Add short algebraic notation for moves
This commit is contained in:
parent
1d9f6f5736
commit
29a05bd092
4 changed files with 229 additions and 13 deletions
|
@ -63,7 +63,7 @@ class Game {
|
|||
}
|
||||
}
|
||||
|
||||
private function getOccupied(array $pieces): FieldBitMap {
|
||||
public function getOccupied(array $pieces): FieldBitMap {
|
||||
$occupiedMap = FieldBitMap::empty();
|
||||
foreach ($pieces as $piece) {
|
||||
$occupiedMap->add($piece->getPosition());
|
||||
|
@ -76,7 +76,7 @@ class Game {
|
|||
return $this->getOccupied($this->pieces);
|
||||
}
|
||||
|
||||
private function getCaptureable(array $pieces, bool $forPawn): FieldBitMap {
|
||||
public function getCaptureable(array $pieces, bool $forPawn): FieldBitMap {
|
||||
$captureableMap = FieldBitMap::empty();
|
||||
foreach ($pieces as $piece) {
|
||||
$captureableMap = $captureableMap->union($piece->getCaptureableMap($forPawn));
|
||||
|
|
|
@ -37,20 +37,126 @@ class Move {
|
|||
);
|
||||
}
|
||||
|
||||
public function getLong(): string {
|
||||
if ($this->castleWith) {
|
||||
if (abs($this->piece->getPosition()->file - $this->castleWith->getPosition()->file) > 3) {
|
||||
return "O-O-O";
|
||||
} else {
|
||||
return "O-O";
|
||||
}
|
||||
private function getCheckMarker(?Game $game = null): string {
|
||||
if (!$game) {
|
||||
return "";
|
||||
} else {
|
||||
return $this->piece . " " .
|
||||
$this->piece->getType()->getShort() . ($this->captures ? "x" : "") . $this->target .
|
||||
($this->promoteTo ? $this->promoteTo->getShort() : "");
|
||||
$game = $game->apply($this);
|
||||
$state = $game->getGameState();
|
||||
|
||||
if ($state == GameState::CHECK) {
|
||||
return "+";
|
||||
} else if ($state == GameState::CHECKMATE) {
|
||||
return "++";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function isCastles(): bool {
|
||||
return !!$this->castleWith;
|
||||
}
|
||||
|
||||
private function isLongCastles(): bool {
|
||||
return abs($this->piece->getPosition()->file - $this->castleWith->getPosition()->file) > 3;
|
||||
}
|
||||
|
||||
private function getCastlesMarker(): string {
|
||||
if ($this->isLongCastles()) {
|
||||
return "O-O-O";
|
||||
} else {
|
||||
return "O-O";
|
||||
}
|
||||
}
|
||||
|
||||
private function getCapturesMarker(): string {
|
||||
return $this->captures ? "x" : "";
|
||||
}
|
||||
|
||||
private function getPromotionMarker(): string {
|
||||
return $this->promoteTo ? $this->promoteTo->getShort() : "";
|
||||
}
|
||||
|
||||
private function getMinimalSourcePositionMarker(Game $game): string {
|
||||
$pieceClass = get_class($this->piece);
|
||||
$piecePosition = $this->piece->getPosition();
|
||||
|
||||
$needsFile = false;
|
||||
$needsRank = false;
|
||||
$needsAny = false;
|
||||
|
||||
foreach ($game->getLegalMoves() as $move) {
|
||||
if (!is_a($move->piece, $pieceClass)) {
|
||||
continue;
|
||||
}
|
||||
if (!$this->target->equals($move->target)) {
|
||||
continue;
|
||||
}
|
||||
if ($this->piece->equals($move->piece)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$otherPosition = $move->piece->getPosition();
|
||||
|
||||
$wouldNeedRank = $piecePosition->file == $otherPosition->file;
|
||||
$wouldNeedFile = $piecePosition->rank == $otherPosition->rank;
|
||||
|
||||
if ($wouldNeedRank) {
|
||||
$needsRank = true;
|
||||
$needsAny = false;
|
||||
}
|
||||
if ($wouldNeedFile) {
|
||||
$needsFile = true;
|
||||
$needsAny = false;
|
||||
}
|
||||
if (!$needsRank && !$needsFile) {
|
||||
$needsAny = true;
|
||||
}
|
||||
}
|
||||
|
||||
$result = "";
|
||||
if ($needsFile || $needsAny) {
|
||||
$result .= $piecePosition->getFileString();
|
||||
}
|
||||
if ($needsRank) {
|
||||
$result .= $piecePosition->getRankString();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getLong(?Game $game = null): string {
|
||||
if ($this->isCastles()) {
|
||||
$result = $this->getCastlesMarker();
|
||||
} else {
|
||||
$result = $this->piece;
|
||||
$result .= $this->getCapturesMarker();
|
||||
$result .= $this->target;
|
||||
$result .= $this->getPromotionMarker();
|
||||
}
|
||||
|
||||
$result .= $this->getCheckMarker($game);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getShort(Game $game): string {
|
||||
if ($this->isCastles()) {
|
||||
$result = $this->getCastlesMarker();
|
||||
} else {
|
||||
$result = $this->piece->getType()->getShort();
|
||||
$result .= $this->getMinimalSourcePositionMarker($game);
|
||||
$result .= $this->getCapturesMarker();
|
||||
$result .= $this->target;
|
||||
$result .= $this->getPromotionMarker();
|
||||
}
|
||||
|
||||
$result .= $this->getCheckMarker($game);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function __toString(): string {
|
||||
return $this->getLong();
|
||||
}
|
||||
|
|
|
@ -21,8 +21,16 @@ class Position {
|
|||
return ($this->rank % 2) ^ ($this->file % 2) ? Side::WHITE : Side::BLACK;
|
||||
}
|
||||
|
||||
public function getFileString(): string {
|
||||
return ["a", "b", "c", "d", "e", "f", "g", "h"][$this->file];
|
||||
}
|
||||
|
||||
public function getRankString(): string {
|
||||
return strval($this->rank + 1);
|
||||
}
|
||||
|
||||
public function __toString(): string {
|
||||
return ["a", "b", "c", "d", "e", "f", "g", "h"][$this->file] . ($this->rank + 1);
|
||||
return $this->getFileString() . $this->getRankString();
|
||||
}
|
||||
|
||||
public function equals(Position $position): bool {
|
||||
|
|
102
tests/Game/MoveTest.php
Normal file
102
tests/Game/MoveTest.php
Normal file
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Game;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class MoveTest extends TestCase {
|
||||
|
||||
public function testShort_noSourceCollision() {
|
||||
$game = new Game([
|
||||
new King(new Position(0,0), Side::WHITE),
|
||||
new King(new Position(7, 7), Side::BLACK),
|
||||
new Pawn(new Position(3, 3), Side::BLACK, true),
|
||||
new Knight(new Position(2, 1), Side::WHITE),
|
||||
], Side::WHITE);
|
||||
|
||||
$subject = new Move(
|
||||
new Knight(new Position(2, 1), Side::WHITE),
|
||||
new Position(3, 3),
|
||||
new Pawn(new Position(3, 3), Side::BLACK),
|
||||
);
|
||||
|
||||
$this->assertEquals("Nxd4", $subject->getShort($game));
|
||||
}
|
||||
|
||||
public function testShort_simpleCollisionAndCheck() {
|
||||
$game = new Game([
|
||||
new King(new Position(0,0), Side::WHITE),
|
||||
new King(new Position(2, 5), Side::BLACK),
|
||||
new Pawn(new Position(3, 3), Side::BLACK, true),
|
||||
new Knight(new Position(2, 1), Side::WHITE),
|
||||
new Knight(new Position(4, 5), Side::WHITE),
|
||||
], Side::WHITE);
|
||||
|
||||
$subject = new Move(
|
||||
new Knight(new Position(2, 1), Side::WHITE),
|
||||
new Position(3, 3),
|
||||
new Pawn(new Position(3, 3), Side::BLACK),
|
||||
);
|
||||
|
||||
$this->assertEquals("Ncxd4+", $subject->getShort($game));
|
||||
}
|
||||
|
||||
public function testShort_fileCollision() {
|
||||
$game = new Game([
|
||||
new King(new Position(0,0), Side::WHITE),
|
||||
new King(new Position(7, 7), Side::BLACK),
|
||||
new Pawn(new Position(3, 3), Side::BLACK, true),
|
||||
new Knight(new Position(2, 1), Side::WHITE),
|
||||
new Knight(new Position(2, 5), Side::WHITE),
|
||||
], Side::WHITE);
|
||||
|
||||
$subject = new Move(
|
||||
new Knight(new Position(2, 1), Side::WHITE),
|
||||
new Position(3, 3),
|
||||
new Pawn(new Position(3, 3), Side::BLACK),
|
||||
);
|
||||
|
||||
$this->assertEquals("N2xd4", $subject->getShort($game));
|
||||
}
|
||||
|
||||
public function testShort_rankCollision() {
|
||||
$game = new Game([
|
||||
new King(new Position(0,0), Side::WHITE),
|
||||
new King(new Position(0, 0), Side::BLACK),
|
||||
new Pawn(new Position(3, 3), Side::BLACK, true),
|
||||
new Knight(new Position(2, 1), Side::WHITE),
|
||||
new Knight(new Position(4, 1), Side::WHITE),
|
||||
], Side::WHITE);
|
||||
|
||||
$subject = new Move(
|
||||
new Knight(new Position(2, 1), Side::WHITE),
|
||||
new Position(3, 3),
|
||||
new Pawn(new Position(3, 3), Side::BLACK),
|
||||
);
|
||||
|
||||
$this->assertEquals("Ncxd4", $subject->getShort($game));
|
||||
}
|
||||
|
||||
public function testShort_fullCollisionAndCheckmate() {
|
||||
$game = new Game([
|
||||
new King(new Position(0,0), Side::WHITE),
|
||||
new King(new Position(4, 5), Side::BLACK),
|
||||
new Pawn(new Position(3, 3), Side::BLACK, true),
|
||||
new Knight(new Position(2, 1), Side::WHITE),
|
||||
new Knight(new Position(2, 5), Side::WHITE),
|
||||
new Knight(new Position(4, 1), Side::WHITE),
|
||||
new Queen(new Position(6, 6), Side::WHITE),
|
||||
new Rook(new Position(3, 7), Side::WHITE),
|
||||
], Side::WHITE);
|
||||
|
||||
$subject = new Move(
|
||||
new Knight(new Position(2, 1), Side::WHITE),
|
||||
new Position(3, 3),
|
||||
new Pawn(new Position(3, 3), Side::BLACK),
|
||||
);
|
||||
|
||||
$this->assertEquals("Nc2xd4++", $subject->getShort($game));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue