Spaces:
Running
Running
| 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 | |
| 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" | |