每日算法 — 使用java实现狼人杀:概率推理与逻辑约束传播

一、游戏简介与算法目标

狼人杀(Werewolf/Mafia)是一款经典的多人社交推理游戏,通常由4-12名玩家参与,分为狼人阵营和好人阵营。游戏白天玩家通过发言和投票放逐疑似狼人的玩家,夜晚狼人秘密击杀好人。好人阵营的目标是找出并放逐所有狼人,狼人阵营的目标是在人数上压倒好人。

本文的核心算法目标包括:
1. 贝叶斯概率推理:根据每轮的发言、投票和死亡信息,更新每个玩家是狼人的后验概率
2. 逻辑约束传播:利用已知的角色信息和游戏逻辑规则,推导其他玩家的身份约束
3. 最优决策策略:基于概率模型选择最有价值的查验目标和投票对象

二、核心算法原理

2.1 贝叶斯概率更新

设玩家i是狼人的先验概率为 P(W_i),观察到证据E后,后验概率为:

P(W_i | E) = P(E | W_i) × P(W_i) / P(E)

证据类型包括:
– 死亡信息:某玩家夜晚被杀,杀手大概率是狼人
– 投票模式:狼人倾向于保护同伴、嫁祸好人
– 发言一致性:狼人发言可能出现逻辑矛盾

2.2 逻辑约束传播

建立约束系统:
– 互斥约束:两个玩家不能同时为狼人(当狼人数量上限确定时)
– 团队约束:若A保护B,则A和B同阵营的概率提升
– 击杀约束:狼人必须每晚击杀一名好人

2.3 最优查验策略

预言家每晚可查验一名玩家。最优策略是选择当前信息熵最大的玩家(即最不确定的玩家)进行查验,以最大化信息增益。

三、Java完整实现

import java.util.*;

/**
 * 狼人杀AI:贝叶斯概率推理与逻辑约束传播
 * 核心算法:贝叶斯更新、信息熵计算、约束传播
 */
public class WerewolfAI {

    // 玩家角色枚举
    enum Role {
        WEREWOLF,   // 狼人
        VILLAGER,   // 平民
        SEER,       // 预言家
        WITCH,      // 女巫
        HUNTER      // 猎人
    }

    // 玩家状态
    static class Player {
        int id;
        String name;
        Role actualRole;        // 真实角色(对AI不可见)
        boolean alive;          // 是否存活

        // 概率模型(对AI可见)
        double werewolfProb;    // 是狼人的概率
        double seerProb;        // 是预言家的概率

        // 行为记录
        List<Integer> votedFor; // 投票记录
        List<Integer> protectedPlayers; // 保护/辩护过的玩家
        List<String> claims;    // 身份声明

        Player(int id, String name) {
            this.id = id;
            this.name = name;
            this.alive = true;
            this.werewolfProb = 0.3; // 初始先验:假设约30%玩家是狼人
            this.seerProb = 0.1;
            this.votedFor = new ArrayList<>();
            this.protectedPlayers = new ArrayList<>();
            this.claims = new ArrayList<>();
        }
    }

    // 游戏状态
    static class GameState {
        List<Player> players;
        int totalWerewolves;    // 狼人总数
        int aliveWerewolves;    // 存活狼人数
        int aliveVillagers;     // 存活好人数
        int day;                // 当前天数
        List<Integer> deathLog; // 死亡记录
        Map<Integer, Boolean> seerResults; // 预言家查验结果

        GameState(int playerCount, int werewolfCount) {
            this.players = new ArrayList<>();
            this.totalWerewolves = werewolfCount;
            this.aliveWerewolves = werewolfCount;
            this.aliveVillagers = playerCount - werewolfCount;
            this.day = 1;
            this.deathLog = new ArrayList<>();
            this.seerResults = new HashMap<>();
        }
    }

    private GameState state;
    private Random random;
    private int myId; // AI自身的玩家ID

