Skip to content

Instantly share code, notes, and snippets.

@esco
Created June 9, 2025 17:44
Show Gist options
  • Save esco/f0e81730bc25523976b14cc498b0194f to your computer and use it in GitHub Desktop.
Save esco/f0e81730bc25523976b14cc498b0194f to your computer and use it in GitHub Desktop.
Guidance provided to Claude Code CLI for building yugioh game engine with prompts

Card Effect System Documentation

1. Introduction

This document describes the architecture and workflow of the card effect system within the Yu-Gi-Oh! Web Duel Interface. The system is responsible for handling the activation, chaining, and resolution of card effects according to Yu-Gi-Oh! rules. It integrates declarative effect definitions (partially implemented via YAML/JSON) with the core game engine logic.

The system aims to manage:

  • Activation: Determining when and if a card effect can be activated.
  • Chaining: Building a sequence of effects when multiple effects are activated in response to each other.
  • Resolution: Executing the effects in the correct (Last-In, First-Out) order.
  • Targeting & Conditions: Handling player choices for targets and verifying activation conditions.
  • State Updates: Ensuring the game state is correctly modified as effects resolve.

2. Core Concepts

  • Effect: A specific action or sequence of actions performed by a card (e.g., "Draw 2 cards", "Destroy 1 monster"). Effects are defined declaratively (partially via YAML/JSON).
  • Trigger: An event or condition in the game that allows a card effect to be activated (e.g., onSummon, onAttackDeclaration, onActivation).
  • Condition: A prerequisite that must be met for an effect to be activated or resolved (e.g., IsPhase: Main1, HasCards: GraveyardPlayer).
  • Targeting: The process of selecting specific cards, zones, or players that an effect will interact with. Can be Manual (player selection) or Automatic.
  • Action: A basic operation performed by an effect (e.g., MoveCard, DestroyCards, ModifyLifePoints, DrawCards).
  • Chain: A sequence of card effects activated in response to each other. Effects on the chain resolve in reverse order of activation (LIFO).
  • Spell Speed: A rule determining which effects can respond to others on a chain (Spell Speed 1 < Spell Speed 2 < Spell Speed 3).
  • Resolution: The process of executing an effect's actions after any chaining is complete.

