feat: Add short algebraic notation for moves

This commit is contained in:
overflowerror 2024-01-09 22:31:41 +01:00
parent 1d9f6f5736
commit 29a05bd092
4 changed files with 229 additions and 13 deletions

View file

@ -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));

View file

@ -37,18 +37,124 @@ class Move {
);
}
public function getLong(): string {
if ($this->castleWith) {
if (abs($this->piece->getPosition()->file - $this->castleWith->getPosition()->file) > 3) {
private function getCheckMarker(?Game $game = null): string {
if (!$game) {
return "";
} else {
$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";
}
} else {
return $this->piece . " " .
$this->piece->getType()->getShort() . ($this->captures ? "x" : "") . $this->target .
($this->promoteTo ? $this->promoteTo->getShort() : "");
}
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 {

View file

@ -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
View 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));
}
}