引言
三国杀将历史角色与卡牌策略深度融合,是研究不完全信息博弈与概率决策的绝佳场景。本文用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。核心思想是将不确定信息转化为概率期望,通过贪心策略在每轮选择最大化期望收益的行动。读者可以在此基础上扩展更复杂的角色技能与多人博弈模型。