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.