Spaces:
Running
Running
| 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 --- | |
| def loader(): | |
| return CardDataLoader("engine/data/cards.json") | |
| 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 | |
| def context(): | |
| return {} | |
| 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 | |
| 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 --- | |
| 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) --- | |
| def parse_ability(text): | |
| return AbilityParser.parse_ability_text(text) | |
| 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 | |
| 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}" | |
| 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) | |
| 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)}" | |
| 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)}" | |
| 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() | |
| 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. | |