"""Game rules configuration for Mantimon TCG. This module defines the master configuration system for all game rules. The engine is highly configurable to support both campaign mode (with fixed rules) and free play mode (with user-adjustable rules). Default values are based on the Mantimon TCG house rules documented in GAME_RULES.md, which use a Pokemon Pocket-inspired energy system with a 40-card main deck and separate 20-card energy deck. Usage: # Use default rules rules = RulesConfig() # Customize specific rules rules = RulesConfig( deck=DeckConfig(min_size=60, max_size=60), prizes=PrizeConfig(count=6), ) # Load from JSON rules = RulesConfig.model_validate_json(json_string) """ from pydantic import BaseModel, Field from app.core.models.enums import EnergyType, PokemonVariant class DeckConfig(BaseModel): """Configuration for deck building rules. Attributes: min_size: Minimum number of cards in the main deck. max_size: Maximum number of cards in the main deck. exact_size_required: If True, deck must be exactly min_size cards. max_copies_per_card: Maximum copies of any single card (by name). max_copies_basic_energy: Max copies of basic energy in energy deck. None means unlimited. min_basic_pokemon: Minimum number of Basic Pokemon required. energy_deck_enabled: If True, use separate energy deck (Pokemon Pocket style). energy_deck_size: Size of the separate energy deck. """ min_size: int = 40 max_size: int = 40 exact_size_required: bool = True max_copies_per_card: int = 4 max_copies_basic_energy: int | None = None min_basic_pokemon: int = 1 energy_deck_enabled: bool = True energy_deck_size: int = 20 class BenchConfig(BaseModel): """Configuration for bench rules. Attributes: max_size: Maximum number of Pokemon on the bench. """ max_size: int = 5 class EnergyConfig(BaseModel): """Configuration for energy attachment rules. Attributes: attachments_per_turn: Number of energy cards that can be attached per turn. types_enabled: List of energy types available in this game. auto_flip_from_deck: If True, flip top card of energy deck at turn start (Pokemon Pocket style). """ attachments_per_turn: int = 1 types_enabled: list[EnergyType] = Field(default_factory=lambda: list(EnergyType)) auto_flip_from_deck: bool = True class PrizeConfig(BaseModel): """Configuration for prize/scoring rules. In core Mantimon TCG rules, "prizes" are replaced with "points" - players score points instead of taking prize cards. This simplifies the game while maintaining the knockout scoring mechanic. Knockout points are determined by the Pokemon's variant (EX, V, GX, etc.), not by its evolution stage. A Basic EX is worth the same as a Stage 2 EX. Attributes: count: Number of points needed to win (or prize cards if using classic rules). per_knockout_normal: Points for knocking out a normal (non-variant) Pokemon. per_knockout_ex: Points scored for knocking out an EX Pokemon. per_knockout_gx: Points scored for knocking out a GX Pokemon. per_knockout_v: Points scored for knocking out a V Pokemon. per_knockout_vmax: Points scored for knocking out a VMAX Pokemon. per_knockout_vstar: Points scored for knocking out a VSTAR Pokemon (3 points). use_prize_cards: If True, use classic prize card mechanic instead of points. prize_selection_random: If True, prize cards are taken randomly (classic). If False, player chooses which prize to take. """ count: int = 4 per_knockout_normal: int = 1 per_knockout_ex: int = 2 per_knockout_gx: int = 2 per_knockout_v: int = 2 per_knockout_vmax: int = 3 per_knockout_vstar: int = 3 use_prize_cards: bool = False prize_selection_random: bool = True def points_for_knockout(self, variant: PokemonVariant) -> int: """Get the number of points scored for knocking out a Pokemon of the given variant. Args: variant: The PokemonVariant of the knocked out Pokemon. Returns: Number of points to score. """ variant_map = { PokemonVariant.NORMAL: self.per_knockout_normal, PokemonVariant.EX: self.per_knockout_ex, PokemonVariant.GX: self.per_knockout_gx, PokemonVariant.V: self.per_knockout_v, PokemonVariant.VMAX: self.per_knockout_vmax, PokemonVariant.VSTAR: self.per_knockout_vstar, } return variant_map.get(variant, self.per_knockout_normal) class FirstTurnConfig(BaseModel): """Configuration for first turn restrictions. These rules apply only to the very first turn of the game (turn 1 for player 1). Attributes: can_draw: Whether the first player draws a card on turn 1. can_attack: Whether the first player can attack on turn 1. can_play_supporter: Whether the first player can play Supporter cards on turn 1. can_attach_energy: Whether the first player can attach energy on turn 1. can_evolve: Whether the first player can evolve Pokemon on turn 1. """ can_draw: bool = True can_attack: bool = True can_play_supporter: bool = True can_attach_energy: bool = False can_evolve: bool = False class WinConditionsConfig(BaseModel): """Configuration for win/loss conditions. Each condition can be enabled or disabled independently. A player wins when any enabled win condition is met. Attributes: all_prizes_taken: Win when a player scores the required number of points. no_pokemon_in_play: Win when opponent has no Pokemon in play. cannot_draw: Win when opponent cannot draw a card at turn start. turn_limit_enabled: Enable maximum turn count (useful for AI matches). turn_limit: Maximum number of turns before game ends. Each player's turn counts as one turn (so 30 = 15 turns per player). turn_timer_enabled: Enable per-turn time limits (multiplayer). turn_timer_seconds: Seconds per turn before timeout (default 90). game_timer_enabled: Enable total game time limit (multiplayer). game_timer_minutes: Total game time in minutes. """ all_prizes_taken: bool = True no_pokemon_in_play: bool = True cannot_draw: bool = True turn_limit_enabled: bool = True turn_limit: int = 30 turn_timer_enabled: bool = False turn_timer_seconds: int = 90 game_timer_enabled: bool = False game_timer_minutes: int = 30 class StatusConfig(BaseModel): """Configuration for status condition effects. Defines the damage values and removal mechanics for each status condition. Attributes: poison_damage: Damage dealt by Poison between turns. burn_damage: Damage dealt by Burn between turns. burn_flip_to_remove: If True, flip coin between turns; heads removes Burn. sleep_flip_to_wake: If True, flip coin between turns; heads removes Sleep. confusion_self_damage: Damage dealt to self on failed confusion flip. """ poison_damage: int = 10 burn_damage: int = 20 burn_flip_to_remove: bool = True sleep_flip_to_wake: bool = True confusion_self_damage: int = 30 class TrainerConfig(BaseModel): """Configuration for Trainer card rules. Attributes: supporters_per_turn: Maximum Supporter cards playable per turn. stadiums_per_turn: Maximum Stadium cards playable per turn. items_per_turn: Maximum Item cards per turn. None means unlimited. tools_per_pokemon: Maximum Tool cards attachable to one Pokemon. """ supporters_per_turn: int = 1 stadiums_per_turn: int = 1 items_per_turn: int | None = None tools_per_pokemon: int = 1 class EvolutionConfig(BaseModel): """Configuration for evolution rules. Attributes: same_turn_as_played: Can evolve a Pokemon the same turn it was played. same_turn_as_evolution: Can evolve a Pokemon the same turn it evolved. first_turn_of_game: Can evolve on the very first turn of the game. """ same_turn_as_played: bool = False same_turn_as_evolution: bool = False first_turn_of_game: bool = False class RetreatConfig(BaseModel): """Configuration for retreat rules. Attributes: retreats_per_turn: Maximum number of retreats allowed per turn. free_retreat_cost: If True, retreating doesn't require discarding energy. """ retreats_per_turn: int = 1 free_retreat_cost: bool = False class RulesConfig(BaseModel): """Master configuration for all game rules. This is the top-level configuration object that contains all rule settings. Default values are based on Mantimon TCG house rules (Pokemon Pocket-inspired with 40-card decks, separate energy deck, and 4 points to win). For standard Pokemon TCG rules, override with: RulesConfig( deck=DeckConfig(min_size=60, max_size=60, energy_deck_enabled=False), prizes=PrizeConfig(count=6, use_prize_cards=True), first_turn=FirstTurnConfig(can_attack=False, can_attach_energy=True), ) Attributes: deck: Deck building configuration. bench: Bench configuration. energy: Energy attachment configuration. prizes: Prize/scoring configuration. first_turn: First turn restrictions. win_conditions: Win/loss condition configuration. status: Status condition effect configuration. trainer: Trainer card rule configuration. evolution: Evolution rule configuration. retreat: Retreat rule configuration. """ deck: DeckConfig = Field(default_factory=DeckConfig) bench: BenchConfig = Field(default_factory=BenchConfig) energy: EnergyConfig = Field(default_factory=EnergyConfig) prizes: PrizeConfig = Field(default_factory=PrizeConfig) first_turn: FirstTurnConfig = Field(default_factory=FirstTurnConfig) win_conditions: WinConditionsConfig = Field(default_factory=WinConditionsConfig) status: StatusConfig = Field(default_factory=StatusConfig) trainer: TrainerConfig = Field(default_factory=TrainerConfig) evolution: EvolutionConfig = Field(default_factory=EvolutionConfig) retreat: RetreatConfig = Field(default_factory=RetreatConfig) @classmethod def standard_pokemon_tcg(cls) -> "RulesConfig": """Create a configuration approximating standard Pokemon TCG rules. Returns: RulesConfig with settings closer to official Pokemon TCG. """ return cls( deck=DeckConfig( min_size=60, max_size=60, energy_deck_enabled=False, ), prizes=PrizeConfig( count=6, use_prize_cards=True, ), first_turn=FirstTurnConfig( can_attack=False, can_play_supporter=False, can_attach_energy=True, ), )