每日算法 — 使用java实现三国杀:卡牌组合优化与技能触发概率评估

引言

三国杀将历史角色与卡牌策略深度融合,是研究不完全信息博弈与概率决策的绝佳场景。本文用Java实现一个简化版的三国杀AI,重点讲解手牌价值评估、技能触发概率计算与最优出牌决策。

核心算法设计

1. 卡牌价值评估模型

每张卡牌在不同场景下价值不同。我们建立动态评估函数,综合考虑当前血量、敌人状态与手牌组合效果。

import java.util.*;

/**
 * 三国杀卡牌类型枚举
 */
enum CardType {
    SHA("杀", 1), SHAN("闪", 2), TAO("桃", 3),
    JUEDOU("决斗", 4), GUOCHAI("过河拆桥", 5),
    SHUNSHOU("顺手牵羊", 6), WUZHONG("无中生有", 7);

    final String name;
    final int code;
    CardType(String name, int code) { this.name = name; this.code = code; }
}

/**
 * 单张卡牌对象
 */
class Card {
    CardType type;
    String suit; // 花色
    int rank;    // 点数

    public Card(CardType type, String suit, int rank) {
        this.type = type; this.suit = suit; this.rank = rank;
    }
}

/**
 * 游戏状态上下文
 */
class GameState {
    int myHp;           // 己方血量
    int myMaxHp;        // 己方最大血量
    int enemyHp;        // 敌方血量
    int distance;       // 攻击距离
    List<Card> handCards; // 手牌
    List<Card> enemyHandGuess; // 对敌方手牌的推测

    public GameState(int myHp, int myMaxHp, int enemyHp, int distance) {
        this.myHp = myHp; this.myMaxHp = myMaxHp;
        this.enemyHp = enemyHp; this.distance = distance;
        this.handCards = new ArrayList<>();
        this.enemyHandGuess = new ArrayList<>();
    }
}

/**
 * 卡牌价值评估器
 * 核心思想:根据当前游戏状态动态计算每张手牌的期望收益
 */
class CardValueEvaluator {

    /**
     * 评估单张卡牌在当前状态下的价值
     * @param card 待评估卡牌
     * @param state 当前游戏状态
     * @return 价值分数(越高越好)
     */
    public double evaluate(Card card, GameState state) {
        switch (card.type) {
            case SHA:
                return evaluateSha(state);
            case SHAN:
                return evaluateShan(state);
            case TAO:
                return evaluateTao(state);
            case JUEDOU:
                return evaluateJuedou(state);
            case GUOCHAI:
                return evaluateGuochai(state);
            case SHUNSHOU:
                return evaluateShunshou(state);
            case WUZHONG:
                return evaluateWuzhong(state);
            default:
                return 1.0;
        }
    }

    /**
     * 评估"杀"的价值
     * 考虑因素:能否打到敌人、敌方是否有闪的概率、击杀可能性
     */
    private double evaluateSha(GameState state) {
        if (state.distance > 1) return 0.1; // 距离不够
        double baseValue = 2.0;
        // 若敌方血量较低,杀的价值更高(有击杀可能)
        if (state.enemyHp <= 1) baseValue += 5.0;
        else if (state.enemyHp <= 2) baseValue += 2.0;
        // 推测敌方有闪的概率(根据已出牌和牌堆剩余)
        double hasShanProb = estimateHasShanProbability(state);
        return baseValue * (1 - hasShanProb * 0.7);
    }

    /**
     * 评估"闪"的价值
     * 血量越低,闪的防御价值越高
     */
    private double evaluateShan(GameState state) {
        double value = 1.5;
        if (state.myHp <= 2) value += 3.0; // 危急状态
        if (state.myHp == 1) value += 4.0; // 濒死状态,闪是救命牌
        return value;
    }

