C語言模擬摜蛋中一方最多炸彈數(shù)的數(shù)學(xué)期望

摜蛋是一種在江蘇、安徽地區(qū)廣為流傳的牌類游戲。由地方的撲克牌局“跑得快”、“八十分”發(fā)展而來。牌局采用四人結(jié)對(duì)競賽,輸贏升級(jí)的方式進(jìn)行。由于使用兩副牌,并且有“逢人配”、“同花順”規(guī)則,故炸彈的個(gè)數(shù)對(duì)于牌局來說非常重要。通過C語言模擬的方式,可以預(yù)估出一位玩家手中拿到的炸彈數(shù)的數(shù)學(xué)期望。對(duì)于評(píng)估自己的牌型有著一定的意義。

名詞解釋

炸彈:大于等于4張相同點(diǎn)數(shù)的牌,或4張王(稱為天王炸彈),或同花順
逢人配:兩張?的主牌稱為逢人配,在與其他牌配合時(shí)可以當(dāng)除了王之外的任意花色任意點(diǎn)數(shù)的牌,主牌的點(diǎn)數(shù)可從2取到A
同花順:相同花色連續(xù)的五張牌,最大的為同花10JQKA,最小的為同花A2345,可當(dāng)炸彈使用


程序框架

程序完全模擬摜蛋的操作流程,總體上共有以下5個(gè)模塊
1.印制牌:按照順序給108張牌賦上花色和點(diǎn)數(shù)
2.洗牌:多次隨機(jī)交換牌的位置,打亂牌的順序
3.發(fā)牌:將洗完的牌輪流發(fā)給4位玩家
4.理牌:(按順序排列玩家1手中的牌【可不做】)找出玩家1手中炸彈個(gè)數(shù)的最大值
5.循環(huán)重復(fù)以上步驟若干次,統(tǒng)計(jì)玩家1拿到炸彈個(gè)數(shù)的平均值并輸出結(jié)果
其中,1、5在主程序中實(shí)現(xiàn),2、3、4通過函數(shù)實(shí)現(xiàn)。


算法闡述

首先需要建立一個(gè)存儲(chǔ)一張牌的花色與點(diǎn)數(shù)的結(jié)構(gòu)體poker。

1.印制牌

創(chuàng)建包含108個(gè)poker結(jié)構(gòu)體的結(jié)構(gòu)體數(shù)組deck(一套牌),隨后第i張牌的點(diǎn)數(shù)即為i模13取余。第1-13、53-65張牌為第一種花色;第14-26、66-78張牌為第二種花色……以此類推,第105、106張牌為小王,算作第5種花色;第107、108張牌為大王,算作第6種花色。

2.洗牌

將印制好的牌堆deck傳入函數(shù)randsort。以偽隨機(jī)數(shù)種子——系統(tǒng)時(shí)間產(chǎn)生兩個(gè)偽隨機(jī)數(shù)i和j,將牌堆中的第i張牌和第j張牌位置交換,重復(fù)上述操作1000次。

3.發(fā)牌

創(chuàng)建四個(gè)poker結(jié)構(gòu)體的結(jié)構(gòu)體數(shù)組p1、p2、p3、p4,用于存儲(chǔ)4個(gè)玩家手中的牌。將結(jié)構(gòu)體數(shù)組deck和p1,p2,p3,p4傳入函數(shù)dealpoker,將deck中的第i張牌發(fā)給第j個(gè)人,其中j為i模4取余。

4.理牌

(1)不考慮逢人配