3. Key Components & Responsibilities

  • GameEngine (src/engine/GameEngine.ts):
    • The central authority for game state and rules.
    • Receives actions (like ACTIVATE_EFFECT) via its dispatch method.
    • Validates actions using RuleValidator.
    • Processes valid actions, often delegating to specialized handlers (ChainResolver, EffectExecutor).
    • Manages the core game loop (phases, turns).
    • Emits STATE_PATCH events reflecting authoritative state changes.
    • Emits other domain-specific events (e.g., ATTACK_TARGETS, CHAIN_LINK_ADDED, EFFECT_RESOLVED).
  • ChainResolver (src/engine/ChainResolver.ts):
    • Manages the building and resolution of chains.
    • Tracks chain links, player priority, and chain status (Idle, ChainBuilding, WaitingForResponse, ChainResolving).
    • Calls the EffectExecutor to resolve each link in LIFO order.
    • Emits chain-related events (CHAIN_LINK_ADDED, CHAIN_RESOLVING, CHAIN_RESOLVED).
  • EffectExecutor (src/engine/EffectExecutor.ts):
    • Orchestrates the execution of a single card effect.
    • Retrieves effect definitions via CardEffectRegistry.
    • Handles effect steps sequentially: condition checking, targeting, action execution.
    • Uses PromptManager for player interactions (targeting).
    • Uses ActionRegistry, FilterRegistry, ConditionRegistry to execute declarative steps.
    • Provides an EngineAPI for actions to interact back with the engine (e.g., queueing state changes or dispatching further actions).
  • CardEffectRegistry (src/engine/CardEffectRegistry.ts):
    • Central point for retrieving effect definitions for a given card ID/GUID.
    • Interfaces with CardEffectLoader to load definitions as needed.
  • CardEffectLoader (src/engine/cardEffects/CardEffectLoader.ts):
    • Loads card effect definitions (currently from JSON files generated from YAML) on demand.
    • Uses CardEffectIndex to map card IDs to filenames.
    • Caches loaded effects.
  • EffectContext (src/engine/effects/EffectContext.ts):
    • Provides contextual information during an effect's execution (activating player, card, targets, chain link number, etc.).
    • Holds references to the engine for state queries.
  • Registries (src/engine/effects/*Registry.ts):
    • ActionRegistry: Contains functions implementing basic effect actions (MoveCard, DestroyCards, etc.).
    • ConditionRegistry: Contains functions implementing effect conditions.
    • FilterRegistry: Contains functions implementing target filters.
  • PromptManager (src/engine/effects/PromptManager.ts):
    • Handles requests for player input during effect resolution (e.g., selecting targets, choosing options).
    • Interacts with the GameEngine to emit prompt events (PROMPT_PLAYER_ACTION).
  • UI Components (src/components/):
    • GameBoardUI, Zone, HandDisplay: Capture user interactions (clicks, drags) that trigger effect activations.
    • PromptHandler: Listens for prompt events and displays UI prompts.
    • ChainStack: Visualizes the current chain based on store updates.
    • CardInfoPanel: Displays details of hovered cards.
  • Stores (src/store/):
    • GameStore: Maintains the primary game state for React components, updated mainly via STATE_PATCH events from the GameEngine. Handles resolving prompts initiated by the engine.
    • UIStore: Manages UI-specific state (highlights, animations, toasts). Processes domain events from the engine for visual feedback.

4. Effect Activation Flow

  1. User Interaction: The player interacts with a card (e.g., clicks "Activate" on a card in a Zone, drags a Spell from HandDisplay to a Zone).
  2. Action Dispatch: The UI component (Zone.tsx or GameBoardUI.tsx via useGameInterface) calls dispatch on the GameEngine with an ACTIVATE_EFFECT action, including playerId, cardId, potentially zoneId (if activating to the field), and guid (passcode for effect lookup).
  3. Engine Validation: GameEngine.validateAction checks basic rules (correct phase, player turn, etc.). If invalid, an ACTION_REJECTED event is emitted.
  4. Engine Processing: GameEngine.processAction handles the ACTIVATE_EFFECT action.
    • If activating from hand to field (e.g., a Spell), it may first queue/dispatch a MOVE_CARD action.
    • It calls Actions.activateEffect to generate the EFFECT_ACTIVATED domain event.
    • Crucially, it initiates the chain process by calling chainResolver.startChain.
  5. Chain Initiation: ChainResolver.startChain:
    • Creates the first ChainLink using details from the EFFECT_ACTIVATED event (cardId, playerId, effectId/guid, targets).
    • Adds the link to its internal chainLinks array.
    • Sets its status to ChainBuilding.
    • Sets priority to the opponent.
    • Emits a CHAIN_LINK_ADDED event via the GameEngine.
  6. Event Propagation: The GameEngine emits EFFECT_ACTIVATED and CHAIN_LINK_ADDED.
  7. Store Updates:
    • UIStore receives CHAIN_LINK_ADDED and updates the visual ChainStack. It might also trigger pulsation/highlight via EFFECT_ACTIVATED.
    • GameStore receives the events but primarily updates its internal chainState based on STATE_PATCH events (which the engine should also emit based on the chain state change, though this link isn't explicit in the provided code but is implied by the architecture).
  8. Priority & Response: The engine, guided by the ChainResolver, now needs to prompt the opponent (or the next player in priority) to respond. This leads to the Chain Building flow.

5. Chain Building & Response Flow

  1. Prompt for Response: After a chain link is added, the GameEngine (likely triggered by ChainResolver state changes or explicit calls) uses the PromptManager to request a response from the player with priority.
  2. Prompt Emission: PromptManager.requestChainResponse (or similar) emits a PROMPT_PLAYER_ACTION or CHAIN_RESPONSE_AVAILABLE event via the GameEngine.
  3. UI Prompt:
    • GameStore receives the prompt event and updates its state (isWaiting, waitingPrompt, currentPrompt).
    • PromptHandler component renders the prompt (e.g., "Respond with an effect?" Yes/No or card selection).
  4. Player Response: The user interacts with the prompt.
  5. Resolve Prompt: The UI calls GameStore.resolvePrompt, providing the user's choice ('Pass' or 'Activate' with card details).
  6. Prompt Resolution: GameStore.resolvePrompt calls PromptManager.resolvePrompt/resolveChainResponse, which fulfills the promise the engine was waiting on.
  7. Engine Receives Response: The GameEngine receives the resolved response.
  8. Dispatch Response Action: The engine dispatches a RESPOND_TO_CHAIN action.
  9. Process Response Action: GameEngine.processAction handles RESPOND_TO_CHAIN.
    • If 'Pass': Calls chainResolver.passPriority.
      • If the other player had already passed, chainResolver.resolveChain is called (Go to Resolution Flow).
      • If this is the first pass, priority shifts to the other player, and the process repeats from Step 1 for that player.
    • If 'Activate': Calls chainResolver.addLink with the new card's details.
      • A new CHAIN_LINK_ADDED event is emitted.
      • Priority shifts to the other player.
      • The process repeats from Step 1 for the other player.

6. Effect Resolution Flow

  1. Trigger Resolution: ChainResolver.resolveChain is called when both players consecutively pass priority.
  2. Status Update: ChainResolver sets status to ChainResolving, emits CHAIN_RESOLVING (start) event.
  3. Process Links (LIFO): ChainResolver.processChainLinks starts processing the chainLinks array in reverse order.
  4. For Each Link:
    • Emit CHAIN_RESOLVING event for the specific link number.
    • Create EffectContext for the link (includes cardId, playerId, targets, link number).
    • Call effectExecutor.executeCardEffect with the card identifier (GUID/passcode or ID) and context.
  5. Execute Effect: EffectExecutor.executeCardEffect:
    • Uses CardEffectRegistry.getEffectsForCard to load the effect definition (using CardEffectLoader if needed).
    • Iterates through the effects steps defined in the loaded effect JSON/YAML.
  6. Execute Step: EffectExecutor.executeEffectStep:
    • Conditions: Checks step conditions using ConditionRegistry. If fail, skips step.
    • Targeting: If targeting exists, calls EffectExecutor.executeTargeting.
      • Gets eligible targets using FilterRegistry.
      • If Manual, uses PromptManager.requestPrompt to ask the player to select targets.
      • Stores selected targets in the EffectContext. If targeting fails (e.g., not enough valid targets, player cancels), the effect step may fail.
    • Actions: Executes each action sequentially using ActionRegistry.getAction.
  7. Execute Action: The specific ActionFunction (e.g., moveCardAction, destroyCardsAction) is called with params, context, and api.
  8. Engine API Interaction: The ActionFunction uses the EngineAPI methods (e.g., api.requestMoveCard).
  9. Engine Queues/Dispatches: The EngineAPI methods either queue state changes or, more likely based on the code, dispatch new actions (e.g., MOVE_CARD, MODIFY_LP) back to the GameEngine.
  10. Inner Dispatch Loop: The GameEngine receives and processes these new actions (MOVE_CARD, etc.), generating domain events (CARD_MOVED, etc.) and corresponding STATE_PATCH events. These are emitted immediately.
  11. Effect Step Completion: Control returns to EffectExecutor after all actions in a step complete. It moves to the next step or finishes the effect.
  12. Link Resolution: Control returns to ChainResolver after effectExecutor.executeCardEffect finishes.
  13. Effect Resolved Event: ChainResolver emits EFFECT_RESOLVED via the GameEngine.
  14. Post-Resolution Handling: GameEngine.handleEffectResolvedEvent checks if the resolved card (e.g., a Normal Spell) should be sent to the Graveyard and dispatches a MOVE_CARD action after a delay if necessary.
  15. Next Link: ChainResolver.processChainLinks waits briefly and proceeds to the next link (next highest number).
  16. Chain Completion: After all links are processed, ChainResolver.processChainLinks resolves.
  17. Chain Resolved Event: ChainResolver emits CHAIN_RESOLVED event via GameEngine, resets its state to Idle.

7. State Changes Sequence (Simplified Example: Player activates Pot of Greed)

  1. Action: User clicks "Activate" on Pot of Greed in hand -> dispatch({ type: 'ACTIVATE_EFFECT', payload: { cardId: 'pog-1', zoneId: 'player-spell-0', guid: '55144522' } })
  2. Engine: Validates action. Processes ACTIVATE_EFFECT.
  3. Engine: Queues/Dispatches MOVE_CARD (Hand -> Field Zone player-spell-0).
  4. Engine: Processes MOVE_CARD, generates CARD_MOVED event and STATE_PATCH for the move. Emits both.
  5. GameStore/UIStore: Receive CARD_MOVED, STATE_PATCH. UIStore triggers animation. GameStore updates state. Card appears face-up in player-spell-0.
  6. Engine: Calls Actions.activateEffect, generates EFFECT_ACTIVATED event.
  7. Engine: Calls chainResolver.startChain.
  8. ChainResolver: Creates Chain Link 1 (Pot of Greed). Sets status ChainBuilding. Priority to opponent. Emits CHAIN_LINK_ADDED via Engine.
  9. Engine: Emits EFFECT_ACTIVATED, CHAIN_LINK_ADDED.
  10. Stores: UIStore updates ChainStack.
  11. Engine: Prompts opponent to respond (via PromptManager). Emits PROMPT_PLAYER_ACTION.
  12. AI/Player: Opponent chooses 'Pass'.
  13. Engine: dispatch({ type: 'RESPOND_TO_CHAIN', payload: { responseType: 'Pass', playerId: 'opponent' } }).
  14. Engine: Calls chainResolver.passPriority. Priority shifts to player.
  15. Engine: Prompts player to respond. Emits PROMPT_PLAYER_ACTION.
  16. UI/Player: Player chooses 'Pass'. UI calls GameStore.resolvePrompt.
  17. Engine: dispatch({ type: 'RESPOND_TO_CHAIN', payload: { responseType: 'Pass', playerId: 'player' } }).
  18. Engine: Calls chainResolver.passPriority. Both passed -> calls chainResolver.resolveChain.
  19. ChainResolver: Sets status ChainResolving. Emits CHAIN_RESOLVING (start). Starts processChainLinks.
  20. ChainResolver: Processes Link 1 (Pot of Greed). Emits CHAIN_RESOLVING (link 1). Calls effectExecutor.executeCardEffect('55144522', context).
  21. EffectExecutor: Loads Pot of Greed effect (drawCards action, count 2). Executes the action.
  22. Action (drawCardsAction): Uses api.requestDrawCard('player', 2).
  23. EngineAPI: Dispatches DRAW_CARD action back to the engine.
  24. Engine: Processes DRAW_CARD. Generates CARD_DRAWN event and STATE_PATCH to update deck/hand. Emits both.
  25. GameStore/UIStore: Receive CARD_DRAWN, STATE_PATCH. Update state. UIStore may animate card draw.
  26. EffectExecutor: Effect step completes.
  27. ChainResolver: Receives completion from executor. Emits EFFECT_RESOLVED via Engine.
  28. Engine: Handles EFFECT_RESOLVED. Pot of Greed is a Normal Spell, so dispatches MOVE_CARD (Field Zone -> Graveyard) after a delay.
  29. ChainResolver: processChainLinks finishes. Emits CHAIN_RESOLVED via Engine. Resets state to Idle.
  30. Engine: (After delay) Processes MOVE_CARD for Pot of Greed -> Graveyard. Generates CARD_MOVED and STATE_PATCH. Emits both.
  31. GameStore/UIStore: Receive events, update state, animate card going to GY.

8. YAML/JSON Effect Loading

  • The system uses dynamic imports (import()) managed by CardEffectLoader.
  • CardEffectIndex maps card identifiers (like passcodes '55144522') to the base filename of their JSON definition (e.g., 'PotOfGreed').
  • When EffectExecutor needs an effect, it asks CardEffectRegistry, which uses the CardEffectLoader to load the corresponding JSON file from src/engine/cardEffects/json/ if not already cached.
  • (Implicitly) These JSON files are assumed to be generated/compiled from the YAML definitions during a build step, although the compiler itself is not fully implemented in the provided code.
  • The GameEngine attempts to preload effects for cards in the initial decks using CardEffectRegistry.initializeCardEffects when the INITIALIZE_GAME action occurs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment