Card Game
A playbook for card games — card data, the deck/hand/discard zones, the turn structure, and
how card effects resolve. This is a compositional skill: it models cards as data and wires
them to UI. It does not re-teach data assets or UI nodes; it defines the zone model, the draw
machinery, and the effect-resolution rules that keep a card game correct and bug-free.
When to use
- Use when building any game where the core objects are cards moving between zones
(deck → hand → play → discard): deckbuilder, TCG/CCG, solitaire, roguelike deckbuilder.
- Use when designing draw/shuffle/reshuffle, turn structure, card costs, or how effects resolve.
When not to use: board/tile state with matching rules → puzzle. RPG with an
incidental card battler → start from rpg. For defining cards as assets, use godot-resources
/ unity-scriptableobjects; for the hand/drag UI, use godot-ui-control.
Core loop
Draw to your hand → spend resources to play cards → effects resolve and change the board →
end the turn (cleanup/discard) → opponent/next phase → repeat until a win condition. Depth
comes from the combinations a hand allows; the engine's job is to resolve them unambiguously.
Must-have systems
- Card data — id, name, cost, type, text, and an effect spec (data, not code).
- Zones — deck (draw pile), hand, play/board, discard, exile/removed; cards live in exactly one.
- Draw + shuffle + reshuffle — draw from deck to hand; reshuffle discard into deck when empty.
- Turn structure — phases (untap/draw/main/combat/end) as a state machine.
- Resource system — mana/energy/actions that gate how much you do per turn.
- Effect resolution — apply a card's effects in a defined order; handle targets and triggers.
- Win/loss condition — life total, deck-out, objective.
- UI — hand layout, drag/drop or tap-to-play, zone counts, targeting affordances.
Design knobs
| Knob |
Effect |
Notes |
| Starting hand / draw-per-turn |
tempo, consistency |
More draw = less variance. |
| Hand size limit |
hoarding vs. use |
Discard down at end of turn. |
| Deck size (min) |
consistency |
Smaller = more reliable combos. |
| Resource curve |
what's playable when |
"Mana curve" paces power. |
| Card rarity / power budget |
balance |
Stronger cards cost more / are rarer. |
| Determinism vs. randomness |
skill vs. swing |
Shuffle + random effects add variance. |
| Reshuffle rules |
deck-out, fatigue |
Reshuffle discard, or punish empty deck. |
| Removal / answers |
counterplay |
Every threat needs an answer in the pool. |
Patterns
1. Zones + draw with automatic reshuffle
# Pseudocode. A card is in exactly one zone at a time; moving = remove here, add there.
def draw(n):
for _ in range(n):
if not deck:
if not discard: # truly empty: deck-out (lose, or take fatigue)
on_deck_out(); return
deck.extend(discard) # reshuffle discard into deck
discard.clear()
shuffle(deck, rng) # use a seeded RNG (see save-systems for replays)
hand.append(deck.pop())
2. Card as data + effect resolution
# Pseudocode. Effects are a data list interpreted by the engine — not bespoke code per card.
card = {
"id": "fireball", "cost": 3, "type": "spell",
"effects": [ {"op": "damage", "amount": 6, "target": "chosen_enemy"} ],
}
def play(card, caster):
if resources[caster] < card.cost: return False # can't afford
resources[caster] -= card.cost
move(card, from_zone=hand, to_zone=play_or_discard(card))
for fx in card.effects:
resolve_effect(fx, caster) # one interpreter handles every card
return True
3. Turn structure as a phase machine
# Pseudocode. Fixed phases keep timing windows (triggers, priority) unambiguous.
PHASES = ["untap", "draw", "main", "combat", "end"]
def take_turn(player):
for phase in PHASES:
enter_phase(player, phase) # fire "on_phase" triggers here
if phase == "draw": draw(1)
if phase == "main": await player_plays_cards()
if phase == "combat": resolve_combat()
if phase == "end": discard_to_hand_limit(player); clear_temporary_effects()
Pitfalls / failure modes
- A card existing in two zones at once → duplication/loss bugs. Enforce "exactly one zone";
move = remove-then-add, and assert no card appears twice.
- Forgetting to reshuffle → draws silently fail or crash on empty deck. Reshuffle discard,
or define deck-out/fatigue explicitly (Pattern 1).
- One function per card → unmaintainable and untestable. Make effects data interpreted
by a small set of operations (Pattern 2).
- Ambiguous effect order / simultaneous triggers → nondeterministic outcomes. Resolve in a
defined order (a queue or stack); document LIFO vs. FIFO (refs).
- Unseeded shuffle in a game that needs replays/undo → can't reproduce. Use a seeded RNG.
- No hand limit / no answers → degenerate hoarding or unbeatable threats. Add a hand cap and
ensure removal exists for every threat archetype.
- Targeting state leaks → a cancelled play leaves the board mid-targeting. Make play
atomic: validate cost + targets first, then commit.
Composition (build it from these skills)
- Card content:
godot-resources / unity-scriptableobjects — define each card as a data asset.
- UI:
game-ui-ux for layout, scaling, and focus navigation; godot-ui-control for hand layout, drag/drop, zone counts, and targeting prompts.
- Persistence/replays:
save-systems for collection, run state (roguelike deckbuilder), and seeded replays.
- Opponent AI:
game-ai for an AI that evaluates playable cards and picks targets.
- Animation/feedback: the engine animation skill for card movement;
audio-design for cues.
- Scripting:
godot-gdscript / unity-csharp-scripting for the effect interpreter.
References
- For the effect queue/stack, keywords/triggers, targeting, deckbuilder vs. constructed
archetypes, and shuffle fairness, read
references/effect-resolution.md.
1---2name: card-game3description: Build a card game: card data, deck/hand/discard zones, draw/shuffle/reshuffle, a turn structure, costs, and effect resolution. Use for a deckbuilder, TCG/CCG, or roguelike deckbuilder.4---5
6# Card Game
7
8A playbook for card games — card data, the deck/hand/discard zones, the turn structure, and
9how card effects resolve. This is a **compositional** skill: it models cards as data and wires
10them to UI. It does not re-teach data assets or UI nodes; it defines the zone model, the draw
11machinery, and the effect-resolution rules that keep a card game correct and bug-free.
12
13## When to use
14
15- Use when building any game where the core objects are **cards** moving between zones
16 (deck → hand → play → discard): deckbuilder, TCG/CCG, solitaire, roguelike deckbuilder.
17- Use when designing draw/shuffle/reshuffle, turn structure, card costs, or how effects resolve.
18
19**When *not* to use:** board/tile state with matching rules → `puzzle`. RPG with an
20incidental card battler → start from `rpg`. For defining cards as assets, use `godot-resources`
21/ `unity-scriptableobjects`; for the hand/drag UI, use `godot-ui-control`.
22
23## Core loop
24
25**Draw to your hand → spend resources to play cards → effects resolve and change the board →
26end the turn (cleanup/discard) → opponent/next phase → repeat until a win condition.** Depth
27comes from the *combinations* a hand allows; the engine's job is to resolve them unambiguously.
28
29## Must-have systems
30
311. **Card data** — id, name, cost, type, text, and an effect spec (data, not code).
322. **Zones** — deck (draw pile), hand, play/board, discard, exile/removed; cards live in exactly one.
333. **Draw + shuffle + reshuffle** — draw from deck to hand; reshuffle discard into deck when empty.
344. **Turn structure** — phases (untap/draw/main/combat/end) as a state machine.
355. **Resource system** — mana/energy/actions that gate how much you do per turn.
366. **Effect resolution** — apply a card's effects in a defined order; handle targets and triggers.
377. **Win/loss condition** — life total, deck-out, objective.
388. **UI** — hand layout, drag/drop or tap-to-play, zone counts, targeting affordances.
39
40## Design knobs
41
42| Knob | Effect | Notes |
43|------|--------|-------|
44| Starting hand / draw-per-turn | tempo, consistency | More draw = less variance. |
45| Hand size limit | hoarding vs. use | Discard down at end of turn. |
46| Deck size (min) | consistency | Smaller = more reliable combos. |
47| Resource curve | what's playable when | "Mana curve" paces power. |
48| Card rarity / power budget | balance | Stronger cards cost more / are rarer. |
49| Determinism vs. randomness | skill vs. swing | Shuffle + random effects add variance. |
50| Reshuffle rules | deck-out, fatigue | Reshuffle discard, or punish empty deck. |
51| Removal / answers | counterplay | Every threat needs an answer in the pool. |
52
53## Patterns
54
55### 1. Zones + draw with automatic reshuffle
56
57```python
58# Pseudocode. A card is in exactly one zone at a time; moving = remove here, add there.
59def draw(n):
60 for _ in range(n):
61 if not deck:
62 if not discard: # truly empty: deck-out (lose, or take fatigue)
63 on_deck_out(); return
64 deck.extend(discard) # reshuffle discard into deck
65 discard.clear()
66 shuffle(deck, rng) # use a seeded RNG (see save-systems for replays)
67 hand.append(deck.pop())
68```
69
70### 2. Card as data + effect resolution
71
72```python
73# Pseudocode. Effects are a data list interpreted by the engine — not bespoke code per card.
74card = {
75 "id": "fireball", "cost": 3, "type": "spell",
76 "effects": [ {"op": "damage", "amount": 6, "target": "chosen_enemy"} ],
77}
78def play(card, caster):
79 if resources[caster] < card.cost: return False # can't afford
80 resources[caster] -= card.cost
81 move(card, from_zone=hand, to_zone=play_or_discard(card))
82 for fx in card.effects:
83 resolve_effect(fx, caster) # one interpreter handles every card
84 return True
85```
86
87### 3. Turn structure as a phase machine
88
89```python
90# Pseudocode. Fixed phases keep timing windows (triggers, priority) unambiguous.
91PHASES = ["untap", "draw", "main", "combat", "end"]
92def take_turn(player):
93 for phase in PHASES:
94 enter_phase(player, phase) # fire "on_phase" triggers here
95 if phase == "draw": draw(1)
96 if phase == "main": await player_plays_cards()
97 if phase == "combat": resolve_combat()
98 if phase == "end": discard_to_hand_limit(player); clear_temporary_effects()
99```
100
101## Pitfalls / failure modes
102
103- **A card existing in two zones at once** → duplication/loss bugs. Enforce "exactly one zone";
104 move = remove-then-add, and assert no card appears twice.
105- **Forgetting to reshuffle** → draws silently fail or crash on empty deck. Reshuffle discard,
106 or define deck-out/fatigue explicitly (Pattern 1).
107- **One function per card** → unmaintainable and untestable. Make effects **data** interpreted
108 by a small set of operations (Pattern 2).
109- **Ambiguous effect order / simultaneous triggers** → nondeterministic outcomes. Resolve in a
110 defined order (a queue or stack); document LIFO vs. FIFO (refs).
111- **Unseeded shuffle in a game that needs replays/undo** → can't reproduce. Use a seeded RNG.
112- **No hand limit / no answers** → degenerate hoarding or unbeatable threats. Add a hand cap and
113 ensure removal exists for every threat archetype.
114- **Targeting state leaks** → a cancelled play leaves the board mid-targeting. Make play
115 atomic: validate cost + targets first, then commit.
116
117## Composition (build it from these skills)
118
119- **Card content:** `godot-resources` / `unity-scriptableobjects` — define each card as a data asset.
120- **UI:** `game-ui-ux` for layout, scaling, and focus navigation; `godot-ui-control` for hand layout, drag/drop, zone counts, and targeting prompts.
121- **Persistence/replays:** `save-systems` for collection, run state (roguelike deckbuilder), and seeded replays.
122- **Opponent AI:** `game-ai` for an AI that evaluates playable cards and picks targets.
123- **Animation/feedback:** the engine animation skill for card movement; `audio-design` for cues.
124- **Scripting:** `godot-gdscript` / `unity-csharp-scripting` for the effect interpreter.
125
126## References
127
128- For the effect queue/stack, keywords/triggers, targeting, deckbuilder vs. constructed
129 archetypes, and shuffle fairness, read `references/effect-resolution.md`.