import numpy as np import pytest from engine.game.game_state import GameState, Phase from engine.models.ability import Effect, EffectType, TargetType from engine.models.card import LiveCard, MemberCard @pytest.fixture def base_state(): state = GameState() state.phase = Phase.MAIN state.current_player = 0 state.players[0].energy_zone = [1000, 1001, 1002, 1003, 1004] # 5 energy state.players[0].hand = [1, 2, 3] # Mock some basic cards m1 = MemberCard( card_id=1, card_no="M1", name="Member 1", cost=2, blades=2, hearts=np.array([2, 0, 0, 0, 0, 0, 0]), blade_hearts=np.zeros(7), ) m2 = MemberCard( card_id=2, card_no="M2", name="Member 2", cost=2, blades=2, hearts=np.array([0, 2, 0, 0, 0, 0, 0]), blade_hearts=np.zeros(7), ) m3 = MemberCard( card_id=3, card_no="M3", name="Member 3", cost=2, blades=2, hearts=np.array([0, 0, 2, 0, 0, 0, 0]), blade_hearts=np.zeros(7), ) state.member_db[1] = m1 state.member_db[2] = m2 state.member_db[3] = m3 return state def test_baton_touch_limit_extension(): state = GameState() state.phase = Phase.MAIN state.players[0].energy_zone = [1000] * 20 # Lots of energy state.member_db[1] = MemberCard( card_id=1, card_no="M1", name="M1", cost=1, blades=1, hearts=[0] * 7, blade_hearts=[0] * 7 ) state.member_db[2] = MemberCard( card_id=2, card_no="M2", name="M2", cost=1, blades=1, hearts=[0] * 7, blade_hearts=[0] * 7 ) state.member_db[3] = MemberCard( card_id=3, card_no="M3", name="M3", cost=1, blades=1, hearts=[0] * 7, blade_hearts=[0] * 7 ) state.member_db[4] = MemberCard( card_id=4, card_no="M4", name="M4", cost=1, blades=1, hearts=[0] * 7, blade_hearts=[0] * 7 ) state.member_db[5] = MemberCard( card_id=5, card_no="M5", name="M5", cost=1, blades=1, hearts=[0] * 7, blade_hearts=[0] * 7 ) p = state.players[0] p.hand = [1, 2, 3, 4] # 1. Fill stage without baton touches p.stage[0] = 1 p.stage[1] = 2 p.stage[2] = 3 p.hand = [4, 5] # Now slots are [1, 2, 3]. Baton count = 0, Limit = 1. # 2. Perform 1st baton touch on Slot 0 # Hand is [4, 5]. Action for Hand 0 to Slot 0 is 1 + 0*3 + 0 = 1. legal = state.get_legal_actions() assert legal[1], "Baton touch 1 (Hand 0 to Slot 0) should be legal" state = state.step(1) p = state.players[0] # Update p to the new state's player assert p.baton_touch_count == 1 assert p.stage[0] == 4 # 3. Try to perform 2nd baton touch (Should be illegal by default limit=1) # Hand is [5]. Action for Hand 0 to Slot 1 is 1 + 0*3 + 1 = 2. legal = state.get_legal_actions() assert not legal[2], "Baton touch 2 (Hand 0 to Slot 1) should be illegal (limit 1 reached)" # 4. Apply Modifier (+1 baton touch) p.continuous_effects.append( {"effect": Effect(EffectType.BATON_TOUCH_MOD, 1, TargetType.SELF), "expiry": "TURN_END"} ) legal = state.get_legal_actions() assert legal[2], "Baton touch 2 should be legal after BATON_TOUCH_MOD" def test_heart_requirement_reduction(base_state): p = base_state.players[0] # Card 1 is in Slot 0 (2 Pink hearts) p.stage[0] = 1 p.hand = [2, 3] # Mock Live Card: Needs 3 Pink hearts l1 = LiveCard(card_id=101, card_no="L1", name="Live 1", score=2, required_hearts=np.array([3, 0, 0, 0, 0, 0, 0])) base_state.live_db[101] = l1 p.live_zone = [101] # 1. Normal Performance: Should fail (2 < 3) guide = p.get_performance_guide(base_state.live_db, base_state.member_db) assert guide["lives"][0]["passed"] == False, "Should fail without reduction" # 2. Add REDUCE_HEART_REQ (-1 Pink) p.continuous_effects.append( {"effect": Effect(EffectType.REDUCE_HEART_REQ, 1, TargetType.SELF, {"color": 1}), "expiry": "LIVE_END"} ) # 3. Now should pass (2 >= 3-1) guide = p.get_performance_guide(base_state.live_db, base_state.member_db) assert guide["lives"][0]["passed"] == True, ( f"Should pass with reduction. Req: {guide['lives'][0]['req']}, Have: {guide['total_hearts']}" ) def test_color_transform(base_state): p = base_state.players[0] # Card 2 has 2 Red hearts. Place in Slot 0. p.stage[0] = 2 # Mock Live Card: Needs 2 Pink hearts l1 = LiveCard( card_id=101, card_no="L101", name="Live 101", score=2, required_hearts=np.array([2, 0, 0, 0, 0, 0, 0]) ) base_state.live_db[101] = l1 p.live_zone = [101] # 1. Normal: Fails (Needs pink, has red) guide = p.get_performance_guide(base_state.live_db, base_state.member_db) assert guide["lives"][0]["passed"] == False, "Should fail (Needs Pink, has Red)" # 2. Add TRANSFORM_COLOR (Red -> Pink) p.continuous_effects.append( { "effect": Effect(EffectType.TRANSFORM_COLOR, 0, TargetType.SELF, {"from_color": 2, "to_color": 1}), "expiry": "LIVE_END", } ) # 3. Now should pass guide = p.get_performance_guide(base_state.live_db, base_state.member_db) assert guide["total_hearts"][0] == 2, f"Should have 2 Pink hearts. Got {guide['total_hearts']}" assert guide["total_hearts"][1] == 0, f"Should have 0 Red hearts. Got {guide['total_hearts']}" assert guide["lives"][0]["passed"] == True, "Should pass after transform"