Most beginners who search "how to make a card game" want the feel of a real tabletop night - a green felt with a fanned thirteen-card hand at the bottom, three opponents around the rim, four cards played into the center in trick order with the suit-matching rule quietly enforced, a Queen of Spades bomb that lands on somebody else's scorecard, and a satisfying trick-collection sweep as the winner drags the four cards toward their pile - not a real-money platform with a shuffle audit trail and gambling licences on day one. Live card rooms are famously demanding at commercial scale because a single project juggles cryptographically fair shuffling, per-player state, correct legal-play validation, opponent AI that follows suit and ducks liability tricks plausibly, and a scoring model that handles special cases like shooting the moon. A browser card game aimed at a portfolio piece or a jam is a different animal. A coding agent scaffolds the deck, the shuffle, the trick validator, and the score sheet from a single prompt, and AI generation covers the felt, the card faces, the opponent portraits, and the shuffle and deal cues. On desktop or web, that means WizardGenie for the play-loop interpreter, AI Image Gen for the felt, cards, and opponent avatars, and SFX Gen for the shuffle riffle and card slides. This guide is the honest end-to-end for how to make a card game in 2026, as a weekend build you can actually finish, with Hearts as the worked example.
What how to make a card game actually means in 2026
The query "how to make a card game" hides three distinct intents. Some searchers want a physical-print how-to for designing a boutique deck with custom art and a rulebook - the sibling how to make a board game guide covers the tabletop-manufacturing end of that thread. A second intent is the deckbuilding roguelike family (Slay the Spire, Dominion, Balatro), where the deck itself evolves across a run - the how to make a deck builder game guide covers that draw-play-discard-reshuffle loop. The intent this article targets is the third: a browser card game that boots into a single-page HTML shell with a green felt table, four seats (you and three AI opponents), a fair Fisher-Yates shuffle of a 52-card deck, thirteen cards dealt to each seat, trick-taking play where each seat plays one card per trick and must follow the led suit if they can, a scoring rule for the ruleset in play, and a hand-end scoreboard that carries between hands. That is a weekend build, it demos the Sorceress toolset honestly, and it is the format most card game tutorial and javascript card game searchers actually want.
The card family this guide picks is Hearts. Hearts needs no trump, no bidding, and no melding, so the play loop stays legible; it is a four-player evasion game where hearts score one point each and the Queen of Spades scores thirteen, the loser is the first player to reach 100 points, and a single seat that manages to take every heart and the Queen in one hand "shoots the moon" and gives 26 points to the other three seats. The Hearts rules on Wikipedia (verified 2026-08-31) cover the full canonical ruleset including the passing phase, the two-of-clubs opening lead, and the "hearts must be broken" rule before hearts can be led - cite that page in your itch.io blurb so players know you shipped a standard Hearts variant, not a house-rule mashup. Swap in Spades by adding a bidding phase and treating spades as trump, or Whist by cycling trump each hand - the scaffolded model in Step 1 covers both with a rules-object swap and about forty lines of new code, so the base build is the honest starting point for any trick-taking card game.
The card game play loop in one minute (shuffle, deal, trick, score)
Five moving parts, cycled every hand. First, shuffle - a fresh 52-card deck runs through a Fisher-Yates pass so no shuffle pattern carries between hands. Second, deal - thirteen cards go to each of four seats in clockwise order starting from the seat left of the dealer, and the dealer button rotates one seat clockwise between hands. Third, lead the trick - the seat holding the two of clubs leads it on the first trick of a hand; subsequent tricks are led by the seat that won the previous trick. Fourth, follow suit - each remaining seat, clockwise, plays a legal card; a seat must follow the led suit if it holds any card of that suit, and if not, it may play any card in its hand (with the "no hearts and no Queen of Spades on the first trick" exception, and the "hearts must be broken" rule before hearts can be led). Fifth, score the trick - the highest card of the led suit wins the trick and drags the four cards to that seat's pile; hearts count one point each, the Queen of Spades counts thirteen. Repeat thirteen times per hand, tally points, deal again, and continue until any seat crosses 100 points - whoever is lowest at that moment wins. That five-step cycle, wrapped by a scoreboard between hands, is the whole html5 card game loop - passing, shoot-the-moon detection, and hand-end animation are polish on top.
Pick your engine for how to make a card game: Canvas, Phaser, or WizardGenie
Three good targets in 2026, each with a different trade-off. Plain HTML Canvas 2D is the honest default and the pick this guide recommends for a first build. A trick-taking card game is light on physics - there is no collision solver, no continuous motion outside of a short card-slide easing curve, and the biggest render loads are the static felt and the 52 card sprites, both of which are cheap. The MDN Canvas API reference (verified 2026-08-31) covers the drawImage sprite-atlas pattern that lets you pack all 52 card faces plus the card-back into a single texture and stamp any card at any table position with one blit. Cache the felt backdrop to an offscreen canvas once at boot, redraw only the moving card sprites and the current trick each frame, and the whole browser card game experience holds a stable 60 fps in any modern browser without any WebGL overhead.
Separate the simulation tick from the render frame - trick-taking card play is inherently turn-based and does not need per-frame simulation. A single tick fires when a player commits a card; everything between plays is either idle or animating a short card slide, off the render loop. This is the same discipline the sibling how to make a poker game guide uses for its betting loop and the how to make blackjack guide uses for its deal loop - a card game just runs the cycle thirteen times per hand instead of once.
Phaser v4.2.1 "Giedi" (released 9 July 2026, verified 2026-08-31 on the official Phaser stable download page) becomes the right pick when a browser card game wants Phaser's Scene system, tween library, and input plugins out of the box. Phaser scenes map cleanly to card-game screens - LobbyScene, TableScene, HandEndScene, GameOverScene - and Phaser's tween chain is a natural fit for the "deal thirteen cards to each seat, one card at a time, 40 ms apart" animation without hand-writing an easing loop. For a first hand where the card-slide animation is the biggest UX bet, Phaser saves an hour; for a pure vanilla-JavaScript learning build, plain Canvas is more instructive.
WizardGenie is not a separate card game engine - it scaffolds whichever of the two you pick from a single natural-language prompt. WizardGenie ships as both a desktop app (Windows installer with auto-update, available to Early Access supporters and above) and a no-install web build. Its coding-model lineup covers Claude Opus 4.7, Claude Sonnet 4.6, GPT-5.5, Gemini 3.1 Pro, DeepSeek V4 Pro, Kimi K2.5, Grok 4.2, and MiniMax M2.7 (verified 2026-08-31 in src/app/_home-v2/_data/tools.ts lines 767-774). For a first card game play loop, any frontier model scaffolds the deck-shuffle-deal-trick-score state machine in one prompt. Pair a frontier planner with a budget executor like DeepSeek V4 Pro or Kimi K2.5 for the typing pass - the dual-agent pattern lands most projects at roughly one-fifth the single-frontier cost.
Step 1 - model deck, hands, tricks, and the score sheet (Hearts as the worked example)
Nothing else in the pipeline matters if the deck is not fair and the legal-play validator is not correct. Start with a small, testable model:
const SUITS = ["c", "d", "h", "s"];
const RANKS = ["2","3","4","5","6","7","8","9","T","J","Q","K","A"];
const RANK_VALUE = Object.fromEntries(RANKS.map((r, i) => [r, i + 2]));
function freshDeck() {
const deck = [];
for (const s of SUITS) for (const r of RANKS) deck.push(r + s);
return deck;
}
function shuffle(deck) {
for (let i = deck.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[deck[i], deck[j]] = [deck[j], deck[i]];
}
return deck;
}
const state = {
seats: [
{ id: "you", name: "You", hand: [], taken: [], score: 0 },
{ id: "ai1", name: "Rook", hand: [], taken: [], score: 0 },
{ id: "ai2", name: "Vera", hand: [], taken: [], score: 0 },
{ id: "ai3", name: "Otto", hand: [], taken: [], score: 0 },
],
dealerIndex: 0,
trick: [],
ledSuit: null,
leaderIndex: 0,
turnIndex: 0,
handIndex: 0,
heartsBroken: false,
firstTrick: true,
};
function dealHand() {
const deck = shuffle(freshDeck());
for (let i = 0; i < 4; i++) state.seats[i].hand = deck.slice(i * 13, (i + 1) * 13);
state.seats.forEach((s) => (s.taken = []));
state.trick = [];
state.ledSuit = null;
state.heartsBroken = false;
state.firstTrick = true;
const twoOfClubs = "2c";
state.leaderIndex = state.seats.findIndex((s) => s.hand.includes(twoOfClubs));
state.turnIndex = state.leaderIndex;
}
function legalCards(seat) {
const hand = seat.hand;
if (state.firstTrick && state.trick.length === 0) return hand.filter((c) => c === "2c");
if (state.trick.length === 0) {
const nonHearts = hand.filter((c) => c[1] !== "h");
if (!state.heartsBroken && nonHearts.length > 0) return nonHearts;
return hand;
}
const followers = hand.filter((c) => c[1] === state.ledSuit);
if (followers.length > 0) return followers;
if (state.firstTrick) {
const noPoints = hand.filter((c) => c[1] !== "h" && c !== "Qs");
if (noPoints.length > 0) return noPoints;
}
return hand;
}
The Fisher-Yates shuffle above is the correct O(n) algorithm - the loop counter starts at the end and picks a random index only from the unshuffled prefix, which is what produces a uniform permutation. The common naive-shuffle bug of writing Math.floor(Math.random() * deck.length) on every iteration produces a biased distribution that leaves cluster patterns detectable after roughly fifty hands (the Fisher-Yates shuffle overview on Wikipedia, verified 2026-08-31, has the classic proof and the wrong-versus-right comparison). For a single-player casual browser card game, Math.random with Fisher-Yates is honest and passes casual scrutiny; for any build that ever ships against another human on the same URL, swap the RNG source for Crypto.getRandomValues (verified 2026-08-31 on MDN) to close the client-seed inspection attack.
The legal-play validator is the second must-get-right component. Unit-test six cases before you generate any art: the two of clubs is the only legal opening lead on the first trick; a seat that holds any club must play a club when clubs are led; a seat holding no clubs on the first trick may not play a heart or the Queen of Spades; a seat holding no hearts may still play the Queen of Spades on a diamond lead; hearts cannot be led until any player has been forced to discard a heart on a non-heart lead; the seat that captures a heart or the Queen of Spades sets heartsBroken = true. Those six tests catch ninety percent of javascript card game validator bugs and let the model swap in Spades or Whist rules with a rules-object edit instead of a rewrite.