Add interactive console testing script for manual testing
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
This commit is contained in:
parent
dba2813f80
commit
35bb001292
353
backend/references/console_testing.py
Normal file
353
backend/references/console_testing.py
Normal file
@ -0,0 +1,353 @@
|
||||
#!/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}))")
|
||||
Loading…
Reference in New Issue
Block a user