Murder.Ai / game /game_engine.py
Justxd22's picture
Implement AI Detective functionality with spectator mode and voice integration
86a82e2
import uuid
from .scenario_generator import generate_crime_scenario
from .llm_manager import LLMManager
from .voice_manager import VoiceManager
from .ai_detective import AIDetective
from mcp import tools
class GameInstance:
def __init__(self, difficulty="medium"):
self.id = str(uuid.uuid4())
self.scenario = generate_crime_scenario(difficulty)
self.llm_manager = LLMManager()
self.voice_manager = VoiceManager()
self.ai_detective = None # Initialized later to avoid circular dep issues if any, or just now.
self.round = 1
self.max_rounds = 3 # 3 Chances
self.points = 10
self.evidence_revealed = [] # List of strings or result dicts
self.logs = [] # Chat logs
self.game_over = False
self.verdict_correct = False
self.eliminated_suspects = []
self.unlocked_evidence = [] # Track unlocked DNA items
# Initialize Agents
self._init_agents()
self.ai_detective = AIDetective(self)
def run_ai_step(self):
"""Executes one turn for the AI Detective."""
if self.game_over:
return {"thought": "Game Over.", "action": "none"}
decision = self.ai_detective.decide_next_move()
action_type = decision.get("action")
thought = decision.get("thought", "Thinking...")
result = {}
if action_type == "use_tool":
tool_name = decision.get("tool_name")
kwargs = decision.get("args", {})
result = self.use_tool(tool_name, **kwargs)
result["type"] = "evidence"
elif action_type == "chat":
suspect_id = decision.get("suspect_id")
msg = decision.get("message")
response = self.question_suspect(suspect_id, msg)
result = {
"type": "chat",
"suspect_id": suspect_id,
"question": msg,
"response": response
}
elif action_type == "accuse":
suspect_id = decision.get("suspect_id")
# Map suspect_id if it's just "suspect_1" (which it is)
# But make_accusation handles ID.
outcome = self.make_accusation(suspect_id)
result = {
"type": "game_over",
"outcome": outcome
}
# Record result for AI memory
self.ai_detective.record_result(action_type, result)
return {
"thought": thought,
"action": action_type,
"result": result
}
def _init_agents(self):
# 1. Detective
detective_context = {
"victim_name": self.scenario["victim"]["name"],
"time_of_death": self.scenario["victim"]["time_of_death"],
"location": self.scenario["evidence"]["location_data"].get("suspect_1_phone", {}).get("8:47 PM", {}).get("location", "Unknown"), # Approximate
"investigation_state": "Initial briefing."
}
self.llm_manager.create_agent("detective", "detective", detective_context)
# 2. Suspects
for i, suspect in enumerate(self.scenario["suspects"]):
role = "murderer" if suspect["is_murderer"] else "witness"
# Generate or retrieve Alibi ID
# In a real app, this should be in the JSON. For now, we synth it.
alibi_id = suspect.get("alibi_id", f"ALIBI-{100+i}")
suspect["alibi_id"] = alibi_id
# Assign Voice
if "gender" in suspect:
suspect["voice_id"] = self.voice_manager.assign_voice(suspect["gender"], suspect.get("role", ""))
# Context for prompt
context = {
"name": suspect["name"],
"victim_name": self.scenario["victim"]["name"],
"alibi_story": suspect["alibi_story"],
"bio": suspect["bio"],
"true_location": suspect["true_location"],
"phone_number": suspect.get("phone_number", "Unknown"),
"alibi_id": alibi_id, # Inject Alibi ID
# Murderer specific
"method": self.scenario["title"],
"location": self.scenario["victim"].get("location", "the scene"),
"motive": suspect["motive"]
}
self.llm_manager.create_agent(suspect["id"], role, context)
def log_event(self, speaker, message):
self.logs.append({"speaker": speaker, "message": message})
def question_suspect(self, suspect_id, question):
if self.game_over:
return "Game Over"
suspect_name = next((s["name"] for s in self.scenario["suspects"] if s["id"] == suspect_id), "Unknown")
# 1. Detective asks (simulated log)
self.log_event("Detective", f"To {suspect_name}: {question}")
# 2. Suspect responds
response = self.llm_manager.get_response(suspect_id, question)
self.log_event(suspect_name, response)
return response
def use_tool(self, tool_name, **kwargs):
if self.points <= 0:
return {"error": "Not enough investigation points!"}
cost = 0
result = {}
# Map tool names to functions
if tool_name == "get_location":
cost = 2
result = tools.get_location(self.scenario, kwargs.get("phone_number"), kwargs.get("timestamp"))
elif tool_name == "get_footage":
cost = 3
result = tools.get_footage(self.scenario, kwargs.get("location"), kwargs.get("time_range"))
# Handle unlocks
if "unlocks" in result:
new_items = []
for item_id in result["unlocks"]:
if item_id not in self.unlocked_evidence:
self.unlocked_evidence.append(item_id)
new_items.append(item_id)
result["newly_unlocked"] = new_items
elif tool_name == "get_dna_test":
cost = 4
result = tools.get_dna_test(self.scenario, kwargs.get("evidence_id"))
elif tool_name == "call_alibi":
cost = 1
result = tools.call_alibi(self.scenario, **kwargs)
else:
return {"error": f"Unknown tool: {tool_name}"}
if "error" in result:
return result # Don't deduct points for errors
self.points -= cost
# Inject input context for AI memory
if isinstance(result, dict):
result["_input_args"] = kwargs
self.evidence_revealed.append(result)
self.log_event("System", f"Used {tool_name}. Cost: {cost} pts. Result: {str(result)}")
return result
def advance_round(self):
# Deprecated: Rounds advance via accusation now
pass
def make_accusation(self, suspect_id):
murderer = next((s for s in self.scenario["suspects"] if s["is_murderer"]), None)
suspect_name = next((s["name"] for s in self.scenario["suspects"] if s["id"] == suspect_id), "Unknown")
if murderer and murderer["id"] == suspect_id:
self.game_over = True
self.verdict_correct = True
return {
"result": "win",
"message": f"CORRECT! {murderer['name']} was the murderer."
}
else:
# Wrong accusation
self.eliminated_suspects.append(suspect_id)
self.log_event("System", f"Incorrect accusation: {suspect_name}. Eliminated.")
if self.round >= self.max_rounds:
self.game_over = True
self.verdict_correct = False
return {
"result": "loss",
"message": f"WRONG. That was your last chance. The killer was {murderer['name']}."
}
else:
# Advance Round
self.round += 1
self.points += 5
return {
"result": "continue",
"message": f"{suspect_name} is INNOCENT. +5 Points. Try again.",
"eliminated_id": suspect_id,
"new_round": self.round,
"new_points": self.points
}
# Global Session Store
SESSIONS = {}
def start_game(difficulty="medium"):
game = GameInstance(difficulty)
SESSIONS[game.id] = game
return game.id, game
def get_game(session_id):
return SESSIONS.get(session_id)