Spaces:
Sleeping
Sleeping
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)
|