LovecaSim / engine /tests /steps /test_recovery_steps.py
trioskosmos's picture
Upload folder using huggingface_hub
bb3fbf9 verified
import numpy as np
from pytest_bdd import given, parsers, scenarios, then, when
from engine.game.game_state import GameState
from engine.models.ability import Effect, EffectType
from engine.models.card import LiveCard, MemberCard
from engine.models.enums import Group
scenarios("../features/recovery.feature")
# --- Fixtures & Givens ---
@given("a player with a discard pile", target_fixture="player_with_discard")
def player_with_discard(context, game_state):
p = game_state.players[0]
p.hand = []
p.discard = []
context["initial_hand_size"] = 0
return p
@given(parsers.parse('the discard pile contains a live card "{name}" with ID {cid:d}'))
def add_live_to_discard(context, game_state, data, name, cid):
p = game_state.players[0]
_, live_db = data
# Create simple live card if not in DB
if cid not in live_db:
# Create a mock/real live card
# We need to insert it into the DB fixture?
# The data fixture loads from JSON. We can monkeypatch into the generic DB.
# Use real LiveCard
live_db[cid] = LiveCard(
card_id=cid,
card_no=f"LIVE-{cid}",
name=name,
score=1000,
required_hearts=np.zeros(7),
abilities=[],
groups=[],
units=[],
img_path="",
)
p.discard.append(cid)
context[f"card_id_{name}"] = cid
@given(parsers.parse('the discard pile contains a member card "{name}" with ID {cid:d}'))
def add_member_to_discard(context, game_state, data, name, cid):
p = game_state.players[0]
member_db, _ = data
# Create mock/real member
if cid not in member_db:
member_db[cid] = MemberCard(
card_id=cid,
card_no=f"TEST-{cid}",
name=name,
cost=1,
groups=[],
hearts=np.zeros(6),
blade_hearts=np.zeros(7),
blades=1,
)
p.discard.append(cid)
context[f"card_id_{name}"] = cid
@given("the discard pile contains the following members:")
def add_members_table(context, game_state, data, datatable):
# p = game_state.players[0]
member_db, _ = data
# datatable is a list of rows (if supported by pytest-bdd table parser)?
# Actually pytest-bdd "datatable" usually comes from Examples or manual parsing if step uses "datatable" param name?
# No, generic steps don't support table automatically unless parsed.
# We should use `scenarios` and `parsers` carefully or manual table parsing.
# For now, we assume simple step implementation parsing context logic manually or using simple loop if possible?
# Actually, allow `datatable` or similar logic.
# Wait, BDD standard step doesn't pass a table object unless specifically configured.
# I'll manually parse headings if needed, but `pytest-bdd` doesn't pass table in step args automatically like Behave.
# I'll implement it by parsing row by row or assume the step handles setup manually?
# Ah, pytest-bdd uses `target_fixture` or we access `request`?
# Actually, simpler: just iterate in the test code? No.
# I will replace the datatable step with individual "And" steps in the feature file if I can't easily parse table.
# BUT, I want to learn. Let's use simple multiple "And" steps or parse distinct lines.
# Actually, let's keep it simple for now and use specific step setups in the feature, or handle dict-like parsing?
# I'll use a simplified implementation where I define specific members in the python code matching the scenario.
# Let's pivot: The step definition will just setup specific hardcoded data for this scenario "Recover a member with filters".
# Or I can use `parsers.parse`? No.
# I'll implement "the discard pile contains the following members:" but the datatable handling is tricky.
# I'll rewrite the feature to be explicit step-by-step to avoid table parsing complexity in `pytest-bdd`.
pass
# Simplified step for the specific scenario
@given("the discard pile contains the test members for filtering")
def add_test_members_filtering(context, game_state, data):
p = game_state.players[0]
member_db, _ = data
members = [
(400, "Kotori", "μ's", 3),
(401, "Honoka", "μ's", 5),
(402, "Chika", "Aqours", 2),
]
for cid, name, group, cost in members:
member_db[cid] = MemberCard(
card_id=cid,
card_no=f"TEST-{cid}",
name=name,
cost=cost,
groups=[Group.from_japanese_name(group)],
hearts=np.zeros(6),
blade_hearts=np.zeros(7),
blades=1,
)
p.discard.append(cid)
context[f"card_id_{name}"] = cid
# --- Whhen steps ---
@when("the player activates an effect to recover a live card")
def activate_recover_live(context, game_state):
p = game_state.players[0]
print(f"DEBUG: Discard before recovery: {p.discard}")
print(f"DEBUG: LiveDB keys: {list(GameState.live_db.keys())}")
print(f"DEBUG: Checking discard: {[cid in GameState.live_db for cid in p.discard]}")
effect = Effect(EffectType.RECOVER_LIVE, 1)
game_state.pending_effects.append(effect)
# Trigger effect resolution
game_state = game_state.step(0)
context["game_state"] = game_state
# Check pending
print(f"DEBUG: Pending effects: {game_state.pending_effects}")
print(f"DEBUG: Post-step pending choices: {game_state.pending_choices}")
@when("the player activates an effect to recover a member card")
def activate_recover_member(context, game_state):
effect = Effect(EffectType.RECOVER_MEMBER, 1)
game_state.pending_effects.append(effect)
game_state = game_state.step(0)
context["game_state"] = game_state
@when(parsers.parse('the player activates an effect to recover a member with group "{group}" and max cost {cost:d}'))
def activate_recover_filtered(context, game_state, group, cost):
effect = Effect(EffectType.RECOVER_MEMBER, 1, params={"group": group, "cost_max": cost})
game_state.pending_effects.append(effect)
game_state = game_state.step(0)
context["game_state"] = game_state
@when(parsers.parse('the player selects "{name}" from the recovery choices'))
def select_card_by_name(context, game_state, name):
current_state = context["game_state"]
cid = context[f"card_id_{name}"]
# Validate choice exists
assert len(current_state.pending_choices) > 0
choice = current_state.pending_choices[0]
assert choice[0] == "SELECT_FROM_DISCARD"
available = choice[1]["cards"]
assert cid in available, f"Card {name} ({cid}) not in available choices {available}"
idx = available.index(cid)
# Action 660 + index
current_state = current_state.step(660 + idx)
context["game_state"] = current_state
# --- Then steps ---
@then(parsers.parse('"{name}" should be in the player\'s hand'))
def check_in_hand_named(context, game_state, name):
current_state = context["game_state"]
p = current_state.players[0]
cid = context[f"card_id_{name}"]
assert cid in p.hand
@then(parsers.parse('"{name}" should not be in the player\'s discard'))
def check_not_in_discard_named(context, game_state, name):
current_state = context["game_state"]
p = current_state.players[0]
cid = context[f"card_id_{name}"]
assert cid not in p.discard
@then(parsers.parse('the recovery choices should include "{name}"'))
def check_choice_includes(context, game_state, name):
current_state = context["game_state"]
cid = context[f"card_id_{name}"]
choice = current_state.pending_choices[0]
available = choice[1]["cards"]
assert cid in available
@then(parsers.parse('the recovery choices should not include "{name}"'))
def check_choice_excludes(context, game_state, name):
current_state = context["game_state"]
cid = context[f"card_id_{name}"]
choice = current_state.pending_choices[0]
available = choice[1]["cards"]
assert cid not in available