#!/usr/bin/env python """Interactive console testing reference for Mantimon TCG core engine. Usage: cd backend && uv run python -i references/console_testing.py This script sets up a complete game state with cards, players, and demonstrates how to use the effects system interactively. After running, you'll have access to: - game: A GameState with two players and active Pokemon - pikachu, charmander: CardInstance objects (active Pokemon) - pikachu_def, charmander_def: CardDefinition objects - rng: A SeededRandom for deterministic testing - All imports ready to use Quick Examples: >>> resolve_effect("deal_damage", make_ctx({"amount": 30})) >>> charmander.damage 30 >>> resolve_effect("apply_status", make_ctx({"status": "poisoned"})) >>> charmander.status_conditions [] """ # ============================================================================= # PATH SETUP (ensures imports work when running from backend/) # ============================================================================= import sys from pathlib import Path # Add backend to path if needed (references/ is directly under backend/) backend_dir = Path(__file__).resolve().parent.parent if str(backend_dir) not in sys.path: sys.path.insert(0, str(backend_dir)) # ============================================================================= # IMPORTS # ============================================================================= # Register all built-in effect handlers import app.core.effects.handlers # noqa: F401 from app.core.config import ( RulesConfig, ) from app.core.effects.base import EffectContext from app.core.effects.registry import list_effects from app.core.enums import ( CardType, EnergyType, PokemonStage, TrainerType, TurnPhase, ) from app.core.models.card import ( Attack, CardDefinition, CardInstance, WeaknessResistance, ) from app.core.models.game_state import GameState, PlayerState from app.core.rng import SeededRandom # ============================================================================= # SAMPLE CARD DEFINITIONS # ============================================================================= pikachu_def = CardDefinition( id="pikachu-001", name="Pikachu", card_type=CardType.POKEMON, stage=PokemonStage.BASIC, hp=60, pokemon_type=EnergyType.LIGHTNING, attacks=[ Attack(name="Gnaw", damage=10), Attack( name="Thunder Shock", cost=[EnergyType.LIGHTNING], damage=20, effect_id="may_paralyze", effect_description="Flip a coin. If heads, the Defending Pokemon is now Paralyzed.", ), ], weakness=WeaknessResistance(energy_type=EnergyType.FIGHTING), retreat_cost=1, ) charmander_def = CardDefinition( id="charmander-001", name="Charmander", card_type=CardType.POKEMON, stage=PokemonStage.BASIC, hp=70, pokemon_type=EnergyType.FIRE, attacks=[ Attack(name="Scratch", damage=10), Attack( name="Ember", cost=[EnergyType.FIRE], damage=30, effect_id="discard_energy", effect_params={"count": 1}, ), ], weakness=WeaknessResistance(energy_type=EnergyType.WATER), retreat_cost=1, ) bulbasaur_def = 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=20), ], weakness=WeaknessResistance(energy_type=EnergyType.FIRE), resistance=WeaknessResistance(energy_type=EnergyType.WATER), retreat_cost=1, ) # Sample trainer card potion_def = 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.", ) # Sample energy card fire_energy_def = CardDefinition( id="fire-energy-001", name="Fire Energy", card_type=CardType.ENERGY, energy_type=EnergyType.FIRE, energy_provides=[EnergyType.FIRE], ) # ============================================================================= # GAME STATE SETUP # ============================================================================= # Create the game state game = GameState( game_id="test-game-001", rules=RulesConfig(), card_registry={ "pikachu-001": pikachu_def, "charmander-001": charmander_def, "bulbasaur-001": bulbasaur_def, "potion-001": potion_def, "fire-energy-001": fire_energy_def, }, players={ "player1": PlayerState(player_id="player1"), "player2": PlayerState(player_id="player2"), }, turn_order=["player1", "player2"], current_player_id="player1", turn_number=1, phase=TurnPhase.MAIN, ) # Create card instances pikachu = CardInstance(instance_id="pika-1", definition_id="pikachu-001") charmander = CardInstance(instance_id="char-1", definition_id="charmander-001") bulbasaur = CardInstance(instance_id="bulba-1", definition_id="bulbasaur-001") # Set up the board game.players["player1"].active.add(pikachu) game.players["player2"].active.add(charmander) game.players["player2"].bench.add(bulbasaur) # Add some cards to decks for i in range(5): game.players["player1"].deck.add( CardInstance(instance_id=f"p1-deck-{i}", definition_id="bulbasaur-001") ) game.players["player2"].deck.add( CardInstance(instance_id=f"p2-deck-{i}", definition_id="bulbasaur-001") ) # Create RNG rng = SeededRandom(seed=42) # ============================================================================= # HELPER FUNCTIONS # ============================================================================= def make_ctx(params: dict, **kwargs) -> EffectContext: """Create an EffectContext with sensible defaults. Args: params: Effect parameters (e.g., {"amount": 30}) **kwargs: Override defaults (source_player_id, source_card_id, target_card_id, etc.) Returns: EffectContext ready to pass to resolve_effect() Example: >>> ctx = make_ctx({"amount": 20}) >>> resolve_effect("deal_damage", ctx) """ defaults = { "game": game, "source_player_id": "player1", "rng": rng, "params": params, } defaults.update(kwargs) return EffectContext(**defaults) def reset_game(): """Reset the game state to initial conditions.""" global pikachu, charmander, bulbasaur, rng # Reset damage and status pikachu.damage = 0 pikachu.status_conditions = [] pikachu.hp_modifier = 0 pikachu.damage_modifier = 0 pikachu.retreat_cost_modifier = 0 pikachu.attached_energy = [] charmander.damage = 0 charmander.status_conditions = [] charmander.hp_modifier = 0 charmander.damage_modifier = 0 charmander.retreat_cost_modifier = 0 charmander.attached_energy = [] bulbasaur.damage = 0 bulbasaur.status_conditions = [] # Reset RNG rng = SeededRandom(seed=42) print("Game state reset!") def show_board(): """Print the current board state.""" print("\n" + "=" * 50) print("PLAYER 1 (current)") p1 = game.players["player1"] if p1.active: poke = p1.get_active_pokemon() poke_def = game.card_registry.get(poke.definition_id) hp = poke_def.hp + poke.hp_modifier if poke_def else "?" print(f" Active: {poke_def.name if poke_def else poke.definition_id}") print(f" HP: {hp - poke.damage}/{hp} Damage: {poke.damage}") print(f" Status: {[s.value for s in poke.status_conditions]}") print(f" Energy: {poke.attached_energy}") print(f" Bench: {len(p1.bench)} Pokemon") print(f" Hand: {len(p1.hand)} cards, Deck: {len(p1.deck)} cards") print("\nPLAYER 2 (opponent)") p2 = game.players["player2"] if p2.active: poke = p2.get_active_pokemon() poke_def = game.card_registry.get(poke.definition_id) hp = poke_def.hp + poke.hp_modifier if poke_def else "?" print(f" Active: {poke_def.name if poke_def else poke.definition_id}") print(f" HP: {hp - poke.damage}/{hp} Damage: {poke.damage}") print(f" Status: {[s.value for s in poke.status_conditions]}") print(f" Energy: {poke.attached_energy}") print(f" Bench: {len(p2.bench)} Pokemon") print(f" Hand: {len(p2.hand)} cards, Deck: {len(p2.deck)} cards") print("=" * 50 + "\n") # ============================================================================= # DEMONSTRATION # ============================================================================= if __name__ == "__main__": print(__doc__) print("\n" + "=" * 60) print("AVAILABLE EFFECT HANDLERS") print("=" * 60) for name in sorted(list_effects()): print(f" - {name}") print("\n" + "=" * 60) print("QUICK REFERENCE") print("=" * 60) print(""" # Deal raw damage (no weakness/resistance) resolve_effect("deal_damage", make_ctx({"amount": 30})) # Deal attack damage (with weakness/resistance) resolve_effect("attack_damage", make_ctx({"amount": 30}, source_card_id="pika-1")) # Apply status condition resolve_effect("apply_status", make_ctx({"status": "poisoned"})) # Heal damage resolve_effect("heal", make_ctx({"amount": 20})) # Draw cards resolve_effect("draw_cards", make_ctx({"count": 3})) # Coin flip damage resolve_effect("coin_flip_damage", make_ctx({"damage_per_heads": 20, "flip_count": 3})) # Bench damage resolve_effect("bench_damage", make_ctx({"amount": 10})) # Show board state show_board() # Reset everything reset_game() """) print("=" * 60) print("INITIAL BOARD STATE") print("=" * 60) show_board() print("Ready for interactive testing!") print("Try: resolve_effect('deal_damage', make_ctx({'amount': 30}))")