共分以下兩種情況考慮炸彈的個(gè)數(shù):
非同花順:統(tǒng)計(jì)玩家1手中所有點(diǎn)數(shù)牌的張數(shù)和王的個(gè)數(shù),找出所有張數(shù)大于等于4的點(diǎn)數(shù),統(tǒng)計(jì)其個(gè)數(shù),結(jié)果即為點(diǎn)數(shù)炸彈的炸彈數(shù)。特別地,當(dāng)某一個(gè)點(diǎn)數(shù)的牌張數(shù)為8張時(shí),需要拆成兩個(gè)炸彈使用,即炸彈數(shù)+1;當(dāng)王的個(gè)數(shù)為4時(shí),炸彈數(shù)+1.
同花順:需要按順序檢索從A開頭到10開頭的同花順。當(dāng)檢索A開頭的同花順時(shí),首先指定一種花色,搜索同花順的第1張牌,若找到,將這張牌與【非整理好的同花順】的牌的最后一張交換位置,繼續(xù)尋找第二張,若找到,將這張牌與【非整理好的同花順】的牌的倒數(shù)第二張交換位置,繼續(xù)尋找……依此類推。如果五張都能找到,檢查這五個(gè)點(diǎn)數(shù)中牌的張數(shù)恰好為4的有幾個(gè)。若多于1個(gè)(如:22223455556),則不算作同花順。若只有1個(gè),則算作同花順,同花順個(gè)數(shù)+1,但是炸彈數(shù)-1,那五張牌稱之為【整理好的同花順】;若沒有,則算作同花順,同花順個(gè)數(shù)+1。考慮到可能出現(xiàn)兩個(gè)完全一樣的同花順,上述5張牌檢索的流程需要進(jìn)行兩遍。
最后,將炸彈數(shù)加上同花順個(gè)數(shù),得到最終結(jié)果

(2)考慮逢人配

首先指定逢人配的點(diǎn)數(shù),隨后檢索玩家一手中的逢人配的位置與個(gè)數(shù)。將逢人配依次視作各張牌面的牌,再統(tǒng)計(jì)其炸彈數(shù),選擇最大值作為最終結(jié)果。
有兩張逢人配時(shí),同理。

5.重復(fù)

重復(fù)上述操作相當(dāng)大次數(shù),計(jì)算出平均值,作為玩家一手中拿到最多炸彈個(gè)數(shù)的數(shù)學(xué)期望的估計(jì)值,并打印結(jié)果。


結(jié)果呈現(xiàn)

output1.png

另外,也可單獨(dú)統(tǒng)計(jì)同花順的期望

output2.png

當(dāng)不算逢人配時(shí),統(tǒng)計(jì)純數(shù)字炸彈的期望,約為1.365


output3.png

與貼吧大神的純概率計(jì)算較為接近

tieba.png

代碼實(shí)現(xiàn)

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>

struct poker //結(jié)構(gòu)體poker中存儲(chǔ)了一張牌的花色和點(diǎn)數(shù)
{
    int suit;
    int num;
};

void randsort(struct poker *a) //randsort表示洗牌,傳入一套牌的結(jié)構(gòu)體數(shù)組
{
    int i,j,T=1000;
    struct poker tmp;
    srand(time(NULL)); //產(chǎn)生一個(gè)偽隨機(jī)數(shù)種子
    for(;T>0;T--)
    {
        i=rand()%108;
        j=rand()%108;
        tmp=a[i];
        a[i]=a[j];
        a[j]=tmp;
    }                  //隨機(jī)交換兩張牌的順序,進(jìn)行1000次
}
void dealpoker(struct poker *p,struct poker *a,struct poker *b,struct poker *c,struct poker *d)//發(fā)牌函數(shù)
{
    int i;
    for(i=0;i<27;i++)
    {
        a[i]=p[i*4];
        b[i]=p[i*4+1];
        c[i]=p[i*4+2];
        d[i]=p[i*4+3];
    }
}

void bubblesort(struct poker *a) //理牌函數(shù),調(diào)試程序時(shí)用
{
    int i,j;
    struct poker tmp;
    for(i=0;i<26;i++)
        for(j=0;j<26-i;j++)
    {
        if(a[j].num>a[j+1].num)
            {
                tmp=a[j];
                a[j]=a[j+1];
                a[j+1]=tmp;
            }
    }
}

