esca 0.3.0

A chess model that answers what is true about a position: variants, positions, games, and move text.
Documentation

esca

crates.io docs.rs PyPI Python CI MIT

A chess model that answers what is true about a position — a Rust crate and a Python package built from it.

The esca is the anglerfish's lure: the small thing that lights up what is in front of it.

Position is placement and state and nothing else. Rules live in Variant implementations — Classic and Chess960 — so a position can be asked the same question under different rules, and a new variant is a new implementation and nothing else. A Game pairs a variant with the moves played, which is what repetition and claimable draws need. Facts answers what is true about a position, both as prose a reader can check and as the flat f32 row a net consumes.

Rust

[dependencies]
esca = "0.1"
use esca::{Game, Schema, Side, classic};

let mut game = Game::new(classic());   // Chess960 rules: `esca::chess960()`
game.play_san("e4").unwrap();
game.play_uci("e7e5").unwrap();
println!("{}", game.position().fen());

let facts = game.facts();              // side-relative, in the mover's view
println!("{}", facts.tactics[Side::Us.index()].legal_move_count);
println!("{}", facts.summary());

let schema = Schema::v1();             // the row a net eats: 2039 f32
println!("{}", facts.encode(schema, schema.all()).len());

Cargo features, none on by default: lichess (streaming reader for the Lichess evaluation dump), pgn (reading and writing games as PGN), polyglot (opening books), openings (the bundled ECO catalogue) and python (the PyO3 module the wheel is built from). Position::polyglot_key needs no feature.

Python

pip install esca
import esca

game = esca.Game()  # Chess960 rules: esca.Game(variant=esca.CHESS960)
game.play_san("e4")
game.play("e7e5")
print(game.position.fen)

facts = game.facts()  # side-relative: index with esca.US / esca.THEM
print(facts.tactics[esca.US].legal_move_count)
print(facts.summary())

rows = esca.encode([game.position.fen])  # (1, 2039) float32, ready for a net
print(rows.shape, esca.SCHEMA_ID)

Wheels are abi3 for Python 3.12 and up. esca.lichess.batches() streams the evaluation dump as encoded batches with their targets.

What it covers

  • Classic chess and Chess960, behind one Variant trait.
  • FEN and EPD, reading KQkq and the AHah of X-FEN and Shredder-FEN alike, and writing KQkq whenever the rook files allow it.
  • Legal move generation into a MoveList that never allocates.
  • UCI move text in either castling spelling, and SAN with the disambiguation it needs.
  • Checkmate, stalemate, insufficient material, the fifty- and seventy-five-move rules, and threefold and fivefold repetition.
  • Facts: fourteen groups of cheap position facts — the board itself, game state, material, pawns, pieces, king, mobility, attacks, exchanges, threats, one-ply tactics, endgame, history and attack planes — plus MoveFacts for every legal move, all side-relative and in the mover's view.
  • Schema, a versioned manifest with a schema_id, and batch encoders that write f32 rows without allocating.
  • Polyglot opening books: the format's own key on every Position, books read, drawn from and built, and an ECO code and name for some 3,800 named positions.

Documentation

License

MIT — see LICENSE.

Acknowledgements

  • cozy-chess (MIT) — the move generator esca stands on.
  • Lichess — the evaluation dump the trainer learns from, the game database, and lichess-org/chess-openings, whose opening names the openings feature bundles (CC0 1.0 Universal Public Domain Dedication).
  • The Polyglot opening-book format and its key scheme, by Fabien Letouzey; the key constants are those published in polyglot-book-rs (MIT OR Apache-2.0).
  • Stockfish and Leela Chess Zero, the engines the UCI client is tested against.