commit f473f94bce9cad5cccc36231f227fa5d37e32502 Author: Cal Corum Date: Fri Jan 23 23:41:34 2026 -0600 Initial project setup: documentation and structure - Add PROJECT_PLAN.md with 7-phase development roadmap - Add CLAUDE.md with AI agent coding guidelines - Add docs/ARCHITECTURE.md with technical deep-dive - Add docs/GAME_RULES.md template for home rule definitions - Create directory structure for frontend, backend, shared diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e8b83dd --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,309 @@ +# CLAUDE.md - Mantimon TCG + +Guidelines for AI coding agents working on this project. + +## Quick Reference + +**Frontend dev server**: `cd frontend && npm run dev` +**Backend dev server**: `cd backend && uvicorn app.main:app --reload` +**Run frontend tests**: `cd frontend && npm run test` +**Run backend tests**: `cd backend && python -m pytest` +**Type check frontend**: `cd frontend && npm run typecheck` +**Lint frontend**: `cd frontend && npm run lint` + +## Project Overview + +Mantimon TCG is a home-rule-modified Pokemon Trading Card Game web application featuring: +- Single-player (AI opponents, puzzle mode) +- Multiplayer (real-time matches) +- Collection system (packs, crafting, deck building) + +## Tech Stack + +### Frontend +| Technology | Purpose | +|------------|---------| +| Vue 3 | UI framework (Composition API + ` + + +``` + +### Phaser Scene Structure + +``` +game/ +├── scenes/ +│ ├── MatchScene.ts # Main gameplay +│ ├── PackOpeningScene.ts # Animated pack reveals +│ └── PuzzleScene.ts # Puzzle mode +├── objects/ +│ ├── Card.ts # Card game object +│ ├── Hand.ts # Hand container +│ ├── Battlefield.ts # Active/bench zones +│ ├── Deck.ts # Deck pile with animations +│ └── DamageCounter.ts # Damage display +├── animations/ +│ ├── cardAnimations.ts # Draw, play, flip +│ ├── attackAnimations.ts # Attack effects +│ └── shuffleAnimations.ts # Deck shuffle +└── utils/ + └── tweenHelpers.ts # Common tween patterns +``` + +### State Management (Pinia) + +```typescript +// stores/game.ts +export const useGameStore = defineStore('game', () => { + // State + const gameState = ref(null) + const pendingAction = ref(null) + const socket = ref(null) + + // Getters + const myHand = computed(() => gameState.value?.myHand ?? []) + const isMyTurn = computed(() => + gameState.value?.currentPlayer === gameState.value?.myPlayerId + ) + const canPlayCard = computed(() => + isMyTurn.value && gameState.value?.phase === 'main' + ) + + // Actions + async function connectToGame(gameId: string) { + socket.value = io('/game', { query: { gameId } }) + + socket.value.on('game:state', (state) => { + gameState.value = state + }) + + socket.value.on('game:error', (error) => { + // Handle error + }) + } + + function sendAction(action: Action) { + socket.value?.emit('game:action', action) + } + + return { + gameState, + myHand, + isMyTurn, + canPlayCard, + connectToGame, + sendAction, + } +}) +``` + +--- + +## Backend Architecture + +### Technology Stack + +- **FastAPI** for REST API and WebSocket handling +- **Python 3.11+** with async/await +- **SQLAlchemy 2.0** (async) for ORM +- **PostgreSQL** for persistent storage +- **Redis** for caching and real-time state +- **python-socketio** for WebSocket server +- **Pydantic v2** for validation + +### Module Structure + +``` +backend/ +├── app/ +│ ├── main.py # FastAPI app, Socket.io setup +│ ├── config.py # Settings (pydantic-settings) +│ ├── dependencies.py # Dependency injection +│ │ +│ ├── api/ # REST endpoints +│ │ ├── auth.py # Login, register, OAuth +│ │ ├── cards.py # Card definitions +│ │ ├── collection.py # User collection CRUD +│ │ ├── decks.py # Deck CRUD +│ │ ├── packs.py # Pack opening +│ │ ├── matchmaking.py # Queue management +│ │ └── puzzles.py # Puzzle definitions +│ │ +│ ├── websocket/ # Real-time handlers +│ │ ├── handlers.py # Socket.io event handlers +│ │ ├── connection_manager.py +│ │ └── events.py # Event type definitions +│ │ +│ ├── core/ # Game engine +│ │ ├── game_engine.py # Main orchestrator +│ │ ├── turn_manager.py # Turn/phase state machine +│ │ ├── card_effects.py # Effect resolution +│ │ ├── rules_validator.py # Action legality +│ │ ├── win_conditions.py # Win/loss detection +│ │ ├── state_manager.py # In-memory game state +│ │ └── ai/ # AI opponents +│ │ ├── base.py # AI interface +│ │ ├── easy.py # Random legal moves +│ │ ├── medium.py # Heuristic-based +│ │ └── hard.py # MCTS/minimax +│ │ +│ ├── models/ # Data models +│ │ ├── database.py # SQLAlchemy models +│ │ └── schemas.py # Pydantic schemas +│ │ +│ ├── services/ # Business logic +│ │ ├── auth_service.py +│ │ ├── card_service.py +│ │ ├── collection_service.py +│ │ ├── deck_service.py +│ │ ├── pack_service.py +│ │ ├── game_service.py +│ │ └── puzzle_service.py +│ │ +│ └── database/ # DB utilities +│ ├── session.py # Async session factory +│ └── migrations/ # Alembic migrations +│ +├── tests/ +│ ├── test_game_engine.py +│ ├── test_card_effects.py +│ ├── test_ai.py +│ └── conftest.py +│ +├── pyproject.toml +└── alembic.ini +``` + +### Request Flow + +``` +HTTP Request + │ + v +FastAPI Router ─────────────────────────────┐ + │ │ + v v +Dependency Injection WebSocket Connection +(auth, db session) │ + │ v + v Socket.io Handler +Service Layer │ + │ v + v Game Engine +Repository/ORM (in-memory state) + │ │ + v v +PostgreSQL Redis + (state cache) +``` + +--- + +## Database Schema + +### Entity Relationship Diagram + +``` ++---------------+ +------------------+ +---------------+ +| users | | collections | | cards | ++---------------+ +------------------+ +---------------+ +| id (PK) |<──────| user_id (FK) | | id (PK) | +| username | | card_id (FK) |───────| name | +| email | | quantity | | set_id | +| password_hash | +------------------+ | hp | +| currency | | type | +| created_at | | attacks (JSON)| ++---------------+ | abilities | + │ | rarity | + │ +---------------+ + │ + │ +------------------+ + │ | decks | + │ +------------------+ + └────────>| id (PK) | + | user_id (FK) | + | name | + | cards (JSON) | + | is_valid | + | created_at | + +------------------+ + ++------------------+ +------------------+ +| matches | | match_players | ++------------------+ +------------------+ +| id (PK) |<──────| match_id (FK) | +| status | | user_id (FK) | +| winner_id (FK) | | deck_id (FK) | +| started_at | | result | +| ended_at | +------------------+ +| game_log (JSON) | ++------------------+ + ++------------------+ +------------------+ +| packs | | pack_contents | ++------------------+ +------------------+ +| id (PK) |<──────| pack_id (FK) | +| name | | card_id (FK) | +| cost | | rarity | +| set_id | | weight | ++------------------+ +------------------+ + ++------------------+ +| puzzles | ++------------------+ +| id (PK) | +| name | +| description | +| difficulty | +| initial_state | +| solution | +| hints (JSON) | ++------------------+ +``` + +### Key Models + +```python +# models/database.py +from sqlalchemy import Column, Integer, String, JSON, ForeignKey, DateTime +from sqlalchemy.orm import relationship +from app.database.session import Base + +class User(Base): + __tablename__ = "users" + + id = Column(Integer, primary_key=True) + username = Column(String(50), unique=True, nullable=False) + email = Column(String(255), unique=True, nullable=False) + password_hash = Column(String(255), nullable=False) + currency = Column(Integer, default=0) + created_at = Column(DateTime, server_default=func.now()) + + collections = relationship("Collection", back_populates="user") + decks = relationship("Deck", back_populates="user") + + +class Card(Base): + __tablename__ = "cards" + + id = Column(String(50), primary_key=True) # e.g., "pikachu_base_001" + name = Column(String(100), nullable=False) + set_id = Column(String(50), nullable=False) + card_type = Column(String(20), nullable=False) # pokemon, trainer, energy + hp = Column(Integer, nullable=True) # Only for Pokemon + pokemon_type = Column(String(20), nullable=True) # fire, water, etc. + attacks = Column(JSON, default=[]) + abilities = Column(JSON, default=[]) + weakness = Column(JSON, nullable=True) + resistance = Column(JSON, nullable=True) + retreat_cost = Column(Integer, default=0) + rarity = Column(String(20), nullable=False) + image_url = Column(String(500), nullable=True) + + +class Deck(Base): + __tablename__ = "decks" + + id = Column(Integer, primary_key=True) + user_id = Column(Integer, ForeignKey("users.id"), nullable=False) + name = Column(String(100), nullable=False) + cards = Column(JSON, nullable=False) # [{"card_id": "...", "quantity": 2}, ...] + is_valid = Column(Boolean, default=False) + created_at = Column(DateTime, server_default=func.now()) + updated_at = Column(DateTime, onupdate=func.now()) + + user = relationship("User", back_populates="decks") +``` + +--- + +## Real-Time Communication + +### Socket.io Events + +#### Client -> Server + +| Event | Payload | Description | +|-------|---------|-------------| +| `game:action` | `{ type, ...params }` | Player performs action | +| `game:resign` | `{}` | Player resigns match | +| `matchmaking:join` | `{ deckId }` | Join matchmaking queue | +| `matchmaking:leave` | `{}` | Leave queue | + +#### Server -> Client + +| Event | Payload | Description | +|-------|---------|-------------| +| `game:state` | `VisibleGameState` | Full state update | +| `game:action_result` | `{ success, error? }` | Action confirmation | +| `game:ended` | `{ winner, reason }` | Match concluded | +| `matchmaking:found` | `{ gameId, opponent }` | Match found | + +### Action Types + +```python +class ActionType(str, Enum): + PLAY_POKEMON = "play_pokemon" # Play basic Pokemon to bench + EVOLVE = "evolve" # Evolve a Pokemon + ATTACH_ENERGY = "attach_energy" # Attach energy card + PLAY_TRAINER = "play_trainer" # Play trainer card + USE_ABILITY = "use_ability" # Activate Pokemon ability + ATTACK = "attack" # Declare attack + RETREAT = "retreat" # Retreat active Pokemon + PASS = "pass" # End turn / pass priority +``` + +### State Synchronization + +```python +# websocket/handlers.py +@sio.on('game:action') +async def handle_action(sid, data: dict): + session = await get_session(sid) + game_id = session['game_id'] + player_id = session['player_id'] + + game = await state_manager.get_game(game_id) + + # Validate + result = await game_engine.validate_action(game, player_id, data) + if not result.valid: + await sio.emit('game:action_result', { + 'success': False, + 'error': result.reason + }, to=sid) + return + + # Execute + new_state = await game_engine.execute_action(game, player_id, data) + await state_manager.save_game(game_id, new_state) + + # Broadcast filtered state to each player + for pid, psid in game.player_sockets.items(): + visible_state = game_engine.get_visible_state(new_state, pid) + await sio.emit('game:state', visible_state, to=psid) + + # Check win conditions + winner = game_engine.check_win_condition(new_state) + if winner: + await handle_game_end(game_id, winner) +``` + +--- + +## Game Engine + +### Turn Structure + +``` +GAME START + │ + v +┌─────────────────────────────────┐ +│ SETUP PHASE │ +│ - Draw 7 cards each │ +│ - Place active + bench │ +│ - Set aside 6 prize cards │ +│ - Flip coin for first turn │ +└─────────────────────────────────┘ + │ + v +┌─────────────────────────────────┐ +│ TURN START │◄──────────────────┐ +│ - Draw 1 card │ │ +└─────────────────────────────────┘ │ + │ │ + v │ +┌─────────────────────────────────┐ │ +│ MAIN PHASE (any order) │ │ +│ - Attach 1 energy (once) │ │ +│ - Play basic Pokemon to bench │ │ +│ - Evolve Pokemon │ │ +│ - Play trainer cards │ │ +│ - Use abilities │ │ +│ - Retreat active (once) │ │ +└─────────────────────────────────┘ │ + │ │ + v │ +┌─────────────────────────────────┐ │ +│ ATTACK PHASE (optional) │ │ +│ - Declare attack │ │ +│ - Resolve damage + effects │ │ +│ - Check knockouts │ │ +│ - Take prize cards │ │ +└─────────────────────────────────┘ │ + │ │ + v │ +┌─────────────────────────────────┐ │ +│ END PHASE │ │ +│ - Apply end-of-turn effects │ │ +│ - Switch to opponent │───────────────────┘ +└─────────────────────────────────┘ + │ + v +┌─────────────────────────────────┐ +│ WIN CONDITION CHECK │ +│ - All prizes taken? │ +│ - Opponent has no Pokemon? │ +│ - Opponent can't draw? │ +│ - Custom home rules? │ +└─────────────────────────────────┘ +``` + +### Card Effect System + +Effects are data-driven with pluggable handlers: + +```python +# core/card_effects.py +from typing import Protocol, Callable, Dict, Any + +class EffectContext: + game: GameState + source_card: Card + source_player: str + target_card: Optional[Card] + target_player: Optional[str] + params: Dict[str, Any] + +EffectHandler = Callable[[EffectContext], Awaitable[None]] + +EFFECT_REGISTRY: Dict[str, EffectHandler] = {} + +def effect_handler(name: str): + """Decorator to register effect handlers.""" + def decorator(func: EffectHandler): + EFFECT_REGISTRY[name] = func + return func + return decorator + +# Example effects +@effect_handler("deal_damage") +async def deal_damage(ctx: EffectContext): + amount = ctx.params["amount"] + ctx.target_card.damage += amount + +@effect_handler("heal") +async def heal(ctx: EffectContext): + amount = ctx.params["amount"] + ctx.target_card.damage = max(0, ctx.target_card.damage - amount) + +@effect_handler("apply_status") +async def apply_status(ctx: EffectContext): + status = ctx.params["status"] # paralyzed, poisoned, asleep, etc. + ctx.target_card.status_conditions.append(status) + +@effect_handler("draw_cards") +async def draw_cards(ctx: EffectContext): + count = ctx.params["count"] + player = ctx.game.players[ctx.source_player] + for _ in range(count): + if player.deck: + player.hand.append(player.deck.pop()) + +@effect_handler("coin_flip") +async def coin_flip(ctx: EffectContext): + """Flip coin, execute sub-effect on heads.""" + result = random.choice(["heads", "tails"]) + if result == "heads" and "on_heads" in ctx.params: + sub_effect = ctx.params["on_heads"] + await resolve_effect(ctx.game, sub_effect, ctx) +``` + +--- + +## AI System + +### Architecture + +```python +# core/ai/base.py +from abc import ABC, abstractmethod + +class AIPlayer(ABC): + """Base class for AI opponents.""" + + @abstractmethod + async def choose_action(self, game_state: GameState) -> Action: + """Given the current game state, return the best action.""" + pass + + def get_legal_actions(self, game_state: GameState) -> List[Action]: + """Returns all legal actions for the AI player.""" + actions = [] + player = game_state.players[self.player_id] + + # Can attach energy? + if not game_state.energy_attached_this_turn: + for card in player.hand: + if card.card_type == "energy": + for target in self.get_energy_targets(game_state): + actions.append(AttachEnergyAction(card.id, target.id)) + + # Can play Pokemon? + for card in player.hand: + if card.card_type == "pokemon" and card.stage == "basic": + if len(player.bench) < 5: + actions.append(PlayPokemonAction(card.id)) + + # ... more action types + + actions.append(PassAction()) # Can always pass + return actions +``` + +### Difficulty Implementations + +```python +# core/ai/easy.py +class EasyAI(AIPlayer): + """Random legal moves with basic priorities.""" + + async def choose_action(self, game_state: GameState) -> Action: + actions = self.get_legal_actions(game_state) + + # Slight preference: attack if possible + attacks = [a for a in actions if isinstance(a, AttackAction)] + if attacks and random.random() > 0.3: + return random.choice(attacks) + + return random.choice(actions) + + +# core/ai/medium.py +class MediumAI(AIPlayer): + """Heuristic-based decision making.""" + + async def choose_action(self, game_state: GameState) -> Action: + actions = self.get_legal_actions(game_state) + + # Score each action + scored = [(self.score_action(game_state, a), a) for a in actions] + scored.sort(reverse=True, key=lambda x: x[0]) + + # Pick top action with some randomness + top_actions = [a for s, a in scored[:3]] + return random.choice(top_actions) + + def score_action(self, state: GameState, action: Action) -> float: + score = 0.0 + + if isinstance(action, AttackAction): + # Prefer knockouts + damage = self.calculate_damage(state, action) + target = state.get_defending_pokemon() + if damage >= target.hp - target.damage: + score += 100 # Knockout! + else: + score += damage * 0.5 + + elif isinstance(action, PlayPokemonAction): + # Value bench presence + score += 10 + + # ... more heuristics + + return score + + +# core/ai/hard.py +class HardAI(AIPlayer): + """Monte Carlo Tree Search for strong play.""" + + def __init__(self, player_id: str, simulations: int = 1000): + super().__init__(player_id) + self.simulations = simulations + + async def choose_action(self, game_state: GameState) -> Action: + root = MCTSNode(game_state, None, self.player_id) + + for _ in range(self.simulations): + node = self.select(root) + node = self.expand(node) + result = self.simulate(node) + self.backpropagate(node, result) + + # Return most visited child's action + best_child = max(root.children, key=lambda c: c.visits) + return best_child.action +``` + +--- + +## Security Considerations + +### Hidden Information + +**Never expose to clients:** +- Deck order (either player) +- Opponent's hand contents +- Unrevealed prize cards +- RNG seeds or future random results + +```python +def get_visible_state(game: GameState, player_id: str) -> VisibleGameState: + """Filter game state to only what player can see.""" + opponent_id = get_opponent(game, player_id) + + return VisibleGameState( + # My full information + my_hand=[card.to_dict() for card in game.players[player_id].hand], + my_deck_count=len(game.players[player_id].deck), + my_prizes=[ + card.to_dict() if card.revealed else {"hidden": True} + for card in game.players[player_id].prizes + ], + + # Opponent's hidden information + opponent_hand_count=len(game.players[opponent_id].hand), + opponent_deck_count=len(game.players[opponent_id].deck), + opponent_prizes_remaining=len([ + p for p in game.players[opponent_id].prizes if not p.taken + ]), + + # Public information + battlefield=game.battlefield.to_dict(), + discard_piles={ + pid: [card.to_dict() for card in player.discard] + for pid, player in game.players.items() + }, + current_turn=game.current_turn, + phase=game.phase, + ) +``` + +### Server Authority + +- All game logic runs server-side +- Client sends intentions, server validates and executes +- Never trust client-provided game state +- RNG uses `secrets` module for unpredictability + +### Input Validation + +```python +async def validate_action(game: GameState, player_id: str, action: dict) -> ValidationResult: + # Is it this player's turn? + if game.current_turn != player_id: + return ValidationResult(False, "Not your turn") + + # Is this action type valid for current phase? + action_type = action.get("type") + if action_type not in VALID_ACTIONS_BY_PHASE[game.phase]: + return ValidationResult(False, f"Cannot {action_type} during {game.phase}") + + # Validate specific action requirements + validator = ACTION_VALIDATORS.get(action_type) + if validator: + return await validator(game, player_id, action) + + return ValidationResult(False, "Unknown action type") +``` diff --git a/docs/GAME_RULES.md b/docs/GAME_RULES.md new file mode 100644 index 0000000..83826fc --- /dev/null +++ b/docs/GAME_RULES.md @@ -0,0 +1,324 @@ +# Mantimon TCG - Game Rules + +This document defines the home-rule modifications for Mantimon TCG, based on a custom hybrid of Pokemon TCG eras. + +> **Note**: This is a template. Sections marked with `[TBD]` need to be filled in with your specific rule choices. + +## Table of Contents + +1. [Base Ruleset](#base-ruleset) +2. [Energy System Modifications](#energy-system-modifications) +3. [Deck Building Rules](#deck-building-rules) +4. [Win Conditions](#win-conditions) +5. [Turn Structure](#turn-structure) +6. [Card Types](#card-types) +7. [Status Conditions](#status-conditions) +8. [Glossary](#glossary) + +--- + +## Base Ruleset + +Mantimon TCG uses a **custom hybrid** ruleset, cherry-picking mechanics from multiple Pokemon TCG eras. + +### Era Influences + +| Mechanic | Era Source | Notes | +|----------|------------|-------| +| Basic turn structure | Classic (Base-Neo) | [TBD] | +| Pokemon stages | [TBD] | Basic, Stage 1, Stage 2, and/or EX/V variants? | +| Trainer card types | [TBD] | Item/Supporter/Stadium split, or classic Trainer? | +| Energy types | [TBD] | How many types? Special energy? | + +### What's NOT Included + +List of official mechanics that are explicitly excluded: + +- [TBD] - e.g., "No VMAX or Gigantamax mechanics" +- [TBD] +- [TBD] + +--- + +## Energy System Modifications + +> **Home Rule Focus Area**: Energy attachment rules + +### Standard Rule (for reference) + +In standard Pokemon TCG, you may attach **one energy card** per turn from your hand to one of your Pokemon. + +### Modified Rule + +[TBD] - Describe your energy system changes here. Examples of possible modifications: + +- [ ] **Multiple attachments**: Attach up to X energy per turn +- [ ] **Type-free energy**: All basic energy counts as colorless +- [ ] **Energy acceleration**: Specific cards/abilities that attach extra energy +- [ ] **Energy generation**: Start of turn gain energy tokens instead of cards +- [ ] **Shared energy pool**: Energy attached to a Pokemon can be used by bench +- [ ] **No energy cards**: Attacks cost action points instead + +### Energy Types in Use + +| Type | Symbol | Description | +|------|--------|-------------| +| [TBD] | | | +| [TBD] | | | +| Colorless | | Any energy satisfies colorless requirements | + +### Special Energy + +| Name | Effect | +|------|--------| +| [TBD] | | +| [TBD] | | + +--- + +## Deck Building Rules + +> **Home Rule Focus Area**: Card limits and restrictions + +### Deck Size + +| Rule | Value | +|------|-------| +| Minimum deck size | [TBD] cards | +| Maximum deck size | [TBD] cards | +| Exact size required? | [TBD] Yes/No | + +### Card Limits + +| Card Type | Max Copies | Notes | +|-----------|------------|-------| +| Pokemon (same name) | [TBD] | | +| Trainer cards (same name) | [TBD] | | +| Energy cards (basic, same type) | [TBD] | | +| Energy cards (special, same name) | [TBD] | | + +### Required Cards + +| Requirement | Value | +|-------------|-------| +| Minimum Basic Pokemon | [TBD] | +| Minimum Energy cards | [TBD] | +| Other requirements | [TBD] | + +### Banned/Restricted Cards + +| Card Name | Status | Reason | +|-----------|--------|--------| +| [TBD] | Banned | | +| [TBD] | Limited to 1 | | + +### Format Rotations + +[TBD] - Which card sets are legal? Do sets rotate out? + +| Set Name | Set Code | Status | +|----------|----------|--------| +| [TBD] | | Legal | +| [TBD] | | Banned | + +--- + +## Win Conditions + +> **Home Rule Focus Area**: Victory conditions + +### Standard Win Conditions (for reference) + +1. Take all 6 prize cards +2. Knock out opponent's last Pokemon in play +3. Opponent cannot draw at start of turn + +### Modified Win Conditions + +[TBD] - Describe your win condition changes. Examples: + +- [ ] **Prize count change**: Take [X] prizes instead of 6 +- [ ] **Alternative victory**: Win by [condition] +- [ ] **Removed condition**: [Which standard condition is removed?] +- [ ] **Sudden death**: [Special rules for tiebreakers] + +### Prize Card Rules + +| Rule | Value | +|------|-------| +| Number of prizes | [TBD] | +| Prizes taken per knockout | [TBD] - (Standard: 1 for basic, 2 for EX/V) | +| Prize card selection | [TBD] - Random or chosen? | + +--- + +## Turn Structure + +### Turn Overview + +``` +1. DRAW PHASE + - Draw 1 card from deck + +2. MAIN PHASE (any order, as many times as allowed) + - Attach energy (once per turn) [TBD if modified] + - Play Basic Pokemon to bench + - Evolve Pokemon + - Play Trainer cards + - Use Abilities + - Retreat Active Pokemon (once per turn) + +3. ATTACK PHASE + - Declare attack (ends turn) + - OR pass without attacking + +4. END PHASE + - Apply end-of-turn effects + - Check for knockouts + - Take prize cards if applicable +``` + +### Turn Modifications + +[TBD] - Any changes to the turn structure? + +| Modification | Description | +|--------------|-------------| +| [TBD] | | + +### First Turn Rules + +| Rule | Value | +|------|-------| +| First player draws on turn 1? | [TBD] Yes/No | +| First player can attack turn 1? | [TBD] Yes/No | +| First player can play Supporters turn 1? | [TBD] Yes/No | + +--- + +## Card Types + +### Pokemon Cards + +| Term | Definition | +|------|------------| +| Basic Pokemon | Can be played directly from hand to bench | +| Stage 1 | Evolves from a Basic Pokemon | +| Stage 2 | Evolves from a Stage 1 Pokemon | +| [TBD] | [Any special Pokemon types like EX, V, etc.?] | + +### Evolution Rules + +| Rule | Value | +|------|-------| +| Can evolve same turn played? | No (unless card effect allows) | +| Can evolve same turn as previous evolution? | [TBD] | +| Evolution on first turn of game? | [TBD] | + +### Trainer Cards + +[TBD] - Which trainer card subtypes are in use? + +| Type | Rules | +|------|-------| +| Item | [TBD] - Unlimited per turn? | +| Supporter | [TBD] - One per turn? | +| Stadium | [TBD] - Stays in play? | +| Tool | [TBD] - Attach to Pokemon? | + +### Energy Cards + +| Type | Description | +|------|-------------| +| Basic Energy | Provides 1 energy of its type | +| Special Energy | [TBD] - Custom effects? | + +--- + +## Status Conditions + +### Active Status Conditions + +| Condition | Effect | Removal | +|-----------|--------|---------| +| Poisoned | [TBD] damage between turns | [TBD] | +| Burned | [TBD] damage + flip to continue | [TBD] | +| Asleep | Cannot attack or retreat, flip to wake | [TBD] | +| Paralyzed | Cannot attack or retreat for 1 turn | End of next turn | +| Confused | Flip to attack, damage self on tails | [TBD] | + +### Condition Stacking + +[TBD] - Can multiple conditions be active? Which override others? + +--- + +## Glossary + +| Term | Definition | +|------|------------| +| Active Pokemon | The Pokemon currently in the battle position | +| Bench | Area for up to 5 Pokemon not currently active | +| Discard Pile | Where knocked out Pokemon and used cards go | +| Prize Cards | Cards set aside at game start, taken on knockouts | +| Retreat | Switching active Pokemon with a benched Pokemon | +| [TBD] | [Add game-specific terms] | + +--- + +## Changelog + +Track rule changes over time: + +| Date | Change | Reason | +|------|--------|--------| +| [Date] | Initial rules document created | Project start | +| | | | + +--- + +## Notes for Implementation + +This section is for developer reference when implementing rules in code. + +### Rule Configuration Schema + +Rules should be configurable via a JSON/YAML file for easy iteration: + +```json +{ + "deck": { + "min_size": 60, + "max_size": 60, + "max_copies_per_card": 4, + "min_basic_pokemon": 1 + }, + "prizes": { + "count": 6, + "per_knockout_basic": 1, + "per_knockout_ex": 2 + }, + "energy": { + "attachments_per_turn": 1 + }, + "first_turn": { + "can_draw": true, + "can_attack": false, + "can_play_supporter": false + }, + "win_conditions": { + "all_prizes_taken": true, + "no_pokemon_in_play": true, + "cannot_draw": true + } +} +``` + +### Validation Hooks + +When implementing rules engine, create validation hooks for: + +- `validate_deck(deck) -> ValidationResult` +- `validate_action(game_state, action) -> ValidationResult` +- `check_win_conditions(game_state) -> Optional[WinResult]` +- `calculate_prizes_for_knockout(pokemon) -> int`