Zurück zu Skills
gamedev-skills/awesome-gamedev-agent-skillsPrüfung bestanden

SKILL DETAIL

card-game

gamedev-skills/awesome-gamedev-agent-skills/card-game

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.

Installationen · 241Quelle ansehen

Installation

npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill card-game

Skill-Dateien

SKILL.md

Zuletzt synchronisiert · 24.10.2026

references/effect-resolution.md›
# Card effect resolution and structure (depth)

Detail behind `SKILL.md`. Engine-neutral pseudocode. Models how card effects resolve, how
triggers stack, and how deck construction shapes the game.

## 1. Effects as data + an interpreter

Define a small vocabulary of **operations**; every card is a list of them. Adding a card means
adding data, not code. This keeps cards testable and prevents per-card spaghetti.

```python
# A handful of ops cover most card games. Extend deliberately.
OPS = {
    "damage":     lambda fx, ctx: deal_damage(ctx.target(fx), fx["amount"]),
    "heal":       lambda fx, ctx: heal(ctx.target(fx), fx["amount"]),
    "draw":       lambda fx, ctx: ctx.owner.draw(fx["amount"]),
    "gain_res":   lambda fx, ctx: add_resource(ctx.owner, fx["amount"]),
    "summon":     lambda fx, ctx: summon(ctx.owner, fx["unit_id"]),
    "buff":       lambda fx, ctx: add_modifier(ctx.target(fx), fx["mod"]),
    "destroy":    lambda fx, ctx: move_to_discard(ctx.target(fx)),
}
def resolve_effect(fx, ctx):
    OPS[fx["op"]](fx, ctx)
```

## 2. The resolution queue / stack

When effects can trigger other effects (e.g. "when a creature dies, draw a card"), resolve
through an explicit structure so order is deterministic:

- **Queue (FIFO):** effects resolve in the order they were added. Intuitive for simple games.
- **Stack (LIFO):** the most recently added effect resolves first — this is how trading card
  games model "responses" (the last thing said happens first). Choose one and document it.

```python
# Stack model (pseudocode): pushing during resolution lets responses resolve first.
stack = []
def push_effect(fx, ctx): stack.append((fx, ctx))
def resolve_stack():
    while stack:
        fx, ctx = stack.pop()          # LIFO: last in resolves first
        resolve_effect(fx, ctx)        # may push more (triggers) -> they resolve next
```

Decide simultaneity rules up front: when several triggers fire at once, order them by a fixed
rule (active player first, then by timestamp) so outcomes are reproducible.

## 3. Triggers and keywords

- **Triggers:** "when X happens, do Y" (on-play, on-death, on-draw, start/end of turn). Register
  listeners by event; when the event fires, push the triggered effects onto the queue/stack.
- **Keywords:** named, reusable abilities (e.g. taunt/guard, lifesteal, poison/DoT, shield).
  Implement each once as a modifier or trigger and tag cards with it — never re-implement per card.

```python
# Event bus (pseudocode): triggers subscribe; effects fire when the event is published.
on("creature_died", lambda ev: [push_effect(t, ctx_for(t)) for t in triggers_for("on_death", ev)])
```

## 4. Targeting

- Resolve targets at the moment of resolution (or at play, per your rules) and **validate** them
  — a target may have left the zone or died before the effect resolves; define "fizzle" behavior.
- Make playing a card **atomic**: validate cost + legal targets first, then pay and commit. A
  cancelled or illegal play must leave zones exactly as they were.

## 5. Deckbuilder vs. constructed

| | Constructed (TCG/CCG) | Deckbuilder (in-run) |
|---|---|---|
| Deck built | Before the game, from a collection | During play (draft/buy cards) |
| Variance | Mulligans, draw order | Reshuffle of a growing deck each "shuffle" |
| Power growth | Fixed deck | Deck thins/grows; combos emerge mid-run |
| Persistence | Collection + decklists | Run state (often roguelike: see `roguelike`) |

In a roguelike deckbuilder, the deck *is* the build: adding/removing cards mid-run is the core
progression. Keep run state separate from any meta-collection (see `save-systems`).

## 6. Shuffle fairness and consistency

- Use a correct **Fisher–Yates** shuffle (engine RNG); naive "sort by random" is biased.
- Seed the RNG if you want reproducible runs, replays, or deterministic undo.
- For digital games, players perceive true-random as "streaky". Some games use a *pity* /
  smoothing system (e.g. bound how long before a needed resource appears). Decide intentionally;
  document it so balance math accounts for it.

```python
def shuffle(cards, rng):                 # Fisher–Yates
    for i in range(len(cards) - 1, 0, -1):
        j = rng.range(0, i + 1)          # 0..i inclusive
        cards[i], cards[j] = cards[j], cards[i]
```

## 7. Mana / resource curves

- The **resource curve** (how much you can spend by turn N) paces power. Cheap cards early,
  expensive payoffs later.
- A deck's **cost distribution** (its "curve") determines consistency: too top-heavy and you
  can't act early; too cheap and you run out of gas. Balance card power against cost so a
  higher cost reliably buys more impact, with rarer cards bending the rule.
SKILL.md›
---
name: card-game
description: >
  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.
---

# 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

1. **Card data** — id, name, cost, type, text, and an effect spec (data, not code).
2. **Zones** — deck (draw pile), hand, play/board, discard, exile/removed; cards live in exactly one.
3. **Draw + shuffle + reshuffle** — draw from deck to hand; reshuffle discard into deck when empty.
4. **Turn structure** — phases (untap/draw/main/combat/end) as a state machine.
5. **Resource system** — mana/energy/actions that gate how much you do per turn.
6. **Effect resolution** — apply a card's effects in a defined order; handle targets and triggers.
7. **Win/loss condition** — life total, deck-out, objective.
8. **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

```python
# 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

```python
# 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

```python
# 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`.