最近无聊,想知道一下玩斗地主的话我能有多大的几率拿到炸弹(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