Initial commit: 网页联机狼人杀(FastAPI+WebSocket 后端 / React+TS+Sass 前端)
This commit is contained in:
commit
7f5ea6bee8
55 files changed
+6999
No files matched your search
@@ -0,0 +1,127 @@
|
||||
"""启发式兜底 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}号的发言前后有点矛盾,今天我可能跟票他。"
|
||||
Reference in new issue
Block a user