feat: Add deserialization for JS move notation

This commit is contained in:
sigmasternchen 2024-10-27 12:36:10 +01:00
parent 7c17ff94b6
commit 9f5b9c83e1
6 changed files with 112 additions and 2 deletions

View file

@ -165,12 +165,27 @@ class Move {
return join(",", [
$this->piece->toJS(),
$this->target,
$this->captures ?? "",
$this->captures?->toJS() ?? "",
$this->promoteTo?->getShort() ?? "",
$this->castleWith ?? ""
$this->castleWith?->toJS() ?? ""
]);
}
static public function fromJS(string $jsStr): Move {
$components = explode(",", $jsStr);
if (count($components) != 5) {
throw new \InvalidArgumentException("unable to deserialize move string: " . $jsStr);
}
return new Move(
Piece::fromJS($components[0]),
Position::fromString($components[1]),
$components[2] != "" ? Piece::fromJS($components[2]) : null,
$components[3] != "" ? PieceType::fromShort($components[3]) : null,
$components[4] != "" ? Piece::fromJS($components[4]) : null,
);
}
public function __toString(): string {
return $this->getLong();
}

View file

@ -55,6 +55,19 @@ abstract class Piece {
]);
}
public static function fromJS(string $jsStr): Piece {
$components = explode("-", $jsStr);
if (count($components) != 3) {
throw new \InvalidArgumentException("unable to deserialize piece string: " . $jsStr);
}
return self::ofType(
PieceType::fromShort($components[1]),
Position::fromString($components[2]),
Side::fromShort($components[0]),
);
}
private static function getClassForType(PieceType $type): string {
switch ($type) {
case PieceType::PAWN:

View file

@ -25,6 +25,18 @@ enum PieceType {
};
}
public static function fromShort(string $short): PieceType {
return match (strtoupper($short)) {
"" => self::PAWN,
"B" => self::BISHOP,
"N" => self::KNIGHT,
"R" => self::ROOK,
"Q" => self::QUEEN,
"K" => self::KING,
default => throw new \InvalidArgumentException("unknown piece type: " . $short)
};
}
public function getLong(): string {
return match ($this) {
self::PAWN => "Pawn",

View file

@ -33,6 +33,18 @@ class Position {
return $this->getFileString() . $this->getRankString();
}
public static function fromString(string $str): Position {
$str = strtolower($str);
$file = ord($str[0]) - ord("a");
$rank = ord($str[1]) - ord("1");
$position = new Position($file, $rank);
if (!$position->isValid()) {
throw new \InvalidArgumentException("position is not valid: " . $str);
}
return $position;
}
public function equals(Position $position): bool {
return $this->file == $position->file && $this->rank == $position->rank;
}

View file

@ -21,4 +21,12 @@ enum Side {
return "b";
}
}
public static function fromShort(string $short): Side {
return match ($short) {
"w" => self::WHITE,
"b" => self::BLACK,
default => throw new \InvalidArgumentException("unknown side: " . $short),
};
}
}

View file

@ -97,4 +97,54 @@ final class MoveTest extends TestCase {
$this->assertEquals("Nc2xd4++", $subject->getShort($game));
}
public function testToJS_capturesAndPromotes() {
$move = new Move(
new Pawn(new Position(1, 6), Side::WHITE),
new Position(2, 7),
new Queen(new Position(2, 7), Side::BLACK),
PieceType::QUEEN,
);
$this->assertEquals("w--b7,c8,b-Q-c8,Q,", $move->toJS());
}
public function testToJS_minimal() {
$move = new Move(
new Queen(new Position(1, 6), Side::WHITE),
new Position(3, 4)
);
$this->assertEquals("w-Q-b7,d5,,,", $move->toJS());
}
public function testToJS_castle() {
$move = new Move(
new King(new Position(4, 0), Side::WHITE),
new Position(2, 0),
null,
null,
new Rook(new Position(0, 0), Side::WHITE)
);
$this->assertEquals("w-K-e1,c1,,,w-R-a1", $move->toJS());
}
public function testFromJS_capturesAndPromotes() {
$move = Move::fromJS("w--b7,c8,b-Q-c8,Q,");
$this->assertEquals("b7xc8Q", $move->getLong());
}
public function testFromJS_minimal() {
$move = Move::fromJS("w-Q-b7,d5,,,");
$this->assertEquals("Qb7d5", $move->getLong());
}
public function testFromJS_castle() {
$move = Move::fromJS("w-K-e1,c1,,,w-R-a1");
$this->assertEquals("O-O-O", $move->getLong());
}
}