Files
werewolf/backend/app/game/ai/fallback.py
T

128 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""启发式兜底 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}号的发言前后有点矛盾,今天我可能跟票他。"