目次
- 手役判定
手役判定
この章では、枚数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() の実装を下記に示す。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 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() はフラッシュの場合に、そのスートの数字を大きい順に結果配列に追加する関数だ。実装を下記に示す。
1 2 3 4 5 6 7 8 9 10 | 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() 実装の続きだ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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() の残りの実装コードだ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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() はペアがある場合に、同じ役での強弱判定のための情報を付加する関数だ。
以下にそのコードを示す。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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++ライブラリ 連載目次