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

88 lines
5.1 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 玩家提示词:身份人设、决策 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)