← Back to home

Backgammon-NN · a neural engine, self-taught then master-taught

It taught itself to play — then hit a wall only a master could break

Chess isn't the only game with a neural network hiding inside it. This is a backgammon engine, built from scratch — and to begin with, nobody taught it how to play. It started with random weights, sat down opposite a copy of itself, and after a few thousand games it had worked out the things good players know: race when you're ahead, build a prime, don't leave a blot under the gun, press for the gammon when you're winning big.

Backgammon has a special place in this story. In 1992 Gerald Tesauro's TD-Gammon famously learned world-class play purely from self-play — one of the first great demonstrations that a network could discover expert judgement on its own, years before it became fashionable. This little engine walks the same path with modern tools.

And then it stopped getting better. Deeper nets, wider nets, new input features, new output shapes, millions more games — a dozen careful experiments, each one landing in almost exactly the same place. The reason turned out to be structural rather than technical: a network trained on labels its own engine produced cannot get better than that engine. Every experiment was, in the end, asking it to grade its own homework.

The way through was to learn from somebody better. GNU Backgammon has been one of the strongest players in the world for two decades, and it will judge a position in a fraction of a millisecond. The current network was trained on 22.5 million positions marked by gnubg — and it beats the best net the self-play years produced, at exactly the same size and speed.

Backgammon-NN is a whole toolkit, not just a board. You can extend the self-play training, distil a stronger engine into a new net, design and build networks of your own, pit engines against each other in automatic matches, and play the result on your PC — in a desktop board or from the console.

Backgammon-NN desktop app — board with checkers and dice, the doubling cube, a live evaluation bar, and a move list with equities
The desktop app: play the net from instant 0-ply up to full Monte-Carlo rollouts, with the doubling cube, a live win-probability bar, and a move list with equities.

Play it now — free

Backgammon-NN app icon — a backgammon board with an ivory checker

Backgammon-NN for Windows

Windows 64-bit · 59 MB · free · no installer

New in v1.12.0: the cube measured decision-by-decision against GNU Backgammon — match-play cube errors cut roughly eight-fold — published Kazaross XG2 match equity, and a faster default opponent.

The whole thing is in one file — the board, the Rust engine and the trained network. There's no installer, no Python and no PyTorch to set up: download it, double-click it, and you're playing. The net runs natively inside the app, so it plays exactly as strongly as it does from source.

It opens on 2-ply search, which replies more or less instantly. Earlier versions switched to Monte-Carlo rollouts on a large machine, on the reasoning that a rollout gets a fixed time budget so more cores buy more playouts. That turned out to be wrong where it mattered: given all 128 logical cores of a Threadripper, the rollout scored 51.0% against 2-ply search — level, not ahead — while spending 0.8 seconds a move instead of answering at once. Defaulting to it charged you a long wait for nothing, so it no longer does. Rollouts are still there in the Opponent box if you want them, and the box also steps down: 1- or 0-ply search, then Neural classic (the previous champion, kept as a gentler rung), then the hand-crafted evaluator, then an opponent that moves at random.

The app isn't code-signed, so Windows SmartScreen will warn about an “unrecognized publisher” — choose More info → Run anyway. Prefer to build it yourself? The source build is a few commands.

The Backgammon-NN app just after a roll of 4-3, with the checkers that can legally move ringed in gold, the doubling cube at the left and the win-probability bar down the right
Rolled 4-3, with every checker that can legally move ringed for you. Click one, click where it goes — the bar down the side tracks your win probability.

Under the hood

A fast, correct core

The engine — board, dice, move generation, evaluators — is written in Rust. Getting the rules exactly right is the whole game, so the move generator is differentially tested against the independent wildbg reference engine across 3.15 million position–dice pairs: zero mismatches.

It learned from itself — then from a master

A network (198 inputs → 256 → 128, then 12 output heads chosen by position class) predicts the chance of a win, gammon and backgammon. The early nets were trained by self-play from random weights — no opening books, no human games. The current one is trained on 22.5 million positions judged by GNU Backgammon, and beats the hand-crafted evaluator 94% of the time.

Runs anywhere, fast

Trained in PyTorch, exported to ONNX, and run natively back inside the Rust engine — the three agree to within a rounding error. On top sits an n-ply search (0/1/2-ply) that looks ahead and averages over every roll of the dice.

Does it actually play well?

The honest test is head-to-head play. Against a player that just makes random legal moves it wins essentially every game — usually by a gammon or backgammon. Against HCE, a competent hand-written evaluator that races and counts pips, the network wins about 94%. Against the previous champion — the best net the self-play era produced, and the same size and speed — it wins 53.4% over 40,000 games.

