Provides a ready-to-use REPL environment with: - Pre-configured game state with two players and active Pokemon - Sample card definitions (Pikachu, Charmander, Bulbasaur, Potion, Energy) - Helper functions: make_ctx(), reset_game(), show_board() - All imports ready (effects, models, config, RNG) Usage: cd backend && uv run python -i references/console_testing.py
354 lines
10 KiB
Python
354 lines
10 KiB
Python
#!/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
|
|
[<StatusCondition.POISONED: 'poisoned'>]
|
|
"""
|
|
|
|
# =============================================================================
|
|
# 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
|
|
# =============================================================================
|
|
|
|
from app.core.models.enums import (
|
|
CardType,
|
|
EnergyType,
|
|
ModifierMode,
|
|
PokemonStage,
|
|
PokemonVariant,
|
|
StatusCondition,
|
|
TrainerType,
|
|
TurnPhase,
|
|
)
|
|
from app.core.config import (
|
|
RulesConfig,
|
|
CombatConfig,
|
|
DeckConfig,
|
|
BenchConfig,
|
|
EnergyConfig,
|
|
PrizeConfig,
|
|
StatusConfig,
|
|
TrainerConfig,
|
|
)
|
|
from app.core.models.card import (
|
|
CardDefinition,
|
|
CardInstance,
|
|
Attack,
|
|
Ability,
|
|
WeaknessResistance,
|
|
)
|
|
from app.core.models.game_state import GameState, PlayerState, Zone
|
|
from app.core.models.actions import (
|
|
PlayPokemonAction,
|
|
EvolvePokemonAction,
|
|
AttachEnergyAction,
|
|
AttackAction,
|
|
RetreatAction,
|
|
PassAction,
|
|
parse_action,
|
|
)
|
|
from app.core.rng import SeededRandom, SecureRandom, create_rng
|
|
from app.core.effects.base import EffectContext, EffectResult, EffectType
|
|
from app.core.effects.registry import resolve_effect, list_effects
|
|
|
|
# Register all built-in effect handlers
|
|
import app.core.effects.handlers # noqa: F401
|
|
|
|
# =============================================================================
|
|
# 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}))")
|