from engine.game.game_state import GameState, initialize_game from engine.models.ability import AbilityCostType, TriggerType def test_optional_energy_cost_bp3_001(): """ Test card LL-bp3-001-R+: {{live_start.png|ライブ開始時}}{{icon_energy.png|E}}x6支払ってもよい:... Verify that the cost is treated as optional in the engine. """ gs = initialize_game(use_real_data=True) # Search for card by number since index might vary card_no = "LL-bp3-001-R+" card = None for c in gs.member_db.values(): if c.card_no == card_no: card = c break assert card is not None, f"Card {card_no} not found" # Find the Live Start ability ability = next(a for a in card.abilities if a.trigger == TriggerType.ON_LIVE_START) # Check if the cost is optional in the database assert ability.costs[0].is_optional, f"Cost for {card.card_no} should be optional" def test_optional_discard_cost_bp1_001(): """ Test card LL-bp1-001-R+: ...好きな組み合わせで合計3枚、控え室に置いてもよい:... Verify that the cost is treated as optional in the engine. """ gs = initialize_game(use_real_data=True) card_no = "LL-bp1-001-R+" card = None for c in gs.member_db.values(): if c.card_no == card_no: card = c break assert card is not None, f"Card {card_no} not found" # This card might have its May Discard in effects if the colon was weird, # but based on my fix it should ideally be in costs if recognized. # Wait, let's see where it landed in cards_compiled.json. # Actually, let's just assert it HAS an optional cost if it should. ability = next(a for a in card.abilities if a.trigger == TriggerType.ON_LIVE_START) # If it's a cost, it should be optional. # Note: Some cards might have 'may discard' in effects instead of costs # if the parser is inconsistent, but the bug report specifically mentioned Costs. if ability.costs: assert ability.costs[0].is_optional, f"Cost for {card.card_no} should be optional" else: # If it's in effects, check if the effect is optional. assert any(e.is_optional for e in ability.effects), f"Effect for {card.card_no} should be optional" def test_optional_discard_cost_logic(): """ Test that if a cost is marked as optional, get_legal_actions allows passing. """ gs = GameState() p_idx = gs.current_player p = gs.players[p_idx] # Inject a dummy pending choice for an optional cost from engine.models.ability import Ability, Cost dummy_ability = Ability( raw_text="Test", trigger=TriggerType.ON_PLAY, effects=[], costs=[Cost(type=AbilityCostType.DISCARD_HAND, value=1, is_optional=True)], ) gs.pending_choices.append( ("TARGET_HAND", {"player_id": p_idx, "is_optional": True, "effect": "discard", "count": 1}) ) mask = gs.get_legal_actions() # Action 0 is 'Pass' / 'Cancel' assert mask[0], "Action 0 (Pass) should be legal for an optional cost"