    public WerewolfAI(int playerCount, int werewolfCount, int myId) {
        this.state = new GameState(playerCount, werewolfCount);
        this.random = new Random();
        this.myId = myId;
        initializePlayers(playerCount);
    }

    private void initializePlayers(int count) {
        for (int i = 0; i < count; i++) {
            state.players.add(new Player(i, "Player" + i));
        }
    }

    /**
     * 贝叶斯概率更新核心算法
     * 根据新证据更新某玩家是狼人的概率
     * @param playerId 目标玩家
     * @param evidence 证据类型:"vote_protect", "vote_betray", "killed", "seer_check"
     * @param likelihood 在该证据下是狼人的似然度(0-1)
     */
    public void bayesianUpdate(int playerId, String evidence, double likelihood) {
        Player p = state.players.get(playerId);
        double prior = p.werewolfProb;

        // 贝叶斯公式: P(W|E) = P(E|W) * P(W) / P(E)
        // P(E) = P(E|W)*P(W) + P(E|~W)*P(~W)
        double pEvidenceGivenWolf = likelihood;
        double pEvidenceGivenNotWolf = 1.0 - likelihood;
        double pEvidence = pEvidenceGivenWolf * prior 
                         + pEvidenceGivenNotWolf * (1 - prior);

        if (pEvidence < 1e-10) pEvidence = 1e-10; // 避免除零

        double posterior = (pEvidenceGivenWolf * prior) / pEvidence;

        // 限制概率范围
        p.werewolfProb = Math.max(0.01, Math.min(0.99, posterior));

        System.out.printf("贝叶斯更新: %s 因[%s] 概率 %.3f -> %.3f%n",
            p.name, evidence, prior, p.werewolfProb);
    }

    /**
     * 处理投票证据
     * 狼人倾向于:保护同伴(不投同伴)、嫁祸好人(集中投好人)
     */
    public void processVote(int voterId, int targetId) {
        Player voter = state.players.get(voterId);
        Player target = state.players.get(targetId);

        voter.votedFor.add(targetId);

        // 如果投的是已经被高度怀疑是狼人的玩家,投票者是好人的概率上升
        if (target.werewolfProb > 0.6) {
            // 这是好人行为:帮助找出狼人
            bayesianUpdate(voterId, "vote_betray", 0.3);
        }
        // 如果投的是被高度怀疑是好人的玩家,投票者是狼人的概率上升
        else if (target.werewolfProb < 0.3) {
            // 这是狼人行为:嫁祸好人
            bayesianUpdate(voterId, "vote_protect", 0.7);
        }

        // 更新被投玩家的概率:被多人集中投票可能是狼人
        long votesAgainst = state.players.stream()
            .filter(p -> p.alive && p.votedFor.contains(targetId))
            .count();
        if (votesAgainst >= 2) {
            bayesianUpdate(targetId, "multi_votes", 0.6);
        }
    }

    /**
     * 处理夜晚死亡信息
     * 狼人通常杀他们认为威胁最大的好人(如跳预言家的)
     */
    public void processNightKill(int victimId) {
        state.deathLog.add(victimId);
        state.players.get(victimId).alive = false;

        // 被杀的玩家几乎不可能是狼人(狼人不会自杀,除非特殊规则)
        bayesianUpdate(victimId, "killed", 0.05);

        // 如果受害者之前被高度怀疑是狼人,说明狼人在做"狼踩狼"的高级操作
        Player victim = state.players.get(victimId);
        if (victim.werewolfProb > 0.5) {
            // 找出其他狼人嫌疑高的玩家,提升其概率
            for (Player p : state.players) {
                if (p.id != victimId && p.alive && p.werewolfProb > 0.4) {
                    bayesianUpdate(p.id, "wolf_on_wolf", 0.65);
                }
            }
        }
    }

