File size: 6,004 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
172
173
174
175
import os
import sys

# Add project root to path
sys.path.insert(0, os.getcwd())

import numpy as np

from engine.game.enums import Phase
from engine.game.game_state import GameState
from engine.models.ability import Ability, AbilityCostType, Cost, Effect, EffectType, TriggerType
from engine.models.card import MemberCard


def test_multi_discard_atomicity():
    """

    Verify that for a 'Discard 2: Draw 1' ability, the 'Draw' effect

    does not happen until BOTH discards are completed.

    """
    game = GameState()
    p1 = game.players[0]

    # Ability: [Startup] Cost: Discard 2 -> Effect: Draw 1
    ability = Ability(
        raw_text="Discard 2: Draw 1",
        trigger=TriggerType.ACTIVATED,
        costs=[Cost(type=AbilityCostType.DISCARD_HAND, value=2)],
        effects=[Effect(effect_type=EffectType.DRAW, value=1)],
    )

    card = MemberCard(
        card_id=1,
        card_no="TEST-001",
        name="Test Card",
        cost=1,
        hearts=np.zeros(7, dtype=np.int32),
        blade_hearts=np.zeros(7, dtype=np.int32),
        blades=1,
        abilities=[ability],
    )
    game.member_db[1] = card

    # Setup: Hand has 3 cards.
    # [100, 101, 102]
    # We want to discard [100, 101] and draw something else.
    p1.hand = [100, 101, 102]
    p1.hand_added_turn = [0, 0, 0]
    p1.stage[0] = 1  # Put test card on stage

    # Deck has [103]
    p1.main_deck = [103]

    from engine.game.enums import Phase

    game.phase = Phase.MAIN

    # 1. Activate ability
    game._execute_action(200)  # Activate Slot 0

    assert len(game.pending_choices) == 1, f"Expected 1 pending choice, got {len(game.pending_choices)}"
    assert game.pending_choices[0][0] == "TARGET_HAND", f"Expected TARGET_HAND choice, got {game.pending_choices[0][0]}"
    assert game.pending_choices[0][1]["count"] == 2, f"Expected count 2, got {game.pending_choices[0][1]['count']}"

    # 2. Perform 1st discard (Choose index 0)
    print(f"DEBUG: Before 1st discard: Hand={p1.hand} Discard={p1.discard} Choices={game.pending_choices}")
    game._execute_action(500)
    print(f"DEBUG: After 1st discard: Hand={p1.hand} Discard={p1.discard} Choices={game.pending_choices}")

    # Check state:
    # If it's correctly atomic:
    # Hand should be [101, 102]
    # Discard should be [100]
    # pending_choices should have 1 item (the 2nd discard)
    # DRAW should NOT have happened yet.

    assert len(p1.hand) == 2, f"Hand size {len(p1.hand)} != 2 after 1st discard"
    assert 103 not in p1.hand, "Should NOT have drawn 103 yet!"

    assert len(game.pending_choices) == 1, f"Expected 1 choice, got {len(game.pending_choices)}"
    assert game.pending_choices[0][1]["count"] == 1

    # 3. Perform 2nd discard (Choose index 0 which is card 101)
    print(f"DEBUG: Before 2nd discard: Hand={p1.hand} Discard={p1.discard} Choices={game.pending_choices}")
    game._execute_action(500)
    print(f"DEBUG: After 2nd discard: Hand={p1.hand} Discard={p1.discard} Choices={game.pending_choices}")

    # Now DRAW should have happened.
    assert 103 in p1.hand, "Should have drawn 103 now"
    # Note: Discard might be empty if a refresh occurred (which it does in this test setup)
    assert len(p1.hand) >= 2


def test_swap_cards_atomicity():
    """

    Verify that for a 'SWAP_CARDS (2)' effect (Discard 2, then Draw 2),

    draws do NOT happen until both discards are done.

    """
    game = GameState()
    p1 = game.players[0]

    # Ability: [On Play] Effect: Swap 2
    ability = Ability(
        raw_text="On Play: Swap 2",
        trigger=TriggerType.ON_PLAY,
        costs=[],
        effects=[Effect(effect_type=EffectType.SWAP_CARDS, value=2)],
    )

    card = MemberCard(
        card_id=1,
        card_no="TEST-002",
        name="Swap Card",
        cost=1,
        hearts=np.zeros(7, dtype=np.int32),
        blade_hearts=np.zeros(7, dtype=np.int32),
        blades=1,
        abilities=[ability],
    )
    game.member_db[1] = card

    # Setup
    p1.hand = [1, 100, 101, 102]
    p1.hand_added_turn = [0, 0, 0, 0]
    p1.main_deck = [200, 201]
    game.phase = Phase.MAIN

    # 1. Play card 1 (triggers ON_PLAY)
    game._execute_action(1)  # Play hand[0] (card 1) to slot 0

    # Effect should be resolving.
    # It should have queued a DISCARD_SELECT choice.
    assert len(game.pending_choices) == 1
    assert game.pending_choices[0][0] == "DISCARD_SELECT"

    # 2. Perform 1st discard
    print(f"DEBUG SWAP: Before 1st discard: Hand={p1.hand} Discard={p1.discard} Choices={game.pending_choices}")
    game._execute_action(500)
    print(f"DEBUG SWAP: After 1st discard: Hand={p1.hand} Discard={p1.discard} Choices={game.pending_choices}")

    # If it's INTERLEAVING (Buggy):
    # Hand will contain a card from the deck already!

    assert 200 not in p1.hand, "Should NOT have drawn first card yet!"
    assert len(game.pending_choices) == 1, "Should have 2nd discard pending"

    # 3. Perform 2nd discard
    game._execute_action(500)
    print(f"DEBUG SWAP: After 2nd discard: Hand={p1.hand} Discard={p1.discard} Choices={game.pending_choices}")

    # Now both should be drawn.
    assert 200 in p1.hand
    assert 201 in p1.hand
    assert len(p1.hand) == 3


if __name__ == "__main__":
    try:
        print("=== RUNNING COST ATOMICITY TEST ===", flush=True)
        test_multi_discard_atomicity()
        print("=== COST TEST PASSED ===", flush=True)

        print("\n=== RUNNING SWAP ATOMICITY TEST ===", flush=True)
        test_swap_cards_atomicity()
        print("=== SWAP TEST PASSED ===", flush=True)

    except AssertionError as e:
        print(f"\n!!! TEST FAILED: {e} !!!", flush=True)
        sys.exit(1)
    except Exception as e:
        print(f"\n!!! ERROR: {e} !!!", flush=True)
        import traceback

        traceback.print_exc()
        sys.exit(1)