LovecaSim / engine /tests /cards /batches /test_bp3_009_nico.py
trioskosmos's picture
Upload folder using huggingface_hub
bb3fbf9 verified
import numpy as np
import pytest
from engine.game.enums import Phase
from engine.game.game_state import GameState
from engine.models.card import MemberCard
# Mock the specific card if it doesn't exist in the loaded DB (unlikely if data is good, but safe)
# PL!-bp3-009-P+ (Nico Yazawa)
# Cost 2
# [On Entry] If you have a member with Cost 13 or more, Draw 1.
# [Action][Turn 1] Tap: Choose Heart (Pink/Yellow/Purple). Gain 1 until end of live.
CARD_ID_NICO = "PL!-bp3-009-P+"
# Note: Using the full-width plus as discovered
@pytest.fixture
def game_state():
gs = GameState()
# Ensure we have the card in DB or mock it
# We will try to find it in the real DB first
found_id = None
if gs.member_db:
# Search by card_no
for cid, c in gs.member_db.items():
if c.card_no == CARD_ID_NICO:
found_id = cid
break
if found_id is None:
# Inject mock if not found (fallback logic)
mock_id = 99009
gs.member_db[mock_id] = MemberCard(
card_id=mock_id,
card_no=CARD_ID_NICO,
name="Nico Yazawa",
cost=2,
blades=2,
hearts=[0, 0, 0, 0, 0, 0, 1], # Dummy
blade_hearts=[0, 0, 0, 0, 0, 0],
# Use raw ability text to test Parser, OR pre-parsed if testing Engine only.
# "【登場】自分のステージにコスト13以上のメンバーがいる場合、カードを1枚引く。\n【起動】【ターン1回】このメンバーをウェイトにする:{{heart_01.png}}か{{heart_03.png}}か{{heart_06.png}}のうち、1つを選ぶ。ライブ終了時まで、選んだハートを1つ得る。"
ability_text="【登場】自分のステージにコスト13以上のメンバーがいる場合、カードを1枚引く。\n【起動】【ターン1回】このメンバーをウェイトにする:ハートのピンクか黄色か紫のうち、1つを選ぶ。ライブ終了時まで、選んだハートを1つ得る。",
)
# Parse abilities for the mock
from compiler.parser import AbilityParser
parsed_abilities = AbilityParser.parse_ability_text(gs.member_db[mock_id].ability_text)
gs.member_db[mock_id].abilities = parsed_abilities
found_id = mock_id
# Also need a Cost 13 Dummy
mock_13_id = 99013
gs.member_db[mock_13_id] = MemberCard(
card_id=mock_13_id,
card_no="DUMMY-13",
name="Big Cost",
cost=13,
blades=1,
hearts=[1, 0, 0, 0, 0, 0, 0],
blade_hearts=[0, 0, 0, 0, 0, 0],
)
return gs, found_id, mock_13_id
def test_nico_on_entry_no_condition(game_state):
gs, nico_id, big_id = game_state
p = gs.players[0]
# Setup: Nico in hand, NO big member on stage
gs.inject_card(0, nico_id, "hand")
p.energy_zone = [200, 201, 202] # 3 Energy
# Clean stage
p.stage = [-1, -1, -1]
p.tapped_members.fill(False)
# Play Nico manually to trigger On Entry
# Move card to stage 0
card_id = p.hand.pop(0)
p.stage[0] = card_id
# Manually trigger On Entry resolution
# Normally handle_play_member would do this.
# We can inject Pending Effects if we want to test Engine resolution,
# OR we can assume Parser works and just test that the Condition holds/fails.
def test_nico_on_entry_no_condition(game_state):
gs, nico_id, big_id = game_state
# Nico in hand
gs.inject_card(0, nico_id, "hand", position=0)
# 5 Energy (to pay cost 2)
gs.players[0].tapped_energy = np.zeros(100, dtype=bool)
gs.players[0].energy_zone = [100, 101, 102, 103, 104]
# Phase MAIN
gs.phase = Phase.MAIN
gs.current_player = 0
initial_hand_size = len(gs.players[0].hand)
# Play Nico (Action 1: hand 0 to slot 0)
gs = gs.step(1)
# Condition: Cost 13+ member on stage.
# Stage has: [Nico(2), -1, -1]. Condition fails.
# Hand after play: 0.
assert len(gs.players[0].hand) == 0
assert gs.players[0].stage[0] == nico_id
def test_nico_on_entry_with_condition(game_state):
gs, nico_id, big_id = game_state
# Setup condition: Big member on slot 1
gs.players[0].stage[1] = big_id
# Nico in hand
gs.inject_card(0, nico_id, "hand", position=0)
# 5 Energy
gs.players[0].tapped_energy = np.zeros(100, dtype=bool)
gs.players[0].energy_zone = [100, 101, 102, 103, 104]
# Deck has cards
gs.players[0].main_deck = [300, 301, 302]
# Phase MAIN
gs.phase = Phase.MAIN
gs.current_player = 0
initial_hand_size = len(gs.players[0].hand)
# Play Nico (Action 1)
gs = gs.step(1)
# Condition: Cost 13+ member on stage.
# Stage has: [Nico(2), Big(13), -1]. Condition succeeds.
# Nico played (-1 hand), Draw 1 (+1 hand). Net change 0 (Nico was 1, then 0, then 1).
assert len(gs.players[0].hand) == initial_hand_size
assert gs.players[0].stage[0] == nico_id
def test_nico_action_ability(game_state):
gs, nico_id, big_id = game_state
# Inject Nico to stage 0
gs.inject_card(0, nico_id, "stage", position=0)
# Manual Ability Construction for Test Verification (since Parser/Engine missing COLOR_SELECT support)
from engine.models.ability import (
Ability,
AbilityCostType,
Condition,
ConditionType,
Cost,
Effect,
EffectType,
TargetType,
TriggerType,
)
# Ability 2: Action - Choose Heart
ability_action = Ability(
raw_text="Manually Constructed Ability",
trigger=TriggerType.ACTIVATED,
costs=[Cost(type=AbilityCostType.TAP_SELF, value=1)],
conditions=[Condition(ConditionType.TURN_1)],
effects=[
Effect(
EffectType.COLOR_SELECT,
value=1,
target=TargetType.PLAYER,
params={"choices": ["pink", "yellow", "purple"]},
),
Effect(
EffectType.ADD_HEARTS,
value=1,
target=TargetType.MEMBER_SELF,
params={
"color": "choice", # Special value indicating use previous choice
"until": "live_end",
},
),
],
)
# Needs to be Ability 2 (Index 1) in the list (On Entry is Index 0)
# Re-fetch parsed abilities to get the first one
from compiler.parser import AbilityParser
parsed_abilities = AbilityParser.parse_ability_text(gs.member_db[nico_id].ability_text)
# Override abilities
gs.member_db[nico_id].abilities = [parsed_abilities[0], ability_action]
# Set phase to MAIN_ACTION
gs.phase = Phase.MAIN
gs.current_player = 0
gs.players[0].tapped_members[0] = False # Ensure Active
gs.action_count_this_turn = 0
gs.verbose = True # Enable verbose logging
# Init JIT
GameState._init_jit_arrays()
# Debug: Print abilities
print(f"\nDEBUG: Abilities on Nico: {gs.member_db[nico_id].abilities}")
# Check legal actions
legal = gs.get_legal_actions()
print(f"DEBUG: Is Action 200 Legal? {legal[200]}")
if not legal[200]:
pytest.fail("Action 200 is not legal! Check parser or conditions.")
# Action ID for activating ability of Member 0 is 200
# Ref: game_state.py:1020 -> mask[200 + i] = True
# Executing action 200
gs = gs.step(200)
# Should trigger Choice (Pink/Yellow/Purple)
assert gs.pending_choices, "Should have pending choice for Heart select"
choice_type, params = gs.pending_choices[0]
assert choice_type == "COLOR_SELECT", f"Expected COLOR_SELECT, got {choice_type}"
# Select Pink (Action 580)
# Ref: game_state.py:1335 -> 580-585 are colors
# Pink=0, index is 0. 580 + 0 = 580.
gs.verbose = True
gs = gs.step(580)
# Verify buff
# Slot 0 should have extra hearts.
eff_hearts = gs.players[0].get_effective_hearts(0, gs.member_db)
# Pink is index 0
# Base is Nico (Dummy) -> [0,0,0,0,0,0,1]
# With buff -> Pink >= 1
assert eff_hearts[0] >= 1, f"Should have gained Pink heart. Got: {eff_hearts}"
# Verify Turn 1 limit
# Try using ability again
# Should be illegal because it is Turn 1
# Check legal actions
legal = gs.get_legal_actions()
assert not legal[200], "Action should be illegal (Turn 1 limit or Tapped)"
# Also verify it is tapped (Cost)
assert gs.players[0].tapped_members[0], "Member should be tapped"