どうでもいいプログラム研究所

とある編集者によるIT、Web、ソフトウェア、プログラミングに関する雑記と覚え書き

Excel VBAでポーカーの役を判定するプログラムを書いてみた

f:id:tdyu5021:20201120211202p:plain

以前、Excel VBAExcelのワークシートを使い、ゲームセンターによく置いてある「ビデオポーカー」を再現してみました。それについては後々このブログで書きたいなと思いますが、その前にポーカーのプログラムを作成する上で最も重要な「役を判定する」部分をどう作ったのかを紹介したいと思います。

はじめに

作ったきっかけは私がゲームセンターのポーカーが好きだったから。プログラミングの勉強の一環として作ってみたいなと思いました。ポーカーの役を判定するアルゴリズムを以前ネットで調べてみたのですが、自分が使える言語での解説はなく読み解くのも大変なので、完成度はどうあれ自分で作ってみることにしました。

役を判定する方法の概要

初心者なりに私が考えた方法は以下です。

  1. まず重複させないように、ランダムに「数字+絵柄」の組み合わせを5つ作る(手札は5枚だから)
  2. 5枚の数字だけを格納する1次元配列を作る・・・(A)
  3. 5枚の絵柄を格納する1次元配列を作る・・・(B)
  4. (A)の配列をソートして昇順に並び替えておく
  5. (B)を用いて、まずフラッシュが成立するかを判定する
  6. フラッシュが成立すれば、ロイヤルフラッシュかストレートフラッシュか、フラッシュかを判定する
  7. フラッシュが成立しなければその他を判定する

ポーカーに限らず、そもそもトランプを使ったゲームのプログラムを作るとき「数字」と「絵柄(スート)」の2つの情報をどう管理するのかベストなのかはいつも疑問に思っていました。数字とスートをまとめた2次元配列を作ればよいのかなと思っていたのですが、今回は数字と絵柄を違う配列にわけることにしました。

その理由はVBAだと2次元配列の操作や管理が面倒だから。多分ほかの言語でやるならこのように2つの配列には決して分割しないでしょうね。

数字の配列(A)と絵柄の配列(B)と2つの配列を作ると、Aの方だけソートしてBをソートしなければ、最初の手札の数字と絵柄の対応が崩れてしまうため、一見不便に思います。

ですが、実際に絵柄の配列の方はフラッシュが成り立つかどうか(=5枚の絵柄がすべて同じか)だけにしか使いませんし、実際ポーカー全体のプログラムを作る上でもこの実装方法でもまったく支障はありませんでした。

役を判定する方法の詳細な解説

実際に1つずつ見ていきます。

5つの「数字+絵柄」を作る方法

まず以下のような2次元配列を作っておきます。これで4×13の二次元配列すべてにTrueが入ります。

For i = 0 To 3
   cardArray2(i) = Array(True, True, True, True, True, True, True, True, True, True, True,   True, True)
Next i

これは「数字+絵柄」の組み合わせをランダムに生成させたとき、一度生成したものをフラグオフしておくことで重複させないようにするための配列です。絵柄の順番はスペード、ハート、クローバー、ダイヤです。例えば、ランダムに数字と絵柄を生成し、スペードの1が出たらcardArray2(0)(0)をFalseに、ハートの2が出たらcardArray2(1)(1)をFalseにするというふうに操作します。

 

実際にランダムに数字と絵柄を生成するのは以下の処理です。

Do Until j = 5
    n1 = Int(Rnd * 4) '//←ランダムに絵柄を生成(0:スペード/1:ハート/2:クローバー/3:ダイヤ)
    n2 = Int(Rnd * 13)'//←ランダムに数字を生成

    If cardArray2(n1)(n2) = True Then
        cardArray2(n1)(n2) = False
        myHandN(j) = cardArray(n1)(n2)
        myHandS(j) = suitArray(n1)
        j = j + 1
    End If
Loop

 

これで、5つの数字の配列はmyHandNに格納され、5つの絵柄の配列はmyHandSに格納されます。

数字をソートして並び替える

配列をソートさせるにはソートアルゴリズムを使えばよいのですが、アルゴリズムを一切勉強していない私はその方法を知らないので、ここは先人の知恵を借りました。なんと幸いにもクイックソートVBAで実装しているブログがありましたので、ここではそれをまるごとコピペして使わせてもらっています。ちなみに以下のブログです。

vbabeginner.net

ソート後の処理

ソート済みの手札の数字の配列ができたら、その配列と絵柄の配列2つを引数に、今回私が考えたポーカーの役を判定する関数「checkPokerHands」関数に渡します。先にソースコードを記載しておきます。

