Inspiration

Most teams would ask an AI for a Monte Carlo bot with pot-odds thresholds. We wanted a bot that wins by being hard to exploit, then takes extra chips from opponents who show clear weaknesses, and we wanted to measure every decision instead of guessing.

What it does

Our bot plays heads-up Short Deck 5-card Pot-Limit Omaha against the official judge:

  • Solver core: a near-equilibrium strategy computed with CFR+ over the complete betting tree (43,081 nodes, generated by the official game.py, so no bet-size approximation), with hands grouped by strength and draw potential (E[HS²]).
  • Exploit layer: it plays the solver strategy first, measures the opponent (raise rates, fold-to-bet rate, and the bluff rate at showdowns), and then overrides specific spots: no bluffs against calling stations, calling down proven over-bluffers, folding more against players who never bluff, and raising every hand against bots whose time bank has run out.
  • Thresholding: low-probability solver actions are dropped, which removes noise from the simplified model.
  • Safety: a staged time guard (about 0.3 to 0.5 s of the 8 s budget per half on a laptop) and a fallback on every path, so it never crashes.

How we built it

  • Fast evaluator: a numpy pair × triple lookup table, 47x faster than the provided scorer and identical on every test hand.
  • Exact simulator: reproduces the official judge's results to the decimal, so thousands of matches could be run offline.
  • Solver pipeline: 1.5M simulated deals, a preflop equity table for all 376,992 starting hands, E[HS²] hand features, then vectorized CFR+ (exploitability about 5 mbb/hand within the abstraction).
  • Testing harness: a 41-opponent gauntlet (AI-typical bots, bots using the official evaluator, weird styles, adaptive exploiters), six red-team rounds of generated attackers, evolved attackers, a white-box best-response bot, and style-switching and fold-equity-hunting bots. Every change had to beat the previous version or it was rejected.

Challenges we ran into

  • Separating noise from real gains: with duplicate scoring, matches between two strong bots swing about ±0.4 bb/deal on their own randomness, so every change needed multi-seed A/B tests.
  • Ideas that sounded right but failed testing: a "call down maniacs" rule that aggressive bots exploited, finer 2D hand buckets, a spot-specific tight-raiser rule and an over-fold fix. All were rejected on the data.
  • Time limits: keeping strong play inside 8 s per 1,000 hands, including under CPU contention.

Accomplishments that we're proud of

  • No losses in the final 41-opponent gauntlet (36.2/41 points, +2.82 bb/deal average); the only draws were against other solver-strength bots.
  • No attacker beat it in six red-team rounds. A counter-strategy built from full knowledge of our strategy lost to it by +3.76 bb/deal.
  • Came out ahead of another team's strong bot in a remote practice match.

What we learned

In this format, rigorous measurement beats clever ideas: most "obvious" improvements made the bot worse. And a balanced base strategy plus evidence-based exploitation beats either one alone.

What's next for Heads Up - Card Sharks

Potential-aware hand abstraction, real-time river solving, and a learned opponent model.

Built With

Share this project:

Updates

Submission history