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,87 @@
|
||||
"""AI 玩家提示词:身份人设、决策 JSON、公开发言。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
RULES = """【狼人杀规则简述】
|
||||
- 夜晚顺序:守卫守护 → 狼人袭击 → 预言家查验 → 女巫用药(解药/毒药各一瓶,每夜限一瓶,首夜可自救)。
|
||||
- 守卫不能连续两晚守同一人;「同守同救」(守卫和女巫同时保护被袭者)会导致该玩家死亡。
|
||||
- 白天:警长竞选(首日)→ 轮流发言 → 全员投票放逐(平票无人出局,警长票计1.5票)。
|
||||
- 猎人被投票或被狼人杀死时可开枪带走一人(被毒杀不能开枪)。
|
||||
- 胜负:狼人杀光所有神职或所有村民(屠边)即获胜;好人放逐所有狼人则获胜。"""
|
||||
|
||||
KIND_QUESTIONS = {
|
||||
"kill": ("作为狼人,选择今晚袭击的目标",
|
||||
"通常优先袭击跳预言家的玩家、发言逻辑威胁大的玩家;也要考虑票型,避开会被怀疑的队友策略。"),
|
||||
"protect": ("作为守卫,选择今晚守护的玩家",
|
||||
"通常守护跳预言家的玩家或你认为重要的神职;也可以空守。"),
|
||||
"check": ("作为预言家,选择今晚查验的玩家",
|
||||
"优先验身份模糊、立场可疑的玩家,积累信息。"),
|
||||
"witch_save": ("作为女巫,决定是否使用解药救人",
|
||||
"如果被袭者是跳预言家的关键角色或你自己(首夜),通常值得救;也要提防狼人骗药。"),
|
||||
"witch_poison": ("作为女巫,决定是否使用毒药毒杀一人",
|
||||
"只有在有较强把握某人是狼时才用毒,误毒好人会严重伤害好人阵营;可以不用。"),
|
||||
"shoot": ("作为猎人,选择开枪带走的目标",
|
||||
"带走你认为最像狼的玩家;没把握也可以不开枪。"),
|
||||
"elect": ("决定是否上警竞选警长",
|
||||
"跳预言家通常应该上警来报查验;狼人可以选择不上警隐藏身份或悍跳预言家。"),
|
||||
"sheriff_vote": ("投票选出警长", "把票投给你认为能带领好人、发言可信的候选人。"),
|
||||
"badge": ("作为死亡的警长,决定警徽流向",
|
||||
"通常把警徽交给最信任的好人(比如你验过的好人);也可以撕毁警徽表达态度。"),
|
||||
"vote": ("投票放逐一名玩家", "根据发言、逻辑和票型投出你最怀疑的狼人;没把握可以弃票。"),
|
||||
}
|
||||
|
||||
|
||||
def _state_text(ctx: dict) -> str:
|
||||
alive = "、".join(f"{p['seat']}号{p['name']}" for p in ctx["alive"])
|
||||
priv = "\n".join(ctx["private"]) if ctx["private"] else "无"
|
||||
return (
|
||||
f"【当前局面】第{ctx['day']}天,{ctx['phase_text']}\n"
|
||||
f"【存活玩家】{alive}\n"
|
||||
f"【你的私有信息】\n{priv}\n"
|
||||
f"【对局记录(公开部分,最近在前为旧)】\n{ctx['log_text'] or '(暂无)'}"
|
||||
)
|
||||
|
||||
|
||||
def decision_messages(kind: str, ctx: dict) -> tuple[str, str]:
|
||||
title, tip = KIND_QUESTIONS.get(kind, (kind, ""))
|
||||
options = ctx["options"]
|
||||
opts_text = "、".join(str(o) + "号" for o in options) if options else "无"
|
||||
allow_skip = kind in ("witch_save", "witch_poison", "shoot", "elect", "badge", "vote", "protect", "kill")
|
||||
system = (
|
||||
f"你正在玩一局狼人杀。你的名字是「{ctx['name']}」,座位号是{ctx['seat']}号。\n"
|
||||
f"你的身份:{ctx['role_name']} —— {ctx['role_desc']}\n"
|
||||
f"你的性格:{ctx['persona'] or '普通玩家'}\n"
|
||||
f"{RULES}\n"
|
||||
"你是一个真实的人类玩家。永远不要提及你是AI、模型或程序。"
|
||||
)
|
||||
user = (
|
||||
f"{_state_text(ctx)}\n\n"
|
||||
f"【你的决策】{title}。\n{tip}\n"
|
||||
f"可选目标(座位号):{opts_text}\n"
|
||||
+ ("你可以选择放弃(target 为 null)。\n" if allow_skip else "必须选择一个目标。\n")
|
||||
+ '只输出 JSON:{"target": 座位号数字或null, "reason": "不超过30字的理由"}'
|
||||
)
|
||||
return system, user
|
||||
|
||||
|
||||
def speech_messages(ctx: dict) -> tuple[str, str]:
|
||||
purpose = ctx.get("purpose") or "day"
|
||||
if purpose == "election":
|
||||
task = "现在是警长竞选阶段,请发表你的竞选发言(表决心、报身份或藏身份,符合你的策略)。"
|
||||
else:
|
||||
task = "轮到你发言了。请分析当前局势,表达你的身份立场、怀疑对象和投票意向。"
|
||||
system = (
|
||||
f"你正在玩一局狼人杀。你的名字是「{ctx['name']}」,座位号是{ctx['seat']}号。\n"
|
||||
f"你的身份:{ctx['role_name']} —— {ctx['role_desc']}\n"
|
||||
f"你的性格:{ctx['persona'] or '普通玩家'}\n"
|
||||
f"{RULES}\n"
|
||||
"你是一个真实的人类玩家。永远不要提及你是AI、模型或程序。"
|
||||
"发言要口语化、简短自然,像真人打字,可以引用别人的发言和票型,不要罗列条目。"
|
||||
)
|
||||
user = f"{_state_text(ctx)}\n\n【任务】{task}\n直接输出发言内容本身(2~5句话),不要任何前缀、引号或解释。"
|
||||
return system, user
|
||||
|
||||
|
||||
def options_json(options: list) -> str:
|
||||
return json.dumps(options, ensure_ascii=False)
|
||||
Reference in new issue
Block a user