Poker Bot
A no-limit hold'em bot that plays one openly declared seat at friends' home games.
I wanted something to bring to my friends' poker nights, so I built a bot that takes a seat at the table. Everyone knows it's a bot. It isn't a hidden helper for one of the players. It has its own chair, its own stack and its own name on the table.
Getting the game to the bot turned out to be harder than I expected. Feeding a live game into a program while a turn clock counts down is tough, so I also built an online poker table. Friends open it on their own phones, each sees only their own cards, and they play against the bot, which has a labelled seat like anyone else.
It learned the way a chess player gets good by playing themselves over and over. I rented graphics cards, let the program play an enormous number of hands against copies of itself, and after each one it nudged itself toward the plays it wished it had made. The method is called Deep CFR, if you want to look it up.
That is the awkward part of playing with real cards. The bot has no eyes, so at a real game I type in what happens: its two cards, what each player does, each card that lands on the board. The phone then shows what the bot would do, and I make that move for it. Home games are messy, with people leaving, buying more chips and changing the stakes halfway through, so the table can be edited between hands without reloading anything. A hand it folds takes about five taps. One it plays all the way through takes fifteen to twenty, which slows the table down, and with a clock running that is a lot of tapping. The online table gets around it: the game is already inside the program, so the bot knows what happened without anyone typing it.

