今回はポーカーの役(Hands)を認識できるようにします。
※12/13注:ループするストレートの判定条件が間違っていました(10・J・Q・K・Aのみ有効で、J・Q・K・A・2のようなループはしない。Aが一番強いから)条件判定は第4回で修正します。
役が揃ったら並べ替える機能までは実装していません(そのうちに?)
ソートできる環境を準備する
役を見つけるには、ソート機能が欠かせません。後述しますが、フラッシュ以外の役を見つけるには、数字の昇順ソートが必要です。
ドローした時のカードは、マークとナンバーを一元的に扱うため、0から51までの一つの乱数で作りました。
カードのマーク(図の青字)は値を13で割った値、ナンバー(赤字)は値を13で割った余りで取得できます。
弱い順に並べる
ポーカーで、カードのナンバーは 1 を除いて13が一番強く、2が一番弱いです。このため、マークも数字が大きいものが強く、数字が小さいものが弱くなるようにします。
ポーカーでもっとも強いマークはスペードです。そのため、マークの優劣を数字の大小で比較できるように、マークをクラブ、ダイヤ、ハート、スペードの順に定義します。
string[] mark = {"club", "diamond", "heart", "spade"};
役の認識
一般的なポーカーの役は以下の通りです。
役(日本語) | (英語) | マーク | ナンバー | 組み合わせ数 | 確率 |
---|---|---|---|---|---|
ストレートフラッシュ | Straight flush | すべて一致 | ストレート | 40 | 0.0015 |
フォーカード | Four of a kind | (Don't care) | 同一ナンバー×4 | 624 | 0.024 |
フルハウス | Full house | (Don't care) | スリーカード+ツーペア | 3,744 | 0.14 |
フラッシュ | Flush | すべて一致 | (Don't care) | 5,108 | 0.2 |
ストレート | Straight | (Don't care) | ストレート | 10,200 | 0.39 |
スリーカード | Three of a kind | (Don't care) | 同一ナンバー×3 | 54,912 | 2.1 |
ツーペア | Two pair | (Don't care) | (同一ナンバー×2)×2 | 123,552 | 4.75 |
ワンペア | Pair | (Don't care) | 同一ナンバー×2 | 1,098,240 | 42.25 |
ノーペア | High Card | (Don't care) | (Don't care) | 1,302,540 | 50 |
↑ Wikipediaから参照しました。
ポーカー・ハンドの一覧 - Wikipedia
判定は以下のとおりです。役のうち、フラッシュ(flush)とストレート(straight)は、他の役には無い特別な認識方法になります。マークで判断するのはフラッシュだけなので、先に判定します。
フラッシュ判定
フラッシュは、5枚のマークが全て一致するかどうかなので、2-5枚目のカードが、全て1枚目のマークと一致するかで判断できます。
ストレート判定
ストレートは、マークを除いて昇順にソートされたカードにおいて、一番小さいものから一番大きいものまで1ずつ変化しています。
フローにすると以下になります。
ループするストレート
しかし、13と1を含むストレート(ここではループストレートと呼ぶことにします)は、途中に段差があって正しく検出できません。
この場合は、段差がある場所の前後で条件を満たし、かつ両端が1と13かどうかで見分けができます。
ループストレートになるカードの条件は、最大と最小で以下になります。
ペア判定
ペアの判断は同じ数のカードの枚数を数え、組数と一番枚数が多かった組で役を判定します。
フローにすると以下のようになります。少し複雑に見えるかもしれませんが、処理の内容は先ほどの通りです。
組数と一番枚数が多かった組の組み合わせで決まる役は以下の通りです。
コード
役の確認のために、デバッグ用のテキストを追加しています(ボタン下)。
前回作成したスート処理(CardSuit.cs)を修正します。
public class CardSuit : MonoBehaviour { /* 定義・処理の一部を略しています */ [SerializeField] TextMeshProUGUI handText = default; string[] mark = {"club", "diamond", "heart", "spade"}; int MAX_CARDS = 5; // ランダムに1枚のカードを取り出す int DrawCardNo() { return (int)UnityEngine.Random.Range(0f, 51f); // 52枚(ジョーカー含まず) } // カードをイメージ名に変換 string GetImageName(int i) { int card = cards[i]; return "card_" + mark[(card/13)] + "_" + ((card%13)+1).ToString().PadLeft(2, '0'); } // カードのイラストを変更 void ChangeCardImage(int i) { Image image = GetImage( i ); if( !ReferenceEquals(image, null) ){ string card = GetImageName( i ); Texture2D texture = Resources.Load(card) as Texture2D; image.sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero); } } // ナンバーのみの配列 List<int> numberOfCards = new List<int>(); int[] m_numberOfCards = new int[5]; void SortWithoutMark() { for( int i = 0; i < MAX_CARDS; i++ ){ numberOfCards[i] = cards[i] % 13; } numberOfCards.Sort(); } // 役 bool flush, straight; int max_paircount, pair_num; bool CheckFlush() { int mark = cards[0]/13; int i, count = 1; for( i = 1; i < MAX_CARDS; i++ ){ if( mark == (cards[i]/13) ){ count++; } else { break; } } if( count == MAX_CARDS ){ return true; } return false; } bool CheckStraight() { int i, a, b, count = 1; // ストレート判定 for( i = 1; i < MAX_CARDS; i++ ){ if( (numberOfCards[i-1] + 1) != numberOfCards[i] ){ break; } count++; } if( count == MAX_CARDS ){ return true; } // ループストレート判定(ストレートから継続) b = i; a = (b+1) % MAX_CARDS; if( (numberOfCards[0] == 0) && (numberOfCards[4] == 12) ){ for( ; i < MAX_CARDS; i++ ){ if( (numberOfCards[b] + 1) != numberOfCards[a] ){ if( (b == (MAX_CARDS-1)) && (a == 0) ){ count++; } break; } count++; b = a; a = (a+1) % MAX_CARDS; } } if( count == MAX_CARDS ){ return true; } return false; } bool CheckPair() { max_paircount = pair_num = 0; int i, count = 1; bool fin = false; for( i = 1; i < MAX_CARDS; i++ ){ if( numberOfCards[i-1] == numberOfCards[i] ){ count++; fin = false; } else { fin = true; } if( fin || (i == (MAX_CARDS-1)) ){ if( count > 1 ){ if( max_paircount < count ){ max_paircount = count; } pair_num++; count = 1; } } } if( (max_paircount == 0) && (pair_num == 0) ){ return false; } else { return true; } } enum HAND_NAME { STRAIGHT_FLUSH = 0, FLUSH, STRAIGHT, FOUR_OF_A_KIND, THREE_OF_A_KIND, TWO_PAIR, PAIR, FULL_HOUSE, HIGH_CARD, BUG, HAND_MAX }; string[] hand_name = new string[] { "Straight flush", "Flush", "Straight", "Four of a kind", "Three of a kind", "Two pair", "Pair", "Full house", "High card", "bug", }; HAND_NAME ConfirmHand() { HAND_NAME hand = HAND_NAME.HIGH_CARD; if( flush || straight || (pair_num > 0) ){ if( flush && straight ){ hand = HAND_NAME.STRAIGHT_FLUSH; } else if( flush ){ hand = HAND_NAME.FLUSH; } else if( straight ){ hand = HAND_NAME.STRAIGHT; } else { switch( pair_num ){ case 1: switch( max_paircount ){ case 4: hand = HAND_NAME.FOUR_OF_A_KIND; break; case 3: hand = HAND_NAME.THREE_OF_A_KIND; break; case 2: hand = HAND_NAME.PAIR; break; default: hand = HAND_NAME.BUG; break; } break; case 2: switch( max_paircount ){ case 3: hand = HAND_NAME.FULL_HOUSE; break; case 2: hand = HAND_NAME.TWO_PAIR; break; default: hand = HAND_NAME.BUG; break; } break; default: hand = HAND_NAME.BUG; break; } } } return hand; } HAND_NAME CheckHands() { // フラッシュ判定 flush = CheckFlush(); // マーク無視で並び替え SortWithoutMark(); // ストレート判定 straight = CheckStraight(); // ペア判定 if( (!flush) && (!straight) ){ CheckPair(); } // 役の最終決定 return ConfirmHand(); } // トリガ public void ChangeCards( bool force ) { int i; HAND_NAME hand; // カードのドロー for( i = 0; i < 5; i++ ){ if( (force | GetToggle( i ).isOn) ){ cards[i] = DrawCardNo(); } } // 役の決定 hand = CheckHands(); // 役を表示する if( !ReferenceEquals(handText, null) ){ handText.text = hand_name[(int)hand]; } // イメージ for( i = 0; i < 5; i++ ){ ChangeCardImage( i ); GetToggle( i ).isOn = false; } } }
プレイ画面
次回
イベント重複チェック、揃いそうな役の推測
本記事に使用しているトランプの絵柄は、いらすとやさんの素材を使用させていただいています。ありがとうございます。
トランプのイラスト(54枚まとめ) | かわいいフリー素材集 いらすとや