Spaces:
Sleeping
Sleeping
| import numpy as np | |
| from engine.game.game_state import GameState | |
| from engine.models.enums import Group | |
| # Issue IDs | |
| ID_ISSUE_541 = 541 # PL!S-pb1-001-R (Cost 13, Opp Hand > My Hand + 2 -> Add Live from Discard) | |
| ID_ISSUE_269 = 269 # PL!N-bp1-012-R+ (3+ Lives & 1+ Niji -> Heart becomes ALL - wait, it gains 2 ALL hearts) | |
| ID_ISSUE_177 = 177 # PL!HS-bp1-007-P (Cost 2E -> Draw 1) | |
| ID_ISSUE_603 = 603 # PL!SP-bp1-010-R (Cost 1E + 1 Discard -> Look 5 Add 1) | |
| def setup_hand(p, cards): | |
| """Refills hand and syncs hand_added_turn for validation.""" | |
| p.hand = list(cards) | |
| p.hand_added_turn = [0] * len(p.hand) | |
| def setup_live_zone(p, cards): | |
| """Refills live zone and syncs revealed for validation.""" | |
| p.live_zone = list(cards) | |
| p.live_zone_revealed = [True] * len(p.live_zone) | |
| def test_issue_541_condition_check(validated_game_state): | |
| """ | |
| Issue 541: Condition 'Opponent hand > My hand' check. | |
| Card: PL!S-pb1-001-R (Cost 13, Condition: Opponent > Self + 2) | |
| """ | |
| gs = validated_game_state | |
| p1 = gs.players[0] | |
| p2 = gs.players[1] | |
| # Give plenty of energy | |
| p1.energy_zone = [0] * 20 # Use valid ID 0 | |
| p1.tapped_energy = np.zeros(100, dtype=bool) | |
| # Setup Hands | |
| # Use real IDs for dummy cards to ensure they are handled by engine logic if needed | |
| member_ids = list(GameState.member_db.keys()) | |
| dummies = [member_ids[0], member_ids[1], member_ids[2], member_ids[3]] | |
| # Condition: Opponent >= Self + 2 | |
| # Case: Opponent (0) < Self (5). Should FAIL. | |
| setup_hand(p1, [ID_ISSUE_541] + dummies) # 5 cards w/ 541 at Index 0 | |
| setup_hand(p2, []) # 0 cards | |
| # Add a live card to discard to be targetable | |
| live_ids = list(GameState.live_db.keys()) | |
| p1.discard = [live_ids[0]] | |
| # Debug legal actions | |
| legal = gs.get_legal_actions() | |
| print(f"DEBUG: Legal actions in Phase.MAIN: {np.where(legal)[0]}") | |
| # Action 1: Play Index 0 to Slot 0 (Main Phase ID mapping: 1 + i*3 + slot) | |
| # (0*3 + 0) + 1 = 1 | |
| gs = gs.step(1) | |
| p1 = gs.players[0] | |
| # Analysis: | |
| # Cost: 13 energy tapped. Hand -> 4. | |
| # Effect should NOT trigger. | |
| assert len(p1.hand) == 4, f"Hand size is {len(p1.hand)}. Action 1 might have failed if it is 5." | |
| assert live_ids[0] not in p1.hand, "Effect triggered incorrectly! Live card added to hand." | |
| assert np.count_nonzero(p1.tapped_energy[:20]) == 13, "Cost was not paid correctly (Energy Tapping)" | |
| def test_issue_269_constant_effect(validated_game_state): | |
| """ | |
| Issue 269: Constant effect 'If 3+ live cards & 1+ Niji -> Heart becomes ALL (Gains 2 Any)' | |
| """ | |
| gs = validated_game_state | |
| p1 = gs.players[0] | |
| # Find a Nijigasaki Live Card | |
| live_db = GameState.live_db | |
| niji_live_id = next((cid for cid, c in live_db.items() if Group.NIJIGASAKI in c.groups), None) | |
| assert niji_live_id is not None, "Need Niji Live card for test" | |
| # Find dummy lives | |
| dummy_lives = [cid for cid in live_db.keys() if cid != niji_live_id][:2] | |
| # Setup Live Zone (3 cards, 1 Niji) -> Condition Met | |
| setup_live_zone(p1, [niji_live_id] + dummy_lives) | |
| # Setup Stage | |
| p1.stage[0] = ID_ISSUE_269 | |
| # Force rule check to apply constants | |
| gs._process_rule_checks() | |
| # Check Hearts | |
| hearts = p1.get_effective_hearts(0, GameState.member_db) | |
| # Index 6 is "Any/All". | |
| # Expected: member's base hearts + 2 from effect | |
| assert hearts[6] >= 2, f"Constant effect failed: Heart total at index 6 is {hearts[6]}. Hearts: {hearts}" | |
| def test_issue_177_activated_draw(validated_game_state): | |
| """ | |
| Issue 177: PL!HS-bp1-007-P Activation (Cost 2E -> Draw 1) | |
| """ | |
| gs = validated_game_state | |
| p1 = gs.players[0] | |
| # Setup Stage | |
| p1.stage[0] = ID_ISSUE_177 | |
| p1.energy_zone = [0] * 5 | |
| p1.tapped_energy = np.zeros(100, dtype=bool) | |
| member_ids = list(GameState.member_db.keys()) | |
| setup_hand(p1, [member_ids[0], member_ids[1]]) | |
| initial_hand = len(p1.hand) | |
| # Action 200: Activate Ability Slot 0 | |
| gs = gs.step(200) | |
| p1 = gs.players[0] | |
| # Verify results | |
| assert np.count_nonzero(p1.tapped_energy[:5]) == 2, "Cost not paid (Energy not tapped)" | |
| assert len(p1.hand) == initial_hand + 1, "Draw effect failed" | |
| def test_issue_603_ui_payload(validated_game_state): | |
| """ | |
| Issue 603: Activated 'Look 5 add 1'. UI issue (cards not shown). | |
| Verify engine populates looked_cards and handles choice. | |
| """ | |
| gs = validated_game_state | |
| p1 = gs.players[0] | |
| # Setup Stage | |
| p1.stage[0] = ID_ISSUE_603 | |
| p1.energy_zone = [0] * 5 | |
| p1.tapped_energy = np.zeros(100, dtype=bool) | |
| # Need 1 card in hand for discard cost | |
| member_ids = list(GameState.member_db.keys()) | |
| setup_hand(p1, [member_ids[0], member_ids[1]]) | |
| # Action 200: Use Ability Slot 0 | |
| gs = gs.step(200) | |
| p1 = gs.players[0] | |
| # Cost: 1 Discard. Choice should be pending. | |
| assert gs.pending_choices, "No choice generated for discard cost" | |
| assert gs.pending_choices[0][0] == "TARGET_HAND" | |
| # Action 500: Discard first card in hand (Resolves Target Hand COST) | |
| gs = gs.step(500) | |
| p1 = gs.players[0] | |
| # Now Effect 2 should trigger (Look 5) | |
| assert len(gs.looked_cards) == 5, f"Engine failed to look at 5 cards, got {len(gs.looked_cards)}" | |
| # Verify follow-up choice | |
| assert gs.pending_choices, "No choice generated for Look & Choose" | |
| assert gs.pending_choices[0][0] == "SELECT_FROM_LIST", f"Unexpected choice {gs.pending_choices[0][0]}" | |
| # Action 600: Select first looked card | |
| gs = gs.step(600) | |
| assert len(gs.players[0].hand) == 2, f"Expected 2 cards (2 - 1 cost + 1 drawn), got {len(gs.players[0].hand)}" | |