    /**
     * 处理预言家查验结果
     * @param checkedId 被查验玩家
     * @param isWerewolf true表示是狼人
     */
    public void processSeerResult(int checkedId, boolean isWerewolf) {
        state.seerResults.put(checkedId, isWerewolf);
        Player p = state.players.get(checkedId);

        if (isWerewolf) {
            p.werewolfProb = 0.95; // 预言家查验为狼人
        } else {
            p.werewolfProb = 0.05; // 预言家查验为好人
        }

        System.out.printf("预言家查验: %s 是%s%n", p.name, isWerewolf ? "狼人" : "好人");
    }

    /**
     * 信息熵计算
     * H(X) = -Σ p(x) * log2(p(x))
     * 熵越大表示不确定性越高,是优先查验的目标
     */
    public double calculateEntropy(Player p) {
        double pWolf = p.werewolfProb;
        double pNotWolf = 1 - pWolf;

        // 避免log(0)
        if (pWolf < 1e-10 || pNotWolf < 1e-10) return 0;

        return -(pWolf * Math.log(pWolf) / Math.log(2) 
               + pNotWolf * Math.log(pNotWolf) / Math.log(2));
    }

    /**
     * 最优查验目标选择(信息熵最大)
     */
    public int selectBestSeerTarget() {
        double maxEntropy = -1;
        int bestTarget = -1;

        for (Player p : state.players) {
            if (p.id == myId || !p.alive || state.seerResults.containsKey(p.id)) {
                continue; // 跳过自己、死者和已查验的
            }

            double entropy = calculateEntropy(p);
            if (entropy > maxEntropy) {
                maxEntropy = entropy;
                bestTarget = p.id;
            }
        }

        return bestTarget;
    }

    /**
     * 约束传播:根据已知信息推导其他玩家的身份约束
     * 核心约束:狼人总数固定,若已确定k个狼人,则剩余狼人在其他玩家中
     */
    public void propagateConstraints() {
        // 统计已确定/高度怀疑的狼人
        List<Player> confirmedWolves = new ArrayList<>();
        List<Player> confirmedVillagers = new ArrayList<>();

        for (Player p : state.players) {
            if (!p.alive) continue;
            if (p.werewolfProb > 0.85) confirmedWolves.add(p);
            else if (p.werewolfProb < 0.15) confirmedVillagers.add(p);
        }

        int remainingWolves = state.totalWerewolves - confirmedWolves.size();
        int uncertainPlayers = state.players.size() - confirmedWolves.size() 
                             - confirmedVillagers.size();

        if (uncertainPlayers > 0 && remainingWolves >= 0) {
            // 剩余不确定玩家中,每个是狼人的基础概率
            double baseProb = (double) remainingWolves / uncertainPlayers;

            for (Player p : state.players) {
                if (!p.alive || p.werewolfProb > 0.85 || p.werewolfProb < 0.15) {
                    continue;
                }
                // 向基础概率靠拢(约束传播平滑更新)
                p.werewolfProb = 0.7 * p.werewolfProb + 0.3 * baseProb;
                p.werewolfProb = Math.max(0.05, Math.min(0.95, p.werewolfProb));
            }
        }

        System.out.printf("约束传播: 已确定狼人%d个,剩余狼人%d个,不确定玩家%d个%n",
            confirmedWolves.size(), remainingWolves, uncertainPlayers);
    }

    /**
     * 最优投票决策:投当前狼人概率最高且存活的玩家
     */
    public int selectBestVoteTarget() {
        double maxProb = -1;
        int bestTarget = -1;

        for (Player p : state.players) {
            if (p.id == myId || !p.alive) continue;
            if (p.werewolfProb > maxProb) {
                maxProb = p.werewolfProb;
                bestTarget = p.id;
            }
        }

        return bestTarget;
    }