Searching deeper has quietly stopped mattering. Looking one move ahead used to beat the same network playing instantly 62% of the time; for today's net it is barely 52%, and against GNU Backgammon it makes no measurable difference at all — the engine scores the same whether it searches nought, one or two plies deep, even though the extra search changes about a third of its moves. That is what a good evaluator does to search: it leaves it nothing to fix.

Two charts. Left: estimated Elo of every opponent in the app — Random 0, HCE 990, Neural classic 0-ply 1433, Neural classic 1-ply 1447, Neural 0-ply 1458, Neural 1-ply 1465, Neural 2-ply 1469, Rollout 1444. Right: the same neural options magnified with error bars, showing they all overlap within about 35 Elo.
Every opponent in the app, measured against the one below it (mirrored dice, 300–6,000 games per rung depending on how expensive the rung is) and chained into an Elo scale with Random anchored at 0. The ladder has two big steps and then a plateau: learning to play at all is worth ~990 points, the neural network over the hand-crafted evaluator another ~443, and after that every remaining choice — a newer net, one more ply, full rollouts — is worth a few tens of points and the error bars overlap. Dice luck compresses backgammon win rates, so read the gaps as indicative rather than exact.

Which opponent should you pick?

Measured on a 64-core machine; each rung played against the one above or below it. “Elo” is chained from those results with Random at 0 — useful for ordering, not an official rating.
OpponentEloWhat it does · when to pick it
Random0 Plays a legal move at random. For learning the rules, or watching the board work.
HCE (heuristic)990 No network at all — hand-written rules: count the pips, avoid blots, make points. Beats a beginner, loses to anyone who has read a book.
Neural classic — 0-ply1433 The previous champion, trained by self-play. Answers instantly. The gentlest of the neural opponents and a real step up from HCE.
Neural classic — 1-ply1447 The same older net, looking one move ahead over all 21 dice rolls.
Neural — 0-ply1458 The current gnubg-taught net, no search. Instant and nearly as strong as anything else here — the best choice if you want a quick game.
Neural — 1-ply1465 Current net plus one ply of lookahead. A fraction of a second per move.
Neural — 2-ply1469 The strongest measured setting, and the app's default. Two plies with candidate pruning; replies more or less instantly.
Rollout (800ms)1444* Plays out each candidate move hundreds of times and averages the result. *Measured on 8 cores — see below.

Two honest caveats about that table. First, the top six are separated by less than the measurement error: 2-ply beat 1-ply by 50.5% over 400 games, which is a coin-flip. Treat them as one band of similar strength, not a strict order.

Second, the Rollout figure deserves an asterisk. A rollout gets a fixed time budget per move, so the number of cores it can use directly determines how many games it plays out, and therefore how accurate its answer is. The row above measures it with 8 cores — roughly a typical laptop — where it comes out slightly below plain 1-ply search.

It was reasonable to assume a big machine would change that, and for a while the app switched to rollouts on any machine with 32 cores or more. It was never tested, and when it finally was, it was wrong: given all 128 logical cores of a Threadripper — and the box to itself, since a rollout starved of cores measures its own handicap — the rollout scored 51.0% against 2-ply over 341 games. Level, not ahead, at roughly fifty times the thinking time per move. So 2-ply is now the default everywhere, and the honest summary of this whole table is that every neural setting plays about as well as every other; what actually differs is how long you wait.

Current net: a 198→256→128 body with 12 output heads chosen by position class — race / crashed / contact — trained on 22.5 million positions labelled by GNU Backgammon. Mirrored-dice head-to-head; game counts given because backgammon results are noisy.
Match-upWin ratePoints per game
vs Random99.8%+2.66
vs HCE (hand-crafted)94.4%+1.93
vs previous champion (40,000 games)53.4%+0.08
vs GNU Backgammon 2-ply — our 0-ply (4,000 games)46.1%−0.11
vs GNU Backgammon 2-ply — equal depth (3,000 games)49.1%−0.02
vs GNU Backgammon, 7-point matches with the cube (400)48.5%
1-ply vs 0-ply (same net)~52%+0.13

Those two GNU Backgammon rows used to be one row, and it said the wrong thing. For months the project reported roughly 46% against GNU Backgammon and treated the remaining four points as the gap still to close. The harness had a bug: it built the network with its search depth hard-wired to zero, so every "equal depth" run was really our static evaluation against a searching opponent. The giveaway was there all along — 0-ply, 1-ply and 2-ply returned 46.1%, 46.3% and 46.3%, three numbers agreeing far too closely across 9,000 games, and that agreement was published as a finding instead of investigated as a bug. Measured properly, the engine is level with GNU Backgammon at equal depth: 49.1% over 3,000 money games and 48.5% of 400 seven-point matches played with the cube. Neither is far enough from 50% to claim more than parity — but parity with GNU Backgammon is where a network distilled from it was always going to top out, and it has arrived there.

