Spaces:
Running
Running
File size: 5,679 Bytes
bb3fbf9 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
import random
import pytest
from pytest_bdd import given, parsers, then, when
from compiler.parser import AbilityParser
from engine.game.data_loader import CardDataLoader
from engine.game.enums import Phase
from engine.game.game_state import GameState
from engine.models.ability import TriggerType
from engine.tests.framework.state_validators import StateValidator
# --- Common Fixtures ---
@pytest.fixture
def loader():
return CardDataLoader("engine/data/cards.json")
@pytest.fixture
def data(loader):
member_db, live_db, energy_pool = loader.load()
GameState.member_db = member_db
GameState.live_db = live_db
return member_db, live_db
@pytest.fixture
def context():
return {}
@pytest.fixture
def game_state(data, context):
gs = GameState()
# Setup simple decks
member_db, live_db = data
available_members = list(member_db.keys())
for p in gs.players:
if available_members:
# Create a simple deck
p.main_deck = [random.choice(available_members) for _ in range(20)]
p.hand = [] # Clear hand for controlled tests
gs.phase = Phase.MAIN
# Store in context for steps to access and update
context["game_state"] = gs
return gs
@pytest.fixture
def validated_game_state(data, context):
"""
A GameState fixture that runs StateValidator after every step.
Useful for ensuring deep integrity in new tests.
"""
class ValidatingGameState(GameState):
def step(self, action: int):
new_state = super().step(action)
# Validate the NEW state (which is a copy)
# But wait, step returns a NEW state object.
# We should probably validate 'new_state' if we want to catch issues there.
# AND validate 'self' if it was mutated (it is in some paths).
# Since GameState.step returns a COPY, 'new_state' is the one to check.
StateValidator.assert_valid(new_state)
return new_state
gs = ValidatingGameState()
# Identical setup to game_state fixture
member_db, live_db = data
available_members = list(member_db.keys())
for p in gs.players:
if available_members:
p.main_deck = [random.choice(available_members) for _ in range(20)]
p.hand = []
gs.phase = Phase.MAIN
context["game_state"] = gs
return gs
# --- Common Steps ---
@given("a player with a deck", target_fixture="player_state")
def player_with_deck(context, game_state):
# game_state fixture ensures context is populated
p = game_state.players[0]
context["initial_hand_size"] = len(p.hand)
context["initial_deck_size"] = len(p.main_deck)
return p
# --- Parser Steps (Generic) ---
@when(parsers.parse('I parse the ability text "{text}"'), target_fixture="parsed_abilities")
def parse_ability(text):
return AbilityParser.parse_ability_text(text)
@then(parsers.parse('I should get an ability with trigger "{trigger}"'))
def check_trigger(parsed_abilities, trigger):
assert len(parsed_abilities) > 0
# Map string to enum if needed, or check name
expected_trigger = getattr(TriggerType, trigger)
assert parsed_abilities[0].trigger == expected_trigger
@then(parsers.parse('the ability should have an effect type "{effect}"'))
def check_effect(parsed_abilities, effect):
if effect == "NONE":
assert len(parsed_abilities[0].effects) == 0
return
found = False
for abi in parsed_abilities:
for eff in abi.effects:
if eff.effect_type.name == effect:
found = True
break
assert found, f"Effect {effect} not found in {parsed_abilities[0].effects}"
@when(parsers.parse("the player draws {count:d} cards"))
def draw_cards(context, player_state, count):
# Note: _draw_cards is in-place
game_state = context["game_state"]
# We must ensure player_state is from the current game_state
# If player_state is passed, it is the initial state object usually.
# But usually draw_cards just acts on P0.
p = game_state.players[player_state.player_id]
game_state._draw_cards(p, count)
@then(parsers.parse("the player's hand size should increase by {count:d}"))
def check_hand_size(context, count):
game_state = context["game_state"]
player_state = game_state.players[0]
expected = context["initial_hand_size"] + count
assert len(player_state.hand) == expected, f"Expected {expected}, got {len(player_state.hand)}"
@then(parsers.parse("the player's deck size should decrease by {count:d}"))
def check_deck_size(context, count):
game_state = context["game_state"]
player_state = game_state.players[0]
expected = context["initial_deck_size"] - count
assert len(player_state.main_deck) == expected, f"Expected {expected}, got {len(player_state.main_deck)}"
@given(parsers.parse("the player has {count:d} cards in hand"))
def player_has_cards(context, game_state, count):
p = game_state.players[0]
# Use simple integers 100+
p.hand = list(range(100, 100 + count))
context["hand_cards"] = p.hand.copy()
@then(parsers.parse("the player should be prompted to select {count:d} card from hand"))
def check_prompt_select_hand(context, game_state, count):
current_state = context.get("game_state", game_state)
assert len(current_state.pending_choices) > 0
choice = current_state.pending_choices[0]
assert choice[0] == "TARGET_HAND"
# Action for TARGET_HAND 0 is 500.
|