    /**
     * 评估"桃"的价值
     * 满血时价值低,濒死时价值极高
     */
    private double evaluateTao(GameState state) {
        if (state.myHp >= state.myMaxHp) return 0.5; // 满血,暂不需要
        if (state.myHp == 1) return 10.0; // 濒死,极高价值
        if (state.myHp == 2) return 4.0;
        return 2.0;
    }

    /**
     * 评估"决斗"的价值
     * 当我方杀多而敌方杀少时,决斗价值高
     */
    private double evaluateJuedou(GameState state) {
        int myShaCount = countCardType(state.handCards, CardType.SHA);
        double enemyShaProb = estimateEnemyShaCount(state);
        if (myShaCount > enemyShaProb + 1) {
            return 3.5 + (myShaCount - enemyShaProb);
        }
        return 1.0;
    }

    /**
     * 评估"过河拆桥"的价值
     * 优先拆敌方关键装备或手牌
     */
    private double evaluateGuochai(GameState state) {
        return 2.8; // 稳定削弱敌方
    }

    /**
     * 评估"顺手牵羊"的价值
     * 距离1时价值极高
     */
    private double evaluateShunshou(GameState state) {
        if (state.distance <= 1) return 3.5;
        return 0.2; // 距离不够无法使用
    }

    /**
     * 评估"无中生有"的价值
     * 手牌越少,价值越高(补充手牌)
     */
    private double evaluateWuzhong(GameState state) {
        int handSize = state.handCards.size();
        if (handSize <= 2) return 4.0;
        if (handSize <= 4) return 2.5;
        return 1.5; // 手牌多时不急需补牌
    }

    /**
     * 估计敌方持有"闪"的概率
     * 基于贝叶斯推理:根据已出牌信息更新概率
     */
    private double estimateHasShanProbability(GameState state) {
        // 简化模型:假设牌堆中闪占30%,敌方手牌随机抽取
        // 若敌方已打出多张闪,则持有概率下降
        int totalShan = 24; // 标准牌堆闪的数量
        int seenShan = countSeenCards(state.enemyHandGuess, CardType.SHAN);
        int remainingShan = totalShan - seenShan;
        int remainingTotal = 160 - state.enemyHandGuess.size(); // 剩余总牌数
        if (remainingTotal <= 0) return 0.0;
        return (double) remainingShan / remainingTotal;
    }

    /**
     * 估计敌方持有的杀的数量
     */
    private double estimateEnemyShaCount(GameState state) {
        int totalSha = 44;
        int seenSha = countSeenCards(state.enemyHandGuess, CardType.SHA);
        int remainingSha = totalSha - seenSha;
        int remainingTotal = 160 - state.enemyHandGuess.size();
        if (remainingTotal <= 0) return 0.0;
        // 期望数量 = 敌方手牌数 * 剩余杀比例
        return state.enemyHandGuess.size() * (double) remainingSha / remainingTotal;
    }

    private int countCardType(List<Card> cards, CardType type) {
        return (int) cards.stream().filter(c -> c.type == type).count();
    }

    private int countSeenCards(List<Card> seen, CardType type) {
        return (int) seen.stream().filter(c -> c.type == type).count();
    }
}

/**
 * 出牌决策引擎
 * 基于贪心策略:每轮选择期望收益最大的单张牌或组合打出
 */
class PlayDecisionEngine {
    private CardValueEvaluator evaluator = new CardValueEvaluator();

    /**
     * 决策当前最优出牌
     * @return 建议打出的卡牌列表(可能为多张组合)
     */
    public List<Card> decidePlay(GameState state) {
        List<Card> bestPlay = new ArrayList<>();
        double bestValue = 0.0;

        // 策略1:单张出牌评估
        for (Card card : state.handCards) {
            double value = evaluator.evaluate(card, state);
            if (value > bestValue) {
                bestValue = value;
                bestPlay = Collections.singletonList(card);
            }
        }

        // 策略2:组合评估(杀+决斗连击)
        List<Card> combo = evaluateCombo(state);
        double comboValue = evaluateComboValue(combo, state);
        if (comboValue > bestValue) {
            bestPlay = combo;
            bestValue = comboValue;
        }

        return bestPlay;
    }

