文档章节

你的斗地主能拿多少炸?

BekeyChao
 BekeyChao
发布于 02/12 16:56
字数 1104
阅读 1073
收藏 21

最近无聊,想知道一下玩斗地主的话我能有多大的概率拿到炸弹(4张同点数牌 或 集齐大小王)。但是我概率学学得不好,于是想到用统计学来试试,随手写了一个程序模拟一下斗地主的发牌过程

面向对象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;
    }
}

三个属性我都用了枚举类来表示,纯粹是因为既然是面向对象,那么就纯粹一点

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属性目的是为了在统计时方便处理

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

    // 权重
    int weight;

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

Suit花色,加入了weight属性作为大小权重,斗地主花色不分大小,不过有些牌会区分,所以随手加一下

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

    int weight;

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

Type牌类型,主要是为了特殊处理大小王,根据权重值,小王比大王小~

计算过程

首先抽象一下玩牌的几个步骤,第一步:拿出一副牌;第二步:洗牌(随机打乱);第三步:发牌;第四步:Play(计算是否有炸弹);我简化了发牌方法,放进主程序中,其他步骤具体实现如下

/**
     * 生成一套有序牌数组
     */
    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

© 著作权归作者所有

共有 人打赏支持
BekeyChao
粉丝 26
博文 10
码字总数 15621
作品 0
南京
程序员
私信 提问
加载中

评论(10)

MyOldTime
MyOldTime
算了懒子么����
shijunti
shijunti
反正提示豆子不足
BekeyChao
BekeyChao

引用来自“marshalys”的评论

直接算多好:
地主:20*19/(54*53)+20*19*18*17/(54*53*52*51) =0.3318
单个农民:17*16/(54*53)+17*16*15*14/(54*53*52*51) =0.1928
不好意思,我是学渣,推不出这个公式。其次,你这个化简了吧?比如王炸的概率应该有异常,可能要特殊处理。然后还是谢谢你的关注。
xcorp
xcorp

引用来自“BoXuan”的评论

提出几点建议,每张牌无需用Card对象,完全可以用一个数字表示,这样可以避免大量的Card对象被创建和垃圾回收。
花色枚举可以为大王(6)、小王(5)、黑桃(4)、红心(3)、梅花(2)、方块(1),除大小王比较特殊外可以用500表示小王,600表示大王,其它牌可以是 “花色 * 100 + 牌点” ,则401表示黑桃A,402表示黑桃2, 403表示黑桃3 …… 413表示黑桃K,以此类推其它红心、梅花、方块的数字表示形式

@BoXuan 学习了
marshalys
marshalys
直接算多好:
地主:20*19/(54*53)+20*19*18*17/(54*53*52*51) =0.3318
单个农民:17*16/(54*53)+17*16*15*14/(54*53*52*51) =0.1928
C
Chuxus_

引用来自“BoXuan”的评论

提出几点建议,每张牌无需用Card对象,完全可以用一个数字表示,这样可以避免大量的Card对象被创建和垃圾回收。
花色枚举可以为大王(6)、小王(5)、黑桃(4)、红心(3)、梅花(2)、方块(1),除大小王比较特殊外可以用500表示小王,600表示大王,其它牌可以是 “花色 * 100 + 牌点” ,则401表示黑桃A,402表示黑桃2, 403表示黑桃3 …… 413表示黑桃K,以此类推其它红心、梅花、方块的数字表示形式
独到的见解👍
银杏果果
银杏果果

引用来自“BoXuan”的评论

提出几点建议,每张牌无需用Card对象,完全可以用一个数字表示,这样可以避免大量的Card对象被创建和垃圾回收。
花色枚举可以为大王(6)、小王(5)、黑桃(4)、红心(3)、梅花(2)、方块(1),除大小王比较特殊外可以用500表示小王,600表示大王,其它牌可以是 “花色 * 100 + 牌点” ,则401表示黑桃A,402表示黑桃2, 403表示黑桃3 …… 413表示黑桃K,以此类推其它红心、梅花、方块的数字表示形式

引用来自“BekeyChao”的评论

主要是复习面向对象,Card是抽象出来的。其次,理论上54张牌,完全可以全部使用枚举类,这样都是天然单例就不会产生对象回收的事情。至于大小王特殊处理,一方面在newCards时,一套循环就可以处理普通牌,而不必判断王牌的状态,另一方面hasBoom中直接取下标处理更优雅一些。如果为了简单像你这样直接约定格式确实会更方便一些,谢谢。
嗯,相对你目前的需求,你这样设计完全足够了,但如果涉及前后端通信及牌型判断和大小比较,用纯数字要简单很多的,哪怕你用54个枚举表示54张牌都是很不方便的,再有就是前端展示,前端只要收到一串数字就直接知道要显示哪些牌,很方便的。
只是建议一下,因为我做过好几款网络棋牌游戏了,希望能给你一些参考,或许你做游戏的时候用得着。
BekeyChao
BekeyChao

引用来自“BoXuan”的评论

