Run Bluffr
Bluffr is open source: a Rust workspace with the poker engine, the trainer, the command line and this site. Everything this site does runs on your own machine too, with the flop, turn and river search switched on, plus an HTTP API this site doesn't host.
Build it
You need Rust 1.85 or later, and Wisp, the web framework the site is written in, checked out beside Bluffr.
git clone https://github.com/wyziedevs/wisp Wisp
git clone https://github.com/wyziedevs/bluffr
cargo install --path Wisp/crates/wisp-cli
cd bluffr && cargo build --release
Give it a blueprint
Bluffr's strategy is a file, data/bluffr.blueprint, learned by self-play. Train your own (it resumes where it stopped, and Ctrl+C saves), or put a trained one from the repository's releases in data/.
./target/release/bluffr train --minutes 30
Thirty minutes plays sensibly; the blueprint behind this site trained for 1.27 billion iterations. Without any blueprint Bluffr still plays, by a simple fallback rule.
Run the site and the API
cd web && wisp dev
That serves this site and the API at http://127.0.0.1:3000. ./target/release/bluffr play plays in the terminal instead, and the README lists the benchmarks, matches and charts the command line has.
Put it online for free
This site is the same app on Cloudflare's free Workers plan, which costs nothing and stops answering for the day rather than billing past its limits. It allows a few milliseconds of computing a request, so the hosted copy plays the blueprint without the search, and leaves the API off.
cd web && wisp build --target cloudflare
cd dist/cloudflare && npx wrangler deploy
npx wrangler secret put WISP_SECRET
web/wrangler.toml sets BLUFFR_SEARCH=off and BLUFFR_API=off; change either to on for your own deployment. WISP_SECRET signs the cookie that holds a player's table, and any long random string will do.
The API
The API runs on your copy, at http://127.0.0.1:3000 with wisp dev. One call does the work: /decide takes a hand from one seat's point of view and returns Bluffr's decision as a typed choice. Every endpoint is a plain URL with query parameters, so a link, curl or a browser's address bar all work. Answers are JSON, and every error is named with a stable code. POST takes the same parameters as GET.
| Endpoint | Parameters | What it does |
|---|---|---|
/decide | cards and the hand (below) | Bluffr's choice for a hand you describe |
/models | id (optional) | The models this server answers with (one: bluffr-1), or one model's card |
/capabilities | The game, the models and the limits | |
/health | Up, the version, and whether Bluffr has its blueprint | |
/sandbox/new | stack_bb, seed, aggression, looseness | Sit down against Bluffr |
/sandbox | id | A sandbox game from your seat |
/sandbox/act | id, action, to | Your move: action=call, action=raise&to=600, or action=r600 |
/sandbox/next | id | Deal the next hand |
/sandbox/end | id | End the game |
Keys and limits
To put your API on the internet, start the server with BLUFFR_API_KEYS=key1,key2 and every endpoint but /health needs a key: add key=<key> to the query, or send Authorization: Bearer <key>. With none set the API is open, as it is on your own machine. Each client gets 120 requests a minute; the 121st is a 429 with retry-after.
Ask for a decision
| Parameter | Default | Meaning |
|---|---|---|
cards | required | The hero's two cards: AsKd or As Kd |
position | button | button (posts the small blind) or big_blind (bb) |
board | none | The cards on the board so far: Qh7c2d |
actions | none | The betting so far, in order, comma separated (below) |
stack | 10000 | Both stacks in chips at the start of the hand, before the blinds |
hero_stack, villain_stack | stack | One side's stack, when they differ |
small_blind, big_blind | 50, 100 | The blinds |
seed | random | Makes the pick from the mix repeatable |
aggression, looseness | 0 | Lean Bluffr's mix, each from -1 to 1 (below) |
model | bluffr-1 | The model to ask |
Each action in actions is f (fold), k (check), c (call), or b or r with a number for a bet or raise to that total on the street: r250. The long names work too (fold, raise250), and a / may mark a new street. Whose turn each action was follows from the rules: the button acts first before the flop and the big blind first after it. The history is replayed and checked, and the board must fit the street the betting reached.
curl -s "http://127.0.0.1:3000/decide?cards=AsKd&position=big_blind&board=Qh7c2d&actions=r250,c,k,b300&seed=4"
That is: Villain raises to 250 on the button and you call, then on the flop you check and Villain bets 300.
The answer has the shape of a System One model's choice: the options are fixed in advance (the legal actions), every one gets a probability, and the result never needs parsing.
{
"model": "bluffr-1",
"answer": {
"type": "choice",
"choice": "call",
"confidence": 0.6494,
"probabilities": {
"fold": 0.0000,
"call": 0.8100,
"raise_1400": 0.1900,
"raise_9900": 0.0000
}
},
"criteria": {
"fold": "Fold",
"call": "Call 300",
"raise_1400": "Raise pot (to 1400)",
"raise_9900": "All-in (to 9900)"
},
"action": { "type": "call", "to": null },
"source": "blueprint",
"spot": {
"street": "flop", "pot": 800, "to_call": 300,
"legal": { "fold": true, "check": false, "call": 300, "raise": { "min_to": 600, "max_to": 9900 } }
},
"usage": { "compute_ms": 0 }
}
The numbers above are for illustration; the front page shows a real one.
| Field | Meaning |
|---|---|
answer.choice | The action Bluffr plays, sampled from its mix. Send seed to make the pick repeatable |
answer.probabilities | Every option's share of the mix, in criteria's order. They add up to 1 |
answer.confidence | 0 to 1: one minus the mix's entropy over its most (even play). 1 is a pure choice |
criteria | What each option name means here, in words |
action | The choice as an action, ready to replay into your own table |
source | search (flop, turn or river, solved for this hand), blueprint (the trained strategy) or heuristic (a spot training never reached) |
spot | The street, the pot, what it costs to call, and every legal action with raise bounds |
usage.compute_ms | Time Bluffr spent thinking |
Send aggression and looseness, each from -1 to 1, to lean Bluffr's mix: up for more bets and raises or more calls, down for more checks or more folds. Both default to 0, the strategy as trained, which is Bluffr's strongest against an opponent you know nothing about.
Option names are stable: fold, check, call, bet_<to> and raise_<to>, where to is the street total after the bet. A mixed strategy is the point: Bluffr balances its actions so that nothing it does gives its hand away, so expect real splits such as 80/20 and do not always take the largest share.
How long a decision takes
Preflop decisions read the trained blueprint: well under a millisecond. Flop decisions solve the flop over a sample of runouts: a few hundred milliseconds. River decisions solve the street: tens of milliseconds. Turn decisions solve the turn with a look ahead through every river card: up to a few hundred milliseconds. usage.compute_ms reports the real figure.
Play a sandbox game
The same engine hosts games against Bluffr, for testing an integration end to end.
curl -s "http://127.0.0.1:3000/sandbox/new?stack_bb=100"
curl -s "http://127.0.0.1:3000/sandbox/act?id=game_…&action=call"
Bluffr answers before each call returns. Its cards stay hidden until a showdown. legal lists what you may do, and suggestions the bet sizes Bluffr itself uses.
A sandbox Bluffr learns how you play over the game. read shows what it has learned: a style (still learning, balanced, tight-aggressive, loose-aggressive, tight-passive, loose-passive, calling station, nit or maniac), a confidence from 0 to 1, the hands it has seen (recent hands count more) and notes on what stands out and what Bluffr does about it. tuning echoes the aggression and looseness the game was created with.
Errors
Errors are JSON with the HTTP status, a stable code and a sentence: {"status": 422, "code": "board_mismatch", "error": "…"}.
| Code | Status | Meaning |
|---|---|---|
unauthorized | 401 | No key, or not one of BLUFFR_API_KEYS |
not_hosted | 404 | A server started with BLUFFR_API=off, as this site is |
unknown_model | 404 | A model other than bluffr-1 |
unknown_game | 404 | A sandbox id that isn't here, or has ended |
missing_id | 400 | A sandbox endpoint without id in the query |
invalid_cards | 422 | Cards that don't parse, repeat, or are too many |
invalid_setup | 422 | A position or stack the game can't start from |
invalid_history | 422 | An action out of turn, illegal, or malformed |
board_mismatch | 422 | The board doesn't fit the street the betting reached |
illegal_action | 422 | A sandbox move the rules don't allow |
hand_over, hand_in_progress, not_your_turn | 409 | A move at the wrong time |
rate_limited | 429 | Over 120 requests a minute |
Where it runs
The API is for software you control: your own games, training tools, simulations and research. It does not connect to ClubGG or any other poker site or client, and it will not be built to.