cmuset / chess-tools
A chess tools library for parsing and exporting chess notations (PGN, SAN, FEN).
Requires
- php: >=8.4
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.87
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^12.3
README
A PHP library for parsing and exporting chess notations: PGN, SAN, and FEN.
Table of Contents
Installation
Requires PHP >= 8.4.
composer require cmuset/chess-tools
Quick Start
use Cmuset\ChessTools\Model\Game; // Parse a PGN string $game = Game::fromPGN($pgn); echo $game->getTag('White'); // 'Kasparov, Garry' echo $game->getResult()->value; // '1-0' // Iterate moves foreach ($game->getMainLine() as $key => $node) { echo $key . ' ' . $node->getMove()->getSAN(); // '1. e4', '1... e5', ... foreach ($node->getVariations() as $variation) { // Alternative lines branching from this move } } // Apply moves to a position use Cmuset\ChessTools\Model\Position; use Cmuset\ChessTools\Tool\Parser\PGNParser; $pos = Position::fromFEN(PGNParser::INITIAL_FEN); $pos->applyMove('e4'); $pos->applyMove('e5'); echo $pos->getFEN(); // 'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1' // Export echo $game->getPGN(); // Full PGN with tags, comments, variations echo $game->getLitePGN(); // Moves only
Tools
Parsers — docs/tools/parser.md
Convert strings into model objects.
| Class | Input | Output |
|---|---|---|
PGNParser |
PGN string | Game or Game[] |
SANParser |
SAN string + color | Move |
FENParser |
FEN string | Position |
use Cmuset\ChessTools\Tool\Parser\PGNParser; $parser = PGNParser::create(); $game = $parser->parse($pgn); // Game | Game[]
Exporters — docs/tools/exporter.md
Serialize model objects back to strings.
| Class | Input | Output |
|---|---|---|
GameExporter |
Game or Variation |
PGN string |
MoveExporter |
Move |
SAN string |
PositionExporter |
Position |
FEN string |
$game->getPGN(); // full PGN $game->getLitePGN(); // moves only $game->getVerbosePgn(); // with resolved source squares and check/mate markers $position->getFEN(); $move->getSAN();
MoveApplier — docs/tools/move-applier.md
Applies a Move to a Position, enforcing all chess rules: castling rights, en passant, promotion, counters, and post-move check validation.
use Cmuset\ChessTools\Tool\MoveApplier\Exception\MoveApplyingException; try { $pos->applyMove('Nf3'); } catch (MoveApplyingException $e) { echo $e->getMoveViolation()->value; // e.g. 'No piece found for the move' } $pos->getLegalMoves(); // Move[] — all legal moves for the side to move
Validators — docs/tools/validator.md
Check positions and games for illegal states.
| Class | Validates |
|---|---|
PositionValidator |
A single Position (kings, check, pawns, en passant) |
GameValidator |
A full Game — replays every move including sub-variations |
use Cmuset\ChessTools\Tool\Validator\PositionValidator; use Cmuset\ChessTools\Tool\Validator\GameValidator; $violations = (new PositionValidator())->validate($pos); // PositionViolationEnum[] $violation = (new GameValidator())->validate($game); // ?GameViolation
Resolvers — docs/tools/resolver.md
Derive information absent from raw SAN: source squares, capture flags, and check/checkmate markers.
| Class | Resolves |
|---|---|
MoveResolver |
A single Move against a Position |
VariationResolver |
All moves in a Variation sequentially |
GameResolver |
The full game main line + result detection |
use Cmuset\ChessTools\Tool\Resolver\GameResolver; GameResolver::create()->resolve($game); // All moves now carry squareFrom, isCapture, isCheck, isCheckmate
VariationSplitter — docs/tools/splitter.md
Extracts all nested variations into a flat list of independent Variation objects, each prefixed with the moves preceding the divergence.
$variations = $game->split(); // Variation[] — first is always the main line
VariationMerger — docs/tools/merger.md
Merges Variation objects back into a main line, inserting diverging moves as nested sub-variations at the correct branching points.
$game->merge($variationA, $variationB);
Model Overview
classDiagram
direction TB
class Game {
tags: array
result: ResultEnum
+fromPGN(string) Game
+getPGN() string
+split() Variation[]
+merge(Variation[]) void
}
class Position {
sideToMove: ColorEnum
castlingRights: CastlingEnum[]
enPassantTarget: CoordinatesEnum
halfmoveClock: int
fullmoveNumber: int
+fromFEN(string) Position
+getFEN() string
+applyMove(Move) void
+getLegalMoves() Move[]
}
class Variation {
identifier: string
+fromPGN(string) Variation
+getPGN() string
+split() Variation[]
+merge(Variation[]) void
}
class MoveNode {
moveNumber: int
nags: int[]
beforeMoveComment: string
afterMoveComment: string
+getKey() string
}
class Move {
isCapture: bool
isCheck: bool
isCheckmate: bool
+fromSAN(string, ColorEnum) Move
+getSAN() string
}
class Square {
+isEmpty() bool
}
Game "1" --> "1" Position : initialPosition
Game "1" --> "1" Variation : mainLine
Variation "1" *-- "*" MoveNode : nodes
MoveNode "1" --> "0..1" Move
MoveNode "1" *-- "*" Variation : variations
Position "1" *-- "64" Square : squares
Move --> PieceEnum
Move --> CoordinatesEnum : to, squareFrom
Move --> CastlingEnum
Square --> PieceEnum
Square --> CoordinatesEnum
Loading
| Class | Description | Docs |
|---|---|---|
Game |
Full PGN game: tags, initial position, main line, result | → |
Position |
Board state: pieces, side to move, castling rights, en passant, counters | → |
Variation |
Ordered collection of MoveNode instances, keyed by "1." / "1..." notation |
→ |
MoveNode |
Node in the move tree: move + move number + comments + NAGs + sub-variations | → |
Move |
Parsed SAN move: piece, destination, flags (capture, check, castling, promotion) | → |
Square |
A board square: coordinates + optional piece | → |
Enums Reference
All domain concepts are PHP string-backed enums.
| Enum | Values | Docs |
|---|---|---|
ColorEnum |
WHITE 'w' · BLACK 'b' |
→ |
PieceEnum |
WHITE_KING 'K' … BLACK_PAWN 'p' (12 cases) |
→ |
CoordinatesEnum |
A1 'a1' … H8 'h8' (64 cases) |
→ |
CastlingEnum |
WHITE_KINGSIDE 'K' · WHITE_QUEENSIDE 'Q' · BLACK_KINGSIDE 'k' · BLACK_QUEENSIDE 'q' |
→ |
ResultEnum |
WHITE_WINS '1-0' · BLACK_WINS '0-1' · DRAW '1/2-1/2' · ONGOING '*' |
→ |
Testing & Development
git clone https://github.com/clemuset/pgn-parser.git
cd pgn-parser
composer install
vendor/bin/phpunit # Run all tests vendor/bin/phpunit tests/path/to/Test.php # Run a single test file vendor/bin/phpunit --filter methodName # Run a single test method vendor/bin/phpstan analyse # Static analysis (level 7) vendor/bin/php-cs-fixer fix # Fix code style vendor/bin/php-cs-fixer fix --dry-run # Check without applying changes
Or with Docker:
make check # fix + analyse + test make test make analyse make fix
MIT License. See LICENSE.