Heads-up no-limit Texas Hold'em in your terminal, against an AI trained with counterfactual regret minimization.
== hand #3 — Mako post(s) the small blind ==
Mako raises to 6.
board: (preflop) pot: 8
you [Ah Kd] stack 38 | Mako [?? ??] stack 34 | 4 to call
[f] fold [c] call 4 [h] half-pot -> 12 [p] pot -> 18 [a] all-in -> 40
you>
Sessions run at 20, 50, or 100 BB (1/2 blinds, stacks reset every hand) with six discrete actions: fold, check, call, half-pot, pot, all-in. Rules are specified exactly in docs/RULES.md.
Requires CMake ≥ 3.20 and a C++20 compiler. No third-party C++ deps.
cmake -S . -B build
cmake --build build -j
./build/mako # asks for a depth, loads the matching artifact
./build/mako --depth 100 # or pick one directlyType help at any prompt. Between hands, analysis reviews the last hand
with true equities and Mako's strategy mix at every decision; stats shows
your session plus lifetime numbers (persisted at ~/.mako/stats.json). Full
reference: docs/CLI.md.
- Rules engine (C++,
cpp/game/) — integer-chip heads-up NLHE with the six-action grid; the single source of truth for both training and play, covered by golden scripted-hand tests. - Abstraction (
cpp/abstraction/, docs/ABSTRACTION.md) — 169 preflop classes; postflop, suit-canonicalized equity-vs-random buckets, finer at deeper stacks (up to 32/48/64). Implemented twice (C++ and pure Pythonpython/mako_ref/) with bit-parity tests. - Trainer (
cpp/trainer/+python/mako_trainer/) — external-sampling linear MCCFR. The C++ core trains at thousands of iterations per second; the pure-Python twin is the readable reference, and the two are tested to produce bit-identical tables and byte-identical artifacts. Deterministic per seed, checkpoint/resume, TOML configs inconfigs/, and a persistent canonical-equity cache shared by every run at every depth. - Artifact (docs/STRATEGY_FORMAT.md) — the
average strategy exported as a versioned binary; game and abstraction
parameters travel inside it. Shipped:
mako-20bb-v2.mako(3M iterations, 184k info sets),mako-50bb-v2.mako(4M, 1.1M), andmako-100bb-v2.mako(5M, 2.9M). - Inference (C++,
cpp/infer/) — loads the artifact, binary-searches the info-set hash, samples from the stored mix with a seeded RNG. Unseen spots (0.00% measured vs random play) use a counted, passive check/call fallback.
uv sync
cmake -S . -B build -DMAKO_BUILD_PYTHON=ON \
-DPython_EXECUTABLE=$PWD/.venv/bin/python \
-Dpybind11_DIR=$(.venv/bin/python -m pybind11 --cmakedir)
cmake --build build -j
uv run mako-train --config configs/smoke.toml # <1 min pipeline check
uv run mako-train --config configs/v2-20bb.toml --prewarm turn # ~35 min from cold
uv run mako-train --config configs/v2-50bb.toml # reuses the equity cache
uv run mako-train --config configs/v2-100bb.toml--prewarm turn precomputes every canonical flop/turn equity across all
cores once; the result lands in strategies/equity.cache and every later
run — any depth — starts warm (bucket equity depends on cards, never stack).
Interrupting a run checkpoints it; resume with --resume <out>.ckpt. Other
depths/bucket counts: copy a config and edit — the artifact carries its
parameters, so play just works. Gate a candidate before shipping it:
uv run mako-eval strategies/mako-20bb-v2.mako --vs strategies/mako-20bb-v1.makoThe same engine and artifact run fully in-browser via an
Embind
façade (cpp/wasm/): MakoSession wraps strategy loading, the
hand loop, and post-hand analysis behind a JSON snapshot API, so a web worker
stays a thin message router. Requires emsdk
(or brew install emscripten):
emcmake cmake -S . -B build-wasm -DMAKO_BUILD_WASM=ON -DMAKO_BUILD_TESTS=OFF
cmake --build build-wasm -j # emits build-wasm/mako.js + mako.wasm
node cpp/wasm/smoke.mjs # plays one full hand + analysis in NodeShip mako.js, mako.wasm, and a .mako artifact together; the consumer
fetches the artifact and passes its bytes to MakoSession.loadStrategy.
Tools: mako-inspect (artifact metadata, per-key mixes, preflop open
table), mako-selfplay (mirror or vs-baseline EV), mako-equity (hand
equity queries), mako-eval (ship gates with confidence intervals).
Every shipped artifact must pass mako-eval (1M-hand matches, 95% CIs):
mirror self-play ≈ 0, clearly +EV vs a passive check/call baseline, and a
near-zero unseen-spot rate. Measured at ship time:
| Artifact | Info sets | Mirror EV | vs check/call | Button edge | Missing keys |
|---|---|---|---|---|---|
| 20 BB v2 | 183,646 | +0.1 ±1.3 | +129 BB/100 | +3.7 | 0 in 8.3M |
| 50 BB v2 | 1,124,499 | −0.9 ±2.1 | +204 BB/100 | +13.3 | 0 in 9.3M |
| 100 BB v2 | 2,854,097 | −1.9 ±2.8 | +229 BB/100 | +14.6 | 1 in 9.4M |
The 20 BB v2 strategy also beats the retired v1 head-to-head by +12.6 ±1.4 BB/100. (The growing button edge with depth is the expected positional effect, not an artifact.)
Honesty about what this is: an approximate equilibrium of a deliberately coarse abstract game — equity-vs-uniform buckets with no range conditioning or potential-aware clustering, and six fixed bet sizes. Do not call it GTO. Deeper stacks amplify abstraction error, which is why the grids are finer there; a strong human hunting abstraction seams will still find them, most easily at 100 BB. See the Honesty section of docs/ABSTRACTION.md.
./build/mako_tests # engine, evaluator, abstraction, inference, trainer, stats
uv run pytest # trainer parity, exporter, tools, C++/Python paritycpp/game/ rules engine docs/RULES.md
cpp/eval/ hand evaluator
cpp/abstraction/ info-set keys docs/ABSTRACTION.md
cpp/trainer/ fast MCCFR core
cpp/infer/ strategy loading docs/STRATEGY_FORMAT.md
cpp/cli/ mako TTY + harness docs/CLI.md
cpp/bindings/ pybind11 module
cpp/wasm/ Embind browser façade
python/mako_trainer/ MCCFR reference + export
python/mako_ref/ pure-Python parity reference
python/mako_tools/ inspect / selfplay / equity / eval
strategies/ shipped artifacts