Architecture
Raccoon follows the standard AlphaZero pipeline: a ResNet policy-value network guides Monte Carlo Tree Search, with self-play generating the training data. OpenSpiel provides game logic; all ML and search code is written from scratch in Python/PyTorch.
Board Encoding
States are encoded as (17, 2, 12) float32 tensors — 17 channels, 2 rows (board halves), 12 columns (points per half).
The 2D layout mirrors the physical board: points 13–24 on the top row (left to right), points 12–1 on the bottom row. Channels:
| Channel(s) | Content |
|---|---|
| 0–3 | Current player checkers: ≥1, ≥2, ≥3, overflow |
| 4–7 | Opponent checkers: ≥1, ≥2, ≥3, overflow |
| 8–9 | Bar counts (current player, opponent) broadcast |
| 10–11 | Borne-off counts (current player, opponent) broadcast |
| 12 | Side-to-move flag |
| 13–14 | Dice values broadcast across columns |
| 15 | Doubles flag |
| 16 | Mid-doubles flag (one die already used in a doubles move) |
All encoding is from the current player’s perspective — the network always sees “my checkers” and “their checkers” in fixed channels, regardless of which physical side is moving. The wrapper applies a perspective flip when it is the second player’s turn.
CHANNEL_NAMES in raccoon/env/encoder.py is the authoritative registry. dump_tensor() pretty-prints all planes for debugging.
Neural Network
PyTorch ResNet in raccoon/model/network.py with a shared convolutional trunk and two heads:
- Policy head: 1352 logits (one per OpenSpiel action). Masked softmax over legal moves gives move probabilities for MCTS.
- Value head: scalar via tanh, in [−1, 1]. Positive means the current player is winning.
Default size: 6 residual blocks, 128 channels — tuned for CPU training. Scale depth/width when GPU is available. Architecture is saved in checkpoints, so --resume automatically picks up the correct shape.
MCTS
AlphaZero-style MCTS in raccoon/search/mcts.py with PUCT selection. Key design choices:
Chance node sampling. Dice rolls are sampled and skipped — the tree contains only decision and terminal nodes. The network is never evaluated at chance nodes. With 100+ simulations, the various dice outcomes are explored naturally through repeated sampling.
Temperature. Controls exploitation vs exploration when selecting a move from visit counts. High temperature (early training) selects near-uniformly; low temperature (late training / evaluation) selects the most-visited move.
Batched inference. Leaf positions collected during a simulation round are evaluated in a single network forward pass, amortizing the GPU overhead.
See MCTS Explained for a deep dive into simulations, PUCT, Dirichlet noise, and the plateau problem.
Training Loop
Implemented in raccoon/train/:
- Self-play (
self_play.py): play N games using current network + MCTS. Record(observation, MCTS policy, outcome)tuples. - Replay buffer (
replay_buffer.py): circular buffer keeps recent positions. New games overwrite the oldest. - SGD (
coach.py): sample mini-batches; minimize cross-entropy(policy) + MSE(value) + L2 (viaweight_decay). - Checkpoint: save network weights; optionally evaluate vs previous checkpoint.
Value targets blend the terminal game outcome and MCTS root Q-value. --value-bootstrap-alpha controls the mix (1.0 = pure outcome, 0.0 = pure Q).
Action Space
OpenSpiel encodes 1352 distinct backgammon actions (base-26 packed checker moves). The policy head outputs a logit for each; illegal moves are masked to −∞ before softmax in raccoon/env/actions.py.
Evaluation
- Checkpoint vs checkpoint (
raccoon/eval/arena.py): tracks whether new iterations improve on old ones. - GNUBG benchmark (
raccoon/eval/gnubg_harness.py): automated money game matches against the GNUBG CLI. The primary success metric.