LoginSignup
0
0

ポーカーの役ができる確率を計算してみた(ジョーカー1枚)

Last updated at Posted at 2024-02-08

ジョーカーを1枚加えて合計53枚のトランプカードを使用した場合の、ポーカーの役ができる確率を計算してみました。


  • 準備
poker.py
"""
ジョーカーを1枚加えて合計53枚のトランプカードを使用した場合の、
ポーカーの役ができる確率を計算する。
"""
# ジョーカー及び13*4枚のカードを用意する
JOKER=(0,0)
CARDS=[JOKER]+[(i%4+1,i//4+1) for i in range(52)]

# 全パターン数
nPatterns=0

# ファイブカードのパターン数
cntFiveOfAKind=0

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

# ストレートフラッシュのパターン数
cntStraightFlush=0

# フォーカードのパターン数
cntFourOfAKind=0

# フルハウスのパターン数
cntFullHouse=0

# フラッシュのパターン数
cntFlush=0

# ストレートのパターン数
cntStraight=0

# スリーカードのパターン数
cntThreeOfAKind=0

# ツーペアのパターン数
cntTwoPair=0

# ワンペアのパターン数
cntOnePair=0

# ノーペア(ハイカード)のパターン数
cntNoPair=0

  • ファイブカードの判定

以下、引数handにおいて、
$0\le i<j\le4$に対して$0\le H_{i,1}\le H_{j,1}\le4$
$i>0$ならば$H_i\neq \text{JOKER}$
0<=i<j<=4に対してhand[i][1]<=hand[j][1]
 i>0ならばhand[0]!=JOKER=(0,0)
を前提とします。

def isFiveOfAKind(hand):
    """ファイブカードの判定。
    ・手札にジョーカーがある
     かつ
    ・残り4枚の数字が全て同じ
    場合、True。
    """
    return hand[0]==JOKER and hand[1][1]==hand[2][1]==hand[3][1]==hand[4][1]

  • ロイヤルストレートフラッシュの判定
def isRoyalStraightFlush(hand):
    """ロイヤルストレートフラッシュの判定
    手札にジョーカーがある場合
    ・残り4枚のスートが全て同じ
     かつ
    ・残り4枚の数字が(A,10,J,Q,K)の中の4個で構成されている
    場合、True。
    手札にジョーカーがない場合
    ・5枚のスートが全て同じ
     かつ
    ・5枚の数字が(A,10,J,Q,K)で構成されている
    場合、True。
    """
    if hand[0]==JOKER: #ジョーカーが手札にある場合
        if not hand[1][0]==hand[2][0]==hand[3][0]==hand[4][0]:
            #異なるスートがある場合、ロイヤルストレートフラッシュでない
            return False
        #残り4枚の数字が(A,10,J,Q),(A,10,J,K),(A,J,Q,K),(10,J,Q,K)ならば
        #ロイヤルストレートフラッシュ成立
        return tuple(hand[i][1] for i in range(1,len(hand))) in [(1,10,11,12),(1,10,11,13),(1,10,12,13),(1,11,12,13),(10,11,12,13)]
    else:
        if hand[0][1]!=1:
            #0枚目がエースでない場合、ロイヤルストレートフラッシュでない
            return False
        for i in range(1,len(hand)):
            if hand[i][0]!=hand[0][0] or hand[i][1]!=9+i:
                #スートが異なる場合、または
                #1~4枚目が(10,J,Q,K)でない場合、即ちi枚目が9+iでない場合
                #ロイヤルストレートフラッシュでない
                return False
        return True

  • ストレートフラッシュの判定

(JOKER,10,J,Q,K)の場合もTrueを返却しますが、後の判定順序で重複カウントしないように制御します。

def isStraightFlush(hand):
    """ストレートフラッシュの判定
    手札にジョーカーがある場合※ジョーカーを0枚目とカウント
    ・残り4枚のスートが全て同じ
     かつ
    ・(2枚目の数字-1枚目の数字,3枚目の数字-1枚目の数字,4枚目の数字-1枚目の数字)=(1,2,3),(1,2,4),(1,3,4),(2,3,4)のいずれかの
     (または、(2枚目の数字-1枚目の数字,3枚目の数字-2枚目の数字,4枚目の数字-3枚目の数字)=(1,1,1),(1,1,2),(1,2,1),(2,1,1)のいずれかの)
    場合、True。
    手札にジョーカーがない場合
    ・5枚のスートが全て同じ
     かつ
    ・1≦i≦4に対して、i枚目の数字=0枚目の数字+iの
     (または、i枚目の数字=i-1枚目の数字+1)の
    場合、True。
    """
    if hand[0]==JOKER: #ジョーカーが手札にある場合
        if not hand[1][0]==hand[2][0]==hand[3][0]==hand[4][0]:
            #異なるスートがある場合、ストレートフラッシュでない
            return False
        #i枚目と1枚目の数字の差で判定
        return tuple(hand[i][1]-hand[1][1] for i in range(2,len(hand))) in [(1,2,3),(1,2,4),(1,3,4),(2,3,4)]
        #(別解)i枚目とi-1枚目の数字の差で判定
        #return tuple(hand[i][1]-hand[i-1][1] for i in range(2,len(hand))) in [(1,1,1),(1,1,2),(1,2,1),(2,1,1)]
    else:
        for i in range(1,len(hand)):
            if hand[i][0]!=hand[0][0] or hand[i][1]!=hand[0][1]+i:
                #異なるスートがある、またはi枚目と0枚目の数字の差がiでない場合、
                #ストレートフラッシュでない
                return False
        return True

  • フォーカードの判定

JOKERが手札にある場合、ファイブカードが成立する場合もTrueを返却しますが、後の判定順序で重複カウントしないように制御します。

def isFourOfAKind(hand):
    """フォーカードの判定
    手札にジョーカーがある場合
    ・残り4枚中3枚の数字が同じならばTrue。
    手札にジョーカーがない場合
    ・5枚中4枚の数字が同じならばTrue。
    """
    if hand[0]==JOKER: #手札にジョーカーがある場合
        #残り4枚中3枚の数字が同じならばフォーカード
        return hand[1][1]==hand[2][1]==hand[3][1]\
            or hand[2][1]==hand[3][1]==hand[4][1]
    else:
        #5枚中4枚の数字が同じならばフォーカード
        return hand[0][1]==hand[1][1]==hand[2][1]==hand[3][1]\
            or hand[1][1]==hand[2][1]==hand[3][1]==hand[4][1]

  • フルハウスの判定

JOKERが手札にある場合、残り4枚中3枚が同じ数字の場合、フォーカードが成立しますが、フルハウスの判定でもTrueを返却することにしています。
4枚同じ数字の場合は勿論ファイブカードです。
後の判定順序で重複カウントしないように制御します。

def isFullHouse(hand):
    """フォーカードの判定
    手札にジョーカーがある場合
    ・残り4枚中3枚の数字が同じ(フルハウスと言えなくもないが、フォーカードに吸収される)
     または
    ・残り4枚中2枚の数字が同じ、更に残りの2枚の数字が同じ
    場合、True。
    手札にジョーカーがない場合
    ・5枚中3枚の数字が同じ、更に残りの2枚の数字が同じならばTrue。
    """
    if hand[0]==JOKER: #手札にジョーカーがある場合
        #3行ある判定条件のうち、上2行はフォーカードと同じ
        return hand[1][1]==hand[2][1]==hand[3][1]\
            or hand[2][1]==hand[3][1]==hand[4][1]\
            or hand[1][1]==hand[2][1] and hand[3][1]==hand[4][1]
    else: #手札にジョーカーがない場合
        return hand[0][1]==hand[1][1]==hand[2][1] and hand[3][1]==hand[4][1]\
            or hand[0][1]==hand[1][1] and hand[2][1]==hand[3][1]==hand[4][1]

  • フラッシュの判定

ロイヤルストレートフラッシュ、ストレートフラッシュの場合もTrueを返却しますが、後の判定順序で重複カウントしないように制御します。

def isFlush(hand):
    """フラッシュの判定
    ・ジョーカーを除くカードが全て同じスートの場合、True。
    """
    j=1 if hand[0]==JOKER else 0
    for i in range(j+1,len(hand)):
        if hand[i][0]!=hand[j][0]:
            return False
    return True

  • ストレートの判定

ロイヤルストレートフラッシュ、ストレートフラッシュの場合もTrueを返却しますが、後の判定順序で重複カウントしないように制御します。

def isRoyalStraight(hand):
    """ストレート(10-J-Q-K-A型)の判定
    手札にジョーカーがある場合
    ・残り4枚の数字が(A,10,J,Q,K)の中の4個で構成されている
     または
    ・残り4枚中、最小の数字以外の3枚と最小の数字との差が
     (1,2,3),(1,2,4),(1,3,4),(2,3,4)でいずれかの
    (または、4枚を数字で昇順ソートした時、隣り合う2枚の差が
     (1,1,1),(1,1,2),(1,2,1),(2,1,1)のいずれかの)
    場合、True。
    手札にジョーカーがない場合
    ・5枚の数字が(A,10,J,Q,K)で構成されている
    または
    ・i枚目と0枚目の数字の差がi(または、i枚目とi-1枚目の数字の差が1)
    ならばTrue。
    """
    if hand[0]==JOKER:
        return tuple(hand[i][1] for i in range(1,len(hand))) in [(1,10,11,12),(1,10,11,13),(1,10,12,13),(1,11,12,13),(10,11,12,13)]
    else:
        if hand[0][1]!=1:
            return False
        for i in range(1,len(hand)):
            if hand[i][1]!=9+i:
                return False
        return True

def isStraight(hand):
    if isRoyalStraight(hand):
        return True
    if hand[0]==JOKER:
        return tuple(hand[i][1]-hand[1][1] for i in range(2,len(hand))) in [(1,2,3),(1,2,4),(1,3,4),(2,3,4)]
    else:
        for i in range(1,len(hand)):
            if hand[i][1]!=hand[0][1]+i:
                return False
        return True

  • スリーカードの判定

ファイブカード、フォーカード、フルハウスの場合もTrueを返却しますが、後の判定順序で重複カウントしないように制御します。

def isThreeOfAKind(hand):
    """スリーカードの判定
    手札にジョーカーがある場合
    ・残り4枚中2枚同じ数字があればTrue。
    手札にジョーカーがない場合
    ・5枚中3枚同じ数字があればTrue。
    """
    if hand[0]==JOKER:
        #ジョーカー以外の4枚中2枚同じ数字があればスリーカード
        return hand[1][1]==hand[2][1] or hand[2][1]==hand[3][1] or hand[3][1]==hand[4][1]
    else:
        #5枚中3枚同じ数字があればスリーカード
        return hand[0][1]==hand[1][1]==hand[2][1] or hand[1][1]==hand[2][1]==hand[3][1] or hand[2][1]==hand[3][1]==hand[4][1]

  • ツーペアの判定

ジョーカーがある場合、ツーペアが成立すれば上位のスリーカードも成立しますが、Trueを返却しておきます。後の判定順序で重複カウントしないように制御します。

def isTwoPair(hand):
    """ツーペアの判定
    手札にジョーカーがある場合
    ・残り4枚中2枚同じ数字があればTrue。(スリーカードと同じ判定条件)
    手札にジョーカーがない場合
    ・5枚中2枚同じ数字、残り3枚中2枚同じ数字ならばTrue。
    """
    if hand[0]==JOKER:
        #残り4枚中2枚同じ数字ならばツーペア(上位のスリーカードに吸収される)
        return hand[1][1]==hand[2][1] or hand[2][1]==hand[3][1] or hand[3][1]==hand[4][1]
    else:
        #同じ数字の組が2組あればツーペア
        return hand[0][1]==hand[1][1] and hand[2][1]==hand[3][1]\
            or hand[0][1]==hand[1][1] and hand[3][1]==hand[4][1]\
            or hand[1][1]==hand[2][1] and hand[3][1]==hand[4][1]

  • ワンペアの判定

上位の役の条件で既にTrueになっている場合もTrueを返却しますが、後の判定順序で重複カウントしないように制御します。

def isOnePair(hand):
    """ワンペアの判定
    手札にジョーカーがある場合
    ・無条件でTrue。
    手札にジョーカーがない場合
    ・5枚中2枚同じ数字があればTrue。
    """
    if hand[0]==JOKER:
        return True
    else:
        for i in range(1,len(hand)):
            if hand[i-1][1]==hand[i][1]:
                #数字の昇順に並んでいる前提なので、
                #隣り合う2枚の数字が同じならワンペア(以上)
                return True
        return False

  • 五重ループを再帰処理で実施
def deal(hand, k):
    """カード配布&判定"""
    global nPatterns
    global cntFiveOfAKind
    global cntRoyalStraightFlush
    global cntStraightFlush
    global cntFourOfAKind
    global cntFullHouse
    global cntFlush
    global cntStraight
    global cntThreeOfAKind
    global cntTwoPair
    global cntOnePair
    global cntNoPair

    if len(hand)<5:
        #手札が5枚になるまで配布
        for i in range(k,len(CARDS)):
            hand.append(CARDS[i])
            deal(hand,i+1)
            hand.pop()
    else:
        #手札が数字の昇順になっていることを確認
        for i in range(1,len(hand)):
            assert(hand[i-1][1]<=hand[i][1])

        #役判定(順番を間違えるとツーペアなのにワンペアと判定されたりします)
        nPatterns+=1
        if isFiveOfAKind(hand):
            cntFiveOfAKind+=1
        elif isRoyalStraightFlush(hand):
            cntRoyalStraightFlush+=1
        elif isStraightFlush(hand): 
            cntStraightFlush+=1
        elif isFourOfAKind(hand):
            cntFourOfAKind+=1
        elif isFullHouse(hand):
            cntFullHouse+=1
        elif isFlush(hand):
            cntFlush+=1
        elif isStraight(hand):
            cntStraight+=1
        elif isThreeOfAKind(hand):
            cntThreeOfAKind+=1
        elif isTwoPair(hand):
            cntTwoPair+=1
        elif isOnePair(hand):
            cntOnePair+=1
        else:
            cntNoPair+=1 

  • 出力形式
def output(str, cnt):
    """役名(左寄せ21桁)、パターン数(右寄せ8桁)、確率(整数部3桁、小数部6桁)で出力
    """
    print(f"{str:<21}:{cnt:>8}({100*cnt/nPatterns:10.6f}%)")

  • メイン部分

defがたくさんあったので、if __name__=="__main__":で始めさせて頂きました。

def main():
    deal([],0)
    output("Five of a Kind",cntFiveOfAKind)
    output("Royal Straight Flush",cntRoyalStraightFlush)
    output("Straight Flush",cntStraightFlush)
    output("Four of a Kind",cntFourOfAKind)
    output("Full House",cntFullHouse)
    output("Flush",cntFlush)
    output("Straight",cntStraight)
    output("Three of a Kind",cntThreeOfAKind)
    output("Two Pair",cntTwoPair)
    output("One Pair",cntOnePair)
    output("No Pair",cntNoPair)
    print('-'*len(f"{str():<21}:{int():>8}({float():10.6f}%)"))
    output("Total",nPatterns)

if __name__=="__main__":
    main()

  • 実行&出力結果
python poker.py
Five of a Kind       :      13(  0.000453%)
Royal Straight Flush :      24(  0.000836%)
Straight Flush       :     180(  0.006272%)
Four of a Kind       :    3120(  0.108723%)
Full House           :    6552(  0.228318%)
Flush                :    7804(  0.271946%)
Straight             :   20532(  0.715479%)
Three of a Kind      :  137280(  4.783800%)
Two Pair             :  123552(  4.305420%)
One Pair             : 1268088( 44.189101%)
No Pair              : 1302540( 45.389651%)
-------------------------------------------
Total                : 2869685(100.000000%)

次回、別の視点から検算する予定です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0