How much can your Doudizhu get fried?

I'm bored recently, and I want to know how likely I can get a bomb (4 cards of the same rank or collect all the big kings) if I play Dou Landlord. But I was not good at learning probability, so I thought of using statistics to try it out, and I wrote a program to simulate the licensing process of Doudizhu.

Object Oriented Card

First of all, according to the OOP idea, I regard the card as an object, and the point and suit are its attributes. In order to deal with the big and small kings, the Type attribute is added.

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;
    }
}

I used the enumeration class to represent the three attributes, purely because since it is object-oriented, it is pure

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 indicates the size of the points, sorted from small to large. The purpose of adding the sequence attribute is to facilitate processing in statistics

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

    // 权重
    int weight;

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

Suit suits, the weight attribute is added as the size weight. The suits of Dou Dizhu are not divided into big and small, but some cards will be distinguished, so add it as you like.

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

    int weight;

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

Type card type, mainly for special handling of big and small kings. According to the weight value, the small king is smaller than the big king~

calculation process

First abstract the steps of playing cards, the first step: take out a deck of cards; the second step: shuffle (randomly shuffle); the third step: deal cards; the fourth step: Play (calculate whether there is a bomb) ; I simplified the licensing method and put it into the main program. The other steps are implemented as follows

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

The game master method, let's calculate the probability of each of the landlord and the peasant.

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));
    }

After running the program once, I found that the running speed was quite fast. Anyway, 100,000 times were less than half a second. Only after running the program did I find that the three cards of the landlord had such a great influence on the probability of taking the bomb, and the probability could be nearly doubled. Of course, the program is written by me casually, and there may be places where data is wrong due to imprecision. If you find it, please correct it. Secondly, in the writing specification of the enumeration class, I stole some laziness, not all capitalized~

地主炸弹概率 0.302310 , 农民炸弹概率 0.186460
运行时 0.217000 s

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324387630&siteId=291194637