你的鬥地主能拿多少炸?

最近無聊,想知道一下玩鬥地主的話我能有多大的機率拿到炸彈(4張同點數牌 或 集齊大小王)。可是我機率學學得很差,因而想到用統計學來試試,隨手寫了一個程序模擬一下鬥地主的發牌過程java

面向對象Card

首先依據OOP思想,我把牌看做是一個對象,點數與花色是其屬性,爲了處理大小王加入了Type屬性數組

public class Card {
    Suit suit;
    Size size;
    Type type;

    Card(Suit suit, Size size) {
        this.suit = suit;
        this.size = size;
        this.type = Type.Ordinary;
    }

    Card(Type type) {
        if (type.equals(Type.Ordinary)) {
            throw new RuntimeException("非法參數");
        }
        this.type = type;
    }
}

三個屬性我都用了枚舉類來表示,純粹是由於既然是面向對象,那麼就純粹一點dom

public enum Size {
    P3(0), P4(1), P5(2), P6(3), P7(4), P8(5), P9(6),
    P10(7), J(8), Q(9), K(10), A(11), P2(12);

    int sequence;

    Size(int sequence) {
        this.sequence =  sequence;
    }
}

Size表示點數的大小,按從小到大排序。加入sequence屬性目的是爲了在統計時方便處理ui

public enum Suit {
    Spade(4), Heart(3), Club(2), Diamond(1);

    // 權重
    int weight;

    Suit(int weight) {
        this.weight = weight;
    }
}

Suit花色,加入了weight屬性做爲大小權重,鬥地主花色不分大小,不過有些牌會區分,因此隨手加一下this

public enum Type {
    Ordinary(0), LITTLE_JOKER(1), BIG_JOKER(2);

    int weight;

    Type(int weight) {
        this.weight = weight;
    }
}

Type牌類型,主要是爲了特殊處理大小王,根據權重值,小王比大王小~code

計算過程

首先抽象一下玩牌的幾個步驟,第一步:拿出一副牌;第二步:洗牌(隨機打亂);第三步:發牌;第四步:Play(計算是否有炸彈);我簡化了發牌方法,放進主程序中,其餘步驟具體實現以下orm

/**
     * 生成一套有序牌數組
     */
    static Card[] newCards() {
        // 牌組用數組表示
        Card[] cards = new Card[54];
        // 遊標i
        int i = 0;
        // 循環放牌 13種大小 * 4種花色 = 52
        for (Size point : Size.values()) {
            for (Suit suit : Suit.values()) {
                cards[i++] = new Card(suit, point);
            }
        }
        // 插入大小王
        cards[52] = new Card(Type.LITTLE_JOKER);
        cards[53] = new Card(Type.BIG_JOKER);
        return cards;
    }

    /**
     * 洗牌
     * @param cards 打亂的牌組
     */
    static Card[] shuffle(Card[] cards) {
        Random random = new Random();
        int len = cards.length;
        // 複雜的 O(n)
        // 遍歷一副牌,每次循環隨機取一張當前牌後面的一張牌(含當前牌)與當前牌交換
        // 在徹底隨機的狀況下,每張牌在每一個位置的機率應該一致,共有 n! 種狀況
        for (int i = 0;i < len; i++) {
            int r = random.nextInt(len - i);
            change(i, r + i, cards);
        }
        return cards;
    }

    // 簡單的交互位置方法
    static void change(int a, int b, Card[] cards) {
        Card temp = cards[a];
        cards[a] = cards[b];
        cards[b] = temp;
    }
    
    static final int DOUBLE_JOKER = 3;
    static final int FULL_SUIT = 10;
    /**
     * 判斷是否有炸彈
     * @param cards 牌組
     * @return true 有炸彈 false 無炸彈
     */
    static boolean hasBoom(Card[] cards) {
        // 構造一個與Size數量等長的初始爲0的數組
        int[] counter = new int[Size.values().length]; //初始化爲0
        // 特殊處理大小王
        int weightOfJoker = 0;
        for (Card card: cards) {
            // 特殊處理大小王
            if (!card.type.equals(Type.Ordinary)) {
                weightOfJoker += card.type.weight;
                // 大小王權重和爲3時即王炸
                if (weightOfJoker == DOUBLE_JOKER) {
                    return true;
                }
                continue;
            }
            // 利用點數序列值爲下標,加上權重值,和爲10時即湊足4張牌
            counter[card.size.sequence] += card.suit.weight;
            if (counter[card.size.sequence] == FULL_SUIT) {
                return true;
            }
        }
        return false;
    }

遊戲主方法,來算算地主和農民各有多少機率吧對象

public static void main(String[] args) {
        long gameStart = System.currentTimeMillis();
        int gameTime = 100000;
        // 農民17張牌計數器
        int nongHasBoom = 0;
        // 地主20張牌計數器
        int diHasBoom = 0;
        // 運行遊戲
        for (int i = 0;i < gameTime; i++) {
            // 拿到一副新牌
            Card[] poker = newCards();
            // 洗牌
            poker = shuffle(poker);
            // 發牌 在隨機的狀況下,連續發和分開發理論上不影響你拿牌的機率,簡化
            Card[] nong = Arrays.copyOf(poker, 17);
            Card[] di = Arrays.copyOfRange(poker, 17, 17 + 20);
            nongHasBoom += hasBoom(nong)? 1 : 0;
            diHasBoom += hasBoom(di)? 1 : 0;
        }
        long gameEnd = System.currentTimeMillis();
        System.out.println(String.format("地主炸彈機率 %f , 農民炸彈機率 %f", diHasBoom * 1.0 / gameTime, nongHasBoom * 0.1 / gameTime));
        System.out.println(String.format("運行時 %f s", (gameEnd - gameStart)/1000.0));
    }

運行一次程序,發現運行速度還挺快,反正10萬次不足半秒,運行才發現,地主三張牌對拿炸彈的機率影響居然這麼大,能夠提高將近一倍的機率。固然程序是我隨便寫的,可能存在不嚴謹致使數據錯誤的地方,若是發現還請斧正。其次在枚舉類的書寫規範上,我偷了一些懶,沒有所有大寫~排序

地主炸彈機率 0.302310 , 農民炸彈機率 0.186460
運行時 0.217000 s
相關文章
相關標籤/搜索