DEV Community

Jack Maniac
Jack Maniac

Posted on

Building a zero-dependency poker fold-equity calculator in one HTML file

I wanted a bluff calculator that a poker player could open on a phone at the table, with nothing to install and no account to make. The result is one HTML file, vanilla JavaScript, no build step, served as static files on Netlify. Live version: https://madmanpoker.netlify.app

(Full disclosure: I also write poker books, and the footer links to one.)

The break-even formula

Start with fold equity: the share of a bet's value that comes from opponents folding. For a pure bluff, where you have no outs and nothing to win when called, the bet breaks even when:

alpha = bet / (pot + bet)

Half a pot needs 33% folds. Two-thirds pot needs 40%. A pot-sized bet needs 50%. The calculator shows this number first, because it's the one players can compare against their own read of the opponent.

const need = B / (B + P);
Enter fullscreen mode Exit fullscreen mode

Adding outs

Once you have equity when called, the break-even moves. If the bet is called, the final pot is P + 2B (the pot, your bet and their call). You win that pot with probability e and lose your bet otherwise:

C = e·(P + 2B) − B

If the caller folds with probability F, the bet's expected value is:

EV(bet) = F·P + (1 − F)·C

Checking gives you a free card, and in this model you win the pot when you hit, so EV(check) = e·P. Setting EV(bet) equal to EV(check) and solving for F gives the fold percentage where betting and checking are even. That's the number the verdict uses:

const C = e * (P + B) - (1 - e) * B;   // EV when called (one caller, no more betting)
const evBet = F * P + (1 - F) * C;
const evChk = e * P;                   // free card, win the pot when you hit
let needChk = (P - C) > 0 ? (evChk - C) / (P - C) : 0;
Enter fullscreen mode Exit fullscreen mode

Where does e come from? Outs divided by unseen cards, with one card to come: 46 unseen on the turn, 47 on the flop. On the river no card is left, so the model sets equity to zero for draws:

const unseen = S.street === 'flop' ? 47 : 46;
const e = S.street === 'river' ? 0 : Math.min(1, S.outs / unseen);
Enter fullscreen mode Exit fullscreen mode

For more than one opponent, the calculator assumes each one folds independently, so the chance everyone folds is F = f^n. Two players who each fold 40% of the time both fold only 16% of the time, which is why multiway bluffs need far more folds per player.

The model's limits

It's a simplified model: one card to come, one caller, no more betting after a call, and independent folds. Real hands are messier. The page says so, because the output is only as good as the fold estimate you feed it.

Build choices

  • One file. CSS and JavaScript are inline. There's no framework, no package.json and no bundler, so the whole thing is readable with View Source.
  • One state object. Pot, bet, street, outs, fold %, opponents and read live in a single object. Each control writes to it and calls update().
  • Reads as presets. "Calling station", "loose-passive", "unknown", "thinking reg" and "nit" set the fold slider to 15, 25, 35, 50 and 60%. Moving the slider switches the read to custom.
  • Shareable state. The inputs are written to query parameters (p, b, s, o, f, n), so a result is a URL, and opening that URL restores the same inputs.
  • Static deploy. Netlify serves the files directly. There's no server code to run or maintain.

If you spot a mistake in the math, tell me. The formulas are all on the page, so you can check them against your own numbers.


The strategy side is in the book Unleash the Madman (Kindle): https://www.amazon.com/dp/B0F2N8LHJW

Top comments (0)