AgentGames

Search titles, descriptions, models and tags. Press Enter to see results.

🃏 Guide

How to make a card game with AI

A card game is a shuffle and a state machine. The one line shuffle is biased, and nested conditions are what make a new rule impossible to add later.

Updated 5 October 2026 7 min read By the Agent Games team
Jump to a section
  1. Four steps to a hand that plays through
  2. Why a random comparator is not a shuffle
  3. A turn is a state machine, not a nest of ifs
  4. A dealing animation is a view, never the state
  5. Common questions
  6. Where to go next

Two small things decide whether a card game holds up, and a first build usually gets both of them wrong. The shuffle has to be even, which the one line version is not. The turn order has to be a state machine, because that is what lets you add a rule next week without unpicking the rest.

Both faults are invisible until they are not

A biased shuffle still deals cards and nobody notices. A turn order written as nested conditions still plays a hand through. Neither shows up in a first test. Both show up the moment you want trumps, a second round, or an opponent allowed to pass.

Four steps to a hand that plays through#

Build the deck and shuffle it properly

Four lines, and the one part of a card game you should never improvise. Get it in before anything can be dealt.

Name the phases before writing any of them

Write the list out. If you cannot name the phases, the game has intentions rather than rules.

Play a whole hand with nothing moving

Cards snapping into place is fine for now. Once things slide, a rule bug and a timing bug look the same.

Add the movement last

A queue of card slides sits on top of a game that already works correctly without any of it.

PromptCopy this, paste it, send it
Build a trick taking card game as ONE self-contained .html file. No
frameworks and no image files. A card is a rounded rectangle with a suit
symbol and a rank drawn as text.

DECK: 32 cards, four suits of eight ranks, built in a loop.

SHUFFLE: Fisher Yates. Walk the array from the last index down to 1.
For each one, pick a random index from 0 up to AND INCLUDING the current
index, then swap the two. Do NOT sort with a random comparator.

PHASES: one variable called phase, holding DEAL, LEAD, RESPOND, RESOLVE,
REFILL, SCORE or GAME_OVER. One function per phase, plus an enter()
that runs once as a phase begins. A click on a card does nothing unless
the current phase accepts it. No nested if chains deciding turn order.

RULES: each player holds five cards. The leader plays one. The follower
must match the led suit if they hold it, otherwise anything goes. The
higher rank in the led suit takes the trick and leads the next one. Both
draw back to five while the deck lasts. Most tricks takes the hand.

OPPONENT: it plays its lowest winning card if it holds one, and its
lowest card otherwise. Keep that decision in a single function so it can
be swapped out later.

ANIMATION: the phase and the hands change first and at once. A queue of
card movements then plays out on screen, and input is locked only while
that queue is draining. No game rule may live inside an animation
callback.

MOTION: every card slide advances by the seconds elapsed since the last
frame, clamped to 0.05, so a tab left in the background does not fling
cards off the table.

PHONE: a tap on a card plays it, and
canvas { touch-action: none; } in the CSS.

CONFIG: one object at the top with the hand size, the suit list, the
rank list, the slide speed and the queue delay, each commented.

Change the rules freely. Hand size, suits, what beats what, who leads first. Leave the shuffle and the phase list exactly as they are. Those two blocks are what the rest of the rules hang off, and a later request worded as a small change is how both of them get rewritten.

Why a random comparator is not a shuffle#

The one line shuffle is a sort with a comparator that returns a random sign. It reads as even and it is not. A sort assumes its comparator is consistent, so if it says one card comes before another, it has to keep saying so. A random comparator breaks that assumption, and the result depends on whichever sorting algorithm the engine happens to run.

What comes out is a deck that has been stirred rather than shuffled. Cards tend to finish near where they started, and some orderings appear far more often than others. In a game of chance that is not cosmetic. It is the game being quietly weighted in a direction you cannot predict and cannot explain to a player.

Two ways to shuffle a deck One of them is a line shorter. It is also not a shuffle. Biased deck.sort(() => Math.random()-.5)The comparator is inconsistentSome orders come out far more oftenCards drift back near where they were Even for (i=n-1; i>0; i--) swap(i, rnd(i+1))Walk from the end, swap backwardsEvery ordering equally likelyOne pass, no sorting at all Test it by dealing the same deck many times and counting where the first card lands.
The right hand version is older than the language and is in every reference under the name Fisher Yates. Ask for it by name and you will get it.

The correct method is one pass with no sorting in it. Start at the last card. Pick a random index from the front of the array up to and including the card you are standing on, swap those two, then step back one place. When you reach the front you have finished. Every ordering of the deck is then equally likely.

One detail belongs in the prompt. The random index has to include the current position, so a card is allowed to stay where it is. Excluding it is a well known off by one, and it produces a shuffle in which no card can remain in place. That is a different distribution, and a worse one, from something that looks like a typo.

Checking it is easy and worth doing once. Deal the same deck a few thousand times and count which position the first card ends up in. An even shuffle gives a flat spread across every slot. The comparator version gives a visible shape, and once you have seen the shape you will not reach for it again.

A turn is a state machine, not a nest of ifs#

