- Rename project instructions file to Claude Code standard name - Add .gitignore to exclude IDE files and frontend-poc/ Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
12 KiB
Mantimon TCG
Guidelines for agentic coding agents working on this codebase.
Project Overview
Mantimon TCG is a home-rule-modified Pokemon Trading Card Game web application inspired by the Gameboy Color game Pokemon TCG. The core experience is a single-player RPG campaign:
- Campaign Mode: Challenge NPCs at themed clubs, defeat Club Leaders to earn medals, collect all medals to face Grand Masters and become Champion
- Collection Building: Win matches to earn booster packs, build your card collection
- Deck Building: Construct decks from your collection to take on tougher opponents
- Multiplayer (Optional): PvP matches for competitive play
Tech Stack
Frontend
| Technology | Purpose |
|---|---|
| Vue 3 | UI framework (Composition API + <script setup>) |
| Phaser 3 | Game canvas (matches, pack opening) |
| TypeScript | Type safety |
| Pinia | State management |
| Tailwind CSS | Styling |
| Socket.io-client | Real-time communication |
| Vite | Build tool |
Backend
| Technology | Purpose |
|---|---|
| FastAPI | REST API framework |
| Python 3.11+ | Backend language |
| SQLAlchemy 2.0 | ORM (async) |
| PostgreSQL | Database |
| Redis | Caching, session storage |
| python-socketio | WebSocket server |
| Pydantic v2 | Validation |
| Alembic | Database migrations |
Quick Commands
Development Servers
cd frontend && npm run dev # Frontend dev server
cd backend && uv run uvicorn app.main:app --reload # Backend dev server
Testing
# Frontend (Vitest)
cd frontend && npm run test # Run all tests
cd frontend && npm run test -- path/to/file # Run single test file
cd frontend && npm run test -- -t "test name" # Run by test name
# Backend (pytest with uv)
cd backend && uv run pytest # Run all tests
cd backend && uv run pytest tests/test_file.py # Single file
cd backend && uv run pytest tests/test_file.py::test_fn # Single test
cd backend && uv run pytest -k "test_name" # By name pattern
cd backend && uv run pytest -x # Stop on first failure
cd backend && uv run pytest --cov=app # With coverage
Linting & Formatting
# Frontend
cd frontend && npm run lint # ESLint
cd frontend && npm run typecheck # TypeScript check
# Backend (uses uv for all commands)
cd backend && uv run black app tests # Format with Black
cd backend && uv run black --check . # Check formatting (CI)
cd backend && uv run ruff check . # Lint with Ruff
cd backend && uv run ruff check --fix . # Auto-fix lint issues
cd backend && uv run mypy app # Type check
Dependency Management (Backend)
cd backend && uv add <package> # Add runtime dependency
cd backend && uv add --dev <package> # Add dev dependency
cd backend && uv sync # Install all dependencies
cd backend && uv lock # Update lock file
Code Style
General Rules
- Line length: 100 characters max
- Indentation: 2 spaces (frontend), 4 spaces (backend)
- Trailing commas in multi-line structures
- Explicit over implicit
TypeScript/Vue
Import order (separated by blank lines):
- Standard library / Vue core
- Third-party packages
- Local imports (use
@/alias)
import { ref, computed } from 'vue'
import { useGameStore } from '@/stores/game'
import type { Card, GameState } from '@/types'
// Always use `import type` for type-only imports
import type { Player } from '@/types/player'
// Prefer const over let
const cards = ref<Card[]>([])
Naming conventions:
| Type | Convention | Example |
|---|---|---|
| Vue components | PascalCase | CardHand.vue, GameBoard.vue |
| Phaser scenes | PascalCase | MatchScene.ts, PackOpeningScene.ts |
| TypeScript files | camelCase | useWebSocket.ts, cardUtils.ts |
| Constants | UPPER_SNAKE_CASE | MAX_HAND_SIZE, PRIZE_COUNT |
Python
Import order (separated by blank lines):
- Standard library
- Third-party packages
- Local imports
from typing import Optional
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from app.models import Card, Player
from app.services.game_service import GameService
Type hints required for all function signatures:
async def get_card(card_id: int, db: AsyncSession = Depends(get_db)) -> Card:
...
Naming conventions:
| Type | Convention | Example |
|---|---|---|
| Modules | snake_case | game_engine.py, card_service.py |
| Database tables | snake_case | user_collections, match_history |
| Constants | UPPER_SNAKE_CASE | MAX_HAND_SIZE, PRIZE_COUNT |
Architecture Patterns
Frontend: Vue + Phaser Integration
Phaser mounts as a Vue component. Communication via event bridge:
// Vue -> Phaser
phaserGame.value?.events.emit('card:play', { cardId, targetId })
// Phaser -> Vue
phaserGame.value?.events.on('animation:complete', handleAnimationComplete)
Frontend: State Management (Pinia)
export const useGameStore = defineStore('game', () => {
const gameState = ref<GameState | null>(null)
const myHand = computed(() => gameState.value?.myHand ?? [])
return { gameState, myHand }
})
Backend: Service Layer
Never bypass services for business logic:
# CORRECT
card = await card_service.get_card(card_id)
result = await game_service.play_card(game_id, player_id, card_id)
# WRONG - direct DB access in endpoint
card = await db.execute(select(Card).where(Card.id == card_id))
Backend: Async by Default
All I/O operations use async/await:
async def resolve_attack(attacker: Card, defender: Card) -> AttackResult:
...
Backend: WebSocket Events
# All game actions go through WebSocket for real-time sync
@sio.on('game:action')
async def handle_game_action(sid, data):
action_type = data['type']
# Validate action is legal
validation = await game_engine.validate_action(game_id, player_id, data)
if not validation.valid:
await sio.emit('game:error', {'message': validation.reason}, to=sid)
return
# Execute and broadcast
new_state = await game_engine.execute_action(game_id, data)
await broadcast_game_state(game_id, new_state)
Game Engine Patterns
Card Effect System
Cards are data-driven. Effects reference handler functions:
# Card definition (JSON/DB)
{
"id": "pikachu_base_001",
"name": "Pikachu",
"hp": 60,
"type": "lightning",
"attacks": [
{
"name": "Thunder Shock",
"cost": ["lightning"],
"damage": 20,
"effect": "may_paralyze", # References effect handler
"effect_params": {"chance": 0.5}
}
]
}
# Effect handler (Python)
@effect_handler("may_paralyze")
async def handle_may_paralyze(context: EffectContext, params: dict) -> None:
if random.random() < params["chance"]:
context.defender.add_status("paralyzed")
Turn State Machine
class TurnPhase(Enum):
DRAW = "draw"
MAIN = "main"
ATTACK = "attack"
END = "end"
# Transitions are explicit
VALID_TRANSITIONS = {
TurnPhase.DRAW: [TurnPhase.MAIN],
TurnPhase.MAIN: [TurnPhase.ATTACK, TurnPhase.END], # Can skip attack
TurnPhase.ATTACK: [TurnPhase.END],
TurnPhase.END: [TurnPhase.DRAW], # Next player's turn
}
Testing Guidelines
Test Docstrings Required
Every test must include a docstring explaining "what" and "why":
@pytest.mark.asyncio
async def test_draw_card():
"""
Test that drawing a card moves it from deck to hand.
Verifies the fundamental draw mechanic works correctly
and updates both zones appropriately.
"""
# test implementation
Frontend Tests (Vitest)
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
describe('CardHand', () => {
it('renders cards in hand', () => {
const wrapper = mount(CardHand, {
props: { cards: [{ id: '1', name: 'Pikachu' }] }
})
expect(wrapper.text()).toContain('Pikachu')
})
})
Critical Security Rules
Hidden Information
Never expose to clients:
- Deck order (either player)
- Opponent's hand contents
- Unrevealed prize cards
- RNG seeds or future random results
# Correct: Only send counts for opponent's hidden zones
opponent_hand_count=len(opponent.hand) # ONLY count, not contents
opponent_deck_count=len(opponent.deck) # ONLY count
Server Authority
- All game logic runs server-side
- Client sends intentions, server validates and executes
- Never trust client-provided game state
Critical Rules Summary
- Git: Never commit directly to
main. Create feature branches. - Commits: Do not commit without user approval.
- Hidden Info: Never send deck contents, opponent hand, or unrevealed prizes to client.
- Validation: Always validate actions server-side. Never trust client.
- Tests: Include docstrings explaining "what" and "why" for each test.
- Phaser in Vue: Keep Phaser scenes focused on rendering. Game logic lives in backend.
- Services: Never bypass the service layer for business logic.
- Core Engine Independence: Keep
app/core/decoupled from DB/network (see below).
Core Engine Independence (Offline Fork Support)
Long-term goal: The
backend/app/core/module should remain forkable as a standalone offline game.
The game engine must stay completely decoupled from network and database concerns to enable a future offline/standalone version of the RPG campaign mode.
Rules for app/core/ Module
| DO | DON'T |
|---|---|
Accept CardDefinition objects as parameters |
Import from app.services or app.api |
Use RandomProvider protocol for RNG |
Import database session types |
Keep state self-contained in GameState |
Make network calls or database queries |
| Use sync logic (async wrappers at service layer) | Require authentication or user sessions |
Load configuration from RulesConfig objects |
Hard-code database connection strings |
Import Boundaries
# ALLOWED in app/core/
from app.core.models import CardDefinition, GameState
from app.core.config import RulesConfig
from app.core.rng import RandomProvider
# FORBIDDEN in app/core/
from app.services.card_service import CardService # NO - DB dependency
from app.api.deps import get_current_user # NO - Auth dependency
from sqlalchemy.ext.asyncio import AsyncSession # NO - DB dependency
Why This Matters
See docs/ARCHITECTURE.md#offline-standalone-fork for full details. The core engine should be directly copyable to a standalone Python application with:
- Card definitions loaded from JSON files
- Save data stored locally
- No network or authentication requirements
Project Structure
mantimon-tcg/
├── frontend/ # Vue 3 + Phaser 3
│ ├── src/
│ │ ├── components/ # Vue components
│ │ ├── pages/ # Route pages
│ │ ├── stores/ # Pinia stores
│ │ ├── game/ # Phaser scenes and game objects
│ │ └── composables/ # Vue composables
│ └── package.json
├── backend/ # FastAPI + PostgreSQL
│ ├── app/
│ │ ├── api/ # REST endpoints
│ │ ├── core/ # Game engine
│ │ ├── models/ # Pydantic + SQLAlchemy models
│ │ ├── services/ # Business logic
│ │ └── websocket/ # Socket.io handlers
│ └── pyproject.toml
└── shared/ # Shared types/schemas