    /**
     * 打印当前概率分布
     */
    public void printProbabilities() {
        System.out.println("\n=== 当前玩家概率分布 ===");
        System.out.printf("%-10s %-8s %-10s %-12s %-15s%n",
            "玩家", "状态", "狼人概率", "信息熵", "投票记录");
        System.out.println("-".repeat(60));

        for (Player p : state.players) {
            String status = p.alive ? "存活" : "死亡";
            double entropy = calculateEntropy(p);
            String votes = p.votedFor.isEmpty() ? "无" : 
                p.votedFor.toString();
            System.out.printf("%-10s %-8s %-10.3f %-12.3f %-15s%n",
                p.name, status, p.werewolfProb, entropy, votes);
        }
        System.out.println();
    }

    /**
     * 模拟一局游戏的推理过程
     */
    public void simulateGame() {
        System.out.println("=== 狼人杀AI概率推理模拟 ===");
        System.out.printf("玩家数: %d,狼人数: %d,AI编号: Player%d%n%n",
            state.players.size(), state.totalWerewolves, myId);

        // 初始概率
        printProbabilities();

        // 模拟第1天:Player2被杀
        System.out.println("【第1夜】Player2被狼人击杀");
        processNightKill(2);
        propagateConstraints();
        printProbabilities();

        // AI是预言家,查验Player3
        int seerTarget = selectBestSeerTarget();
        System.out.println("【第1夜】AI预言家查验 Player" + seerTarget);
        processSeerResult(seerTarget, true); // 假设查验结果为狼人
        propagateConstraints();
        printProbabilities();

        // 白天投票
        System.out.println("【第1天白天】玩家投票");
        processVote(1, 3);
        processVote(4, 3);
        processVote(5, 1);
        propagateConstraints();
        printProbabilities();

        // 第2夜
        System.out.println("【第2夜】Player5被狼人击杀");
        processNightKill(5);
        propagateConstraints();
        printProbabilities();

        // 最终决策
        int voteTarget = selectBestVoteTarget();
        System.out.println("【AI决策】最优投票目标: Player" + voteTarget 
            + " (狼人概率: " + String.format("%.3f", 
                state.players.get(voteTarget).werewolfProb) + ")");

        int nextSeer = selectBestSeerTarget();
        if (nextSeer != -1) {
            System.out.println("【AI决策】下次查验目标: Player" + nextSeer 
                + " (信息熵: " + String.format("%.3f", 
                    calculateEntropy(state.players.get(nextSeer))) + ")");
        }
    }

    public static void main(String[] args) {
        WerewolfAI ai = new WerewolfAI(6, 2, 0); // 6人局,2狼人,AI是Player0
        ai.simulateGame();
    }
}

四、算法复杂度分析

操作 时间复杂度 空间复杂度 说明
贝叶斯更新 O(1) O(1) 单玩家概率更新
约束传播 O(n) O(1) n为玩家数
信息熵计算 O(1) O(1) 单玩家熵值
最优查验选择 O(n) O(1) 遍历所有存活玩家
最优投票选择 O(n) O(1) 遍历所有存活玩家
每轮完整推理 O(n²) O(n) 包含所有更新和约束传播

五、扩展思考

  1. 更复杂的似然模型:可引入玩家历史行为模式、发言情感分析等特征,训练逻辑回归或神经网络模型替代固定的似然值。
  2. 多轮信息累积:引入时间衰减因子,较早的投票行为对当前概率的影响逐渐降低。
  3. 联盟检测:狼人倾向于形成隐性联盟,可用图论方法检测投票网络中的异常聚类。
  4. 反侦察策略:狼人AI可学习”深水狼”策略——故意表现像好人,甚至牺牲同伴换取信任。

六、总结

本文通过狼人杀游戏讲解了贝叶斯概率推理、信息熵计算和逻辑约束传播三个核心算法。Java实现构建了一个可运行的AI推理系统,能够根据游戏事件动态更新玩家身份概率,并基于信息论原则做出最优决策。该框架可扩展至更复杂的社交推理和不确定信息决策场景。