第25章 狼人杀游戏案例
本章深入分析AgentScope-Java中的狼人杀游戏示例,这是一个完整的多智能体博弈系统,展示了如何使用框架构建复杂的交互式游戏。
25.1 项目概述
25.1.1 游戏特性
狼人杀(Werewolf)是一个经典的社交推理游戏,AgentScope-Java的实现具有以下特性:
| 特性 | 描述 |
|---|---|
| 多智能体博弈 | 多个AI Agent扮演不同角色进行对抗 |
| Human-in-the-Loop | 支持人类玩家参与游戏 |
| 角色系统 | 村民、狼人、预言家、女巫、猎人 |
| Web界面 | 基于SSE的实时Web界面 |
| 多语言支持 | 中英文本地化 |
| 事件可见性 | 基于角色的信息隔离 |
25.1.2 项目结构
werewolf-hitl/
├── src/main/java/io/agentscope/examples/werewolf/
│ ├── web/
│ │ ├── WerewolfWebGame.java # 游戏主逻辑
│ │ ├── WerewolfWebController.java # Web控制器
│ │ ├── WebUserInput.java # 用户输入处理
│ │ ├── GameEventEmitter.java # 事件发射器
│ │ ├── GameEvent.java # 事件模型
│ │ └── EventVisibility.java # 事件可见性
│ ├── entity/
│ │ ├── Player.java # 玩家实体
│ │ ├── Role.java # 角色枚举
│ │ └── GameState.java # 游戏状态
│ ├── model/
│ │ ├── VoteModel.java # 投票Schema
│ │ ├── SeerCheckModel.java # 预言家查验Schema
│ │ ├── WitchHealModel.java # 女巫救人Schema
│ │ ├── WitchPoisonModel.java # 女巫毒人Schema
│ │ └── HunterShootModel.java # 猎人开枪Schema
│ ├── localization/
│ │ ├── GameMessages.java # 游戏消息接口
│ │ ├── PromptProvider.java # 提示词提供者
│ │ └── LanguageConfig.java # 语言配置
│ ├── WerewolfGameConfig.java # 游戏配置
│ ├── GameConfiguration.java # 角色配置
│ └── WerewolfUtils.java # 工具类
└── src/main/resources/
└── templates/ # 前端模板
25.1.3 架构图
┌──────────────────────────────────────────────────────────────────────┐
│ Werewolf Game Architecture │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Web Layer │ │
│ │ ┌──────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ WerewolfWeb │ │ SSE Events │ │ WebSocket │ │ │
│ │ │ Controller │ │ (实时推送) │ │ (用户输入) │ │ │
│ │ └────────┬─────────┘ └────────┬────────┘ └────────┬────────┘ │ │
│ └───────────┼─────────────────────┼────────────────────┼──────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Game Engine │ │
│ │ ┌──────────────────────────────────────────────────────────┐ │ │
│ │ │ WerewolfWebGame │ │ │
│ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │
│ │ │ │ Night Phase │ │ Day Phase │ │ Win Check │ │ │ │
│ │ │ │ - 狼人杀人 │ │ - 讨论发言 │ │ - 狼人胜利 │ │ │ │
│ │ │ │ - 女巫行动 │ │ - 投票处决 │ │ - 村民胜利 │ │ │ │
│ │ │ │ - 预言查验 │ │ - 猎人开枪 │ │ │ │ │ │
│ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │
│ │ └──────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ ReActAgent │ │ ReActAgent │ │ UserAgent │ │
│ │ (AI玩家) │ │ (AI玩家) │ │ (人类玩家) │ │
│ │ │ │ │ │ │ │
│ │ ┌───────────┐│ │ ┌───────────┐│ │ ┌───────────┐│ │
│ │ │ 角色: 狼人│ │ │ │ 角色: 村民│ │ │ │角色: 预言家││ │
│ │ │ 记忆 │ │ │ │ 记忆 │ │ │ │WebUserInput││ │
│ │ │ 策略 │ │ │ │ 策略 │ │ │ │ ││ │
│ │ └───────────┘│ │ └───────────┘│ │ └───────────┘│ │
│ └───────────────┘ └───────────────┘ └───────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Support Systems │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ GameEventEmitter│ │ GameState │ │ Localization │ │ │
│ │ │ (事件广播) │ │ (状态管理) │ │ (多语言) │ │ │
│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
25.2 核心实体设计
25.2.1 角色系统
/**
* 游戏角色枚举
* 每个角色属于村民阵营或狼人阵营
*/
public enum Role {
VILLAGER("Villager", "villager"), // 村民
WEREWOLF("Werewolf", "werewolf"), // 狼人
SEER("Seer", "seer"), // 预言家
WITCH("Witch", "witch"), // 女巫
HUNTER("Hunter", "hunter"); // 猎人
private final String displayName;
private final String camp; // "villager" 或 "werewolf"
Role(String displayName, String camp) {
this.displayName = displayName;
this.camp = camp;
}
public String getDisplayName() {
return displayName;
}
public String getCamp() {
return camp;
}
public boolean isWerewolf() {
return this == WEREWOLF;
}
public boolean isVillagerCamp() {
return !isWerewolf();
}
}
25.2.2 玩家实体
/**
* 玩家实体
* 包含Agent引用、角色、状态和特殊道具
*/
public class Player {
private final AgentBase agent; // Agent实例
private final String name; // 玩家名称
private final Role role; // 角色
private final boolean isHuman; // 是否人类玩家
private boolean isAlive; // 是否存活
// 女巫特有状态
private boolean witchHasHealPotion; // 是否有解药
private boolean witchHasPoisonPotion; // 是否有毒药
private Player(Builder builder) {
this.agent = builder.agent;
this.name = builder.name;
this.role = builder.role;
this.isHuman = builder.isHuman;
this.isAlive = true;
// 初始化角色特有状态
if (role == Role.WITCH) {
this.witchHasHealPotion = true;
this.witchHasPoisonPotion = true;
}
}
public static Builder builder() {
return new Builder();
}
// 状态修改方法
public void kill() {
this.isAlive = false;
}
public void resurrect() {
this.isAlive = true;
}
public void useHealPotion() {
this.witchHasHealPotion = false;
}
public void usePoisonPotion() {
this.witchHasPoisonPotion = false;
}
// Builder类
public static class Builder {
private AgentBase agent;
private String name;
private Role role;
private boolean isHuman = false;
public Builder agent(AgentBase agent) {
this.agent = agent;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder role(Role role) {
this.role = role;
return this;
}
public Builder isHuman(boolean isHuman) {
this.isHuman = isHuman;
return this;
}
public Player build() {
if (agent == null || name == null || role == null) {
throw new IllegalStateException("Agent, name, and role are required");
}
return new Player(this);
}
}
}
25.2.3 游戏状态
/**
* 游戏状态管理
* 跟踪所有玩家状态、回合数和夜间事件
*/
public class GameState {
private final List<Player> allPlayers;
private final List<Player> seers;
private final List<Player> witches;
private final List<Player> hunters;
private int currentRound;
private Player lastNightVictim; // 昨晚被狼人杀死的玩家
private Player lastPoisonedVictim; // 昨晚被女巫毒死的玩家
private boolean lastVictimResurrected; // 昨晚受害者是否被救
public GameState(List<Player> allPlayers) {
this.allPlayers = new ArrayList<>(allPlayers);
this.currentRound = 0;
// 查找特殊角色玩家
this.seers = findPlayersByRole(Role.SEER);
this.witches = findPlayersByRole(Role.WITCH);
this.hunters = findPlayersByRole(Role.HUNTER);
}
// 存活状态查询
public List<Player> getAlivePlayers() {
return allPlayers.stream()
.filter(Player::isAlive)
.collect(Collectors.toList());
}
public List<Player> getAliveWerewolves() {
return getAlivePlayers().stream()
.filter(p -> p.getRole() == Role.WEREWOLF)
.collect(Collectors.toList());
}
public List<Player> getAliveVillagers() {
return getAlivePlayers().stream()
.filter(p -> p.getRole().isVillagerCamp())
.collect(Collectors.toList());
}
// 胜利条件检查
public boolean checkWerewolvesWin() {
int aliveWerewolves = getAliveWerewolves().size();
int aliveVillagers = getAliveVillagers().size();
return aliveWerewolves > 0 && aliveWerewolves >= aliveVillagers;
}
public boolean checkVillagersWin() {
return getAliveWerewolves().isEmpty();
}
// 回合管理
public void nextRound() {
this.currentRound++;
}
public void clearNightResults() {
this.lastNightVictim = null;
this.lastPoisonedVictim = null;
this.lastVictimResurrected = false;
}
}
25.3 游戏引擎实现
25.3.1 游戏初始化
public class WerewolfWebGame {
private final GameEventEmitter emitter;
private final PromptProvider prompts;
private final GameMessages messages;
private final WebUserInput userInput;
private final Role selectedHumanRole;
private final GameConfiguration gameConfig;
private DashScopeChatModel model;
private GameState gameState;
private Player humanPlayer;
/**
* 初始化游戏
* 分配角色、创建Agent、设置初始状态
*/
private GameState initializeGame() {
// 步骤1:生成角色列表
List<Role> roles = new ArrayList<>();
for (int i = 0; i < gameConfig.getVillagerCount(); i++)
roles.add(Role.VILLAGER);
for (int i = 0; i < gameConfig.getWerewolfCount(); i++)
roles.add(Role.WEREWOLF);
for (int i = 0; i < gameConfig.getSeerCount(); i++)
roles.add(Role.SEER);
for (int i = 0; i < gameConfig.getWitchCount(); i++)
roles.add(Role.WITCH);
for (int i = 0; i < gameConfig.getHunterCount(); i++)
roles.add(Role.HUNTER);
Collections.shuffle(roles); // 随机分配
// 步骤2:确定人类玩家位置
int humanPlayerIndex = -1;
if (userInput != null) {
if (selectedHumanRole != null) {
// 根据选择的角色找到位置
for (int i = 0; i < roles.size(); i++) {
if (roles.get(i) == selectedHumanRole) {
humanPlayerIndex = i;
break;
}
}
} else {
// 随机分配角色
humanPlayerIndex = new Random().nextInt(roles.size());
}
}
// 步骤3:创建玩家和Agent
List<Player> players = new ArrayList<>();
for (int i = 0; i < roles.size(); i++) {
String name = playerNames.get(i);
Role role = roles.get(i);
boolean isHuman = (i == humanPlayerIndex);
AgentBase agent;
if (isHuman) {
// 人类玩家使用UserAgent
agent = UserAgent.builder()
.name(name)
.inputMethod(userInput)
.build();
} else {
// AI玩家使用ReActAgent
agent = ReActAgent.builder()
.name(name)
.sysPrompt(prompts.getSystemPrompt(role, name))
.model(model)
.memory(new InMemoryMemory())
.toolkit(new Toolkit())
.structuredOutputReminder(StructuredOutputReminder.PROMPT)
.build();
}
Player player = Player.builder()
.agent(agent)
.name(name)
.role(role)
.isHuman(isHuman)
.build();
players.add(player);
if (isHuman) {
humanPlayer = player;
}
}
// 步骤4:设置事件可见性
if (humanPlayer != null) {
emitter.setHumanPlayer(humanPlayer.getRole(), humanPlayer.getName());
}
// 步骤5:发送初始化事件
emitter.emitGameInit(godViewPlayersInfo, playerViewInfo);
return new GameState(players);
}
}
25.3.2 游戏主循环
/**
* 启动游戏主循环
*/
public void start() throws Exception {
emitter.emitSystemMessage(messages.getInitializingGame());
// 创建共享模型
String apiKey = System.getenv("DASHSCOPE_API_KEY");
model = DashScopeChatModel.builder()
.apiKey(apiKey)
.enableThinking(true)
.defaultOptions(GenerateOptions.builder()
.thinkingBudget(512)
.build())
.modelName(WerewolfGameConfig.DEFAULT_MODEL)
.formatter(new DashScopeMultiAgentFormatter())
.stream(false)
.build();
gameState = initializeGame();
emitStatsUpdate();
// 游戏主循环
for (int round = 1; round <= MAX_ROUNDS; round++) {
gameState.nextRound();
// 夜晚阶段
emitter.emitPhaseChange(round, "night");
nightPhase();
if (checkGameEnd()) break;
// 白天阶段
emitter.emitPhaseChange(round, "day");
dayPhase();
if (checkGameEnd()) break;
}
announceWinner();
}
25.3.3 夜晚阶段
/**
* 夜晚阶段处理
* 狼人杀人 → 女巫行动 → 预言家查验
*/
private void nightPhase() {
emitter.emitSystemMessage(messages.getNightPhaseTitle());
gameState.clearNightResults();
// 1. 狼人杀人
Player victim = werewolvesKill();
if (victim != null) {
gameState.setLastNightVictim(victim);
victim.kill();
emitter.emitSystemMessage(
messages.getWerewolvesChose(victim.getName()),
EventVisibility.WEREWOLF_ONLY);
}
// 2. 女巫行动
for (Player witch : gameState.getWitches()) {
if (witch.isAlive()) {
witchActions(witch);
}
}
// 3. 预言家查验
for (Player seer : gameState.getSeers()) {
if (seer.isAlive()) {
seerCheck(seer);
}
}
// 4. 公布夜晚结果
Player nightVictim = gameState.getLastNightVictim();
boolean wasResurrected = gameState.isLastVictimResurrected();
if (nightVictim != null && !wasResurrected) {
emitter.emitPlayerEliminated(
nightVictim.getName(),
messages.getRoleDisplayName(nightVictim.getRole()),
"killed");
}
emitStatsUpdate();
emitter.emitSystemMessage(messages.getNightPhaseComplete());
}
25.3.4 狼人杀人逻辑
/**
* 狼人讨论并投票杀人
* 使用MsgHub实现狼人私聊
*/
private Player werewolvesKill() {
List<Player> werewolves = gameState.getAliveWerewolves();
if (werewolves.isEmpty()) {
return null;
}
// 狼人讨论只对狼人可见
emitter.emitSystemMessage(
messages.getSystemWerewolfDiscussing(),
EventVisibility.WEREWOLF_ONLY);
// 使用MsgHub创建狼人私聊频道
try (MsgHub werewolfHub = MsgHub.builder()
.name("WerewolfDiscussion")
.participants(werewolves.stream()
.map(Player::getAgent)
.toArray(AgentBase[]::new))
.announcement(prompts.createWerewolfDiscussionPrompt(gameState))
.enableAutoBroadcast(true)
.build()) {
werewolfHub.enter().block();
// 狼人轮流发言
for (Player werewolf : werewolves) {
if (werewolf.isHuman()) {
// 人类狼人发言
String humanInput = userInput.waitForInput(
WebUserInput.INPUT_SPEAK,
messages.getPromptWerewolfDiscussion(),
null).block();
if (humanInput != null && !humanInput.isEmpty()) {
emitter.emitPlayerSpeak(
humanPlayer.getName(),
humanInput,
"werewolf_discussion");
// 广播给其他狼人
werewolfHub.broadcast(List.of(
Msg.builder()
.name(humanPlayer.getName())
.role(MsgRole.USER)
.content(TextBlock.builder().text(humanInput).build())
.build()
)).block();
}
} else {
// AI狼人发言
Msg response = werewolf.getAgent().call().block();
String content = utils.extractTextContent(response);
emitter.emitPlayerSpeak(
werewolf.getName(),
content,
"werewolf_discussion");
}
}
// 关闭自动广播,进入投票阶段
werewolfHub.setAutoBroadcast(false);
Msg votingPrompt = prompts.createWerewolfVotingPrompt(gameState);
// 收集投票
List<Msg> votes = new ArrayList<>();
List<String> voteTargetOptions = gameState.getAlivePlayers().stream()
.filter(p -> p.getRole() != Role.WEREWOLF)
.map(Player::getName)
.collect(Collectors.toList());
for (Player werewolf : werewolves) {
if (werewolf.isHuman()) {
// 人类狼人投票
String voteTarget = userInput.waitForInput(
WebUserInput.INPUT_VOTE,
messages.getPromptWerewolfVote(),
voteTargetOptions).block();
emitter.emitPlayerVote(
werewolf.getName(),
voteTarget,
"",
EventVisibility.WEREWOLF_ONLY);
votes.add(Msg.builder()
.name(werewolf.getName())
.role(MsgRole.USER)
.content(TextBlock.builder().text(voteTarget).build())
.metadata(Map.of("targetPlayer", voteTarget, "reason", ""))
.build());
} else {
// AI狼人使用结构化输出投票
Msg vote = werewolf.getAgent()
.call(votingPrompt, VoteModel.class)
.block();
votes.add(vote);
VoteModel voteData = vote.getStructuredData(VoteModel.class);
emitter.emitPlayerVote(
vote.getName(),
voteData.targetPlayer,
voteData.reason,
EventVisibility.WEREWOLF_ONLY);
}
}
// 统计投票结果
Player killedPlayer = utils.countVotes(votes, gameState);
// 广播杀人结果给所有狼人
werewolfHub.broadcast(List.of(
Msg.builder()
.name("Moderator Message")
.role(MsgRole.USER)
.content(TextBlock.builder()
.text(messages.getSystemWerewolfKillResult(
killedPlayer != null ? killedPlayer.getName() : null))
.build())
.build()
)).block();
return killedPlayer;
}
}
25.3.5 女巫行动
/**
* 女巫使用解药或毒药
*/
private void witchActions(Player witch) {
Player victim = gameState.getLastNightVictim();
boolean isHumanWitch = witch.isHuman();
emitter.emitSystemMessage(
messages.getSystemWitchActing(),
EventVisibility.WITCH_ONLY);
boolean usedHeal = false;
// 解药决策
if (witch.isWitchHasHealPotion() && victim != null) {
emitter.emitSystemMessage(
messages.getSystemWitchSeesVictim(victim.getName()),
EventVisibility.WITCH_ONLY);
if (isHumanWitch) {
// 人类女巫决策
String healChoice = userInput.waitForInput(
WebUserInput.INPUT_WITCH_HEAL,
messages.getPromptWitchHeal(victim.getName()),
List.of("yes", "no")).block();
if ("yes".equalsIgnoreCase(healChoice)) {
victim.resurrect();
witch.useHealPotion();
gameState.setLastVictimResurrected(true);
usedHeal = true;
emitter.emitPlayerAction(
witch.getName(),
messages.getRoleDisplayName(Role.WITCH),
messages.getActionWitchUseHeal(),
victim.getName(),
messages.getActionWitchHealResult(),
EventVisibility.WITCH_ONLY);
emitter.emitPlayerResurrected(victim.getName());
}
} else {
// AI女巫使用结构化输出决策
Msg healDecision = witch.getAgent()
.call(prompts.createWitchHealPrompt(victim), WitchHealModel.class)
.block();
WitchHealModel healModel = healDecision.getStructuredData(WitchHealModel.class);
if (Boolean.TRUE.equals(healModel.useHealPotion)) {
victim.resurrect();
witch.useHealPotion();
gameState.setLastVictimResurrected(true);
usedHeal = true;
emitter.emitPlayerResurrected(victim.getName());
}
}
}
// 毒药决策(类似逻辑)
if (witch.isWitchHasPoisonPotion()) {
// ... 毒药使用逻辑
}
emitStatsUpdate();
}
25.4 结构化输出Schema
25.4.1 投票Schema
/**
* 投票结构化输出
* 用于狼人杀人投票和白天处决投票
*/
public class VoteModel {
@JsonPropertyDescription("The name of the player you want to vote for")
public String targetPlayer;
@JsonPropertyDescription("Brief reason for your vote")
public String reason;
}
25.4.2 预言家查验Schema
/**
* 预言家查验结构化输出
*/
public class SeerCheckModel {
@JsonPropertyDescription("The name of the player you want to check")
public String targetPlayer;
}
25.4.3 女巫行动Schema
/**
* 女巫解药决策
*/
public class WitchHealModel {
@JsonPropertyDescription("Whether to use the heal potion to save the victim")
public Boolean useHealPotion;
}
/**
* 女巫毒药决策
*/
public class WitchPoisonModel {
@JsonPropertyDescription("Whether to use the poison potion")
public Boolean usePoisonPotion;
@JsonPropertyDescription("The name of the player to poison (if using poison)")
public String targetPlayer;
}
25.4.4 猎人开枪Schema
/**
* 猎人开枪决策
*/
public class HunterShootModel {
@JsonPropertyDescription("Whether the hunter wants to shoot someone")
public Boolean shoot;
@JsonPropertyDescription("The name of the player to shoot")
public String targetPlayer;
}
25.5 事件系统与可见性
25.5.1 事件可见性枚举
/**
* 事件可见性控制
* 不同角色只能看到特定事件
*/
public enum EventVisibility {
ALL, // 所有人可见
GOD_ONLY, // 仅上帝视角
WEREWOLF_ONLY, // 仅狼人可见
WITCH_ONLY, // 仅女巫可见
SEER_ONLY, // 仅预言家可见
HUNTER_ONLY, // 仅猎人可见
PLAYER_SPECIFIC // 特定玩家可见
}
25.5.2 事件发射器
/**
* 游戏事件发射器
* 根据可见性规则向前端推送事件
*/
public class GameEventEmitter {
private final Sinks.Many<GameEvent> eventSink;
private Role humanPlayerRole;
private String humanPlayerName;
public void setHumanPlayer(Role role, String name) {
this.humanPlayerRole = role;
this.humanPlayerName = name;
}
/**
* 发射系统消息
*/
public void emitSystemMessage(String message) {
emitSystemMessage(message, EventVisibility.ALL);
}
public void emitSystemMessage(String message, EventVisibility visibility) {
if (shouldShowEvent(visibility)) {
GameEvent event = GameEvent.systemMessage(message);
eventSink.tryEmitNext(event);
}
}
/**
* 发射玩家发言事件
*/
public void emitPlayerSpeak(String playerName, String content, String context) {
GameEvent event = GameEvent.playerSpeak(playerName, content, context);
eventSink.tryEmitNext(event);
}
/**
* 发射投票事件
*/
public void emitPlayerVote(
String voterName,
String targetName,
String reason,
EventVisibility visibility) {
if (shouldShowEvent(visibility)) {
GameEvent event = GameEvent.playerVote(voterName, targetName, reason);
eventSink.tryEmitNext(event);
}
}
/**
* 判断事件是否应该显示给当前人类玩家
*/
private boolean shouldShowEvent(EventVisibility visibility) {
switch (visibility) {
case ALL:
return true;
case GOD_ONLY:
return false; // 人类玩家看不到上帝视角
case WEREWOLF_ONLY:
return humanPlayerRole == Role.WEREWOLF;
case WITCH_ONLY:
return humanPlayerRole == Role.WITCH;
case SEER_ONLY:
return humanPlayerRole == Role.SEER;
case HUNTER_ONLY:
return humanPlayerRole == Role.HUNTER;
default:
return true;
}
}
}
25.6 人类玩家交互
25.6.1 WebUserInput实现
/**
* Web用户输入处理
* 通过WebSocket接收人类玩家输入
*/
public class WebUserInput implements UserInputBase {
public static final String INPUT_SPEAK = "speak";
public static final String INPUT_VOTE = "vote";
public static final String INPUT_WITCH_HEAL = "witch_heal";
public static final String INPUT_WITCH_POISON = "witch_poison";
public static final String INPUT_SEER_CHECK = "seer_check";
public static final String INPUT_HUNTER_SHOOT = "hunter_shoot";
private final Sinks.Many<GameEvent> eventSink;
private final Map<String, CompletableFuture<String>> pendingInputs;
/**
* 等待用户输入
*/
public Mono<String> waitForInput(
String inputType,
String prompt,
List<String> options) {
// 创建输入请求ID
String requestId = UUID.randomUUID().toString();
// 向前端发送输入请求
GameEvent requestEvent = GameEvent.inputRequest(
requestId, inputType, prompt, options);
eventSink.tryEmitNext(requestEvent);
// 等待用户响应
CompletableFuture<String> future = new CompletableFuture<>();
pendingInputs.put(requestId, future);
return Mono.fromFuture(future)
.timeout(Duration.ofMinutes(5)) // 5分钟超时
.doFinally(signal -> pendingInputs.remove(requestId));
}
/**
* 接收用户输入响应
*/
public void receiveInput(String requestId, String value) {
CompletableFuture<String> future = pendingInputs.get(requestId);
if (future != null) {
future.complete(value);
}
}
@Override
public Mono<Msg> getUserInput(String prompt) {
return waitForInput(INPUT_SPEAK, prompt, null)
.map(input -> Msg.builder()
.role(MsgRole.USER)
.content(TextBlock.builder().text(input).build())
.build());
}
}
25.6.2 不同角色的交互流程
┌─────────────────────────────────────────────────────────────────┐
│ 人类玩家交互流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 狼人角色: │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 夜晚: ││
│ │ 1. 看到其他狼人发言 ││
│ │ 2. INPUT_SPEAK: 参与讨论 ││
│ │ 3. INPUT_VOTE: 选择杀人目标 ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ 预言家角色: │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 夜晚: ││
│ │ 1. INPUT_SEER_CHECK: 选择查验目标 ││
│ │ 2. 看到查验结果(狼人/好人) ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ 女巫角色: │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 夜晚: ││
│ │ 1. 看到昨晚受害者 ││
│ │ 2. INPUT_WITCH_HEAL: 是否使用解药 ││
│ │ 3. INPUT_WITCH_POISON: 是否使用毒药(选择目标) ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ 所有角色: │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 白天: ││
│ │ 1. INPUT_SPEAK: 参与讨论 ││
│ │ 2. INPUT_VOTE: 投票处决 ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ 猎人角色(被处决时): │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 1. INPUT_HUNTER_SHOOT: 是否开枪(选择目标) ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
25.7 本地化系统
25.7.1 消息接口
/**
* 游戏消息接口
* 支持多语言文本获取
*/
public interface GameMessages {
// 阶段标题
String getNightPhaseTitle();
String getDayPhaseTitle();
String getNightPhaseComplete();
// 系统消息
String getSystemWerewolfDiscussing();
String getSystemWitchActing();
String getSystemSeerActing();
String getWerewolvesChose(String victimName);
String getSystemWerewolfKillResult(String victimName);
String getSystemWitchSeesVictim(String victimName);
// 提示词
String getPromptWerewolfDiscussion();
String getPromptWerewolfVote();
String getPromptWitchHeal(String victimName);
String getPromptWitchPoison();
String getPromptSeerCheck();
// 角色显示
String getRoleDisplayName(Role role);
String getRoleSymbol(Role role);
// 动作描述
String getActionWitchUseHeal();
String getActionWitchHealResult();
String getActionWitchHealSkip();
// ... 更多消息
}
25.7.2 本地化配置
# messages_zh.properties
night.phase.title=夜幕降临,所有人请闭眼
day.phase.title=天亮了,请睁眼
system.werewolf.discussing=狼人们正在讨论...
system.witch.acting=女巫请睁眼
system.seer.acting=预言家请睁眼
role.villager=村民
role.werewolf=狼人
role.seer=预言家
role.witch=女巫
role.hunter=猎人
symbol.villager=🏠
symbol.werewolf=🐺
symbol.seer=🔮
symbol.witch=🧙♀️
symbol.hunter=🎯
25.8 本章小结
25.8.1 技术要点
| 技术 | 应用场景 |
|---|---|
| MsgHub | 狼人私聊频道 |
| UserAgent | 人类玩家交互 |
| 结构化输出 | 投票、决策Schema |
| 事件可见性 | 信息隔离 |
| SSE | 实时事件推送 |
| 本地化 | 多语言支持 |
25.8.2 设计模式
- 状态机模式:游戏阶段切换(夜晚→白天)
- 观察者模式:事件发射与订阅
- 策略模式:不同角色的行动策略
- 工厂模式:Agent创建
- Builder模式:实体构建
25.8.3 可扩展性
狼人杀案例展示了如何构建复杂的多智能体博弈系统:
- 新增角色:继承Role枚举,添加对应的行动逻辑
- 新增技能:创建新的结构化输出Schema
- 修改规则:调整胜利条件和阶段逻辑
- 前端定制:通过事件系统适配不同UI
25.8.4 下一步学习
掌握狼人杀案例后,建议继续学习:
- 第26章:奶茶店多智能体系统(分布式协作、微服务架构)
这将展示如何将多智能体系统应用到企业级业务场景中。