How good is it? Against careless or wild players it wins clearly in my tests. Against a very tight player who only ever plays strong hands, it roughly breaks even. Against Slumbot, a well-known benchmark bot, an earlier version lost clearly, so it is not an unbeatable machine. Early on it also folded a pair of jacks nine times out of ten in one common spot, which is about the worst habit a poker player can have. I fixed some of that, but it still folds too much.
It has played a couple of home games. That's far too few to say anything about how good it is, because over that many hands luck is most of what happens in poker, so everything above comes from simulations.
I also started a helper that reads an online club app from screenshots and tells me what the bot would do. It was kind of a bust. I didn't put much effort into it, because by then I had built the online table.
What's next
The over-folding is still there, and I have not re-run the Slumbot match on the newer version. A truly strong bot would need weeks of rented computing time, which I haven't spent yet.
What I wanted
I wanted a poker bot I could bring to my friends' game nights and have them play against. A home game is messy. People join and leave, rebuy and change the blinds halfway through, and stacks run from short to deep. I wanted a bot that copes with a table that keeps changing and runs on a laptop.
The bot sits in one seat and everyone at the table knows it's a bot. It isn't there to help a hidden player. I started it in August 2026, and finished with a 17-hour push to have something usable for a game night. It has since played a couple of home games.
The hard part turned out to be feeding the live game to the bot while a turn clock is ticking down. With real cards it can't see the table, so a person, in practice me, types the hands and actions into a phone and the bot suggests its move. That works, but it is slow. So I also built an online table that friends join from their own phones, with the bot in a labelled seat. The program already has the game state there, so nothing needs typing.
How it works
Everything sits on a hand engine I wrote in Python for two to nine players. It handles blinds, min-raise tracking, the short all-in rule (a small all-in raise doesn't reopen the betting), side pots, uncalled-bet refunds and odd-chip awards. The live table rebuilds the engine from scratch after every typed update, so the engine stays the one place that knows the pots and what's legal.
The strategy is trained with Deep CFR, a 2019 research method. Traversals of the game tree are sampled, neural networks predict the regrets, and a final policy network learns the average strategy. The network embeds the cards (hole, flop, turn, river) and combines them with betting features. I trained on rented GPUs from Vast.ai, and training resumes where it stopped after a preemption.
What is Deep CFR?
CFR is short for counterfactual regret minimisation. The program plays against itself, and after each pass it asks, for every decision it made, how much better some other action would have done. That gap is the regret, and it shifts toward the actions it regrets not having taken. Averaged over many passes, the strategy gets close to unexploitable in two-player games. Deep CFR swaps the giant table of regrets, which is far too big to store for poker, for neural networks that estimate them.
Three choices mattered. The action menu is fold, call, five bet sizes (0.33, 0.5, 0.75, 1 and 1.5 times the pot) and all-in. Training hands randomise the table size and the stack depth (roughly 20 to 200 big blinds), so one network, the generalist, covers a range of tables. And the average strategy is weighted by iteration squared, so the near-random early iterations stop dragging it down.
The online table is a small FastAPI server on the same engine. Each phone opens a WebSocket, picks a name and a seat, and gets a private view with only its own hole cards. The server starts a hand when two people are seated, plays the bot on its turn, and checks or folds for a player who runs out the turn clock. The generalist is the default bot there.
With real cards, the operator enters the bot's cards, each opponent action and each board card, then taps the suggested action. The console seats two to six players. Between hands I can add or remove players, rebuy and change the blinds, and the bot stays loaded. Click through one decision below.
What is a blueprint, and what does search add?
A blueprint is a strategy worked out in advance for the whole game, so it has to be rough about any single spot. Search starts from the exact situation in front of the bot and works that one spot out in more detail, then falls back on the blueprint for everything after. The strongest poker bots in the research literature, Libratus and Pluribus, use that pairing. Mine does it too, but only heads-up.

Results
Everything here comes from simulations, not from live play. The matches are scored in duplicate and measured in big blinds per 100 hands. Pick a bot and an opponent below. The same numbers for the two current bots are in the table underneath.
What are duplicate scoring and bb/100?
A big blind (bb) is the smallest forced bet, so bb/100 is how many of them the bot wins per hundred hands. Duplicate scoring plays each deal twice with the seats swapped, once with the bot holding each side of the cards, so luck of the draw cancels out and a smaller sample gives a fairer comparison. A positive number means the bot is ahead.
| Bot | Opponent | Result |
|---|---|---|
| Heads-up specialist | Random | +110 |
| Heads-up specialist | Calling station | +147 |
| Heads-up specialist | Tight nit, break-even | +2.9 |
| Generalist, at several table sizes | Random | +200 to +800 |
The bot is not unbeatable. Against an exact best-response calculation, the heads-up bot is exploitable by about 330 to 346 mbb per game at 20 big blinds. Roughly, a strong bot sits under 100 and a fully solved one in single digits.
What does exploitable mean here?
Imagine an opponent who knows exactly how the bot plays and answers every decision perfectly. Exploitability is how much that opponent wins per game, in milli-big-blinds, where 1,000 mbb is one big blind. A perfect strategy scores zero. The calculation is exact, with no luck in it, but it only works in a smaller version of the game.
That smaller version is heads-up with one preflop betting round, where the later streets are replaced by all-in equity. So the number is exact and repeatable, but it isn't full poker, and I have no equivalent for deep stacks or for more players.
The generalist is noisier. It's roughly break-even to slightly negative against a disciplined nit, and its edge is over loose and passive players. I expect that is closer to a home game than a disciplined nit is, but a couple of games isn't a test. The console seats two to six players, and I have no results above six.
What didn't work
The first generalist was bad. It folded jacks 92% of the time on the heads-up button and aces about 19%. To tell a bug from undertraining I compared the policy network with the advantage network. Weighting the average toward later iterations, and a bigger, longer run, reduced the problem without removing it. Both current bots still over-fold the button, more as stacks get deeper.
| Bot | Hand | Folds | Where |
|---|---|---|---|
| First generalist | Jacks | 92% | Button |
| First generalist | Aces | about 19% | Button |
| Heads-up specialist | Aces | about 18% | 100 bb |
| Heads-up specialist | Kings | about 87% | 100 bb |
| Generalist (current) | Aces | 0% | 100 bb |
The heads-up specialist was only trained at 20 to 60 big blinds, which is why it falls apart at 100. The generalist is the default, and the specialist is for short-stacked heads-up only.
The first generalist lost to Slumbot. Slumbot is the standard heads-up benchmark. Over 2,000 hands that version lost 81.7 bb/100, with a 95% interval of -115 to -58. It beat call-stations, maniacs and random players clearly, and lost to the nit at six-handed. I've retrained since and haven't re-run the Slumbot match. Search looked better against it in a short sample, but heads-up variance is far too high to put a number on that.
Search is not a cure. It passes its sanity checks, for example it never folds the nuts, and on the first generalist it fixed the worst single spots: jacks went from folding 92% of the time to 0%. But on the current bots it doesn't lower worst-case exploitability, because its ceiling is the quality of the blueprint underneath it.
A dead end: my original blueprint (about 500 MB) no longer loads with the current feature encoding, so every default now points at the current generalist.
The console is also slow when the bot stays in a hand, because one person types every action. A hand the bot folds takes about 5 taps and one it plays deep takes 15 to 20. I haven't timed it at a real table. That, with a clock running, is the main reason I built the online table.
The screen-reading helper. I also started a helper that reads an online club app from phone screenshots and shows the bot's move. It was kind of a bust. I didn't put much effort into it, because by then I had built the online table.
What's unfinished
I haven't re-run Slumbot on the current bots, and everything above is against scripted opponents. A couple of home games isn't a measurement. Search for three or more players doesn't exist. I planned an optional layer that adapts to each opponent, and I haven't built it.
There's also a model that imitates human play, trained on a public set of hand histories: about 199,000 hands, of which 163,565 were usable, giving 1.8 million decisions. I have no results from it. A stronger bot would need weeks of GPU time, a finer action menu and proper deep-stack subgame solving.
- Stack
- Python, PyTorch, Deep CFR, FastAPI, WebSockets, a mobile web console and a multiplayer phone table
- Training
- Rented GPUs (Vast.ai); resumable
- Status
- Prototype
- Code
- Not public