The network has been rebuilt and retrained many times to get here. The first breakthrough was depth — a second hidden layer broke a ceiling a single wider layer couldn't. Later steps changed shape rather than size: a Stockfish-NNUE-style net with one shared body and multiple output heads, the head chosen by position class — race, crashed, or contact, borrowing GNU Backgammon's classification — so one network specialises for each phase without splitting its training data. But each step gained less than the last, and richer input features, tried twice, failed outright.

Then it stopped altogether, and the reason turned out to be more interesting than the architecture. Every way of generating training labels ran through the engine itself: self-play results, its own Monte-Carlo rollouts, its own search values. And a network distilled from its own engine cannot exceed it. Every label was, in the end, its own opinion — so a dozen careful experiments all landed in the same place.

The way out was to learn from somebody else. GNU Backgammon — world-class, decades mature — will judge a position in a fraction of a millisecond, and it turns out you can ask it 2,600 times a second. The current network (shipped in v1.10.0) is trained on 22.5 million positions labelled by gnubg, from scratch, in under an hour. It beats the best self-play net 53.4% over 40,000 games at the same size and the same speed, and closes about 38% of the remaining gap to gnubg itself. There is a hard ceiling to this: a student cannot pass its teacher, and at 46% against gnubg's 2-ply play we are some way short of that ceiling but can no longer reach it by asking gnubg more questions.

More than a board — a self-learning lab

Backgammon-NN isn't a finished object; it's something to run, train, and push further:

  • Train it two ways. Keep the self-play loop running, or distil a stronger teacher: the toolkit can label millions of positions with GNU Backgammon's own evaluation and train on those instead — which is how the current net was built.
  • Build new networks. Change the shape — width, depth, inputs — and train a fresh net from scratch to see how strong it becomes.
  • Run automatic matches. Pit engines head-to-head in mirrored-dice matches — a new net against an old one, against the heuristic, or the engine against itself — and read off the win rate and points-per-game.
  • Play it two ways. A polished graphical desktop app (roll, move, double, watch the evaluation bar) and a lightweight text-only console app that plays right in your terminal — both let you pick the engine's search depth.

Sit down and play

Play it however you like: a polished graphical desktop board or a quick text-only console game in your terminal. In the desktop app you roll the dice (they tumble), click a checker and its destination, and the engine answers — its checkers sliding across the board while a panel logs every move with its equity.

You get the full game, including the parts that make backgammon backgammon:

  • A doubling cube that actually understands the position. Offer a double and the engine decides whether to take or drop — and it will double you back when the game turns. The decision comes from cubeful equity computed from the network's own win, gammon and backgammon probabilities, so gammon threats move it: a dry race at 78% is a comfortable take, while the same 78% with heavy gammon chances is a pass. It also knows when it is too good to double — when playing on for the gammon beats cashing.
  • Match play, with the Crawford rule. Play for money, or a match to 1, 3, 5, 7 or 11 points. In a match the cube decisions change character: what counts is the chance of winning the match, not points, so a take that is routine for money can be quite wrong at 2-away. Match equity comes from the published Kazaross XG2 rollout table — the one GNU Backgammon itself uses — with separate values for the Crawford game and for the games after it, when the cube returns and the trailer's chances jump sharply. The game after either side reaches match-point is the Crawford game, played with no cube.
  • A live evaluation bar. A chess-style bar down the side shows your win probability in real time, from your point of view — it swings as the position turns.
  • Choose your opponent. Play the instant 0-ply network, give it one or two plies of lookahead, turn on full Monte-Carlo rollouts — or drop back to the previous champion or the hand-crafted evaluator for a gentler game.

Built with: Rust (engine core), PyTorch (training), ONNX + tract (native inference), and PySide6 (the desktop board). A small self-taught net, a correct engine, and a board to lose to it on.

Run it from source

Backgammon-NN is open source. With Rust (stable) and Python 3.9+ installed, you can build it and be playing in a few minutes:

# 1 · clone the repository
git clone https://github.com/Chris-Whittington-Chess/Backgammon-NN
cd Backgammon-NN

# 2 · create a Python environment and install the dependencies
python -m venv .venv
.venv/Scripts/pip install maturin numpy torch onnx onnxruntime PySide6

# 3 · build the Rust engine's Python bindings
#     --features onnx builds the native net and rollout engine in
cd crates/bgpy
../../.venv/Scripts/maturin develop --release --features onnx
cd ../..

# 4 · play — the graphical desktop app…
.venv/Scripts/python gui/app.py

# …or the text-only console app
.venv/Scripts/python trainer/console_play.py

Commands are shown for Windows; on macOS or Linux use .venv/bin/ in place of .venv/Scripts/. The repository ships with a trained network, so the app plays straight after the build — or kick off your own training run with trainer/train.py, and benchmark engines against each other with the match runner.