← Things
2022-05HTMLCSSJavaScriptUnderscore.jsBootstrap

Blackjack.

My first project when I started with HTML, CSS, and JavaScript. A simple game—mostly an excuse to make something real and see if the code would actually run.

Source →
Screenshot of a Blackjack game with player and computer hands

What I wanted to solve

I wasn't trying to invent a better Blackjack. I was trying to answer a more basic question: can I take rules I understand in my head and turn them into something that actually runs in a browser?

Up to that point, HTML and CSS felt like drawing. JavaScript was the first time the page could decide things—deal a card, disable a button, declare a winner. Blackjack was a good first problem because the rules are strict enough to force real logic, but small enough to finish.

What I built

A single-page Blackjack game:

  • player vs computer
  • new game / hit / stand
  • live score updates
  • card images rendered into the DOM
  • winner decided with a simple alert

The UI was intentionally plain: Bootstrap buttons, a black header, overlapping card images. The interesting part wasn't the design—it was getting the game state to behave correctly.

How it works

The whole game lives inside a module pattern (miModulo) so the deck, scores, and hands stay private. The page only gets one public method: iniciarJuego().

Deck and cards

Cards are represented as strings like 2C, AH, KS:

  • numbers 2–10 for pip cards
  • A, J, Q, K for specials
  • C, D, H, S for suits

crearDeck() builds the 52-card deck, then Underscore's _.shuffle() randomizes it. Drawing a card is just deck.pop().

That encoding was useful because the same string drives both scoring (valorCarta) and the image path (assets/cartas/${carta}.png).

Scoring

Face cards are worth 10. Aces start as 11 and soft-adjust down to 1 when the hand would bust:

js
while (puntos > 21 && ases > 0) {
  puntos -= 10;
  ases--;
}

That was the first “non-obvious” rule I had to encode carefully. Showing the raw sum of card values isn't enough—Blackjack scoring needs memory of how many aces are still soft.

Turns

The player loop is event-driven:

  1. Pedir carta draws for the player, updates points, renders the card.
  2. Bust immediately ends the round.
  3. Hitting exactly 21 (or pressing Detener) hands control to the computer.
  4. The computer keeps drawing while under the player's score.
  5. determinarGanador() compares the two totals.

The computer turn is a do...while, not a second human-style click loop. Once the player stops, the house plays out automatically.

UI state

Buttons start disabled until a new game begins. After the player busts or stands, hit/stand lock again. Scores live in <small> tags next to each player title, and cards are appended as <img> nodes with a negative margin so they overlap.

What I learned

  • Separating state (deck, hands, scores) from rendering (DOM updates) made the game easier to reason about, even in vanilla JS.
  • Small rules compound: ace soft-counting, turn order, and button enabling/disabling all have to stay consistent or the game feels broken.
  • A module IIFE is a crude but useful way to avoid dumping everything into the global scope.
  • Finishing something imperfect teaches more than planning something perfect. This project exists because I shipped it.

Looking back, the code is early-career code—minified build checked in, alerts for outcomes, Bootstrap for layout. But it was the first time I connected interface, state, and rules into one working system. Everything else started there.