§projects / blackjack
Blackjack
A blackjack rules engine written from scratch in TypeScript, a React table to play it on, and a strategy trainer — plus two independent ways of proving the "correct" play is actually correct.
§the engine
The engine builds a shoe, deals, and adjudicates a full hand — hit, stand, double, split, surrender, insurance, dealer hole-card and peek — with the dealer's drawing rule (hit or stand on soft 17) as a swappable strategy. The table under test here is Vegas rules: dealer hits soft 17, double after split, split aces draw one card, no resplit aces, no surrender. The React front-end is a thin adapter over that engine — it just subscribes to engine state and renders it, so the game you play and the strategy the math evaluates are the exact same code.
§proving the strategy
The interesting problem isn't playing a hand — it's knowing a basic-strategy chart is actually optimal. I check it two independent ways that have to agree:
An exact enumerator: composition-tracking, zero variance. It walks every dealer outcome against the precise remaining-deck composition to get stand / hit / double EVs to the last decimal.
A Monte-Carlo simulator: it plays a whole strategy through the real engine over shuffled shoes and reads back the mean payout, with a standard error. Same questions, completely different method.
When the simulator's measured payout lands on the enumerator's exact EV within its error bars, both are trustworthy. One is math; the other is a witness.
§a result — strategy drifts with deck count
Basic strategy gets taught as one fixed chart, but the right play depends on how many decks are in the shoe. Running the exact enumerator across deck counts (dealer stands on soft 17, non-pair hands vs up-cards 2–9), 9 of 120 cells actually change. Every place the correct action shifts:
| hand | 1 dk | 2 dk | 4 dk | 6 dk | 8 dk | ∞ |
|---|---|---|---|---|---|---|
| hard 9 vs 2 | DD | DD | H | H | H | H |
| hard 12 vs 4 | H | H | H | H | S | S |
| hard 12 vs 6 | H | S | S | S | S | S |
| hard 13 vs 2 | H | S | S | S | S | S |
| soft 13 vs 4 | DD | H | H | H | H | H |
| soft 13 vs 5 | DD | DD | DD | DD | DD | H |
| soft 14 vs 4 | DD | H | H | H | H | H |
| soft 15 vs 4 | DD | DD | DD | DD | DD | H |
| soft 17 vs 2 | DD | H | H | H | H | H |
DD double · H hit · S stand — e.g. 12 vs 6 flips from hit to stand once you go past a single deck; soft 15 vs 4 doubles all the way until the shoe is effectively infinite.