Fast Rust toolkit for PGN parsing, legal move generation, and chess game analysis.
ply is an open-source Rust chess infrastructure project focused on correctness, predictable behavior, and practical tooling.
It provides a composable core for:
- parsing and serializing FEN,
- representing board state and applying legal moves,
- parsing PGN and reconstructing games from SAN,
- extracting per-game summaries and aggregate statistics,
- exporting analysis data as JSON,
- running these workflows from a CLI.
This project is not a top-tier competitive chess engine and does not currently implement evaluation/search designed to compete with Stockfish-class engines. The primary goal is reliable chess data infrastructure for developers, analysts, and downstream tooling.
- Build a clean Rust codebase for chess data processing that is easy to inspect and extend.
- Keep move legality and replay logic in a reusable library, with a thin CLI layer on top.
- Support real workflows: validating PGN collections, summarizing games, and inspecting positions.
- FEN parsing and serialization
- strict 6-field validation (
board, side-to-move, castling, en-passant, halfmove, fullmove) - normalized round-trip rendering
- strict 6-field validation (
- Board and position representation
- typed model for color, piece kind, square, castling rights, clocks, and side-to-move
- Legal move generation
- pseudo-legal generation + king safety filtering
- castling, en-passant, and promotion handling
- PGN parsing and game reconstruction
- tag parsing and movetext tokenization
- SAN move resolution against generated legal moves
- full game replay to final position
- Per-game summaries and aggregate statistics
- player/result/plies summary fields
- opening move frequencies (White and Black first moves)
- castling distribution (kingside, queenside, no castling)
- total/average captures, checks, and promotions
- average game length by result type
- Perft support
- reusable perft API for legal move generation validation
- CLI command for quick node-count checks
- JSON export
- structured JSON for aggregate stats and per-game summaries
- CLI workflows
- validate PGN files
- summarize reconstructed games
- print aggregate stats (text or JSON)
- inspect FEN positions and legal moves
- run perft against startpos or custom FEN
ply treats correctness as a core requirement and validates behavior at multiple layers:
- Move legality validation: legal move generation is covered by integration tests for baseline positions and special rules like en passant.
- Replay validation: PGN reconstruction resolves SAN against generated legal moves and is exercised with fixture-based tests.
- Perft regression checks: known perft node counts (including start position and tricky reference positions) are tested to catch move-generation regressions.
- CLI-level checks: command integration tests verify that key workflows (
validate,stats --json,perft) execute and emit expected output.
- PGN comments and variations are stripped during tokenization (baseline parser behavior).
- SAN handling supports common game notation used in standard game files; edge-case PGN dialects may require further expansion.
- No engine evaluation/search module yet.
# Validate whether games can be reconstructed legally
ply validate games.pgn
# Print one-line summaries per game
ply summarize games.pgn
# Compute aggregate stats and emit JSON
ply stats games.pgn --json
# Parse a FEN and list legal moves in coordinate notation
ply fen "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" --legal-moves
# Run perft on start position (or pass --fen "<fen>")
ply perft --depth 3You can also run the binary through Cargo during development:
cargo run -- summarize tests/fixtures/sample_games.pgnHuman-readable summary (ply summarize tests/fixtures/sample_games.pgn):
1. WhitePlayer vs BlackPlayer | result=1-0 | plies=7
2. Alpha vs Beta | result=1/2-1/2 | plies=8
JSON stats (ply stats tests/fixtures/sample_games.pgn --json):
{
"stats": {
"average_captures": 0.5,
"average_checks": 0.5,
"average_plies": 7.5,
"average_plies_black_wins": null,
"average_plies_draws": 8.0,
"average_plies_unresolved": null,
"average_plies_white_wins": 7.0,
"average_promotions": 0.0,
"black_first_moves": {
"d5": 1,
"e5": 1
},
"black_wins": 0,
"draws": 1,
"games": 2,
"games_with_kingside_castle": 0,
"games_with_no_castling": 2,
"games_with_queenside_castle": 0,
"total_captures": 1,
"total_checks": 1,
"total_plies": 15,
"total_promotions": 0,
"unresolved": 0,
"white_first_moves": {
"d4": 1,
"e4": 1
},
"white_wins": 1
},
"games": [
{
"event": "Miniature",
"white": "WhitePlayer",
"black": "BlackPlayer",
"result": "1-0",
"plies": 7,
"winner": "white"
},
{
"event": "DrawnGame",
"white": "Alpha",
"black": "Beta",
"result": "1/2-1/2",
"plies": 8,
"winner": null
}
]
}Perft (ply perft --depth 3):
depth: 3
nodes: 8902
elapsed_ms: 24
nps: 357520
The repository is organized as a library-first core with a small command-line frontend.
src/lib.rs- public module surface for library users
src/board/mod.rs- core chess domain model (pieces, squares, position, castling rights, move struct)
src/fen.rs- FEN parser/serializer and validation errors
src/movegen.rs- pseudo-legal and legal move generation, attack detection, and state transitions
src/pgn.rs- PGN parsing, SAN normalization/resolution, and game reconstruction pipeline
src/stats.rs- summary and aggregate metrics over reconstructed games
src/export.rs- JSON DTO conversion layer for stable CLI output
src/main.rs- clap-based CLI command routing
tests/- integration coverage for FEN/movegen, PGN replay/stats, and CLI behavior
benches/movegen.rs- criterion benchmark for legal move generation throughput from start position
git clone https://github.com/moritzmyrz/ply.git
cd ply
cargo buildcargo run -- validate tests/fixtures/sample_games.pgn
cargo run -- summarize tests/fixtures/sample_games.pgn
cargo run -- stats tests/fixtures/sample_games.pgn --json
cargo run -- fen "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" --legal-moves
cargo run -- perft --depth 3cargo testThe project includes a simple Makefile for common development commands:
make fmt # cargo fmt
make lint # cargo clippy --all-targets --all-features -- -D warnings
make test # cargo test
make bench # cargo benchIf you prefer direct Cargo commands:
cargo fmt
cargo clippy --all-targets --all-features -- -D warnings
cargo test
cargo bench- Improve opening metadata extraction and ECO classification support.
- Introduce Zobrist hashing for efficient position keys and caching.
- Add evaluation/search scaffolding (non-competitive baseline engine components).
- Provide bindings/WASM targets for browser and polyglot tooling integration.
Active development. Current work is focused on building a correct, well-tested core for chess parsing, reconstruction, and analysis workflows before expanding engine-oriented capabilities.
Licensed under GPL-3.0-or-later. See LICENSE.