第25章 狼人杀游戏案例

第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 设计模式

  1. 状态机模式:游戏阶段切换(夜晚→白天)
  2. 观察者模式:事件发射与订阅
  3. 策略模式:不同角色的行动策略
  4. 工厂模式:Agent创建
  5. Builder模式:实体构建

25.8.3 可扩展性

狼人杀案例展示了如何构建复杂的多智能体博弈系统:

  • 新增角色:继承Role枚举,添加对应的行动逻辑
  • 新增技能:创建新的结构化输出Schema
  • 修改规则:调整胜利条件和阶段逻辑
  • 前端定制:通过事件系统适配不同UI

25.8.4 下一步学习

掌握狼人杀案例后,建议继续学习:

  • 第26章:奶茶店多智能体系统(分布式协作、微服务架构)

这将展示如何将多智能体系统应用到企业级业务场景中。

← 返回目录