Engine, players, and simulation harness for the Finnish board game Kimble.
Requires uv. Then:
make setup
make test
make sim
Registered strategies for the stats, record, and tournament subcommands:
random— uniform over legal movescapture— capture if possible, else randomrunner— always advance the most-advanced piecerelease— release from home whenever possibledefensive— capture first, then largest resulting progressrescue— capture > move a threatened piece to safety > defensivecautious— capture > prefer landings no opponent can reach > advanceguarded— the strongest heuristic: capture the most-advanced victim > rescue > danger-weighted safe advance using the Markov die's next-roll distributionaz— the AlphaZero player (needs--az-ckpt, see below)
Rank any set of players with a balanced round-robin (every 4-player lineup, every seat rotation) reporting win rates and Plackett-Luce ratings fitted from full finish orders:
make tournament CKPT=checkpoints/best.pt # all players incl. az
uv run python -m kimble tournament --strategies guarded cautious defensive random
An AlphaZero-style self-play player lives in kimble/az/: a policy+value network
(PyTorch) guiding an expectimax MCTS that models the stochastic die with explicit chance
nodes. It is 4-player only and learns from games it plays against itself.
The AI player needs PyTorch, which is an optional dependency (the core engine stays zero-dep). Install it alongside the dev tools:
uv pip install -e ".[dev,ai]"
On Apple Silicon (e.g. M-series Macs) training uses the MPS backend automatically; it falls back to CUDA, then CPU.
Self-play and evaluation games run in parallel across CPU worker processes (the net
is small enough that CPU inference beats the GPU); SGD itself uses MPS/CUDA when
available. Worker count is num_workers in kimble/az/config.py (0 = auto: all
cores minus two, 1 = single process).
make train # full training run (uses MPS/GPU if available)
make train SMOKE=1 # fast, tiny, CPU-only smoke run to check the pipeline
Each iteration runs self-play, trains the net (cosine-decayed LR), evaluates against the
configured opponent lineup, and writes a checkpoint. Outputs (all git-ignored; smoke
runs write to separate */smoke* dirs so they never clobber a real run):
- per-iteration checkpoints →
checkpoints/az-iterNNN.pt - best checkpoint by eval win-rate →
checkpoints/best.pt(use this one for play/eval; later iterations are not always better — but note the selection is optimistic, since best.pt is the max over many noisy evals; confirm its strength on fresh seeds) - recorded self-play games →
games/selfplay/*.json.gz(gzippedkimble-game/1JSON)
Self-play is pure by default (four AZ seats). Set opponent_strategy in the config
(e.g. "guarded") to train against three copies of a heuristic instead, with the AZ
seat rotating and only its positions recorded as samples; eval_lineup picks the
opponents used for evaluation and best.pt selection ("tactics" or "guarded").
Hyperparameters live in kimble/az/config.py (DEFAULT, SANITY, SMOKE presets) — edit
them to tune a run. Entry point: python -m kimble.az.train [--smoke|--sanity] [--no-record] [--init CKPT]. --init warm-starts from an existing checkpoint (same
network shape), e.g. to continue from a previous run's best.pt instead of training from
scratch.
make az-eval CKPT=checkpoints/az-iter009.pt
This reports the win-rate against the tactics lineup. Full options:
uv run python -m kimble.az.evaluate --ckpt PATH [--games N] [--sims N] \
[--die markov|uniform] [--device auto|mps|cuda|cpu]
az is a registered strategy for the stats and record subcommands; point it at a
checkpoint with --az-ckpt (and optionally --az-sims, default 100):
uv run python -m kimble stats --games 200 \
--strategies az defensive capture runner \
--az-ckpt checkpoints/az-iter009.pt --az-sims 100
Recorded self-play games are gzipped, and the visualizer reads .json.gz directly — open
it with make viz and load any file from games/selfplay/.