Craps是一种经典的赌场骰子游戏,玩家通过投掷两颗六面骰子进行博弈。游戏的第一轮称为”出场掷”(Come Out Roll),根据点数总和决定后续流程:掷出7或11直接获胜,掷出2、3或12直接落败,掷出4、5、6、8、9、10则确立”点数”(Point),进入追逐阶段继续投掷,直到再次掷出点数获胜或掷出7落败。本文将用Java完整实现Craps游戏的核心逻辑,并深入分析其概率模型与期望值计算。
游戏规则与状态机建模
Craps的核心在于清晰的状态流转。游戏可抽象为有限状态机:初始状态为COME_OUT,掷骰后根据点数转移到WIN、LOSE或POINT_ESTABLISHED。进入点数状态后,持续掷骰直至命中目标点数或7。这种状态机设计为算法实现提供了清晰的框架。
不同点数组合的概率是分析关键。两颗骰子共有36种等可能结果:7的出现概率最高(6/36 = 16.67%),其次是6和8(各5/36 = 13.89%),2和12最低(各1/36 = 2.78%)。理解这些概率是制定投注策略的基础。
核心算法:概率计算与期望值分析
胜率计算
Craps中”通过线”(Pass Line)投注的胜率计算需要分情况讨论:
- 出场掷直接获胜:P(7)+P(11) = 6/36 + 2/36 = 8/36
- 出场掷直接落败:P(2)+P(3)+P(12) = 1/36 + 2/36 + 1/36 = 4/36
- 确立点数后的条件概率:若点数为4,则需在4和7之间竞争,P(获胜|点数=4) = 3/(3+6) = 3/9
综合胜率 = 8/36 + Σ[P(确立点数=k) × P(命中k | k或7)]
蒙特卡洛模拟验证
对于复杂投注组合的精确计算,蒙特卡洛模拟是有效的验证手段。通过大量随机实验统计胜率,可与理论值相互印证。
Java实现
import java.util.Random;
import java.util.Map;
import java.util.HashMap;
/**
* Craps掷骰子游戏完整实现
* 包含游戏状态机、概率计算与蒙特卡洛模拟
*/
public class CrapsGame {
// 游戏状态枚举
enum GameState {
COME_OUT, // 出场掷阶段
POINT_ESTABLISHED, // 已确立点数
WIN, // 获胜
LOSE // 落败
}
private final Random random;
private GameState state;
private int point; // 确立的点数
private int rollCount;
public CrapsGame() {
this.random = new Random();
reset();
}
/**
* 重置游戏状态
*/
public void reset() {
this.state = GameState.COME_OUT;
this.point = 0;
this.rollCount = 0;
}
/**
* 投掷两颗骰子,返回点数总和
*/
public int rollDice() {
int die1 = random.nextInt(6) + 1;
int die2 = random.nextInt(6) + 1;
rollCount++;
return die1 + die2;
}
/**
* 执行一次投掷并根据当前状态更新游戏状态
* @return 当前回合的点数总和
*/
public int playRound() {
if (state == GameState.WIN || state == GameState.LOSE) {
throw new IllegalStateException("游戏已结束,请先调用reset()");
}
int roll = rollDice();
if (state == GameState.COME_OUT) {
// 出场掷阶段
switch (roll) {
case 7: case 11:
state = GameState.WIN;
break;
case 2: case 3: case 12:
state = GameState.LOSE;
break;
default:
point = roll;
state = GameState.POINT_ESTABLISHED;
break;
}
} else if (state == GameState.POINT_ESTABLISHED) {
// 追逐阶段
if (roll == point) {
state = GameState.WIN;
} else if (roll == 7) {
state = GameState.LOSE;
}
// 其他点数继续游戏
}
return roll;
}
/**
* 完整运行一局游戏直到结束
* @return true表示获胜,false表示落败
*/
public boolean playGame() {
reset();
while (state != GameState.WIN && state != GameState.LOSE) {
playRound();
}
return state == GameState.WIN;
}
/**
* 计算理论胜率(Pass Line投注)
*/
public static double calculateTheoreticalWinRate() {
// 出场掷直接获胜:7或11
double instantWin = 8.0 / 36.0;
// 出场掷直接落败:2、3或12
double instantLose = 4.0 / 36.0;
// 确立点数后的条件胜率
Map<Integer, Double> pointProb = new HashMap<>();
pointProb.put(4, 3.0 / 9.0);
pointProb.put(5, 4.0 / 10.0);
pointProb.put(6, 5.0 / 11.0);
pointProb.put(8, 5.0 / 11.0);
pointProb.put(9, 4.0 / 10.0);
pointProb.put(10, 3.0 / 9.0);
// 各点数确立概率
Map<Integer, Double> establishProb = new HashMap<>();
establishProb.put(4, 3.0 / 36.0);
establishProb.put(5, 4.0 / 36.0);
establishProb.put(6, 5.0 / 36.0);
establishProb.put(8, 5.0 / 36.0);
establishProb.put(9, 4.0 / 36.0);
establishProb.put(10, 3.0 / 36.0);
double pointWin = 0.0;
for (int p : pointProb.keySet()) {
pointWin += establishProb.get(p) * pointProb.get(p);
}
return instantWin + pointWin;
}
/**
* 蒙特卡洛模拟估算胜率
* @param trials 模拟次数
*/
public static double monteCarloSimulation(int trials) {
CrapsGame game = new CrapsGame();
int wins = 0;
for (int i = 0; i < trials; i++) {
if (game.playGame()) {
wins++;
}
}
return (double) wins / trials;
}
/**
* 计算两颗骰子各点数总和的概率分布
*/
public static Map<Integer, Double> calculateDiceDistribution() {
Map<Integer, Integer> counts = new HashMap<>();
for (int i = 1; i <= 6; i++) {
for (int j = 1; j <= 6; j++) {
int sum = i + j;
counts.put(sum, counts.getOrDefault(sum, 0) + 1);
}
}
Map<Integer, Double> distribution = new HashMap<>();
for (int sum : counts.keySet()) {
distribution.put(sum, counts.get(sum) / 36.0);
}
return distribution;
}
public GameState getState() { return state; }
public int getPoint() { return point; }
public int getRollCount() { return rollCount; }
public static void main(String[] args) {
// 输出概率分布
System.out.println("=== 两颗骰子点数概率分布 ===");
Map<Integer, Double> dist = calculateDiceDistribution();
for (int i = 2; i <= 12; i++) {
System.out.printf("点数 %2d: %.4f (%.2f%%)%n", i, dist.get(i), dist.get(i) * 100);
}
// 理论胜率
double theoreticalRate = calculateTheoreticalWinRate();
System.out.printf("%n理论胜率(Pass Line): %.6f (%.4f%%)%n", theoreticalRate, theoreticalRate * 100);
// 蒙特卡洛模拟
int[] trialCounts = {1000, 10000, 100000, 1000000};
System.out.println("\n=== 蒙特卡洛模拟结果 ===");
for (int trials : trialCounts) {
double mcRate = monteCarloSimulation(trials);
System.out.printf("模拟 %,d 次: 胜率 = %.6f, 误差 = %.6f%n",
trials, mcRate, Math.abs(mcRate - theoreticalRate));
}
// 演示一局完整游戏
System.out.println("\n=== 演示一局游戏 ===");
CrapsGame demo = new CrapsGame();
while (demo.getState() != GameState.WIN && demo.getState() != GameState.LOSE) {
int roll = demo.playRound();
if (demo.getRollCount() == 1) {
System.out.println("出场掷: " + roll);
} else {
System.out.println("第" + demo.getRollCount() + "掷: " + roll + " (目标点数: " + demo.getPoint() + ")");
}
}
System.out.println("结果: " + (demo.getState() == GameState.WIN ? "获胜!" : "落败!") + " 共投掷" + demo.getRollCount() + "次");
}
}
复杂度分析
- 单次投掷:O(1)时间复杂度,O(1)空间复杂度
- 完整游戏:期望投掷次数约为3.38次(通过精确概率计算可得),故期望时间复杂度为O(1)
- 蒙特卡洛模拟:N次试验的时间复杂度为O(N),空间复杂度为O(1)
- 概率计算:通过枚举36种骰子组合,时间复杂度O(1)
扩展思路
基于本文的实现框架,可进一步探索:多种投注方式(不通过线、来注、现场注)的期望值差异;利用大数定律验证长期盈亏趋势;引入玩家资金管理策略(如凯利公式)优化投注比例。Craps虽然规则简单,但其概率结构蕴含丰富的数学内涵,是理解条件概率与期望值的绝佳案例。