每日算法 — 使用java实现Craps:条件概率与期望值最优投注策略

游戏场景与算法概述

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%以下。读者可以在此基础上探索更复杂的投注组合策略,深入理解概率论与期望优化在博弈决策中的应用。