mantimon-tcg/backend/app/core/config.py
Cal Corum 1123d61067 Extend core card models with rarity tiers and card subtypes
Add CardRarityTier enum for pull rate calculations (common through
crown_rare). Add CardSubtype enum for Pokemon classifications (basic,
stage1, stage2, ex, etc.). Update CardDefinition model with new fields
for subtypes and rarity display.
2026-01-31 15:43:07 -06:00

384 lines
14 KiB
Python

"""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.enums import EnergyType, ModifierMode, 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.
starting_hand_size: Number of cards drawn at game start.
"""
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
starting_hand_size: int = 7
class ActiveConfig(BaseModel):
"""Configuration for active Pokemon slot rules.
Supports standard single-battle (1 active) or double-battle variants
(2 active Pokemon per player).
Attributes:
max_active: Maximum number of Pokemon in the active position.
Default is 1 (standard single battle). Set to 2 for double battles.
"""
max_active: int = 1
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.). 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).
per_knockout_radiant: Points scored for knocking out a Radiant Pokemon (2 points).
per_knockout_prism_star: Points scored for knocking out a Prism Star Pokemon (2 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
per_knockout_radiant: int = 2
per_knockout_prism_star: int = 2
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,
PokemonVariant.RADIANT: self.per_knockout_radiant,
PokemonVariant.PRISM_STAR: self.per_knockout_prism_star,
}
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).
turn_timer_warning_thresholds: Percentage of time remaining to send warnings.
Default [50, 25] means warnings at 50% and 25% remaining.
turn_timer_grace_seconds: Extra seconds granted on reconnection.
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
turn_timer_warning_thresholds: list[int] = Field(default_factory=lambda: [50, 25])
turn_timer_grace_seconds: int = 15
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.
stadium_same_name_replace: If True, a stadium can replace another stadium
with the same name. If False (default), you cannot play a stadium if
a stadium with the same name is already in play. Standard Pokemon TCG
rules prohibit same-name stadium replacement.
"""
supporters_per_turn: int = 1
stadiums_per_turn: int = 1
items_per_turn: int | None = None
tools_per_pokemon: int = 1
stadium_same_name_replace: bool = False
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 CombatConfig(BaseModel):
"""Configuration for combat damage calculations.
Controls how weakness and resistance modify damage. Standard Pokemon TCG uses
multiplicative weakness (x2) and additive resistance (-30), but these can be
customized for house rules or game variants.
Cards can override these defaults via their WeaknessResistance definitions.
Attributes:
weakness_mode: How weakness modifies damage (multiplicative or additive).
weakness_value: Default weakness modifier value.
- For multiplicative: damage * value (e.g., 2 for x2)
- For additive: damage + value (e.g., 20 for +20)
resistance_mode: How resistance modifies damage (multiplicative or additive).
resistance_value: Default resistance modifier value.
- For multiplicative: damage * value (e.g., 0.5 for half damage)
- For additive: damage + value (e.g., -30 for -30 damage)
Examples:
Standard Pokemon TCG (x2 weakness, -30 resistance):
CombatConfig(
weakness_mode=ModifierMode.MULTIPLICATIVE,
weakness_value=2,
resistance_mode=ModifierMode.ADDITIVE,
resistance_value=-30,
)
Additive weakness/resistance (+20/-20):
CombatConfig(
weakness_mode=ModifierMode.ADDITIVE,
weakness_value=20,
resistance_mode=ModifierMode.ADDITIVE,
resistance_value=-20,
)
"""
weakness_mode: ModifierMode = ModifierMode.MULTIPLICATIVE
weakness_value: int = 2
resistance_mode: ModifierMode = ModifierMode.ADDITIVE
resistance_value: int = -30
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.
active: Active Pokemon slot 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.
combat: Combat damage calculation configuration.
"""
deck: DeckConfig = Field(default_factory=DeckConfig)
active: ActiveConfig = Field(default_factory=ActiveConfig)
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)
combat: CombatConfig = Field(default_factory=CombatConfig)
@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,
),
)