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();
|
$occupiedMap = FieldBitMap::empty();
|
||||||
foreach ($pieces as $piece) {
|
foreach ($pieces as $piece) {
|
||||||
$occupiedMap->add($piece->getPosition());
|
$occupiedMap->add($piece->getPosition());
|
||||||
|
@ -76,7 +76,7 @@ class Game {
|
||||||
return $this->getOccupied($this->pieces);
|
return $this->getOccupied($this->pieces);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getCaptureable(array $pieces, bool $forPawn): FieldBitMap {
|
public function getCaptureable(array $pieces, bool $forPawn): FieldBitMap {
|
||||||
$captureableMap = FieldBitMap::empty();
|
$captureableMap = FieldBitMap::empty();
|
||||||
foreach ($pieces as $piece) {
|
foreach ($pieces as $piece) {
|
||||||
$captureableMap = $captureableMap->union($piece->getCaptureableMap($forPawn));
|
$captureableMap = $captureableMap->union($piece->getCaptureableMap($forPawn));
|
||||||
|
|
|
@ -37,20 +37,126 @@ class Move {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLong(): string {
|
private function getCheckMarker(?Game $game = null): string {
|
||||||
if ($this->castleWith) {
|
if (!$game) {
|
||||||
if (abs($this->piece->getPosition()->file - $this->castleWith->getPosition()->file) > 3) {
|
return "";
|
||||||
return "O-O-O";
|
|
||||||
} else {
|
|
||||||
return "O-O";
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return $this->piece . " " .
|
$game = $game->apply($this);
|
||||||
$this->piece->getType()->getShort() . ($this->captures ? "x" : "") . $this->target .
|
$state = $game->getGameState();
|
||||||
($this->promoteTo ? $this->promoteTo->getShort() : "");
|
|
||||||
|
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 {
|
public function __toString(): string {
|
||||||
return $this->getLong();
|
return $this->getLong();
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,16 @@ class Position {
|
||||||
return ($this->rank % 2) ^ ($this->file % 2) ? Side::WHITE : Side::BLACK;
|
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 {
|
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 {
|
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