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