"""Starter deck definitions for Mantimon TCG. This module defines the 5 starter decks available to new players: - Grass: Bulbasaur, Caterpie, and Bellsprout evolution lines - Fire: Charmander, Growlithe, and Ponyta lines - Water: Squirtle, Poliwag, and Horsea lines - Psychic: Abra, Gastly, and Drowzee lines - Lightning: Pikachu, Magnemite, and Voltorb lines Each deck contains exactly 40 Pokemon/Trainer cards + 20 energy cards, following Mantimon TCG house rules. Deck composition philosophy: - 3 evolution lines (4 cards each for basics, 3-4 for evolutions) - Basic support trainers for card draw and healing - Type-specific energy (14) + colorless (6) Usage: from app.data.starter_decks import get_starter_deck, STARTER_TYPES deck = get_starter_deck("grass") cards = deck["cards"] # {card_id: quantity} energy = deck["energy_cards"] # {type: quantity} """ from typing import Protocol, TypedDict class DeckSizeConfig(Protocol): """Protocol for deck size configuration. Defines the minimal interface needed for deck validation. Any object with these attributes (like DeckConfig) satisfies this protocol. """ min_size: int energy_deck_size: int class StarterDeckDefinition(TypedDict): """Type definition for starter deck structure.""" name: str description: str cards: dict[str, int] energy_cards: dict[str, int] # Available starter deck types STARTER_TYPES: list[str] = ["grass", "fire", "water", "psychic", "lightning"] # Classic starter types (always available) CLASSIC_STARTERS: list[str] = ["grass", "fire", "water"] # Rotating starter types (may change seasonally) ROTATING_STARTERS: list[str] = ["psychic", "lightning"] STARTER_DECKS: dict[str, StarterDeckDefinition] = { "grass": { "name": "Forest Guardian", "description": "A balanced Grass deck featuring Bulbasaur, Caterpie, and Bellsprout evolution lines.", "cards": { # Bulbasaur line (11 cards) "a1-001-bulbasaur": 4, "a1-002-ivysaur": 3, "a1-003-venusaur": 2, # Caterpie line (9 cards) "a1-005-caterpie": 4, "a1-006-metapod": 3, "a1-007-butterfree": 2, # Bellsprout line (9 cards) "a1-018-bellsprout": 4, "a1-019-weepinbell": 3, "a1-020-victreebel": 2, # Tangela (2 cards) - extra basics "a1-024-tangela": 2, # Trainers (11 cards) "a1-219-erika": 2, "a1-216-helix-fossil": 2, "a1-217-dome-fossil": 2, "a1-218-old-amber": 2, "a1-224-brock": 3, }, "energy_cards": { "grass": 14, "colorless": 6, }, }, "fire": { "name": "Inferno Blaze", "description": "An aggressive Fire deck featuring Charmander, Growlithe, and Ponyta evolution lines.", "cards": { # Charmander line (9 cards) "a1-033-charmander": 4, "a1-034-charmeleon": 3, "a1-035-charizard": 2, # Growlithe line (6 cards) "a1-039-growlithe": 4, "a1-040-arcanine": 2, # Ponyta line (7 cards) "a1-042-ponyta": 4, "a1-043-rapidash": 3, # Vulpix (3 cards) - extra basics "a1-037-vulpix": 3, # Magmar (4 cards) - extra basics "a1-044-magmar": 4, # Trainers (11 cards) "a1-221-blaine": 3, "a1-216-helix-fossil": 2, "a1-217-dome-fossil": 2, "a1-218-old-amber": 2, "a1-224-brock": 2, }, "energy_cards": { "fire": 14, "colorless": 6, }, }, "water": { "name": "Tidal Wave", "description": "A versatile Water deck featuring Squirtle, Poliwag, and Horsea evolution lines.", "cards": { # Squirtle line (9 cards) "a1-053-squirtle": 4, "a1-054-wartortle": 3, "a1-055-blastoise": 2, # Poliwag line (9 cards) "a1-059-poliwag": 4, "a1-060-poliwhirl": 3, "a1-061-poliwrath": 2, # Horsea line (6 cards) "a1-070-horsea": 4, "a1-071-seadra": 2, # Seel line (5 cards) - extra "a1-064-seel": 3, "a1-065-dewgong": 2, # Trainers (11 cards) "a1-220-misty": 3, "a1-216-helix-fossil": 2, "a1-217-dome-fossil": 2, "a1-218-old-amber": 2, "a1-224-brock": 2, }, "energy_cards": { "water": 14, "colorless": 6, }, }, "psychic": { "name": "Mind Over Matter", "description": "A control-focused Psychic deck featuring Abra, Gastly, and Drowzee evolution lines.", "cards": { # Abra line (9 cards) "a1-115-abra": 4, "a1-116-kadabra": 3, "a1-117-alakazam": 2, # Gastly line (9 cards) "a1-120-gastly": 4, "a1-121-haunter": 3, "a1-122-gengar": 2, # Drowzee line (6 cards) "a1-124-drowzee": 4, "a1-125-hypno": 2, # Slowpoke line (5 cards) - extra "a1-118-slowpoke": 3, "a1-119-slowbro": 2, # Trainers (11 cards) "a1-225-sabrina": 3, "a1-216-helix-fossil": 2, "a1-217-dome-fossil": 2, "a1-218-old-amber": 2, "a1-224-brock": 2, }, "energy_cards": { "psychic": 14, "colorless": 6, }, }, "lightning": { "name": "Thunder Strike", "description": "A fast Lightning deck featuring Pikachu, Magnemite, and Voltorb evolution lines.", "cards": { # Pikachu line (7 cards) "a1-094-pikachu": 4, "a1-095-raichu": 3, # Magnemite line (7 cards) "a1-097-magnemite": 4, "a1-098-magneton": 3, # Voltorb line (7 cards) "a1-099-voltorb": 4, "a1-100-electrode": 3, # Electabuzz (4 cards) - extra basics "a1-101-electabuzz": 4, # Blitzle line (4 cards) "a1-105-blitzle": 2, "a1-106-zebstrika": 2, # Trainers (11 cards) "a1-226-lt-surge": 3, "a1-216-helix-fossil": 2, "a1-217-dome-fossil": 2, "a1-218-old-amber": 2, "a1-224-brock": 2, }, "energy_cards": { "lightning": 14, "colorless": 6, }, }, } def get_starter_deck(starter_type: str) -> StarterDeckDefinition: """Get a starter deck definition by type. Args: starter_type: One of grass, fire, water, psychic, lightning. Returns: StarterDeckDefinition with name, description, cards, and energy_cards. Raises: ValueError: If starter_type is not valid. Example: deck = get_starter_deck("grass") print(f"Deck: {deck['name']}") print(f"Total cards: {sum(deck['cards'].values())}") """ if starter_type not in STARTER_DECKS: raise ValueError( f"Invalid starter type: {starter_type}. " f"Must be one of: {', '.join(STARTER_TYPES)}" ) return STARTER_DECKS[starter_type] def validate_starter_decks(config: DeckSizeConfig) -> dict[str, list[str]]: """Validate all starter deck definitions against config rules. Checks that each deck has the correct number of cards and energy as defined by the provided configuration. Args: config: Configuration providing min_size and energy_deck_size. Typically a DeckConfig instance, but any object satisfying the DeckSizeConfig protocol works. Returns: Dictionary mapping deck type to list of validation errors. Empty dict if all decks are valid. Example: from app.core.config import DeckConfig config = DeckConfig() errors = validate_starter_decks(config) if errors: for deck_type, deck_errors in errors.items(): print(f"{deck_type}: {deck_errors}") """ errors: dict[str, list[str]] = {} for deck_type, deck in STARTER_DECKS.items(): deck_errors: list[str] = [] # Check card count against config total_cards = sum(deck["cards"].values()) if total_cards != config.min_size: deck_errors.append(f"Expected {config.min_size} cards, got {total_cards}") # Check energy count against config total_energy = sum(deck["energy_cards"].values()) if total_energy != config.energy_deck_size: deck_errors.append(f"Expected {config.energy_deck_size} energy, got {total_energy}") if deck_errors: errors[deck_type] = deck_errors return errors def get_starter_card_ids(starter_type: str) -> list[str]: """Get list of all card IDs in a starter deck. Args: starter_type: One of grass, fire, water, psychic, lightning. Returns: List of card IDs (without quantities). Example: card_ids = get_starter_card_ids("grass") # ["a1-001-bulbasaur", "a1-002-ivysaur", ...] """ deck = get_starter_deck(starter_type) return list(deck["cards"].keys())