提出几点建议,每张牌无需用Card对象,完全可以用一个数字表示,这样可以避免大量的Card对象被创建和垃圾回收。
花色枚举可以为大王(6)、小王(5)、黑桃(4)、红心(3)、梅花(2)、方块(1),除大小王比较特殊外可以用500表示小王,600表示大王,其它牌可以是 “花色 * 100 + 牌点” ,则401表示黑桃A,402表示黑桃2, 403表示黑桃3 …… 413表示黑桃K,以此类推其它红心、梅花、方块的数字表示形式
主要是复习面向对象,Card是抽象出来的。其次,理论上54张牌,完全可以全部使用枚举类,这样都是天然单例就不会产生对象回收的事情。至于大小王特殊处理,一方面在newCards时,一套循环就可以处理普通牌,而不必判断王牌的状态,另一方面hasBoom中直接取下标处理更优雅一些。如果为了简单像你这样直接约定格式确实会更方便一些,谢谢。
银杏果果
银杏果果
提出几点建议,每张牌无需用Card对象,完全可以用一个数字表示,这样可以避免大量的Card对象被创建和垃圾回收。
花色枚举可以为大王(6)、小王(5)、黑桃(4)、红心(3)、梅花(2)、方块(1),除大小王比较特殊外可以用500表示小王,600表示大王,其它牌可以是 “花色 * 100 + 牌点” ,则401表示黑桃A,402表示黑桃2, 403表示黑桃3 …… 413表示黑桃K,以此类推其它红心、梅花、方块的数字表示形式
滴滴丶哔哔
滴滴丶哔哔
可以的
实在是憋不住了,吐槽一下

中国的厂商都是这么坑爹么?用酷狗随机播放,从来都是随机那几首,qq斗地主发牌经常是大家都有好几个炸!一个简单的随机算法被他们折腾成啥了? 好吧有人说酷狗会智能的帮你选择你喜欢的歌,...

王振威
2012/07/31
1K
17
h5牛牛源码出售你的斗地主能拿多少炸?

最近无聊,想知道一下玩斗地主的话我能有多大的概率拿到炸弹(4张同点数牌 或 集齐大小王)。但是我概率学学得不好,于是想到用统计学来试试,随手写了一个程序模拟一下斗地主的发牌过程 面向...

sinat_41780517
03/02
0
0
【英语】二月份,我说重生,你说是!

一、接一月 其实一月份过完了,马上就到了二月份,二月份冬天过去了,马上就暖和了,学习英语的热情也上涨了不少。小编又开始整编了一下队伍,大家学习的热情也不断的上涨了,这个还是不错的...

kisscatforever
2017/02/25
0
0
斗地主AI算法——第一章の业务逻辑

转眼间快到了五月,帝都的天气也变的非常梦幻。 时而酷暑炎热,时而狂风席卷。 而不管外面如何,我们也只能在办公室里茕茕无依的撸着代码,无可奈何的负着韶华。 世界是寂寞的,寂寞到不只是...

sm9sun
2017/04/26
0
0
棋牌游戏市场几种主流的实物兑换体系分析

  网狐在之前发布的文章说过:棋牌游戏运营的过程,在很大程度上是跟政策博弈的过程。在国内,因为棋牌游戏行业的特殊性,棋牌从业人士一直致力于行业的阳光化、可持续化发展。毕竟棋牌游戏...

网狐棋牌开发
2017/12/13
0
0

没有更多内容

加载失败,请刷新页面

加载更多

PHP生成CSV之内部换行

当我们使用PHP将采集到的文件内容保存到csv文件时,往往需要将采集内容进行二次过滤处理才能得到需要的内容。比如网页中的换行符,空格符等等。 对于空格等处理起来都比较简单,这里我们单独...

豆花饭烧土豆
今天
2
0
使用 mjml 生成 thymeleaf 邮件框架模板

发邮件算是系统开发的一个基本需求了,不过搞邮件模板实在是件恶心事,估计搞过的同仁都有体会。 得支持多种客户端 支持响应式 疼彻心扉的 outlook 多数客户端只支持 inline 形式的 css 布局...

郁也风
今天
8
0
让哲学照亮我们的人生——读《医务工作者需要学点哲学》有感2600字

让哲学照亮我们的人生——读《医务工作者需要学点哲学》有感2600字: 作者:孙冬梅;以前读韩国前总统朴槿惠的著作《绝望锻炼了我》时,里面有一句话令我印象深刻,她说“在我最困难的时期,...

原创小博客
今天
4
0
JAVA-四元数类

public class Quaternion { private final double x0, x1, x2, x3; // 四元数构造函数 public Quaternion(double x0, double x1, double x2, double x3) { this.x0 = ......

Pulsar-V
今天
18
0
Xshell利用Xftp传输文件,使用pure-ftpd搭建ftp服务

Xftp传输文件 如果已经通过Xshell登录到服务器,此时可以使用快捷键ctrl+alt+f 打开Xftp并展示Xshell当前的目录,之后直接拖拽传输文件即可。 pure-ftpd搭建ftp服务 pure-ftpd要比vsftp简单,...

野雪球
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部