Inspiration

Poker is a game of incomplete information: you never see your opponent's cards, so every decision is a bet on probabilities and on how the other player thinks. Heads-up Short Deck Pot-Limit Omaha makes this even harder: 5 hole cards, a 36-card deck, flushes beating full houses, and huge equity swings. We wanted to build a bot that doesn't just play "good poker" in a vacuum, but figures out who it is playing against and adapts.

What it does

outliers-bot is a heads-up Short Deck PLO agent that, at every decision:

  1. Estimates its equity (chance of winning at showdown) with fast Monte Carlo simulation.
  2. Weights the opponent's possible hands by a learned range model: what this specific opponent has actually shown down after betting, calling or checking.
  3. Picks an action by comparing its equity with the price it's being offered (pot odds), and adjusts its aggression to the opponent's tendencies (how often they fold, call or bluff).

How we built it

Preflop. We precomputed the equity of every one of the ~20,000 distinct 5-card starting-hand types against a random hand and stored it in a lookup table. Every possible starting hand is also given a strength rank from 1 to 10.

Postflop equity. The bot simulates hundreds of possible opponent hands and remaining board cards, and counts how often it wins. Opponent hands that fit how that opponent has been playing count more, and unlikely ones count less.

Speed. Plain Python could only simulate about 30 hands per decision, which was far too noisy. We rewrote the hand evaluator in vectorized NumPy with precomputed lookup tables, making it about 20× faster. That allows 500–700 simulated hands per decision within the 8-seconds-per-1000-hands time limit.

Opponent modelling. For every type of action an opponent takes (raise, call, bet, check), the bot records how strong the hands they showed down were, and uses that to predict their range in future hands. It also tracks how often each opponent folds, calls and raises against its bets.

Decisions. The bot calls when its equity beats the pot odds by a safety margin, value-bets and raises with strong hands, and bluffs only against opponents who fold often. Against opponents who call a lot, it stops bluffing and just value-bets.

Testing. We built an offline match simulator that plays every deal from both seats with the same cards to cancel out card luck, against a pool of test opponents: random, calling station, always-raise, tight, loose and aggressive styles. We compared versions by expected match points, not just chips, because a match only counts as a win when the result is statistically clear.

Challenges

  • Time limits: about 4–7 ms per decision on average. The bot measures its own speed and adjusts how many hands it simulates, staying at about 3 of the 8 allowed seconds.
  • Noise: poker results swing a lot, so most "improvements" were just luck. We needed dozens of test matches per version to trust a small difference.
  • A sneaky stats bug: our fold-rate estimate overstated how often every opponent folded. Fixing it naively made results worse, so we kept the aggressive behaviour and added a separate, correct call-rate statistic to switch off bluffing against opponents who call a lot.
  • Things that didn't work: a quick self-play game-theory (CFR) model scored far worse than the adaptive bot in the time available, so we dropped it.

What we learned

  • Speed is strategy: making the simulator 20× faster was the single biggest improvement.
  • In a points-based tournament, turning close matches into clear wins matters more than average chips won.
  • Exploiting opponent tendencies (bluff the folders, value-bet the callers) beat every parameter we tuned.
  • Measure everything with enough samples, because poker results lie over small runs.

What's next

Train a full game-theory (CFR) strategy for Short Deck PLO and combine it with the opponent model, so the bot is hard to exploit by default and exploits opponents when it's safe.

Built With

Share this project:

Updates

Submission history