#!/usr/bin/env python """Interactive game walkthrough demonstrating the Mantimon TCG engine. Usage: cd backend && uv run python references/game_walkthrough.py This script creates a new game from scratch, narrates each step, and allows the user to progress interactively through game setup and a simulated turn. Press Enter to continue through each step, or 'q' to quit. """ # ============================================================================= # PATH SETUP # ============================================================================= import sys from pathlib import Path backend_dir = Path(__file__).resolve().parent.parent if str(backend_dir) not in sys.path: sys.path.insert(0, str(backend_dir)) # ============================================================================= # IMPORTS # ============================================================================= import asyncio # Register effect handlers import app.core.effects.handlers # noqa: F401 from app.core import ( CardType, EnergyType, GameEngine, GameState, ModifierMode, PokemonStage, RulesConfig, TrainerType, TurnPhase, create_rng, ) from app.core.models import ( AttachEnergyAction, Attack, AttackAction, CardDefinition, CardInstance, WeaknessResistance, ) # ============================================================================= # TERMINAL COLORS # ============================================================================= class Colors: """ANSI color codes for terminal output.""" HEADER = "\033[95m" BLUE = "\033[94m" CYAN = "\033[96m" GREEN = "\033[92m" YELLOW = "\033[93m" RED = "\033[91m" BOLD = "\033[1m" UNDERLINE = "\033[4m" END = "\033[0m" def header(text: str) -> str: """Format text as a header.""" return f"{Colors.BOLD}{Colors.HEADER}{text}{Colors.END}" def success(text: str) -> str: """Format text as success (green).""" return f"{Colors.GREEN}{text}{Colors.END}" def info(text: str) -> str: """Format text as info (cyan).""" return f"{Colors.CYAN}{text}{Colors.END}" def warning(text: str) -> str: """Format text as warning (yellow).""" return f"{Colors.YELLOW}{text}{Colors.END}" def error(text: str) -> str: """Format text as error (red).""" return f"{Colors.RED}{text}{Colors.END}" def bold(text: str) -> str: """Format text as bold.""" return f"{Colors.BOLD}{text}{Colors.END}" # ============================================================================= # DISPLAY HELPERS # ============================================================================= def clear_screen(): """Clear the terminal screen.""" print("\033[2J\033[H", end="") def print_divider(char: str = "=", width: int = 70): """Print a divider line.""" print(char * width) def wait_for_continue() -> bool: """Wait for user to press Enter. Returns False if user wants to quit.""" try: response = input(f"\n{info('[Press Enter to continue, or q to quit]')} ") return response.lower() != "q" except (KeyboardInterrupt, EOFError): return False def print_narration(text: str): """Print narration text with formatting.""" print(f"\n{Colors.CYAN}{text}{Colors.END}") def print_action(text: str): """Print an action being taken.""" print(f" {Colors.YELLOW}>>> {text}{Colors.END}") def print_result(text: str): """Print the result of an action.""" print(f" {Colors.GREEN} {text}{Colors.END}") def show_game_state(game: GameState, engine: GameEngine): """Display the current game state in a nice format.""" print_divider() print(header("GAME STATE")) print_divider() print( f"\n{bold('Turn:')} {game.turn_number} | {bold('Phase:')} {game.phase.value} | {bold('Current Player:')} {game.current_player_id}" ) for player_id in game.turn_order: player = game.players[player_id] is_current = player_id == game.current_player_id marker = " *" if is_current else "" print(f"\n{bold(f'Player: {player_id}{marker}')}") print(f" Score: {player.score} / {game.rules.prizes.count} to win") # Active Pokemon active = player.get_active_pokemon() if active: card_def = game.get_card_definition(active.definition_id) if card_def: max_hp = card_def.hp + active.hp_modifier current_hp = max_hp - active.damage status = ", ".join(s.value for s in active.status_conditions) or "none" energy = len(active.attached_energy) print( f" {bold('Active:')} {card_def.name} ({current_hp}/{max_hp} HP, {energy} energy, status: {status})" ) else: print(f" {bold('Active:')} {warning('None!')}") # Bench bench_names = [] for card in player.bench.cards: card_def = game.get_card_definition(card.definition_id) if card_def: max_hp = card_def.hp + card.hp_modifier current_hp = max_hp - card.damage bench_names.append(f"{card_def.name} ({current_hp}/{max_hp})") bench_str = ", ".join(bench_names) if bench_names else "empty" print(f" {bold('Bench:')} {bench_str} ({len(player.bench)}/{game.rules.bench.max_size})") # Hand and Deck print( f" {bold('Hand:')} {len(player.hand)} cards | {bold('Deck:')} {len(player.deck)} cards | {bold('Energy Zone:')} {len(player.energy_zone)} cards" ) print() print_divider() def show_card(card_def: CardDefinition, indent: str = " "): """Display a card definition.""" print(f"{indent}{bold(card_def.name)} ({card_def.card_type.value})") if card_def.card_type == CardType.POKEMON: print( f"{indent} Stage: {card_def.stage.value if card_def.stage else 'N/A'}, HP: {card_def.hp}" ) print(f"{indent} Type: {card_def.pokemon_type.value if card_def.pokemon_type else 'N/A'}") if card_def.attacks: print(f"{indent} Attacks:") for attack in card_def.attacks: cost = ", ".join(e.value for e in attack.cost) if attack.cost else "free" print(f"{indent} - {attack.name}: {attack.damage} damage (cost: {cost})") if card_def.weakness: print(f"{indent} Weakness: {card_def.weakness.energy_type.value}") if card_def.resistance: print(f"{indent} Resistance: {card_def.resistance.energy_type.value}") print(f"{indent} Retreat Cost: {card_def.retreat_cost}") elif card_def.card_type == CardType.TRAINER: print(f"{indent} Type: {card_def.trainer_type.value if card_def.trainer_type else 'N/A'}") if card_def.effect_description: print(f"{indent} Effect: {card_def.effect_description}") elif card_def.card_type == CardType.ENERGY: provides = ( ", ".join(e.value for e in card_def.energy_provides) if card_def.energy_provides else "N/A" ) print(f"{indent} Provides: {provides}") # ============================================================================= # CARD DEFINITIONS # ============================================================================= def create_card_definitions() -> dict[str, CardDefinition]: """Create all card definitions for the demo game.""" cards = {} # === POKEMON CARDS === # Pikachu - Basic Lightning cards["pikachu-001"] = CardDefinition( id="pikachu-001", name="Pikachu", card_type=CardType.POKEMON, stage=PokemonStage.BASIC, hp=60, pokemon_type=EnergyType.LIGHTNING, attacks=[ Attack( name="Gnaw", cost=[], damage=10, ), Attack( name="Thunder Shock", cost=[EnergyType.LIGHTNING], damage=30, effect_id="coin_flip_damage", effect_params={"base_damage": 30, "bonus_on_heads": 0, "apply_paralysis": True}, effect_description="Flip a coin. If heads, the Defending Pokemon is now Paralyzed.", ), ], weakness=WeaknessResistance(energy_type=EnergyType.FIGHTING), retreat_cost=1, ) # Raichu - Stage 1 Lightning (evolves from Pikachu) cards["raichu-001"] = CardDefinition( id="raichu-001", name="Raichu", card_type=CardType.POKEMON, stage=PokemonStage.STAGE_1, evolves_from="Pikachu", hp=100, pokemon_type=EnergyType.LIGHTNING, attacks=[ Attack( name="Thunder Punch", cost=[EnergyType.LIGHTNING], damage=50, ), Attack( name="Thunderbolt", cost=[EnergyType.LIGHTNING, EnergyType.LIGHTNING, EnergyType.COLORLESS], damage=100, effect_id="discard_energy", effect_params={"count": "all"}, effect_description="Discard all Energy attached to this Pokemon.", ), ], weakness=WeaknessResistance(energy_type=EnergyType.FIGHTING), retreat_cost=1, ) # Charmander - Basic Fire cards["charmander-001"] = CardDefinition( id="charmander-001", name="Charmander", card_type=CardType.POKEMON, stage=PokemonStage.BASIC, hp=70, pokemon_type=EnergyType.FIRE, attacks=[ Attack( name="Scratch", cost=[], damage=10, ), Attack( name="Ember", cost=[EnergyType.FIRE], damage=40, effect_id="discard_energy", effect_params={"count": 1, "energy_type": "fire"}, effect_description="Discard 1 Fire Energy attached to this Pokemon.", ), ], weakness=WeaknessResistance(energy_type=EnergyType.WATER), retreat_cost=1, ) # Squirtle - Basic Water cards["squirtle-001"] = CardDefinition( id="squirtle-001", name="Squirtle", card_type=CardType.POKEMON, stage=PokemonStage.BASIC, hp=60, pokemon_type=EnergyType.WATER, attacks=[ Attack( name="Bubble", cost=[EnergyType.WATER], damage=20, ), Attack( name="Water Gun", cost=[EnergyType.WATER, EnergyType.COLORLESS], damage=40, ), ], weakness=WeaknessResistance(energy_type=EnergyType.LIGHTNING), retreat_cost=1, ) # Bulbasaur - Basic Grass cards["bulbasaur-001"] = CardDefinition( id="bulbasaur-001", name="Bulbasaur", card_type=CardType.POKEMON, stage=PokemonStage.BASIC, hp=70, pokemon_type=EnergyType.GRASS, attacks=[ Attack( name="Vine Whip", cost=[EnergyType.GRASS], damage=30, ), Attack( name="Leech Seed", cost=[EnergyType.GRASS, EnergyType.COLORLESS], damage=30, effect_id="heal", effect_params={"amount": 10, "target": "self"}, effect_description="Heal 10 damage from this Pokemon.", ), ], weakness=WeaknessResistance(energy_type=EnergyType.FIRE), resistance=WeaknessResistance(energy_type=EnergyType.WATER, value=-30), retreat_cost=2, ) # Rattata - Basic Normal cards["rattata-001"] = CardDefinition( id="rattata-001", name="Rattata", card_type=CardType.POKEMON, stage=PokemonStage.BASIC, hp=40, pokemon_type=EnergyType.COLORLESS, attacks=[ Attack( name="Bite", cost=[EnergyType.COLORLESS], damage=20, ), ], weakness=WeaknessResistance(energy_type=EnergyType.FIGHTING), retreat_cost=0, # Free retreat! ) # === TRAINER CARDS === cards["potion-001"] = CardDefinition( id="potion-001", name="Potion", card_type=CardType.TRAINER, trainer_type=TrainerType.ITEM, effect_id="heal", effect_params={"amount": 30}, effect_description="Heal 30 damage from one of your Pokemon.", ) cards["professor-001"] = CardDefinition( id="professor-001", name="Professor's Research", card_type=CardType.TRAINER, trainer_type=TrainerType.SUPPORTER, effect_id="draw_cards", effect_params={"count": 7, "discard_hand": True}, effect_description="Discard your hand and draw 7 cards.", ) # === ENERGY CARDS === for energy_type in [EnergyType.LIGHTNING, EnergyType.FIRE, EnergyType.WATER, EnergyType.GRASS]: cards[f"{energy_type.value}-energy"] = CardDefinition( id=f"{energy_type.value}-energy", name=f"{energy_type.value.title()} Energy", card_type=CardType.ENERGY, energy_type=energy_type, energy_provides=[energy_type], ) return cards # ============================================================================= # DECK BUILDERS # ============================================================================= def build_player1_deck( card_registry: dict[str, CardDefinition], ) -> tuple[list[CardInstance], list[CardInstance]]: """Build Player 1's main deck and energy deck (Lightning theme).""" main_deck = [] energy_deck = [] # Add Pokemon to main deck for i in range(4): main_deck.append(CardInstance(instance_id=f"p1-pikachu-{i}", definition_id="pikachu-001")) for i in range(3): main_deck.append(CardInstance(instance_id=f"p1-raichu-{i}", definition_id="raichu-001")) for i in range(2): main_deck.append(CardInstance(instance_id=f"p1-rattata-{i}", definition_id="rattata-001")) # Add trainers for i in range(4): main_deck.append(CardInstance(instance_id=f"p1-potion-{i}", definition_id="potion-001")) for i in range(2): main_deck.append( CardInstance(instance_id=f"p1-professor-{i}", definition_id="professor-001") ) # Pad to 40 cards with more Pokemon while len(main_deck) < 40: idx = len(main_deck) main_deck.append( CardInstance(instance_id=f"p1-pikachu-extra-{idx}", definition_id="pikachu-001") ) # Energy deck (20 Lightning energy) for i in range(20): energy_deck.append( CardInstance(instance_id=f"p1-lightning-{i}", definition_id="lightning-energy") ) return main_deck, energy_deck def build_player2_deck( card_registry: dict[str, CardDefinition], ) -> tuple[list[CardInstance], list[CardInstance]]: """Build Player 2's main deck and energy deck (Fire theme).""" main_deck = [] energy_deck = [] # Add Pokemon to main deck for i in range(4): main_deck.append( CardInstance(instance_id=f"p2-charmander-{i}", definition_id="charmander-001") ) for i in range(3): main_deck.append( CardInstance(instance_id=f"p2-bulbasaur-{i}", definition_id="bulbasaur-001") ) for i in range(2): main_deck.append(CardInstance(instance_id=f"p2-squirtle-{i}", definition_id="squirtle-001")) # Add trainers for i in range(4): main_deck.append(CardInstance(instance_id=f"p2-potion-{i}", definition_id="potion-001")) for i in range(2): main_deck.append( CardInstance(instance_id=f"p2-professor-{i}", definition_id="professor-001") ) # Pad to 40 cards while len(main_deck) < 40: idx = len(main_deck) main_deck.append( CardInstance(instance_id=f"p2-charmander-extra-{idx}", definition_id="charmander-001") ) # Energy deck (mix of Fire and Grass) for i in range(12): energy_deck.append(CardInstance(instance_id=f"p2-fire-{i}", definition_id="fire-energy")) for i in range(8): energy_deck.append(CardInstance(instance_id=f"p2-grass-{i}", definition_id="grass-energy")) return main_deck, energy_deck # ============================================================================= # MAIN WALKTHROUGH # ============================================================================= async def run_walkthrough(): """Run the interactive game walkthrough.""" clear_screen() print_divider("*") print(header(" MANTIMON TCG - INTERACTIVE GAME WALKTHROUGH")) print_divider("*") print_narration(""" Welcome to the Mantimon TCG game engine walkthrough! This script will demonstrate how the game engine works by: 1. Creating card definitions 2. Building decks for two players 3. Initializing a new game 4. Walking through game setup (shuffle, draw, place basics) 5. Simulating a full turn with actions Let's begin! """) if not wait_for_continue(): print("\nGoodbye!") return # ========================================================================= # STEP 1: Create Card Definitions # ========================================================================= clear_screen() print_divider() print(header("STEP 1: Creating Card Definitions")) print_divider() print_narration(""" First, we create CardDefinition objects for all cards in the game. These are immutable templates that define a card's properties: - Name, type, HP, attacks - Weaknesses, resistances, retreat cost - Effect IDs for special abilities Cards in play use CardInstance objects that reference these definitions. """) print_action("Creating card definitions...") card_registry = create_card_definitions() print_result(f"Created {len(card_registry)} card definitions") print("\nSample cards:") show_card(card_registry["pikachu-001"]) print() show_card(card_registry["charmander-001"]) if not wait_for_continue(): return # ========================================================================= # STEP 2: Create Rules Configuration # ========================================================================= clear_screen() print_divider() print(header("STEP 2: Creating Rules Configuration")) print_divider() print_narration(""" The RulesConfig defines all game rules - deck sizes, prize counts, per-turn limits, and more. Mantimon TCG uses house rules inspired by Pokemon Pocket: - 40-card main deck + 20-card energy deck - 4 points to win (no prize cards) - 1 energy attachment per turn (from energy zone) - First turn player can attack """) print_action("Creating RulesConfig with default Mantimon rules...") rules = RulesConfig() print_result("Rules created!") print(f"\n Deck size: {rules.deck.min_size} cards") print(f" Energy deck: {rules.deck.energy_deck_size} cards") print(f" Points to win: {rules.prizes.count}") print(f" Bench size: {rules.bench.max_size}") print(f" Energy per turn: {rules.energy.attachments_per_turn}") print(f" Starting hand: {rules.deck.starting_hand_size} cards") if not wait_for_continue(): return # ========================================================================= # STEP 3: Build Decks # ========================================================================= clear_screen() print_divider() print(header("STEP 3: Building Player Decks")) print_divider() print_narration(""" Each player needs a main deck and an energy deck. The main deck contains Pokemon and Trainers. The energy deck contains only Energy cards. Player 1 is playing a Lightning deck with Pikachu and Raichu. Player 2 is playing a mixed Fire/Grass deck. """) print_action("Building Player 1's deck (Lightning theme)...") p1_deck, p1_energy = build_player1_deck(card_registry) print_result(f"Main deck: {len(p1_deck)} cards, Energy deck: {len(p1_energy)} cards") print_action("Building Player 2's deck (Fire/Grass theme)...") p2_deck, p2_energy = build_player2_deck(card_registry) print_result(f"Main deck: {len(p2_deck)} cards, Energy deck: {len(p2_energy)} cards") if not wait_for_continue(): return # ========================================================================= # STEP 4: Initialize Game Engine # ========================================================================= clear_screen() print_divider() print(header("STEP 4: Initializing the Game Engine")) print_divider() print_narration(""" The GameEngine is the main orchestrator. It handles: - Game creation and initialization - Action validation and execution - Turn and phase management - Win condition checking We create it with our rules and a random number generator. """) print_action("Creating SeededRandom (seed=42 for reproducibility)...") rng = create_rng(seed=42) print_result("RNG created") print_action("Creating GameEngine with rules...") engine = GameEngine(rules=rules, rng=rng) print_result("Engine created!") if not wait_for_continue(): return # ========================================================================= # STEP 5: Create the Game # ========================================================================= clear_screen() print_divider() print(header("STEP 5: Creating the Game")) print_divider() print_narration(""" Now we call engine.create_game() with: - Player IDs - Each player's decks (main + energy) - The card registry The engine will: 1. Validate deck legality 2. Shuffle both decks 3. Deal starting hands (7 cards) 4. Flip energy from energy deck to energy zone 5. Set up turn order """) print_action("Creating game with two players...") decks = { "player1": p1_deck, "player2": p2_deck, } energy_decks = { "player1": p1_energy, "player2": p2_energy, } game_creation = engine.create_game( player_ids=["player1", "player2"], decks=decks, energy_decks=energy_decks, card_registry=card_registry, ) game = game_creation.game print_result(f"Game created! ID: {game_creation.game.game_id[:8]}...") print_result(f"Turn order: {game_creation.game.turn_order}") print_result(f"Current phase: {game_creation.game.phase.value}") if not wait_for_continue(): return # ========================================================================= # STEP 6: Setup Phase - Place Basic Pokemon # ========================================================================= clear_screen() print_divider() print(header("STEP 6: Setup Phase - Placing Basic Pokemon")) print_divider() print_narration(""" During setup, each player must place at least one Basic Pokemon as their Active Pokemon. They may also place Basic Pokemon on the bench. The game is in SETUP phase until all players have placed their active. Let's look at each player's hand and place their Pokemon. """) for player_id in game.turn_order: player = game.players[player_id] print(f"\n{bold(f'{player_id} hand:')}") basics_in_hand = [] for card in player.hand.cards: card_def = game.get_card_definition(card.definition_id) if card_def: card_type = f"({card_def.card_type.value})" if card_def.card_type == CardType.POKEMON and card_def.stage == PokemonStage.BASIC: basics_in_hand.append((card, card_def)) card_type = f"({card_def.card_type.value} - BASIC)" print(f" - {card_def.name} {card_type}") if basics_in_hand: # Place first basic as active card, card_def = basics_in_hand[0] print_action(f"Placing {card_def.name} as {player_id}'s active Pokemon") # In a real game this would be a SelectActiveAction during forced action # For the demo, we manually move the card player.hand.remove(card.instance_id) player.active.add(card) print_result(f"{card_def.name} is now active!") # Place additional basics on bench (up to 2 more for demo) for bench_card, bench_def in basics_in_hand[1:3]: print_action(f"Placing {bench_def.name} on bench") player.hand.remove(bench_card.instance_id) player.bench.add(bench_card) print_result(f"{bench_def.name} benched!") # Advance to draw phase game.phase = TurnPhase.DRAW game.turn_number = 1 if not wait_for_continue(): return # ========================================================================= # STEP 7: View Initial Game State # ========================================================================= clear_screen() print_divider() print(header("STEP 7: Initial Game State")) print_divider() print_narration(""" Setup is complete! Both players have active Pokemon. Let's view the game state before starting the first turn. """) show_game_state(game, engine) if not wait_for_continue(): return # ========================================================================= # STEP 8: Turn 1 - Draw Phase # ========================================================================= clear_screen() print_divider() print(header("STEP 8: Turn 1 - Draw Phase")) print_divider() current_player = game.get_current_player() print_narration(f""" It's {game.current_player_id}'s turn! The turn begins with the DRAW phase: 1. Draw a card from the deck 2. Flip an energy from the energy deck to the energy zone In Mantimon TCG (Pokemon Pocket style), you get one energy per turn automatically from your energy deck. """) # Draw a card drawn_card = current_player.deck.draw() if drawn_card: current_player.hand.add(drawn_card) card_def = game.get_card_definition(drawn_card.definition_id) print_action("Drawing a card from deck...") print_result(f"Drew: {card_def.name if card_def else drawn_card.definition_id}") # Flip energy flipped_energy = current_player.energy_deck.draw() if flipped_energy: current_player.energy_zone.add(flipped_energy) card_def = game.get_card_definition(flipped_energy.definition_id) print_action("Flipping energy from energy deck...") print_result(f"Flipped: {card_def.name if card_def else flipped_energy.definition_id}") game.phase = TurnPhase.MAIN print_result("Advancing to MAIN phase") print(f"\n{bold('Hand size:')} {len(current_player.hand)} cards") print(f"{bold('Energy zone:')} {len(current_player.energy_zone)} energy available") if not wait_for_continue(): return # ========================================================================= # STEP 9: Turn 1 - Main Phase Actions # ========================================================================= clear_screen() print_divider() print(header("STEP 9: Turn 1 - Main Phase")) print_divider() print_narration(f""" In the MAIN phase, {game.current_player_id} can: - Play Basic Pokemon to the bench - Evolve Pokemon (not on first turn they're played) - Attach energy from the energy zone (1 per turn) - Play Trainer cards - Use Pokemon abilities - Retreat the active Pokemon Let's attach energy to our active Pokemon! """) # Find energy in energy zone active_pokemon = current_player.get_active_pokemon() active_def = game.get_card_definition(active_pokemon.definition_id) if active_pokemon else None if current_player.energy_zone.cards and active_pokemon: energy_card = current_player.energy_zone.cards[0] energy_def = game.get_card_definition(energy_card.definition_id) print_action( f"Attaching {energy_def.name if energy_def else 'energy'} to {active_def.name if active_def else 'active Pokemon'}..." ) # Create and execute attach energy action action = AttachEnergyAction( energy_card_id=energy_card.instance_id, target_pokemon_id=active_pokemon.instance_id, from_energy_zone=True, ) result = await engine.execute_action(game, game.current_player_id, action) if result.success: print_result( f"Energy attached! {active_def.name if active_def else 'Active'} now has {len(active_pokemon.attached_energy)} energy" ) else: print(error(f"Failed: {result.message}")) if not wait_for_continue(): return # ========================================================================= # STEP 10: Turn 1 - Attack Phase # ========================================================================= clear_screen() print_divider() print(header("STEP 10: Turn 1 - Attack Phase")) print_divider() print_narration(f""" {game.current_player_id} advances to the ATTACK phase! Their active {active_def.name if active_def else "Pokemon"} can use one of its attacks if it has enough energy attached. """) game.phase = TurnPhase.ATTACK if active_pokemon and active_def and active_def.attacks: print(f"\n{bold('Available attacks:')}") for i, attack in enumerate(active_def.attacks): cost = ", ".join(e.value for e in attack.cost) if attack.cost else "free" print(f" [{i}] {attack.name}: {attack.damage} damage (cost: {cost})") # Check what attacks we can afford attached_energy_count = len(active_pokemon.attached_energy) print(f"\n{bold('Attached energy:')} {attached_energy_count}") # Try the first attack (usually free or low cost) chosen_attack = active_def.attacks[0] print_action(f"Using {chosen_attack.name}!") action = AttackAction(attack_index=0) result = await engine.execute_action(game, game.current_player_id, action) if result.success: print_result("Attack successful!") print_result(result.message) # Show opponent's Pokemon state opponent = game.get_opponent(game.current_player_id) opp_active = opponent.get_active_pokemon() if opp_active: opp_def = game.get_card_definition(opp_active.definition_id) max_hp = opp_def.hp + opp_active.hp_modifier if opp_def else 0 print_result( f"Opponent's {opp_def.name if opp_def else 'Pokemon'}: {max_hp - opp_active.damage}/{max_hp}" ) else: print(error(f"Attack failed: {result.message}")) if not wait_for_continue(): return # ========================================================================= # STEP 11: End of Turn # ========================================================================= clear_screen() print_divider() print(header("STEP 11: End of Turn")) print_divider() print_narration(""" The turn ends after the attack (or if the player passes). End of turn processing: 1. Apply between-turn effects (poison damage, burn damage, etc.) 2. Check for knockouts 3. Award points for knockouts 4. Check win conditions 5. Switch to next player's turn """) # Pass to end turn (this triggers end of turn processing) game.phase = TurnPhase.END print_action("Processing end of turn...") # Advance to next player old_player = game.current_player_id game.advance_turn() print_result(f"Turn ended for {old_player}") print_result(f"It's now {game.current_player_id}'s turn!") print_result(f"Turn number: {game.turn_number}") if not wait_for_continue(): return # ========================================================================= # FINAL: Show Final State # ========================================================================= clear_screen() print_divider() print(header("WALKTHROUGH COMPLETE!")) print_divider() print_narration(""" That concludes the game engine walkthrough! You've seen how the engine: - Creates and manages card definitions - Builds and validates decks - Handles game initialization - Processes turn phases and actions - Manages combat and damage The game will continue with Player 2's turn, and play proceeds until someone reaches 4 points (or another win condition is met). """) show_game_state(game, engine) print(f"\n{success('Thanks for exploring the Mantimon TCG engine!')}") print( f"\nTo continue experimenting, run: {info('uv run python -i references/console_testing.py')}" ) # ============================================================================= # ENTRY POINT # ============================================================================= if __name__ == "__main__": try: asyncio.run(run_walkthrough()) except KeyboardInterrupt: print("\n\nWalkthrough interrupted. Goodbye!") except Exception as e: print(error(f"\nError: {e}")) raise