ポーカーの役ができる確率を計算してみた（ジョーカー無し）

[ポーカーの役ができる確率を計算してみました](https://github.com/anonymously115/repository115/blob/main/poker.c)。但し、ジョーカーを除いています。
- - -
- ###### C言語で作成するので、インクルード
```poker.c
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
```
- - -
- ###### 全カードの枚数と手札の枚数を宣言します。
```c
/* カードの枚数 */
const int N_CARDS=52;

/* カード（C[i][0]はマーク、C[i][1]は数字(A⇒0,2⇒1,...,10⇒9,J⇒10,Q⇒11,K⇒12)を表します） */
int CARDS[N_CARDS][2];

/* 手札の枚数 */
const int N_HANDS=5;
```
- - -
- ###### 初期設定
:::note
グローバル変数`CARDS`において、
$0\le i<j<52$ならば$C_{i,1}\le C_{j,1}$
（`0<=i<j<52`ならば`CARDS[i][1]<=CARDS[j][1]`）
となるようにします。
:::
```c
/* 初期設定 */
void init(){
    for(int i=0;i<N_CARDS;i++){
        //マーク
        CARDS[i][0]=i%4;
        
        //数字
        CARDS[i][1]=i/4;

        //j<iに対して、数字部分CARDS[j][1]<=CARDS[i][1]が保証される。
        if(i>0)assert(CARDS[i-1][1]<=CARDS[i][1]);
    }
}
```
- - -
- ###### 役のできるパターン数を初期化
```c
/* 全パターン数 */
int nPatterns=0;

/* ロイヤルストレートフラッシュのパターン数 */
int cntRoyalStraightFlush=0;

/* ストレートフラッシュのパターン数 */
int cntStraightFlush=0;

/* フォーカードのパターン数 */
int cntFourOfAKind=0;

/* フルハウスのパターン数 */
int cntFullHouse=0;

/* フラッシュのパターン数 */
int cntFlush=0;

/* ストレートのパターン数 */
int cntStraight=0;

/* スリーカードのパターン数 */
int cntThreeOfAKind=0;

/* ツーペアのパターン数 */
int cntTwoPair=0;

/* ワンペアのパターン数 */
int cntOnePair=0;

/* ノーペア（ハイカード）のパターン数 */
int cntNoPair=0;
```
- - -
- ###### ロイヤルストレートフラッシュの判定
:::note
以下、引数`hand`において、
$0\le i<j\le4$ならば$H_{i,1}\le H_{j,1}$
（`0<=i<j<=4`ならば`hand[i][1]<=hand[j][1]`）
を前提とします。
:::
```c
/* ロイヤルストレートフラッシュの判定
 * ・５枚すべて同じスート
 * 　かつ
 * ・0枚目がA、1枚目が10、2枚目がJ、3枚目がQ、4枚目がK（内部的には{0,9,10,11,12}）
 * ならばtrue。
 */
bool isRoyalStraightFlush(int hands[N_HANDS][2]){
    if(hands[0][1]!=0){//0番目のカードがエース(0)でなければ
        //ロイヤルストレートフラッシュでない
        return false;
    }
    for(int i=1;i<N_HANDS;i++){
        if(hands[i][0]!=hands[0][0] || hands[i][1]!=i+8){//i枚目のスートが0枚目と異なる、またはi枚目の数字がi+9（内部的にはi+8）でなければ
            //ロイヤルストレートフラッシュでない
            return false;
        }
    }
    return true;
}
```
- - -
- ###### ストレートフラッシュの判定
```c
/* ストレートフラッシュの判定
 * ・５枚すべて同じスート
 * 　かつ
 * ・i枚目の数字が0枚目の数字+i（または、i枚目の数字がi-1枚目の数字+1）
 * ならばtrue。
 */
bool isStraightFlush(int hands[N_HANDS][2]){
    for(int i=1;i<N_HANDS;i++){
        if(hands[i][0]!=hands[0][0] || hands[i][1]!=hands[0][1]+i){//i枚目のスートが0枚目と異なる、またはi枚目の数字が0枚目の数字+iでなければ
            //ストレートフラッシュでない
            return false;
        }
    }
    return true;
}
```
- - -
- ###### フォーカードの判定
```c
/* フォーカードの判定
 * ・先頭４枚が同じ数字
 * 　または
 * ・末尾４枚が同じ数字
 * ならばtrue。
 */
bool isFourOfAKind(int hands[N_HANDS][2]){
    if(hands[0][1]==hands[1][1] && hands[1][1]==hands[2][1] && hands[2][1]==hands[3][1]){//先頭４枚が同じ数字ならば
        //フォーカードである
        return true;
    }else if(hands[1][1]==hands[2][1] && hands[2][1]==hands[3][1] && hands[3][1]==hands[4][1]){//末尾４枚が同じ数字ならば
        //フォーカードである
        return true;
    }
    return false;
}
```
- - -
- ###### フルハウスの判定
```c
/* フルハウスの判定
 * ・先頭３枚が同じ数字　かつ　末尾２枚が同じ数字
 * 　または
 * ・先頭２枚が同じ数字　かつ　末尾３枚が同じ数字
 * ならばtrue。
 */
bool isFullHouse(int hands[N_HANDS][2]){
    if(hands[0][1]==hands[1][1] && hands[1][1]==hands[2][1] && hands[3][1]==hands[4][1]){//先頭３枚が同じ数字　かつ　末尾２枚が同じ数字ならば
        //フルハウスである
        return true;
    }else if(hands[0][1]==hands[1][1] && hands[2][1]==hands[3][1] && hands[3][1]==hands[4][1]){//先頭２枚が同じ数字　かつ　末尾３枚が同じ数字ならば
        //フルハウスである
        return true;
    }
    return false;
}
```
- - -
- ###### フラッシュの判定
:::note
ロイヤルストレートフラッシュ、ストレートフラッシュが成り立つ場合もtrueを返却します。後の判定順序で制御します。
:::
```c
/* フラッシュの判定
 * ・５枚すべて同じスート
 * ならばtrue。
 */
 bool isFlush(int hands[N_HANDS][2]){
    for(int i=1;i<N_HANDS;i++){
        if(hands[i][0]!=hands[0][0]){//異なるスートがあれば
            //フラッシュでない
            return false;
        }
    }
    return true;
}
```
- - -
- ###### ストレートの判定
:::note
ロイヤルストレートフラッシュ、ストレートフラッシュが成り立つ場合もtrueを返却します。後の判定順序で制御します。
:::
```c
/* ストレート（10-J-Q-K-A型）の判定
 * ・0枚目がA、1枚目が10、2枚目がJ、3枚目がQ、4枚目がK（内部的には{0,9,10,11,12}）
 * ならばtrue。
 */
bool isRoyalStraight(int hands[N_HANDS][2]){
    if(hands[0][1]!=0){//0枚目がAでない
        //ロイヤルストレートでない
        return false;
    }
    for(int i=1;i<N_HANDS;i++){
        if(hands[i][1]!=i+8){//i枚目の数字がi+9（内部的にはi+8）でなければ
            //ロイヤルストレートでない
            return false;
        }
    }
    return true;
}

/* ストレートフラッシュの判定
 * ・10-J-Q-K-A型のストレート
 * 　または
 * ・i枚目の数字が0枚目の数字+i（または、i枚目の数字がi-1枚目の数字+1）
 * ならばtrue。
 */
bool isStraight(int hands[N_HANDS][2]){
    if(isRoyalStraight(hands)){//10-J-Q-K-A型のストレートならば
        //ストレートである
        return true;
    }
    for(int i=1;i<N_HANDS;i++){
        if(hands[i][1]!=hands[0][1]+i){//i枚目の数字が0枚目の数字+iでなければ
            //ストレートでない
            return false;
        }
    }
    return true;
}
```
- - -
- ###### スリーカードの判定
:::note
フォーカード、フルハウスが成り立つ場合もtrueを返却します。後の判定順序で制御します。
:::
```c
/* スリーカードの判定
 * ・先頭３枚が同じ数字
 * 　または
 * ・真ん中３枚が同じ数字
 * 　または
 * ・末尾３枚が同じ数字
 * ならばtrue。
 */
 bool isThreeOfAKind(int hands[N_HANDS][2]){
    if(hands[0][1]==hands[1][1] && hands[1][1]==hands[2][1]){//先頭３枚が同じ数字ならば
        //スリーカードである
        return true;
    }else if(hands[1][1]==hands[2][1] && hands[2][1]==hands[3][1]){//真ん中３枚が同じ数字ならば
        //スリーカードである
        return true;
    }else if(hands[2][1]==hands[3][1] && hands[3][1]==hands[4][1]){//末尾３枚が同じ数字ならば
        //スリーカードである
        return true;
    }
    return false;
}
```
- - -
- ###### ツーペアの判定
:::note
フォーカード、フルハウスが成り立つ場合もtrueを返却します。後の判定順序で制御します。
:::
```c
/* ツーペアの判定
 * ・0枚目と1枚目が同じ数字、かつ、2枚目と3枚目が同じ数字
 * 　または
 * ・0枚目と1枚目が同じ数字、かつ、3枚目と4枚目が同じ数字
 * 　または
 * ・1枚目と2枚目が同じ数字、かつ、3枚目と4枚目が同じ数字
 * ならばtrue。
 */
 bool isTwoPair(int hands[N_HANDS][2]){
    if(hands[0][1]==hands[1][1] && hands[2][1]==hands[3][1]){//0枚目と1枚目が同じ数字、かつ、2枚目と3枚目が同じ数字ならば
        //ツーペアである
        return true;
    }else if(hands[0][1]==hands[1][1] && hands[3][1]==hands[4][1]){//0枚目と1枚目が同じ数字、かつ、3枚目と4枚目が同じ数字ならば
        //ツーペアである
        return true;
    }else if(hands[1][1]==hands[2][1] && hands[3][1]==hands[4][1]){//1枚目と2枚目が同じ数字、かつ、3枚目と4枚目が同じ数字ならば
        //ツーペアである
        return true;
    }
    return false;
}
```
- - -
- ###### ワンペアの判定
:::note
今までtrueと判定された役が成り立つ場合もtrueを返却します。後の判定順序で制御します。
:::
```c
/* ワンペアの判定
 * ・隣り合う２枚が同じ数字
 * ならばtrue。
 */
bool isOnePair(int hands[N_HANDS][2]){
    for(int i=1;i<N_HANDS;i++){
        if(hands[i-1][1]==hands[i][1]){//隣り合う２枚が同じ数字ならば
            //ワンペアである。
            return true;
        }
    }
    return false;
}
```
- - -
- ###### カードを配る＆役判定
```c
/*
 * 五重ループを再帰処理で実施します。
 */
void deal(int m, int hands[N_HANDS][2], int k){
    if(m<N_HANDS){//まだ５枚配られていなければ
        for(int i=k;i<N_CARDS;i++){//(m+1)重ループ目
            //手札セット
            memcpy(hands[m],CARDS[i],sizeof(CARDS[i]));
            //次のカードを配る
            deal(m+1,hands,i+1);
        }
    }else{//５枚配られたら
        //i<jに対してhands[i][1]<=hands[j][1]が保証されている
        for(int i=1;i<N_HANDS;i++){
            //隣り合う２枚で確認
            assert(hands[i-1][1]<=hands[i][1]);
        }
        //パターン数インクリメント
        nPatterns++;
        
        //役判定（処理順を間違えると、ツーペアなのにワンペアと判定されたりします!!）
        if(isRoyalStraightFlush(hands)){//ロイヤルストレートフラッシュ
            cntRoyalStraightFlush++;
        }else if(isStraightFlush(hands)){//ストレートフラッシュ
            cntStraightFlush++;
        }else if(isFourOfAKind(hands)){//フォーカード
            cntFourOfAKind++;
        }else if(isFullHouse(hands)){//フルハウス
            cntFullHouse++;
        }else if(isFlush(hands)){//フラッシュ
            cntFlush++;
        }else if(isStraight(hands)){//ストレート
            cntStraight++;
        }else if(isThreeOfAKind(hands)){//スリーカード
            cntThreeOfAKind++;
        }else if(isTwoPair(hands)){//ツーペア
            cntTwoPair++;
        }else if(isOnePair(hands)){//ワンペア
            cntOnePair++;
        }else{//ノーペア（ハイカード）
            cntNoPair++;
        }
    }
}
```
- - -
- ###### 出力形式
```c
/* 出力形式を定義します
 * 役名：左寄せ21文字分
 * パターン数：右寄せ8桁分
 * 確率：整数部分3桁、小数部分6桁
 * return : 出力文字数
 */
int print(const char* str, int cnt){
    return printf("%-21s:%8d(%10.6f%%)\n",str,cnt,100.0*cnt/nPatterns);
}
```
- - -
- ###### メイン処理
```c
int main(int argc, char* argv[]){
    //初期処理
    init();
    
    //手札を宣言
    int hands[N_HANDS][2];

    //2,598,960通りの手札の役を判定
    deal(0,hands,0);

    //それぞれの役のパターン数と確率を出力します。
    print("Royal Straight Flush",cntRoyalStraightFlush);
    print("Straight Flush",cntStraightFlush);
    print("Four of a Kind",cntFourOfAKind);
    print("Full House",cntFullHouse);
    print("Flush",cntFlush);
    print("Straight",cntStraight);
    print("Three of a Kind",cntThreeOfAKind);
    print("Two Pair",cntTwoPair);
    print("One Pair",cntOnePair);
    int len=print("No Pair",cntNoPair);//出力文字数を取得しておく
    for(int i=0;i<len;i++){
        //合計出力前の横棒を出力する
        putchar('-');
    }
    putchar('\n');
    //合計を出力
    print("Total",nPatterns);
    return 0;
}
```
- - -
- ###### コンパイル＆実行
```
gcc poker.c -o poker
poker
```
- - -
- ###### 出力結果
```
Royal Straight Flush :       4(  0.000154%)
Straight Flush       :      36(  0.001385%)
Four of a Kind       :     624(  0.024010%)
Full House           :    3744(  0.144058%)
Flush                :    5108(  0.196540%)
Straight             :   10200(  0.392465%)
Three of a Kind      :   54912(  2.112845%)
Two Pair             :  123552(  4.753902%)
One Pair             : 1098240( 42.256903%)
No Pair              : 1302540( 50.117739%)
--------------------------------------------
Total                : 2598960(100.000000%)
```
[こちらの記事](https://ja.wikipedia.org/wiki/%E3%83%9D%E3%83%BC%E3%82%AB%E3%83%BC%E3%83%BB%E3%83%8F%E3%83%B3%E3%83%89%E3%81%AE%E4%B8%80%E8%A6%A7)と出力結果が一致しました。