int bombcheck(struct poker *a) //檢驗(yàn)炸彈的數(shù)量
{
    int i,j,n,ans=0,t,l=0,ths=0,king=0,pd;
    int numb[13]={0};          //numb數(shù)組存儲(chǔ)每一個(gè)數(shù)字的牌有幾張
    struct poker tmp;          //臨時(shí)牌結(jié)構(gòu)體,檢索同花順時(shí)用
    for(i=1;i<=13;i++)
    {
        for(j=0;j<27;j++)
            if(a[j].num==i)
                numb[i-1]++;
        if(numb[i-1]>=4) ans++;//如果某一數(shù)字的張數(shù)不少于4,炸彈數(shù)+1
        if(numb[i-1]==8) ans++;//如果某一數(shù)字的張數(shù)為8,則拆成兩個(gè)炸彈,炸彈數(shù)+1
        if(a[j].suit>4)
            king++;            //統(tǒng)計(jì)王的張數(shù)
    }
    if(king==4) ans++;         //如果有4張王,炸彈數(shù)+1
    for(i=1;i<=10;i++)         //檢索同花順,從A開頭檢驗(yàn)到10開頭
        {
            for(j=0;j<8;j++)  //先指定一種花色
            {
                for(n=0;n<27-5*ths;n++)    //ths表示同花順的個(gè)數(shù)
                    if(a[n].num==i&&a[n].suit==j%4+1)
                {
                    tmp=a[n];a[n]=a[26-5*ths];a[26-5*ths]=tmp; //在前27-5ths中找指定花色的第一張牌,如果找到,將其與牌組中除同花順的最后一張牌交換順序,如果找不到,不執(zhí)行操作
                    for(n=0;n<27-5*ths;n++)
                        if(a[n].num==i+1&&a[n].suit==j%4+1)
                        {
                            tmp=a[n];a[n]=a[25-5*ths];a[25-5*ths]=tmp;//在前27-5ths中找指定花色的第二張牌,如果找到,將其與牌組中除同花順的倒數(shù)第二張牌交換順序
                            for(n=0;n<27-5*ths;n++)
                                if(a[n].num==i+2&&a[n].suit==j%4+1)
                                {
                                    tmp=a[n];a[n]=a[24-5*ths];a[24-5*ths]=tmp;  //同理
                                    for(n=0;n<27-5*ths;n++)
                                        if(a[n].num==i+3&&a[n].suit==j%4+1)
                                        {
                                            tmp=a[n];a[n]=a[23-5*ths];a[23-5*ths]=tmp;  //同理
                                            if(i!=10)
                                                pd=i+4;
                                                else
                                                    pd=1;                                  //pd確定同花順的最后一張牌值為多少
                                            for(n=0;n<27-5*ths;n++)
                                                if(a[n].num==pd&&a[n].suit==j%4+1)
                                                {
                                                    tmp=a[n];a[n]=a[22-5*ths];a[22-5*ths]=tmp;  //同理
                                                    l=0;                                        //l為這個(gè)同花順中數(shù)字對(duì)應(yīng)的牌數(shù)恰好為4的個(gè)數(shù)
                                                    for(t=0;t<5;t++)
                                                    {
                                                        if(numb[i+t]==4)
                                                            l++;
                                                    }
                                                    if(l<=1)
                                                        ths++;                                  //如果不多于2個(gè),則同花順個(gè)數(shù)+1(多于兩個(gè)則組炸彈而不是同花順)
                                                    if(l==1)
                                                        ans--;                                  //如果恰為1個(gè),則組同花順不組炸彈,同花順個(gè)數(shù)+1,炸彈個(gè)數(shù)-1
                                                }
                                        }
                                }

                        }
                    }
                }
            }
    ans+=ths; //把炸彈數(shù)加上同花順的個(gè)數(shù)
    return ans;
}

int bomb(struct poker *a,int frp)  //考慮到逢人配以后的炸彈個(gè)數(shù)
{
    int i,j,m,n,p=0,t,ans=0;
    int location[2]={0};                  //location數(shù)組表示逢人配的位置
    struct poker tmp[27];                 //tmp為為避免位置信息丟失臨時(shí)存儲(chǔ)數(shù)據(jù)的結(jié)構(gòu)體數(shù)組
    for (i=0;i<27;i++)
        if(a[i].suit==1&&a[i].num==frp)
        {location[p]=i;p++;}              //如果找到逢人配,逢人配的個(gè)數(shù)p +1,同時(shí)記錄逢人配位置
    if(p==1)
        for(i=1;i<=4;i++)                 //讓逢人配模擬每一張非王的牌
        {
            a[location[0]].suit=i;
            for(j=1;j<=13;j++)
            {
                a[location[0]].num=j;
                for(t=0;t<27;t++)
                    tmp[t]=a[t];          //改變逢人配牌面后,將牌組信息賦給臨時(shí)牌組tmp,避免bombcheck函數(shù)尋找同花順時(shí)打亂順序
                ans=(bombcheck(tmp)>ans)?bombcheck(tmp):ans;   //ans取每一種情況的最大值
            }
        }
    if(p==2)                              //有兩個(gè)逢人配時(shí),同理
        for(i=1;i<=4;i++)
        {
            a[location[0]].suit=i;
            for(j=1;j<=13;j++)
            {
                a[location[0]].num=j;
                for(m=1;m<=4;m++)
                {
                    a[location[1]].suit=m;
                    for(n=1;n<=13;n++)
                    {
                        a[location[1]].num=n;
                        for(t=0;t<27;t++)
                            tmp[t]=a[t];
                        ans=(bombcheck(tmp)>ans)?bombcheck(tmp):ans;
                    }
                }
            }
        }
    if(p==0)    //如果沒有逢人配,直接檢查炸彈個(gè)數(shù)
        ans=bombcheck(a);
    return ans;
}

