From-scratch chess engine · Rust
A chess engine you can actually read.
Alpha-beta search, a Zobrist-hashed transposition table, and a full UCI interface, in about 4,000 lines of dependency-free Rust you can follow end to end.
Watch it think
Zugzwang speaks UCI over stdin/stdout, so any chess GUI can drive it. Or play a full game right in your terminal. Both transcripts below are copied straight from the release binary.
uci id name Zugzwang id author Charlie Krug uciok isready readyok position startpos moves e2e4 e7e5 go depth 4 bestmove b1c3 quit
Your move (or 'quit'): e2e4 . . . . . . . . . . . . P . . . . . . . . . . . Engine plays b8c6 r . b q k b n r p p p p p p p p . . n . . . . .
What's under the hood
Most hobby chess projects stop at legal move generation. Zugzwang goes all the way to something that plays a real game behind a real GUI, and keeps every layer small enough to read.
move generationLegal moves, verified by perft
Full rules including castling, en passant, and promotion, checked against known perft node counts through depth 5 (4,865,609 nodes) and the Kiwipete position, so the move generator is provably correct, not just plausible.
searchAlpha-beta with quiescence
Negamax with alpha-beta pruning, iterative deepening against a time budget, and a quiescence search that plays out captures and promotions past the horizon so tactics aren't misjudged mid-exchange.
transposition tableZobrist-hashed caching
Positions are keyed by a Zobrist hash and cached in a fixed-size table, so the search reuses work when the same position is reached by a different move order. Move ordering (MVV-LVA, killers, history) makes the pruning bite.
uci protocolPlugs into any GUI
Implements uci, isready, ucinewgame, position, and go with depth, movetime, and wtime/btime/movestogo, so Arena, CuteChess, or en croissant can play it out of the box.
Build it in a minute
Stable Rust, no external chess crates. Clone, build, and you have a working engine.
# build the optimized binary and run the tests cargo build --release cargo test # print the starting board and exit ./target/release/zugzwang # UCI mode, for a chess GUI to drive over stdin/stdout ./target/release/zugzwang uci # play a game against the engine in the terminal ./target/release/zugzwang play # move-generation node counts, depths 1..5 ./target/release/zugzwang perft 5
A readable reference for how a chess engine fits together
Zugzwang is a complete terminal chess engine written from scratch in Rust. It is built for the programmer who has read that alpha-beta pruning, Zobrist hashing, and the UCI protocol exist, but has never seen them wired together in code short enough to follow in one sitting. Production engines like Stockfish are tens of thousands of lines of hand-tuned bitboard code; excellent to compete with, hard to learn from. Zugzwang trades raw strength for clarity: a simple 8x8 board, a straightforward legal move generator, and a search you can trace by hand.
Every layer is separable. The board and move generator model the rules and nothing else, validated with perft against the standard reference positions. The search decides which moves are worth exploring and how deep, using negamax with alpha-beta pruning, iterative deepening, a transposition table, and move ordering so the pruning actually cuts. The evaluation scores a position from material plus piece-square tables. On top sits a thin UCI adapter that reads commands from stdin and writes the best move to stdout, which is all any chess GUI needs to use it as an opponent.
It is also directly playable. A terminal play mode lets you play a full game without a GUI at all, entering moves in coordinate notation and watching the board redraw after each reply. It detects checkmate, stalemate, the fifty-move rule, insufficient material, and threefold repetition, so a game ends the way a game should.
Frequently asked questions
Is Zugzwang strong enough to beat me?
Probably, at casual strength. Playing strength was never the goal for v1: correctness and protocol completeness were. It searches a few ply deep with quiescence and piece-square evaluation, which is enough for a solid club-level game but well short of a tuned engine. Improving strength is the open-ended work that comes after v1.
Which chess GUIs work with it?
Any GUI that speaks UCI, which is effectively all of them: Arena, CuteChess, en croissant, and xboard with a UCI adapter. Point the GUI at the zugzwang uci binary and add it as an engine.
Why is the board a plain array and not bitboards?
Readability. A flat 64-square array keeps move generation simple to follow and easy to test while the shape of the engine is what matters. A bitboard rewrite is the headline item on the backlog for when performance becomes the point.
What notation does it accept?
Coordinate algebraic notation, the same notation UCI uses: e2e4 for a move, e7e8q for a promotion. There is no SAN parser (Nf3-style input) yet.
Does it have any dependencies?
None beyond the Rust standard library. The board, move generator, search, and UCI layer are all original code, which is the entire point of the project.