"""启发式兜底 AI:无 LLM 配置(或 LLM 调用失败)时保证游戏可玩。""" from __future__ import annotations import random class HeuristicAgent: def __init__(self, player) -> None: self.player = player # ------------------------------------------------------------------ async def decide(self, kind: str, ctx: dict): handler = getattr(self, f"_decide_{kind}", None) if handler is None: return None return handler(ctx) async def speak(self, ctx: dict) -> str: return self._speak(ctx) # ------------------------------------------------------------------ 内部 def _alive_others(self, ctx: dict) -> list[int]: me = ctx["seat"] return [p["seat"] for p in ctx["alive"] if p["seat"] != me] def _claim_targets(self, ctx: dict) -> list[int]: claimed = [s for s in ctx.get("claimed_seers", [])] me = self.player mates = set(ctx.get("teammates", [])) return [s for s in claimed if s != me.seat and s not in mates] # ------------------------------------------------------------------ 决策 def _decide_kill(self, ctx: dict): victims = [o for o in ctx["options"]] claims = self._claim_targets(ctx) pool = [s for s in claims if s in victims] if pool: return random.choice(pool) return random.choice(victims) if victims else None def _decide_protect(self, ctx: dict): claims = [s for s in self._claim_targets(ctx) if s in ctx["options"]] if claims: return random.choice(claims) return random.choice(ctx["options"]) if ctx["options"] else None def _decide_check(self, ctx: dict): return random.choice(ctx["options"]) if ctx["options"] else None def _decide_witch_save(self, ctx: dict): target = ctx["options"][0] if ctx["options"] else None if target is None: return None if target == ctx["seat"]: return target # 首夜自救 if target in ctx.get("claimed_seers", []): return target return None # 留药 def _decide_witch_poison(self, ctx: dict): return None # 保守:不毒 def _decide_shoot(self, ctx: dict): return random.choice(ctx["options"]) if ctx["options"] else None def _decide_elect(self, ctx: dict): if ctx.get("role") == "seer": return 1 if ctx.get("role") == "werewolf": return 1 if random.random() < 0.3 else None return 1 if random.random() < 0.15 else None def _decide_sheriff_vote(self, ctx: dict): return random.choice(ctx["options"]) if ctx["options"] else None def _decide_badge(self, ctx: dict): claims = [s for s in ctx.get("claimed_seers", []) if s in ctx["options"]] if claims: return random.choice(claims) return random.choice(ctx["options"]) if ctx["options"] else None def _decide_vote(self, ctx: dict): claims = [s for s in self._claim_targets(ctx) if s in ctx["options"]] if claims and ctx.get("role") != "werewolf": # 好人跟跳预言家的对跳/划水者走:简单起见投被多疑的目标 pass pool = [o for o in ctx["options"] if o != ctx["seat"]] if not pool: return None if ctx.get("role") == "werewolf": mates = set(ctx.get("teammates", [])) pool = [o for o in pool if o not in mates] or pool return random.choice(pool) # ------------------------------------------------------------------ 发言 def _speak(self, ctx: dict) -> str: role = ctx.get("role") day = ctx.get("day", 1) purpose = ctx.get("purpose") seat = ctx["seat"] alive = [p["seat"] for p in ctx["alive"] if p["seat"] != seat] pick = random.choice(alive) if alive else None if purpose == "election": if role == "seer": return "我是预言家,想拿警徽带队报查验,请大家支持我。" return f"我{seat}号想为团队做点贡献,上警听听大家的发言。" if role == "seer": results = ";".join( f"{r['seat']}号是{r['result']}" for r in ctx.get("seer_results", [])[-2:]) or "暂时没有信息" return f"我是预言家,我的查验:{results}。请好人记住我的票。" if role == "werewolf": lines = [ f"我是个好人,昨天{pick}号的发言我觉得有点飘,大家可以听听。", "我没有太多信息,先听后置位怎么说。", f"这局信息很少,我倾向于{pick}号有问题,先投为敬。", ] return random.choice(lines) if role == "witch": return "我的身份信息暂时不方便透露,从发言上看我觉得节奏有点乱,大家稳住。" if role == "hunter": return f"我是普通村民视角,{pick}号的发言我听不太懂,保留怀疑。" if role == "guard": return "我从守卫……呃,从好人视角看,这局要小心悍跳,先听大家的。" if day == 1: return f"我是村民,没有什么信息,{pick}号的发言给我感觉还行,先观察。" return f"听到现在,我觉得{pick}号的发言前后有点矛盾,今天我可能跟票他。"