LovecaSim / ai /verify_search_agent.py
trioskosmos's picture
Upload ai/verify_search_agent.py with huggingface_hub
c7ff874 verified
import os
import sys
import numpy as np
# Ensure project root is in path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from ai.search_prob_agent import SearchProbAgent, YellOddsCalculator
from engine.game.enums import Phase as PhaseEnum
from engine.game.game_state import GameState
from engine.models.card import LiveCard, MemberCard
def test_odds_calculator():
print("Testing YellOddsCalculator...")
# Mock databases
member_db = {
1: MemberCard(
card_id=1,
card_no="M1",
name="Red Girl",
cost=1,
hearts=np.array([1, 0, 0, 0, 0, 0, 0]),
blade_hearts=np.array([1, 0, 0, 0, 0, 0, 0]),
blades=1,
),
2: MemberCard(
card_id=2,
card_no="M2",
name="Blue Girl",
cost=1,
hearts=np.array([0, 0, 0, 0, 0, 1, 0]),
blade_hearts=np.array([0, 0, 0, 0, 0, 1, 0]),
blades=1,
),
}
live_db = {
100: LiveCard(
card_id=100, card_no="L1", name="Red Live", score=2, required_hearts=np.array([2, 0, 0, 0, 0, 0, 0])
), # Needs 2 Red
}
calc = YellOddsCalculator(member_db, live_db)
# Situation: 1 Red on stage, 1 Red in deck, 1 Yell remaining.
stage_hearts = np.array([1, 0, 0, 0, 0, 0, 0])
deck = [1, 2, 2, 2] # 1 Red, 3 Blue
# 1 Yell: Odds should be 1/4 = 0.25
odds = calc.calculate_odds(deck, stage_hearts, [100], num_yells=1, samples=1000)
print(f"Odds with 1 yell: {odds:.3f} (Expected ~0.25)")
# 2 Yells: Odds should be higher.
# Combinations of 2 from 4: (1,B), (1,B), (1,B), (B,B), (B,B), (B,B) -> 3/6 = 0.5
odds = calc.calculate_odds(deck, stage_hearts, [100], num_yells=2, samples=1000)
print(f"Odds with 2 yells: {odds:.3f} (Expected ~0.50)")
# Needs ANY
live_db[101] = LiveCard(
card_id=101, card_no="L2", name="Any Live", score=1, required_hearts=np.array([0, 0, 0, 0, 0, 0, 2])
)
odds = calc.calculate_odds(deck, stage_hearts, [101], num_yells=1, samples=1000)
print(
f"Any Odds with 1 yell (Stage has 1, needs 2 total): {odds:.3f} (Expected ~1.0 because staging 1 counts as any if no specific color? Wait.)"
)
# Actually engine check_meet for ANY:
# staging [1,0,0,0,0,0,0] -> 1 Red.
# L101 needs 2 ANY.
# Red heart CAN be used as ANY.
# So we have 1 'available any'. We need 2.
# If Yell gives 1 Blue, we have 2 hearts total -> Successful.
# Since every card in deck has 1 heart, any yell card makes it successful.
def test_search_agent():
print("\nTesting SearchProbAgent...")
# Clear and set class-level databases (REQUIRED by game engine)
GameState.member_db = {
1: MemberCard(
card_id=1,
card_no="M1",
name="Cheap",
cost=1,
hearts=np.array([1, 0, 0, 0, 0, 0, 0]),
blade_hearts=np.array([1, 0, 0, 0, 0, 0, 0]),
blades=1,
),
10: MemberCard(
card_id=10,
card_no="M10",
name="Expensive",
cost=5,
hearts=np.array([1, 1, 1, 1, 1, 1, 1]),
blade_hearts=np.array([1, 1, 1, 1, 1, 1, 1]),
blades=3,
),
}
GameState.live_db = {
100: LiveCard(
card_id=100, card_no="L1", name="Easy Live", score=2, required_hearts=np.array([0, 0, 0, 0, 0, 0, 1])
),
}
state = GameState()
state.phase = PhaseEnum.MAIN
state.current_player = 0
state.players[0].hand = [1, 10]
state.players[0].energy_zone = [200, 200] # 2 energy
legal_mask = state.get_legal_actions()
legal_indices = np.where(legal_mask)[0]
print(f"Legal actions: {legal_indices}")
# Debug: Manually test step() to see if it works
print(f"\n[DEBUG] Before step: Stage = {list(state.players[0].stage)}, Hand = {state.players[0].hand}")
for action in legal_indices[:4]:
ns = state.copy()
ns = ns.step(action)
print(f" Action {action}: Stage = {list(ns.players[0].stage)}, Hand = {ns.players[0].hand}")
agent = SearchProbAgent(depth=1)
action = agent.choose_action(state, 0)
print(f"\nChosen action: {action}")
# Expected: Should play card 1 (id 1) because it can afford it (cost 1 < 2).
# Card 10 (id 10) costs 5, which is too much.
# Action 1 (index 0 in hand to slot 0) or similar.
if __name__ == "__main__":
test_odds_calculator()
test_search_agent()