    /**
     * 评估 combos:例如先拆敌方手牌再杀
     */
    private List<Card> evaluateCombo(GameState state) {
        List<Card> combo = new ArrayList<>();
        // 简单组合:过河拆桥 + 杀(先拆闪再杀)
        Card guochai = findFirst(state.handCards, CardType.GUOCHAI);
        Card sha = findFirst(state.handCards, CardType.SHA);
        if (guochai != null && sha != null && state.distance <= 1) {
            combo.add(guochai);
            combo.add(sha);
        }
        return combo;
    }

    private double evaluateComboValue(List<Card> combo, GameState state) {
        if (combo.isEmpty()) return 0.0;
        double sum = 0.0;
        for (Card c : combo) sum += evaluator.evaluate(c, state);
        return sum * 1.2; // 组合有协同加成
    }

    private Card findFirst(List<Card> cards, CardType type) {
        for (Card c : cards) if (c.type == type) return c;
        return null;
    }
}

/**
 * 主程序入口:演示AI决策过程
 */
public class SanguoshaAI {
    public static void main(String[] args) {
        // 构造一个测试场景
        GameState state = new GameState(3, 4, 2, 1);
        state.handCards.add(new Card(CardType.SHA, "黑桃", 7));
        state.handCards.add(new Card(CardType.SHAN, "红桃", 2));
        state.handCards.add(new Card(CardType.GUOCHAI, "梅花", 3));
        state.handCards.add(new Card(CardType.TAO, "红桃", 8));

        CardValueEvaluator eval = new CardValueEvaluator();
        System.out.println("=== 手牌价值评估 ===");
        for (Card c : state.handCards) {
            System.out.printf("%s (%.1f)\n", c.type.name, eval.evaluate(c, state));
        }

        PlayDecisionEngine engine = new PlayDecisionEngine();
        List<Card> decision = engine.decidePlay(state);
        System.out.println("\n=== AI决策 ===");
        for (Card c : decision) {
            System.out.println("打出: " + c.type.name);
        }
    }
}

2. 技能触发概率模型

三国杀中角色技能往往与卡牌花色、点数相关。我们以”关羽-武圣”为例,计算红牌当杀使用的期望收益。

/**
 * 技能触发概率计算器
 * 以关羽"武圣"为例:可将红色牌当杀使用
 */
class SkillProbabilityCalculator {

    /**
     * 计算"武圣"技能的期望收益
     * @param handCards 当前手牌
     * @return 额外可打出的"杀"的期望数量
     */
    public double calculateWushengValue(List<Card> handCards) {
        int redCards = 0;
        for (Card c : handCards) {
            if (isRedSuit(c.suit)) redCards++;
        }
        // 红色牌中,非杀的牌才能转化为额外的杀
        int redNonSha = redCards - countShaInRed(handCards);
        return Math.max(0, redNonSha) * 0.8; // 转化有80%概率成功(考虑被无懈)
    }

    private boolean isRedSuit(String suit) {
        return "红桃".equals(suit) || "方块".equals(suit);
    }

    private int countShaInRed(List<Card> cards) {
        int count = 0;
        for (Card c : cards) {
            if (c.type == CardType.SHA && isRedSuit(c.suit)) count++;
        }
        return count;
    }
}

复杂度分析

  • 手牌评估:O(n),n为手牌数量
  • 组合搜索:O(n²),枚举两两组合
  • 概率推理:O(m),m为已见牌数量

整体决策在毫秒级完成,满足实时对战需求。

总结

本文从卡牌价值评估、概率推理、组合决策三个层面构建三国杀AI。核心思想是将不确定信息转化为概率期望,通过贪心策略在每轮选择最大化期望收益的行动。读者可以在此基础上扩展更复杂的角色技能与多人博弈模型。