128 lines
5.4 KiB
Python
128 lines
5.4 KiB
Python
"""启发式兜底 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}号的发言前后有点矛盾,今天我可能跟票他。"
|