さくさく理解する Godot 入門(ただし2Dに限る)応用編 テキサスホールデムポーカー【第4回】
目次
- 手役判定
手役判定
この章では、枚数2枚~7枚の与えられたカード配列を引数とし、それらから作ることのできる最強の役を判定する check_hand(v) の説明を行う。
下図はショーダウンにおいて、手札をさらしている状態だが、プレイヤーパネル下部にポーカー役名 (フルハウス・3オブアカインド)が表示されている。この役判定が check_hand() で行われている。
ショーダウンにおいて、どのプレイヤーが勝ったのかを決めるために、 手持ちカード・共有カードからポーカーの役を判定する必要があるのはもちろんのこと、 共有カードが0,3,4枚である途中のラウンドでも、その時点での手役を表示するために、引数のカード枚数は可変となっている。
また、同じ役であった場合にも強弱を判定可能にする必要があるので、check_hand() が返す値は単なる数値ではなく、 [手役値, ランク1,ランク2,…] という配列にしている。ランクとはカードの数字のことで、 手役により意味のある数値が付加的に追加される。
例えば、5, 5, 5, K, K のフルハウスの場合、[FULL_HOUSE, RANK_5, RANK_K] が返る。
こうしておけば、配列の先頭から順に値を比較することで役の強弱を判定できる。
check_hand() の実装を下記に示す。
enum { # 手役 HIGH_CARD = 0, ONE_PAIR, TWO_PAIR, THREE_OF_A_KIND, STRAIGHT, FLUSH, FULL_HOUSE, FOUR_OF_A_KIND, STRAIGHT_FLUSH, ROYAL_FLUSH, N_KIND_HAND, }; # 手役判定 # return: [手役値, ランク1,ランク2,...] func check_hand(v : Array) -> Array: var rcnt = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # 各ランク枚数 var scnt = [0, 0, 0, 0] # 各スート枚数 for i in range(v.size()): # 手札のランク・スートの数を数える rcnt[card_to_rank(v[i])] += 1 scnt[card_to_suit(v[i])] += 1 var s = -1 # フラッシュの場合のスート if scnt[CLUBS] >= 5: s = CLUBS # スートが5枚以上あればフラッシュ elif scnt[DIAMONDS] >= 5: s = DIAMONDS elif scnt[HEARTS] >= 5: s = HEARTS elif scnt[SPADES] >= 5: s = SPADES if s >= CLUBS: # フラッシュ確定の場合 var bitmap = 0 # ランクをビット値に変換したものの合計 for i in v.size(): if( card_to_suit(v[i]) == s ): # 同一スートの場合 bitmap |= 1 << card_to_rank(v[i]) var mask = 0x1f00 # AKQJT for i in range(9): if( (bitmap & mask) == mask ): return [STRAIGHT_FLUSH, mask] # ストレートフラッシュ mask >>= 1 if( bitmap == 0x100f ): # 1 0000 00000 1111 = 5432A return [STRAIGHT_FLUSH, 0x0f] # 5432A よりも 65432 の方が強い return add_rank(v, s, FLUSH) # 単なるフラッシュ .....
まずは enum で役名を宣言している。強い役ほど大きい数字になっている。
関数の実装では、まず各ランク・スートの数を数える。どれかのスート枚数が5を超えていればフラッシュが確定となる。
ストレートフラッシュの場合もあるので、再度すべてのカードを調べ、当該スートの場合は、ランクをビットマップにした総和を計算する。 それが 0x1f00, 0x0f80, …, 0x001f, 0x100f のいずれかであればストレートフラッシュ、そうでなければ単なるフラッシュとなる。
add_rank() はフラッシュの場合に、そのスートの数字を大きい順に結果配列に追加する関数だ。実装を下記に示す。
func add_rank(v, s, hand): # フラッシュの場合に、そのスートの数字を大きい順に結果配列に追加 var rnk = [] for i in range(v.size()): if( card_to_suit(v[i]) == s ): # 同一スートの場合 rnk.push_back(card_to_rank(v[i])) rnk.sort() # 昇順ソート var t = [hand] for i in range(5): # 大きいランクから5枚を配列に追加 t.push_back(rnk[-1-i]) # ランクを降順に格納 return t
以下は check_hand() 実装の続きだ。
func check_hand(v : Array) -> Array: ..... var threeOfAKindRank1 = -1 # 3 of a Kind の rcnt インデックス var threeOfAKindRank2 = -1 # 3 of a Kind の rcnt インデックス その2 var pairRank1 = -1 # ペアの場合の rcnt インデックス var pairRank2 = -1 # ペアの場合の rcnt インデックス その2、pairRank1 > pairRank2 とする for r in range(13): if( rcnt[r] == 4): return [FOUR_OF_A_KIND, r] # 4 of a kind は他のプレイヤーと同じ数字になることはない if( rcnt[r] == 3): if( threeOfAKindRank1 < 0 ): threeOfAKindRank1 = r else: threeOfAKindRank2 = r elif( rcnt[r] == 2): if pairRank1 < 0: pairRank1 = r elif pairRank2 < 0: if pairRank1 > r: pairRank2 = r else: pairRank2 = pairRank1 pairRank1 = r else: if r > pairRank1: pairRank2 = pairRank1 pairRank1 = r if r > pairRank2: pairRank2 = r # 3カード*2 もフルハウス if( threeOfAKindRank1 >= 0 && (pairRank1 >= 0 || threeOfAKindRank2 >= 0) ): return [FULL_HOUSE, threeOfAKindRank1] # 3 of a kind は他のプレイヤーと同じ数字になることはない .....
次にランクごとの枚数を調べ、ペア系の役を探す。
同じランクが4枚揃っていれば、4オブアカインド確定なので、[FOUR_OF_A_KIND, r] を返す。r はランクだ。 まずありえないことだが、複数人が4オブアカインドの場合に、優劣をつけるための情報だ。 もしそうなったら、壮絶なベット合戦が繰り広げられることだろう。
同じランクが3枚、2枚の場合もチェックし、そうであればそのランクを threeOfAKindRank1, 2, pairRank1, pairRank2 に保持する。 そして、3枚が2種類、または3枚と2枚があればフルハウスとなる。
以下が、check_hand() の残りの実装コードだ。
func check_hand(v : Array) -> Array: ..... var bitmap = 0 var mask = 1 for i in range(13): if( rcnt[i] != 0 ): bitmap |= mask mask <<= 1 mask = 0x1f00 # AKQJT for i in range(9): if( (bitmap & mask) == mask ): return [STRAIGHT, mask] mask >>= 1 if( (bitmap & 0x100f) == 0x100f ): # 5432A return [STRAIGHT, 0x0f] # 5432A より 65432 の方が強い if( threeOfAKindRank >= 0 ): return [THREE_OF_A_KIND, threeOfAKindRank] # 3 of a kind は他のプレイヤーと同じ数字になることはない if( pairRank2 >= 0 ): #return [TWO_PAIR] return add_rank_pair(v, pairRank1, pairRank2, TWO_PAIR) if( pairRank1 >= 0 ): return add_rank_pair(v, pairRank1, -1, ONE_PAIR) #return [ONE_PAIR] return add_rank_pair(v, -1, -1, HIGH_CARD)
ストレート、3オブアカインド、2ペア、ワンペアをチェックし、そうであればその役情報を返す。
add_rank_pair() はペアがある場合に、同じ役での強弱判定のための情報を付加する関数だ。
以下にそのコードを示す。
func add_rank_pair(v, p1, p2, hand): # ペアの場合に、ペア以外の数字を大きい順に結果配列に追加 var rnk = [] for i in range(v.size()): var r = card_to_rank(v[i]) if r != p1 && r != p2: # ペアの数字でない場合 rnk.push_back(r) rnk.sort() # 昇順ソート var t = [hand] var n = 5 # 配列に追加する枚数 if p2 >= 0: # 2ペアの場合 t.push_back(p1) t.push_back(p2) n = 1 elif p1 >= 0: # 1ペアの場合 t.push_back(p1) n = 3 n = min(rnk.size(), n) for i in range(n): t.push_back(rnk[rnk.size()-1-i]) # ランクを降順に格納 return t
TechProjin Godot入門 関連連載リンク
Godotで学ぶゲーム制作
さくさく理解するGodot入門 連載目次
標準C++ライブラリの活用でコーディング力UP!
「競技プログラミング風」標準C++ライブラリ 連載目次