DEV Community

Devanshu Biswas
Devanshu Biswas

Posted on

Blackjack has no engine — just a shuffled 52-card array, two hands, and an ace that flips 11 1 to dodge a bust

I keep discovering that the games I assume need a framework need almost nothing at all. Blackjack is the latest: no canvas, no physics, no timer loop driving the world. It's a shuffled array of 52 cards, two hands, a chip balance, and a single string that says whose turn it is. I built the whole thing — real deck, real ace logic, a dealer that plays by an iron rule, 3:2 blackjack payouts and a chip balance that survives a refresh — in about 130 lines of vanilla JavaScript. Here's how it actually holds together.

The whole game is a deck, two hands and a phase

There's nothing to simulate. A card is the smallest object I could write — { rank, suit }. The deck is an array of 52 of them. player and dealer are arrays I draw into, balance and bet are numbers, and phase is one of "bet" | "player" | "dealer" | "over". That is the entire state. Every rule below only ever pokes at a couple of these variables, and the screen is just a redraw of them after each action — no game loop needed, because nothing moves on its own.

A real deck, shuffled by Fisher–Yates

I refused to fake the deck. Build it by pairing every suit with every rank (4 × 13 = 52), then shuffle it properly:

for (let i = d.length - 1; i > 0; i--){    // Fisher–Yates
  const j = Math.floor(Math.random() * (i + 1));
  [d[i], d[j]] = [d[j], d[i]];             // swap
}
Enter fullscreen mode Exit fullscreen mode

Fisher–Yates is the one provably-unbiased shuffle — every one of the 52! orderings is equally likely, in a single backwards pass. The naive "sort by a random key" trick is subtly biased; this isn't. I deal by pop()-ing off the end.

The ace trick is the whole game in two lines

This is the part I actually enjoyed. Faces are worth 10, numbers their pips, and an ace starts at 11 — the friendly value. Then the scorer decides whether it needs to shrink:

function handValue(hand){
  let total = 0, aces = 0;
  for (const c of hand){ total += cardValue(c.rank); if (c.rank === "A") aces++; }
  while (total > 21 && aces > 0){ total -= 10; aces--; }   // ace 11 → 1
  return { total, soft: aces > 0 };        // soft = an ace is still 11
}
Enter fullscreen mode Exit fullscreen mode

Count every ace as 11, and while you'd bust with an ace still worth 11, subtract 10 to knock one down to a 1 — repeat until you either fit under 21 or run out of aces. That single loop is all of soft/hard-hand logic. A "soft 18" (A+7) can't bust on the next card because the ace can always fall back to one; the moment it has to, the same hand becomes a "hard 18". Two lines replace what would otherwise be a sprawl of special cases, and I genuinely think it's the most elegant bit of code in the whole game.

The dealer has no choices, and that's the point

When you stand, the dealer flips its hole card and obeys exactly one instruction: draw while under 17, stop at 17 or more. No bluffing, no reading you — a fixed rule is what turns blackjack into a game of known odds. I run it as a timed loop so each card lands with a beat of suspense:

function dealerPlay(){
  const step = () => {
    if (handValue(dealer).total < 17){ dealer.push(deck.pop()); render(); setTimeout(step, 600); }
    else { settle(); render(); }
  };
  setTimeout(step, 600);
}
Enter fullscreen mode Exit fullscreen mode

Payouts, and the edge that's baked into turn order

Settling is a short ladder: a natural blackjack (21 on the first two cards, checked by hand.length === 2) pays a premium 3:2, an ordinary win pays even money, a tie is a push and your bet comes back. What surprised me building it is where the house edge actually lives — not in the payouts, but in turn order: you act first, so you bust and lose your bet before the dealer even reveals. The 3:2 blackjack bonus is the sweetener that keeps that edge down to roughly half a percent under good play. Balance is saved to localStorage so your chips survive a refresh.

That's the lesson I keep relearning: the falling numbers are the game. No engine, no card images — a deck, two hands, and one clever scoring loop.

Play a few hands (place a bet, then Hit or Stand):
https://dev48v.infy.uk/game/day46-blackjack.html

Top comments (0)