Blackjack
Vegas-rules Blackjack with persistent balance tracking, built twice in two different stacks. The Python/Tkinter version was a forcing function for learning a stateful GUI primitive; the browser port is plain DOM + CSS to prove the same gameplay loop without a framework.
architecture
flowchart TB
Engine["Game engine<br/>shoe · hand · settle"]
PyUI["Tkinter UI<br/>desktop"]
WebUI["DOM + CSS UI<br/>browser"]
LS[("Local persistence<br/>balance + settings")]
Engine --> PyUI
Engine --> WebUI
PyUI --> LS
WebUI --> LSBoth implementations share the same core engine shape — a shoe of N decks, a hand evaluator that handles soft 17, and a settlement function. The UI layer is the only thing that changes between desktop and browser. Balance and settings persist locally per implementation (file on Tkinter, localStorage in the browser) so neither version needs a backend.
decisions worth reading
The point was to learn two GUI primitives, not just to ship one game. Building it cold in Tkinter and again in plain JS forced me to keep the engine engine and the UI UI — the parts that didn't need to change between stacks didn't change.
The whole game state fits in one object. Reaching for a framework would have added more ceremony than logic. Plain `addEventListener` + a single render function makes every state transition explicit, which is also nicer for a learning artifact.
It's the player-friendlier rule (S17), which keeps the game beatable for casual sessions. Was tempted by H17 for a marginally more interesting decision tree on the dealer side, but ultimately picked S17 because it's the one most casinos still use on the strip.
code highlights
Hand value handling soft aces
Treats aces as 11 by default, then demotes them to 1 only as needed to keep the hand under 22. Same logic powers both the player and dealer evaluators.
function handValue(cards) {
let total = 0;
let aces = 0;
for (const card of cards) {
if (card.rank === 'A') {
aces += 1;
total += 11;
} else if (['J', 'Q', 'K'].includes(card.rank)) {
total += 10;
} else {
total += Number(card.rank);
}
}
while (total > 21 && aces > 0) {
total -= 10;
aces -= 1;
}
return total;
}