int main()
{
    int i,j,bmb,test;           //bmb為每次炸彈個(gè)數(shù),test表示模擬次數(shù)
    float sum=0;                //sum為求和
    char pm[3]={'\0'};          //pm為牌面顯示數(shù)字
    struct poker deck[108],p1[27],p2[27],p3[27],p4[27];//deck表示一套牌,
    for(i=0;i<13;i++)           //對(duì)一套牌中的每張牌按順序賦值
    {
        for(j=0;j<8;j++)
            deck[i+j*13].num=i+1;
    }
    for(i=0;i<8;i++)
    {
        for(j=0;j<13;j++)
            deck[j+i*13].suit=i%4+1;
    }
    deck[104].num=100;
    deck[104].suit=5;
    deck[105].num=100;
    deck[105].suit=5;
    deck[106].num=200;
    deck[106].suit=6;
    deck[107].num=200;
    deck[107].suit=6;           //對(duì)最后4張王賦值
    for(i=1;i<=13;i++)          //逢人配從A到K
        {
            for(test=0;test<=10000;test++)
            {
                randsort(deck);  //洗牌
                dealpoker(deck,p1,p2,p3,p4);  //發(fā)牌
    //bubblesort(p1);
    /*bubblesort(p2);
    bubblesort(p3);
    bubblesort(p4);*/                         //理牌
                bmb=bomb(p1,i);
                sum+=bmb;
            }
                switch (i)
                {
                    case 1: pm[0]='A';break;
                    case 10:pm[0]='1';pm[1]='0';break;
                    case 11:pm[0]='J';pm[1]='\0';break;
                    case 12:pm[0]='Q';pm[1]='\0';break;
                    case 13:pm[0]='K';pm[1]='\0';break;
                    default:pm[0]=i+48;
                }
                printf("逢人配為紅桃%s時(shí)拿到炸彈的期望約為%f\n",pm,sum/10000); //打印逢人配信息
                sum=0;
        }
    return 0;
}

總結(jié)

炸彈不多是常事,且用且珍惜啊XD

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 你的數(shù)學(xué)直覺怎么樣?你能憑借直覺,迅速地判斷出誰的概率大,誰的概率小嗎?下面就是 26 個(gè)這樣的問題。如果你感興趣...
    cnnjzc閱讀 7,466評(píng)論 0 12
  • 我看縫紉機(jī)樂隊(duì),我記住了里面一句最心酸的話:“錢可以救命,但理想不能?!蹦歉杏X好像我們做了完全的努力,也全是白費(fèi)。...
    歸還不過還好閱讀 220評(píng)論 2 1
  • [問答03 | 父母才是孩子的起跑線2017.07.13留言] 大多數(shù)家長非常關(guān)注對(duì)孩子的培養(yǎng),從兒童抓起,各種各...
    早知今日閱讀 392評(píng)論 5 5
  • 讓我們坐上時(shí)光機(jī), 一起到未來2023年這一年, 這一年, 是我們認(rèn)知自我升級(jí)的第一輩子,我和007的戰(zhàn)友們一起踏...
    絕版小貝Anni閱讀 420評(píng)論 3 4
  • 《超級(jí)個(gè)體》提問官古典的每日一問:24/30 周末啊,今天的話題比較八卦)——前段時(shí)間咪蒙和自己實(shí)習(xí)生各自寫文撕逼...
    Katrina程閱讀 187評(píng)論 0 0

友情鏈接更多精彩內(nèi)容