渡された引数のarNが5つの数字の配列であり、arSが絵柄の配列です。関数の戻り値は数字にしています(ロイヤルフラッシュが出たら7、ストレートフラッシュがでたら5など)。

Function checkPokerHands(ByVal arN As Variant, ByVal arS As Variant) As Integer
Dim num As Integer
Dim a As Integer, b As Integer, c As Integer, d As Integer

a = arN(4) - arN(3)
b = arN(3) - arN(2)
c = arN(2) - arN(1)
d = arN(1) - arN(0)

If arS(0) = arS(1) And arS(1) = arS(2) And arS(2) = arS(3) And arS(3) = arS(4) Then
    If arN(0) = 1 And arN(1) = 10 And arN(2) = 11 And arN(3) = 12 And arN(4) = 13 Then
        num = 7 '//ロイヤルフラッシュ
    ElseIf a = 1 And b = 1 And c = 1 And d = 1 Then
        num = 6 ' //ストレートフラッシュ
    Else
        num = 3 '//フラッシュ
    End If
Else
    If a = 1 And b = 1 And c = 1 And d = 1 Then
        num = 2 '//ストレート
    ElseIf arN(0) = 1 And arN(1) = 10 And arN(2) = 11 And arN(3) = 12 And arN(4) = 13 Then
        num = 2 '//ロイヤルストレート
    Else
        '//以下4カード、フルハウス、3カード、2ペアを判定
        '//(手札の中で同じ数字の組み合わせが見つかった回数で上記を判定)
        For maxNum = 3 To 0 Step -1
        i = 3 - maxNum
            For j = 1 To 1 + maxNum
                If arN(i) = arN(i + j) Then
                    cnt = cnt + 1
                End If
            Next j
        Next maxNum
    
        Select Case cnt
        Case 6
            num = 5 '//4カード
        Case 4
            num = 4 '//フルハウス
        Case 3
            num = 1 '//3カード
        Case 2
            num = 0 '//2ペア
        Case Else
            num = -1 '//1ペアもしくはブタ
        End Select
    End If
End If
    
checkPokerHands = num

End Function

まずはフラッシュかどうかを判定する

判定方法を解説します。まず上記では、絵柄の配列を使って、If文でフラッシュが成立するか否かを最初に判定しています。その後、数字が10,11,12,13,1だったらロイヤルフラッシュになります。もし手札の数字の1枚めと2枚め、2枚めと3枚目・・・というふうに隣同士の差を調べ、すべて1だったらストレートフラッシュ、それ以外ならただのフラッシュという判定を行っています。

フラッシュ以外を判定する

次にフラッシュ以外だった場合の処理です。まず先にストレートが成り立つかを判定します。これは先述の通り、手札内の各札の差が1ずつ並んでいるか、もしくは10,11,12,13,1(ロイヤルストレート)になればOKなので判定は簡単です。

もし上記が成り立たなかった場合、4カードかフルハウスか、3カードか2カードか、1ペアかブタを判定します。ここも私が思いついたのは、

「5枚の手札のうち2枚のカードが同じ数字になる数を数えて判断する」という方法です。その数を数えている部分が以下です。

For maxNum = 3 To 0 Step -1
i = 3 - maxNum
    For j = 1 To 1 + maxNum
        If arN(i) = arN(i + j) Then
            cnt = cnt + 1
        End If
    Next j
Next maxNum

例えば、1が4つそろった4カードとして

カードA:1

カードB:1

カードC:1

カードD:1

カードE:2

という手札があるとします。

このとき、カードA=カードB、カードA=カードC、カードA=カードD、カードB=カードC、カードC=カードD、というように同じ数字のペアが5つあるので、4カードの場合は「5」となります。

同じように、フルハウスは4、3カードは3、2カードは2、1ペアが1、ブタが0というように、偶然にもきれいに分かれるので、とても判定が楽です。ちなみに今回のポーカーのプログラムでは1ペアは役成立にみなしていないので、ブタと同じ扱いにしています。こうして役が判定されたらそれに応じて戻り値を設定します。

私が作ったポーカーのプログラムでは、戻り値を数字で欲しかったので、上記のサンプルもそうなっています。

最後に

以上、結構シンプルですね。最初「難しいんだろうなぁ・・」という先入観があったので二の足を踏んでいたのですが、案外すぐに作れました。まあクイックソートの部分は他の人のコードをそのまま作ったので、それを自分で書こうとしたらもっと時間がかかったかもしれません。肝心のExcelポーカーの方は、また別途ご紹介したいと思います。