游戏场景与算法概述
Craps(双骰子赌博)是一款经典的赌场概率游戏。玩家投掷两颗六面骰子,根据点数总和决定胜负。本文用Java实现:条件概率精确计算各结果的出现概率、期望值分析不同投注方式的长期收益,以及蒙特卡洛模拟验证理论结果。
核心算法一:条件概率精确计算
Craps的核心规则:首投(Come Out Roll)投出7或11直接赢,2、3或12直接输,其他点数(4,5,6,8,9,10)成为”点数”(Point),需继续投掷直到再次投出该点数(赢)或投出7(输)。
import java.util.*;
public class CrapsProbability {
private static final int SIDES = 6;
// 计算两个骰子所有组合的概率分布
public static Map<Integer, Double> getSumProbabilities() {
Map<Integer, Double> probs = new HashMap<>();
for (int i = 1; i <= SIDES; i++) {
for (int j = 1; j <= SIDES; j++) {
int sum = i + j;
probs.merge(sum, 1.0 / 36.0, Double::sum);
}
}
return probs;
}
// 计算"Pass Line"投注的获胜概率
// 首投7/11赢,2/3/12输,其他点数则需先出点数再出7
public static double calculatePassLineWinRate() {
Map<Integer, Double> probs = getSumProbabilities();
double winRate = probs.getOrDefault(7, 0.0) + probs.getOrDefault(11, 0.0);
int[] points = {4, 5, 6, 8, 9, 10};
for (int point : points) {
double pPoint = probs.getOrDefault(point, 0.0);
double pSeven = probs.getOrDefault(7, 0.0);
// 建立点数后,赢的概率 = P(Point) / (P(Point) + P(7))
double pWinAfterPoint = pPoint / (pPoint + pSeven);
winRate += pPoint * pWinAfterPoint;
}
return winRate;
}
// 计算建立某点数后,最终赢的条件概率
public static Map<Integer, Double> getPointWinProbabilities() {
Map<Integer, Double> probs = getSumProbabilities();
Map<Integer, Double> result = new HashMap<>();
int[] points = {4, 5, 6, 8, 9, 10};
for (int point : points) {
double pPoint = probs.getOrDefault(point, 0.0);
double pSeven = probs.getOrDefault(7, 0.0);
result.put(point, pPoint / (pPoint + pSeven));
}
return result;
}
public static void printProbabilities() {
Map<Integer, Double> sumProbs = getSumProbabilities();
System.out.println("=== 两骰子点数概率 ===");
sumProbs.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(e -> System.out.printf("Sum %d: %.4f (%.2f%%)%n",
e.getKey(), e.getValue(), e.getValue() * 100));
System.out.printf("%nPass Line 获胜概率: %.4f (%.2f%%)%n",
calculatePassLineWinRate(), calculatePassLineWinRate() * 100);
System.out.println("
=== 建立点数后的获胜条件概率 ===");
getPointWinProbabilities().forEach((k, v) ->
System.out.printf("Point %d: %.4f%n", k, v));
}
}
核心算法二:期望值与最优投注策略
不同投注方式的赔率和胜率不同,导致期望值(EV)差异显著。负期望值代表赌场优势。
public class BettingStrategy {
public enum BetType {
PASS_LINE(1.0, 0.4929), // 赔率1:1,胜率约49.29%
DONT_PASS(1.0, 0.4928), // 赔率1:1,胜率约49.28%(略低于Pass Line)
FIELD(1.0, 0.4444), // 赔率1:1(2和12额外赔付),胜率较低
ANY_7(4.0, 0.1667), // 赔率4:1,胜率16.67%
HARD_8(9.0, 0.1111), // 赔率9:1,特定组合
COME_ODDS_4(2.0, 0.3333); // 点数4的Odds投注,真实赔率
final double payout; // 赔付倍数
final double winRate;
BetType(double payout, double winRate) { this.payout = payout; this.winRate = winRate; }
public double expectedValue() {
return winRate * payout - (1 - winRate) * 1.0;
}
public double houseEdge() {
return -expectedValue();
}
}
// 比较所有投注方式的期望值
public static void analyzeAllBets() {
System.out.println("=== 各投注方式期望值分析 ===");
System.out.printf("%-15s %10s %10s %12s %12s%n",
"投注类型", "赔付", "胜率", "期望值", "赌场优势");
for (BetType bet : BetType.values()) {
System.out.printf("%-15s %10.1f %9.2f%% %11.4f %11.2f%%%n",
bet.name(), bet.payout, bet.winRate * 100,
bet.expectedValue(), bet.houseEdge() * 100);
}
}
// 凯利公式:计算最优投注比例
public static double kellyCriterion(double winRate, double odds) {
// f* = (bp - q) / b,其中b=赔率,p=胜率,q=败率
double b = odds;
double p = winRate;
double q = 1 - p;
return (b * p - q) / b;
}
}
核心算法三:蒙特卡洛模拟验证
通过大量随机模拟验证理论概率和期望值的正确性。
import java.util.Random;
public class CrapsMonteCarlo {
private Random random = new Random();
private int wins = 0, losses = 0;
public boolean playOneRound() {
int firstRoll = rollDice();
if (firstRoll == 7 || firstRoll == 11) { wins++; return true; }
if (firstRoll == 2 || firstRoll == 3 || firstRoll == 12) { losses++; return false; }
int point = firstRoll;
while (true) {
int roll = rollDice();
if (roll == point) { wins++; return true; }
if (roll == 7) { losses++; return false; }
}
}
private int rollDice() {
return random.nextInt(6) + 1 + random.nextInt(6) + 1;
}
public SimulationResult simulate(int rounds) {
wins = 0; losses = 0;
int bankroll = 1000;
int bet = 10;
int maxBankroll = bankroll;
int minBankroll = bankroll;
for (int i = 0; i < rounds; i++) {
boolean won = playOneRound();
bankroll += won ? bet : -bet;
maxBankroll = Math.max(maxBankroll, bankroll);
minBankroll = Math.min(minBankroll, bankroll);
}
return new SimulationResult(
(double)wins / rounds, bankroll, maxBankroll, minBankroll);
}
public static class SimulationResult {
public double winRate;
public int finalBankroll, maxBankroll, minBankroll;
public SimulationResult(double wr, int fb, int max, int min) {
winRate = wr; finalBankroll = fb; maxBankroll = max; minBankroll = min;
}
}
public static void main(String[] args) {
CrapsProbability.printProbabilities();
BettingStrategy.analyzeAllBets();
System.out.println("
=== 蒙特卡洛模拟(100万轮) ===");
CrapsMonteCarlo mc = new CrapsMonteCarlo();
SimulationResult result = mc.simulate(1_000_000);
System.out.printf("模拟胜率: %.4f%n", result.winRate);
System.out.printf("理论胜率: %.4f%n", CrapsProbability.calculatePassLineWinRate());
System.out.printf("初始资金: 1000, 最终: %d, 最高: %d, 最低: %d%n",
result.finalBankroll, result.maxBankroll, result.minBankroll);
}
}
复杂度分析
| 算法模块 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 概率分布计算 | O(1)(固定36种组合) | O(1) |
| 胜率公式计算 | O(1) | O(1) |
| 期望值分析 | O(b),b为投注类型数 | O(1) |
| 蒙特卡洛模拟 | O(n),n为模拟轮数 | O(1) |
总结
本文通过条件概率精确推导了Craps各结果的概率,利用期望值分析揭示了不同投注方式的赌场优势,并用蒙特卡洛模拟验证了理论结果。最优策略是选择Pass Line + Odds组合投注,可将赌场优势降至约0.5%以下。读者可以在此基础上探索更复杂的投注组合策略,深入理解概率论与期望优化在博弈决策中的应用。