mantimon-tcg/.claude/frontend-poc/README.md
2026-01-31 22:06:10 -06:00

6.4 KiB

Frontend POC - F3 Phaser Demo

This is a proof-of-concept implementation of the Mantimon TCG frontend through Phase F3 (Phaser Integration). It demonstrates Vue 3 + Phaser 3 integration with real card data.

What This POC Demonstrates

  • Vue-Phaser Integration: Bidirectional communication between Vue components and Phaser scenes
  • Card Rendering: Real card images with type-colored borders and damage counters
  • State Synchronization: Game state flows from Vue to Phaser via the gameBridge
  • Interactive Cards: Click and hover events propagate from Phaser back to Vue

Running the Demo

cd .claude/frontend-poc
npm install
npm run dev

Navigate to /demo after logging in (requires auth).

Key Lessons Learned

1. Scene Initialization Timing

Problem: PhaserGame.vue was calling createGame(container) without passing scenes, resulting in an empty canvas.

Solution: Import and pass the scenes array:

import { createGame, scenes } from '@/game'
game.value = createGame(container.value, scenes)

File: src/components/game/PhaserGame.vue

2. Event Bridge Timing (Critical)

Problem: DemoPage listened for PhaserGame's Vue ready event (Phaser core ready), but this fires BEFORE MatchScene's create() runs. When state was sent, MatchScene hadn't subscribed to events yet.

Solution: Listen for the gameBridge's ready event instead, which MatchScene emits at the END of create() after subscribing to events:

// WRONG - fires too early
function handlePhaserReady(game: Phaser.Game): void {
  emitToBridge('state:updated', gameState.value) // MatchScene not ready!
}

// CORRECT - wait for MatchScene
onMounted(() => {
  onBridgeEvent('ready', handleSceneReady)
})

function handleSceneReady(): void {
  emitToBridge('state:updated', gameState.value) // MatchScene is subscribed
}

Files: src/pages/DemoPage.vue, src/game/scenes/MatchScene.ts

3. Card Image Loading

Problem: Card definitions have image_path field (e.g., "a1/094-pikachu.webp") but the loader was constructing paths from set_id and set_number.

Solution: Add loadCardImageFromPath() function that uses image_path directly:

// Card.ts - prefer image_path
if (this.cardDefinition.image_path) {
  loadedKey = await loadCardImageFromPath(
    this.scene,
    textureKey,
    this.cardDefinition.image_path
  )
}

Files: src/game/assets/loader.ts, src/game/objects/Card.ts

4. Asset Base URL

Card images are served from /game/cards/{image_path}:

  • Card back: /game/cards/card_back.webp
  • Pokemon: /game/cards/a1/094-pikachu.webp
  • Energy: /game/cards/basic/lightning.webp

File: src/game/assets/manifest.ts (ASSET_BASE_URL = '/game')

Layout Issues (For Phase F4)

The demo revealed several layout issues that need fixing:

  1. Opponent cards upside down - Rotation not applied correctly for opponent's side
  2. Cards overlapping in zones - Zone arrangement logic needs work
  3. Hand cards cut off - Hand zone extends beyond viewport
  4. Zone positioning - Active/bench/pile positions need adjustment
  5. Card sizes - May need responsive sizing based on viewport

Architecture Overview

Vue Component (DemoPage/GamePage)
    |
    | emits 'state:updated'
    v
gameBridge (mitt event emitter)
    |
    | subscribed in create()
    v
MatchScene (Phaser Scene)
    |
    | delegates rendering
    v
StateRenderer
    |
    | creates/updates
    v
Zone objects (ActiveZone, BenchZone, HandZone, etc.)
    |
    | contain
    v
Card objects (with image loading, damage counters)

Key Files

File Purpose
src/game/bridge.ts GameBridge class - typed event emitter for Vue-Phaser communication
src/composables/useGameBridge.ts Vue composable with auto-cleanup
src/game/scenes/MatchScene.ts Main Phaser scene - subscribes to bridge events
src/game/sync/StateRenderer.ts Converts VisibleGameState to Phaser objects
src/game/objects/Card.ts Card game object with image loading
src/game/objects/Zone.ts Base zone class for card containers
src/game/layout.ts Board layout calculations

State Flow

  1. Vue initializes state (mockGameState.ts or from WebSocket)
  2. Vue emits to bridge: emitToBridge('state:updated', gameState)
  3. MatchScene receives: Handler calls stateRenderer.render(state)
  4. StateRenderer processes:
    • Creates zones if needed
    • Updates zone positions from layout
    • Creates/updates Card objects from state
    • Sets cards face-up/face-down based on visibility
  5. User interacts with card (click/hover in Phaser)
  6. Card emits to bridge: gameBridge.emit('card:clicked', {...})
  7. Vue receives: Handler updates UI (selected card, etc.)

Type Definitions

Key types are in src/types/game.ts:

interface VisibleGameState {
  game_id: string
  viewer_id: string
  players: Record<string, VisiblePlayerState>
  card_registry: Record<string, CardDefinition>
  phase: TurnPhase
  is_my_turn: boolean
  // ...
}

interface CardDefinition {
  id: string
  name: string
  card_type: 'pokemon' | 'trainer' | 'energy'
  image_path?: string  // Added for image loading
  // ...
}

interface CardInstance {
  instance_id: string
  definition_id: string
  damage: number
  attached_energy: CardInstance[]
  // ...
}

Testing the Demo

The demo page (/demo) provides:

  • Debug Panel: Shows zone counts, game state, selected card
  • Controls:
    • Next Phase - cycles through turn phases
    • Toggle Turn - switches between player/opponent turn
    • +10 Damage buttons - adds damage to active Pokemon
    • Reset State - returns to initial state

What's NOT in This POC

  • Real WebSocket connection (uses mock state)
  • Game actions (play card, attack, etc.)
  • Animations for card movement
  • Proper responsive scaling
  • Prize card reveal
  • Stadium cards
  • Energy attachment display on cards

Recommendations for True Game Page

  1. Keep the bridge pattern - It works well for decoupling Vue and Phaser
  2. Fix layout before adding features - Get zone positions right first
  3. Add animation system - Cards should animate when moving between zones
  4. Implement action validation UI - Highlight valid targets before confirming
  5. Consider zone click handling - For dropping cards, not just card clicks
  6. Test on mobile early - Touch targets and scaling will need tuning