引言
诈金花(又称”炸金花”、”三张牌”)是一款广泛流行的扑克博弈游戏。玩家仅持三张手牌,通过观察对手下注行为进行概率推理和虚张声势。其本质是不完全信息动态博弈的典型代表。本文用Java实现贝叶斯概率推理引擎,根据公共信息和对手行为动态更新牌型概率分布,并给出最优下注决策建议。
核心算法设计
1. 牌型判定与概率基线
诈金花牌型从大到小依次为:豹子(三张相同)> 同花顺 > 同花 > 顺子 > 对子 > 单张。由于每人仅三张牌,精确计算各类牌型的先验概率是贝叶斯推理的基础。
2. 贝叶斯概率推理
设事件H为”对手持有某类牌型”,观测E为”对手的下注行为”。根据贝叶斯定理:
$$P(H|E) = \frac{P(E|H) \times P(H)}{P(E)}$$
通过设定不同牌型对应的下注倾向(强牌更可能加注,弱牌更可能弃牌),我们可以根据对手行为反推其牌型分布。
3. 期望值决策模型
在每一轮下注时,计算”跟注”、”加注”、”弃牌”三种行动的期望收益,选择期望值最大的行动。
完整Java实现
import java.util.*;
/**
* 诈金花博弈决策引擎
* 包含牌型判定、贝叶斯概率推理与期望值决策
*/
public class ZhaJinHua {
static class Card {
int suit; // 0-3
int rank; // 1-13
Card(int s, int r) { suit = s; rank = r; }
int value() { return rank; }
public String toString() {
String[] s = {"♠","♥","♣","♦"};
String[] r = {"","A","2","3","4","5","6","7","8","9","10","J","Q","K"};
return s[suit] + r[rank];
}
}
enum HandRank {
HIGH_CARD(1, "单张"), PAIR(2, "对子"), STRAIGHT(3, "顺子"),
FLUSH(4, "同花"), STRAIGHT_FLUSH(5, "同花顺"), TRIPLE(6, "豹子");
final int level; final String name;
HandRank(int l, String n) { level = l; name = n; }
}
static class HandEval {
HandRank rank;
int[] kickers; // 用于同类型牌型的大小比较
HandEval(HandRank r, int[] k) { this.rank = r; this.kickers = k; }
}
/**
* 牌型判定:返回牌型等级和比较键
*/
public static HandEval evaluate(Card[] hand) {
if (hand.length != 3) return null;
Arrays.sort(hand, (a, b) -> b.rank - a.rank); // 降序
boolean isFlush = hand[0].suit == hand[1].suit && hand[1].suit == hand[2].suit;
boolean isStraight = (hand[0].rank == hand[1].rank + 1 && hand[1].rank == hand[2].rank + 1) ||
(hand[0].rank == 13 && hand[1].rank == 2 && hand[2].rank == 1); // A23特殊顺子
if (hand[0].rank == hand[1].rank && hand[1].rank == hand[2].rank) {
return new HandEval(HandRank.TRIPLE, new int[]{hand[0].rank});
}
if (isFlush && isStraight) {
return new HandEval(HandRank.STRAIGHT_FLUSH, new int[]{hand[0].rank});
}
if (isFlush) {
return new HandEval(HandRank.FLUSH, new int[]{hand[0].rank, hand[1].rank, hand[2].rank});
}
if (isStraight) {
int high = (hand[0].rank == 13 && hand[2].rank == 1) ? 3 : hand[0].rank;
return new HandEval(HandRank.STRAIGHT, new int[]{high});
}
if (hand[0].rank == hand[1].rank) {
return new HandEval(HandRank.PAIR, new int[]{hand[0].rank, hand[2].rank});
}
if (hand[1].rank == hand[2].rank) {
return new HandEval(HandRank.PAIR, new int[]{hand[1].rank, hand[0].rank});
}
return new HandEval(HandRank.HIGH_CARD, new int[]{hand[0].rank, hand[1].rank, hand[2].rank});
}
/**
* 计算先验概率:从52张牌中随机抽取3张的牌型分布
*/
public static Map<HandRank, Double> priorProbabilities() {
Map<HandRank, Long> counts = new HashMap<>();
for (HandRank r : HandRank.values()) counts.put(r, 0L);
List<Card> deck = new ArrayList<>();
for (int s = 0; s < 4; s++) for (int r = 1; r <= 13; r++) deck.add(new Card(s, r));
Card[] hand = new Card[3];
long total = enumerate(deck, 0, 0, hand, counts);
Map<HandRank, Double> probs = new HashMap<>();
for (HandRank r : HandRank.values()) {
probs.put(r, counts.get(r) / (double) total);
}
return probs;
}
private static long enumerate(List<Card> deck, int start, int depth,
Card[] hand, Map<HandRank, Long> counts) {
if (depth == 3) {
HandEval ev = evaluate(hand.clone());
counts.put(ev.rank, counts.get(ev.rank) + 1);
return 1;
}
long total = 0;
for (int i = start; i <= deck.size() - (3 - depth); i++) {
hand[depth] = deck.get(i);
total += enumerate(deck, i + 1, depth + 1, hand, counts);
}
return total;
}
/**
* 贝叶斯推理引擎
* 根据对手下注行为更新其牌型概率分布
*/
static class BayesianEngine {
// 下注行为类型
enum Action { FOLD, CHECK, CALL, RAISE, ALL_IN }
// 似然矩阵:P(行为|牌型) —— 基于经验统计
// 行:牌型,列:行为
private double[][] likelihood;
private Map<HandRank, Double> prior;
BayesianEngine() {
prior = priorProbabilities();
initLikelihood();
}
private void initLikelihood() {
// 假设:强牌更可能加注,弱牌更可能弃牌
// 行顺序:HIGH_CARD, PAIR, STRAIGHT, FLUSH, STRAIGHT_FLUSH, TRIPLE
// 列顺序:FOLD, CHECK, CALL, RAISE, ALL_IN
likelihood = new double[][] {
{0.40, 0.30, 0.20, 0.08, 0.02}, // 单张:较大概率弃牌或看牌
{0.20, 0.25, 0.30, 0.20, 0.05}, // 对子:中等概率跟注
{0.10, 0.15, 0.30, 0.35, 0.10}, // 顺子:大概率加注
{0.08, 0.12, 0.25, 0.40, 0.15}, // 同花:更可能加注
{0.05, 0.08, 0.20, 0.42, 0.25}, // 同花顺:高概率加注或全押
{0.02, 0.05, 0.15, 0.38, 0.40} // 豹子:极高概率全押
};
}
/**
* 根据观测到的行为,更新对手牌型后验概率
*/
Map<HandRank, Double> updatePosterior(Action action) {
int actionIdx = action.ordinal();
Map<HandRank, Double> posterior = new HashMap<>();
HandRank[] ranks = HandRank.values();
double evidence = 0;
for (int i = 0; i < ranks.length; i++) {
evidence += likelihood[i][actionIdx] * prior.get(ranks[i]);
}
for (int i = 0; i < ranks.length; i++) {
double post = (likelihood[i][actionIdx] * prior.get(ranks[i])) / evidence;
posterior.put(ranks[i], post);
}
// 更新先验为后验(递归贝叶斯更新)
prior = posterior;
return posterior;
}
/**
* 计算对手牌型强于给定牌型的概率
*/
double probStrongerThan(HandRank myRank) {
double prob = 0;
for (Map.Entry<HandRank, Double> e : prior.entrySet()) {
if (e.getKey().level > myRank.level) {
prob += e.getValue();
} else if (e.getKey().level == myRank.level) {
// 同等级时假设50%概率对手更大(简化处理)
prob += e.getValue() * 0.5;
}
}
return prob;
}
}
/**
* 期望值决策:计算跟注、加注、弃牌的期望收益
*/
static class DecisionEngine {
/**
* 计算跟注的期望收益
* @param pot 底池大小
* @param callAmount 跟注金额
* @param winProb 获胜概率
*/
static double expectedValueCall(double pot, double callAmount, double winProb) {
return winProb * pot - (1 - winProb) * callAmount;
}
/**
* 计算加注的期望收益(简化模型)
* @param pot 底池
* @param raiseAmount 加注金额
* @param winProb 获胜概率
* @param foldProb 对手弃牌概率
*/
static double expectedValueRaise(double pot, double raiseAmount,
double winProb, double foldProb) {
double winShowdown = winProb * (pot + raiseAmount);
double winFold = foldProb * pot;
double lose = (1 - winProb - foldProb) * raiseAmount;
return winShowdown + winFold - lose;
}
/**
* 推荐最优行动
*/
static String recommendAction(double pot, double callAmount, double winProb,
double foldProb) {
double evCall = expectedValueCall(pot, callAmount, winProb);
double evRaise = expectedValueRaise(pot, callAmount * 2, winProb, foldProb);
double evFold = 0;
System.out.printf("EV(跟注)=%.2f, EV(加注)=%.2f, EV(弃牌)=%.2f%n",
evCall, evRaise, evFold);
if (evRaise > evCall && evRaise > evFold && evRaise > 0) return "加注";
if (evCall > evFold && evCall > 0) return "跟注";
return "弃牌";
}
}
/**
* 计算在已知自己手牌和若干公共信息下,获胜的精确概率
* 使用蒙特卡洛模拟对手随机手牌
*/
public static double monteCarloWinRate(Card[] myHand, List<Card> knownCards, int iterations) {
HandEval myEval = evaluate(myHand);
List<Card> deck = generateDeck();
deck.removeAll(knownCards);
int wins = 0, ties = 0, total = 0;
Random rand = new Random();
for (int i = 0; i < iterations; i++) {
Collections.shuffle(deck, rand);
Card[] oppHand = new Card[3];
for (int j = 0; j < 3; j++) oppHand[j] = deck.get(j);
HandEval oppEval = evaluate(oppHand);
int cmp = compareHands(myEval, oppEval);
if (cmp > 0) wins++;
else if (cmp == 0) ties++;
total++;
}
return (wins + ties * 0.5) / total;
}
private static int compareHands(HandEval a, HandEval b) {
if (a.rank.level != b.rank.level) return a.rank.level - b.rank.level;
for (int i = 0; i < a.kickers.length && i < b.kickers.length; i++) {
if (a.kickers[i] != b.kickers[i]) return a.kickers[i] - b.kickers[i];
}
return 0;
}
private static List<Card> generateDeck() {
List<Card> deck = new ArrayList<>();
for (int s = 0; s < 4; s++) for (int r = 1; r <= 13; r++) deck.add(new Card(s, r));
return deck;
}
// 演示主方法
public static void main(String[] args) {
// 先验概率
System.out.println("=== 诈金花牌型先验概率 ===");
Map<HandRank, Double> prior = priorProbabilities();
for (HandRank r : HandRank.values()) {
System.out.printf("%-12s: %.4f%% (%1.6f)%n", r.name, prior.get(r) * 100, prior.get(r));
}
// 贝叶斯推理演示
System.out.println("\n=== 贝叶斯推理演示 ===");
BayesianEngine bayes = new BayesianEngine();
System.out.println("初始先验概率(对手):");
printPosterior(bayes.prior);
System.out.println("\n观测到对手'加注'后,更新后验概率:");
bayes.updatePosterior(BayesianEngine.Action.RAISE);
printPosterior(bayes.prior);
System.out.println("\n观测到对手'全押'后,再次更新:");
bayes.updatePosterior(BayesianEngine.Action.ALL_IN);
printPosterior(bayes.prior);
// 蒙特卡洛胜率计算
System.out.println("\n=== 蒙特卡洛胜率模拟 ===");
Card[] myHand = {new Card(0, 13), new Card(1, 12), new Card(2, 11)}; // AKQ
List<Card> known = new ArrayList<>(Arrays.asList(myHand));
double winRate = monteCarloWinRate(myHand, known, 50000);
System.out.printf("手牌 %s 的胜率: %.2f%%%n", Arrays.toString(myHand), winRate * 100);
// 决策建议
System.out.println("\n=== 决策建议 ===");
HandEval myEval = evaluate(myHand);
double probOppStronger = bayes.probStrongerThan(myEval.rank);
double myWinProb = 1 - probOppStronger;
System.out.printf("对手牌型强于你的概率: %.2f%%%n", probOppStronger * 100);
String action = DecisionEngine.recommendAction(100, 10, myWinProb, 0.1);
System.out.println("推荐行动: " + action);
}
static void printPosterior(Map<HandRank, Double> post) {
for (HandRank r : HandRank.values()) {
System.out.printf(" %-12s: %.2f%%%n", r.name, post.get(r) * 100);
}
}
}
算法复杂度分析
| 算法模块 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 牌型判定 | O(1) | O(1) |
| 先验概率精确计算 | O(C(52,3)) = O(22100) | O(1) |
| 贝叶斯更新 | O( | HandRank |
| 蒙特卡洛胜率 | O(N) | O(1) |
| 期望值决策 | O(1) | O(1) |
扩展思考
- 对手建模:不同玩家的下注模式差异巨大,可通过在线学习(如指数加权移动平均)动态调整似然矩阵。
- 多轮博弈树:构建扩展式博弈树,使用反事实遗憾最小化(CFR)算法求解近似纳什均衡策略。
- 心理博弈层:引入”虚张声势”概率模型,分析对手在弱牌时选择加注的 Bluff 频率。