Write turn order as conditions and the first version works fine. The second version has a condition inside a condition. Then you want a rule such as following suit. Now the function that handles a click decides four things at once: whose turn it is, whether the card is legal, whether the trick is finished, and whether the hand is over.

A state machine pulls those questions apart. One variable holds the phase. One function runs as a phase begins. One function handles input, and its first act is to ask whether the current phase accepts input at all. A click on a card during RESOLVE is not a special case to be handled. It is a click the machine ignores.

The phases of one hand of cards Each phase takes one kind of input and names where it can go next. DEAL Shuffle, deal, choose a leader. Takes no input. LEAD The leader plays one card. Other clicks ignored. RESPOND The follower plays. Only a legal card is taken. RESOLVE Compare, award the trick, set the next leader. REFILL Both draw back up. An empty deck means SCORE. SCORE Show the result. The only input is a new hand. Adding trumps later touches RESOLVE and nothing else. That is what the list buys you.
Six phases is about right for a trick taking game. A player who has never seen the code can read this list and tell you what the game does.

The list matters more than how you implement it. A matching game needs fewer phases: DEAL, FIRST_PICK, SECOND_PICK, COMPARE, and then back to FIRST_PICK or on to SCORE. Writing that down shows you straight away that COMPARE is where a pair is kept or turned back over, and that SECOND_PICK has to refuse a card already face up.

PhaseWhat it acceptsWhere it can go
LEADA click on a card in the leader's handRESPOND
RESPONDA legal card from the followerRESOLVE
RESOLVENothing. It runs and moves on by itselfREFILL or SCORE
REFILLNothingLEAD
SCOREA click on the new hand buttonDEAL or GAME_OVER

Two of these columns are the bug report you would otherwise have written out by hand a week later.

The payoff shows up when you add a rule. Trumps change RESOLVE. A pass or a discard is a new phase with one way in and one way out. Neither touches the other phases, and neither asks you to work out what a click handler was already doing before you arrived.

A dealing animation is a view, never the state#

A deal that plays out rather than appearing all at once is worth having. The mistake is letting the animation own the cards. Suppose a card arrives in the hand only when its slide finishes. The hand is then wrong for the length of the slide, and every rule that reads the hand has to know about the gap.

Do it the other way round. Shuffle and deal in one go, so the model of the game is right immediately. Then push a list of card movements onto a queue and let the drawing code work through it. The hand already holds the cards. The queue is only deciding where to draw them on the way in.

Input is the one thing the queue is allowed to touch. Lock clicks while it drains, because a player who plays a card part way through a deal will not understand what happened next. That lock is a single boolean, and it is the entire connection between the two systems.

  • Never resolve a trick in a callback. A finished slide should do one thing, which is take itself off the queue. The moment a rule runs there, the rules depend on the frame rate.
  • Let the animation be skipped. A click while cards are moving should empty the queue rather than join it. The state is already correct, so skipping ahead costs nothing.
  • Time the slides in seconds. A slide measured in frames takes twice as long on a slow machine, and a slow deal reads as a hang rather than as a flourish.
  • Save the model, not the motion. If a hand survives a reload, store the phase and the hands. A half drawn deal is not something to restore.

🃏Card and board games written by AI

Common questions#

Can AI make a card game?

Yes, and the drawing is the easy part, since a card is a rectangle with text on it. The two things to be specific about are the shuffle and the phase list. Left to itself a model will often produce the one line shuffle and a click handler that decides everything.

What is wrong with sorting an array with a random comparator?

A sort needs a comparator that gives the same answer every time it is asked about the same pair. A random one does not, so the outcome depends on the sorting algorithm underneath. Some orderings become much more likely than others, which in a card game means a weighted deal.

How do I shuffle a deck properly in JavaScript?

Fisher Yates. Loop from the last index down to one. Each time, pick a random index between zero and the current index inclusive, and swap the two entries. One pass, no sorting, and every ordering equally likely. Ask for it by name and the model will write it correctly.

How should I write turn order?

As one phase variable and one function per phase. Input is checked against the phase before anything else happens, so a click arriving at the wrong moment is ignored rather than handled. The alternative grows a new nested condition for every rule you add.

How do I add a rule to a card game I already built?

Find the phase it belongs to and change only that one. A trump suit is a change to how the trick is compared. A discard is a new phase with one way in and one way out. If you cannot say which phase a rule belongs to, the phases are not separate yet.

How do I write a computer opponent for a card game?

Start with one rule and keep it in a single function. Play the lowest card that still wins, otherwise play the lowest card at all. That is enough to feel like an opponent. Keeping it in one place means a better version is a replacement rather than a rewrite.

Where to go next#

A card game is one of the few genres where a finished thing can be small. One hand, one opponent, one rule worth thinking about. Publish it and the page keeps the model and the prompt beside it, which is the part the next person reads.

The method underneath is in how to make a game with AI. If you want a human on the other side of the table rather than a function, the two player guide covers sharing one screen. Phases and rule layers go further in the strategy guide, and fixes for particular faults are in the prompt guide. The card games here show which rule sets survive a stranger playing them.