リーチ表示の第3回目。今回は、どのカードが出たら役が確定するかを予測します。
これまでの修正との関連
第4回は手持ち札はどの役に近いか、第5回はどの手持ち札がリーチなのか、をそれぞれ予測しました。
第4回:手持ち札はどの役に近いか
そろいやすいポーカー#4 リーチの認識(Unity2019.4.4f1) - ゲーム化!tomo-manaのブログ
第5回:どの手持ち札がリーチなのか
そろいやすいポーカー#5 リーチの認識2(Unity2019.4.4f1) - ゲーム化!tomo-manaのブログ
今回(第6回)は、どのカードが出たら役が確定するかを予測します。
方針
山札の全てのカード(52枚)に対して、以下の役の狙い札にされているかどうかをマークします。
役リーチ | 狙い札 |
---|---|
ストレートフラッシュ | ストレート4 && フラッシュ4 |
フラッシュ | 最頻マーク |
ストレート | ストレートパターン |
ワンペア→スリーカード→フォーカード ツーペア→フルハウス(役強化) |
ペアを構成している同一ナンバー |
ワンペア→ツーペア スリーカード→フルハウス(役追加) |
ペアを構成していない同一ナンバー※ |
ハイカード→ワンペア(最小役生成) | 手持ち札と同じナンバー |
以下の構造で保持することにします。
public class 山札の状態 { public byte ストレートフラッシュフラグ; public byte フラッシュフラグ; public byte ストレートフラグ; public byte 役強化フラグ; public byte 役追加フラグ; public byte 最小役生成フラグ; public void リセット() { ストレートフラッシュフラグ = 0; フラッシュフラグ = 0; ストレートフラグ = 0; 役強化フラグ = 0; 役生成フラグ = 0; 最小役生成フラグ = 0; } } 山札の状態[] 狙い札 = new 山札の状態[52];
それぞれの役での狙い札を見ていきます。
フラッシュ
前回(第5回)のフラッシュの図で見た場合、ダイヤが最頻マークになります。リーチするには、最頻マークが手持ち札の過半数(3枚以上)を構成している必要がありました。
この場合、狙い札は以下のようになります。
最大で山札のうち10枚がフラッシュの狙い札になります。
こうして狙い札の数を数えてみると、手持ち札によって、狙い札の数が変わらないのがフラッシュの特徴のようです。この後ストレートを見ていきますが、実際にポーカーをしている時、ストレートよりフラッシュの方が総組合せ数が少ないはずなのに、体感的にストレートより狙いやすく感じるのはこれが原因かと思います。
フラッシュリーチ条件 | 狙い札の数 |
---|---|
フラッシュ3 | 10枚以下 |
フラッシュ4 | 9枚以下 |
ストレート
次に、ストレートを見ていきます。同じく前回(第5回)のストレートの図で見た場合、5枚中3枚以上がストレートの条件を満たす組み合わせ(ストレートパターン)は、{A, 2, 3, 4, 5} {3, 4, 5, 6, 7} {4, 5, 6, 7, 8} {5, 6, 7, 8, 9} の4通りです。
この場合、ストレートの狙い札は {2, 4, 6, 9} の4種類、各マーク4種を加味して 4 x 4 =16枚 が対象になります。
色付けしてみると気づくのですが、手持ち札によって、狙い札の数に大きな差が出るのがストレートの特徴です。
なお、最大はうまく説明できませんが、おそらく4種類×4マーク=16枚です。5枚ある手持ち札の中央値にあたるカードから数えて両側±4以内のカードしか、ストレート狙い札になれないためです。
最小は1種類×4マーク=4枚です。Aを含むストレートで、あと1枚でそろう場合がこれに該当します。
なお、あと1枚でストレートとなる場合、狙い札の数は両端待ちで8枚、その他の場合で4枚のため、いずれもフラッシュの方が狙い札の数は多くなります。
まとめると以下になります。
ストレートリーチ条件 | 狙い札の数 |
---|---|
ストレート3 | 16枚以下 |
ストレート4(両面待ち) | 8枚以下 |
ストレート4(その他) | 4枚以下 |
役強化(ワンペア→スリーカード→フォーカード、ツーペア→フルハウス)
ここからは、ペア系を見ていきます。
第4回で使った図を使うと、ちょうど図の矢印が予測になります(遷移をあらわします)。
緑の線は、同じナンバーのカードが増える線(ここでは役強化と呼びます)です。
●ワンペアまたはスリーカードの場合
すでに役を構成しているのと同じナンバーが来たら役強化(ワンペア→スリーカード→フォーカード)。狙い札の数はワンペア→スリーカードで2枚、スリーカード→フォーカードで1枚です。
スリーカードの狙い札が少ないことはびっくりですが、ワンペア→スリーカードの場合、捨てる手持ち札が最大で3枚のため、もう少し揃いやすい印象があるのかもしれません。
●ツーペアの場合
すでに役を構成している2組のどちらかと同じナンバーが来たら役強化(ツーペア→フルハウス)。狙い札の数はワンペア→スリーカードの2倍で4枚です。
役追加(ワンペア→ツーペア、スリーカード→フルハウス)
次に、最小役がある状態で、役を追加する動きを見ていきます。先ほどの図で、青の線は、役になっていないナンバーのカードが増える線(ここでは役追加と呼びます)です。
上記の図では4のペア形成になっていますが、ここでは便宜的に {A, 7, 8} を持っている場合を示します。狙い札の数は9枚です。
最小役生成(ハイカード→ワンペア)
最後に、役が無い状態で、最小の役を構成する動きを見ていきます。先ほどの図で、紫の線は、手持ちの札のどれかが役を構成する線(ここでは最小役生成と呼びます)です。
手持ち札のどれかを残して、それがワンペアを構成する場合、狙い札はそれぞれ3枚です。通常のポーカーでは、ハイカードが出た時は5枚すべてを交換が定石ですが、それはハイカードが50%、ワンペアが42.25%だからです。通常は全部交換する方が合理的かもしれません。
以下、複数の役の組み合わせについても触れておきます。
ストレートフラッシュ
通常、ストレートフラッシュが狙えるという心理になるのは、ストレート候補が4枚、かつフラッシュ候補が4枚、といった、かなり好条件の場合に限られると思います(偏見かもしれませんが)。ストレートフラッシュへのリーチの場合、他のストレート狙い札、フラッシュ狙い札とは別格に扱いたいので、この特別な狙い札には、専用の色を分けることにします。
フルハウス
少しこれまでで検討が漏れていたのが、フルハウスへのリーチでした。フルハウスへのリーチは、スリーカードからのリーチと、ツーペアからのリーチで予測のされ方が異なります。
●スリーカード→フルハウスへのリーチ
スリーカードからフルハウスへのリーチは、スリーカード→フォーカードのリーチと、役追加のリーチの両方がかかった状態になります。これは特に違和感はありません。
●ツーペア→フルハウスへのリーチ
一方、ツーペアからフルハウスへのリーチの場合、役を構成しない残り1枚は、どう考えても捨てるカードです。そのため、何らかのリーチを表示させる必要はないのですが、これまでの考えだと役候補になります。
待ちカードの予測(設計)
フラッシュ
int 最大ナンバー = 13; void フラッシュ狙い札予測() { if( フラッシュリーチ ){ 走査番号 = 最大ナンバー * 最頻マーク; // 開始点 for( A~K ){ if( !ドロー済( 走査番号 ) ){ 狙い札[ 走査番号 ].フラッシュフラグ++; } 走査番号++; } } }
ストレート
void ストレート狙い札予測() { if( ストレートリーチ ){ for( すべてのストレートパターン ){ if( ストレートパターン[i] >= ストレートリーチ閾値 ){ for( 手持ち札の枚数分だけ ){ 狙い中 = true; if( すでに保持しているカード ){ 狙い中 = false; } if( 狙い中 ){ for( すべての同一ナンバーのマークにつき ){ if( !ドロー済( no ) ){ 狙い札[ no ].ストレートフラグ++; if( ストレートフラッシュ条件 ){ 狙い札[ no ].ストレートフラッシュフラグ++; } } } } } } } } }
ペア
void UpdateDeckPair() { int no; for( すべての手持ち札 ){ no = 手持ち札[i]%MAX_NUMBERS; if( ペアリーチ ){ for( すべてのマーク ){ if( !ドロー済( no ) ){ 狙い札[ no ].ペアフラグ++; } } } else { if( 役強化リーチ ){ if( ナンバー出現数 > 1 ){ for( すべてのマーク ){ if( !ドロー済( no ) ){ 狙い札[ no ].役強化フラグ++; } } } } if( 役追加リーチ ){ if( ナンバー出現数 == 1 ){ if( ペア数 == 1 ){ for( すべてのマーク ){ if( !ドロー済( no ) ){ 狙い札[ no ].役追加フラグ++; } } } } } } } }
コード
山札のカード状態の更新(CardSuit.cs)
public class WantNumber { public byte straightFlush; public byte flush; public byte straight; public byte over3; public byte over2pair; public byte pair; public void Reset() { straightFlush = 0; flush = 0; straight = 0; over3 = 0; over2pair = 0; pair = 0; } } public class CardSuit : MonoBehaviour { // 追加分のみ void UpdateDeckFlash() { int no; if( r_flush ){ no = MAX_NUMBERS * maxMarkId; for(int i = 0; i < MAX_NUMBERS; i++){ if( !Drawed( no ) ){ wantNum[ no ].flush++; } no++; } } } void CheckFlush() { /* 前略 */ UpdateDeckFlash(); } void UpdateDeckStraight() { int i, j, k, l, no; bool hint; if( r_straight ){ for( i = 0; i < MAX_STRAIGHT_COMBINATION_NUMBER; i++ ){ if( straightPtnCount[i] >= STRAIGHT_REACHING_THRESHOLD ){ for( j = 0; j < MAX_CARDS; j++ ){ no = (i + j)%MAX_NUMBERS; hint = true; for( l = 0; l < MAX_CARDS; l++ ){ if( cards[l]%MAX_NUMBERS == no ){ hint = false; } } if( hint ){ for( k = 0; k < MAX_MARKS; k++ ){ no = (i + j)%MAX_NUMBERS + k * MAX_NUMBERS; if( !Drawed( no ) ){ wantNum[ no ].straight++; if( (straightPtnCount[i] == 4) && (k == maxMarkId) && (maxMarkCount == 4) ){ wantNum[ no ].straightFlush++; } } } } } } } } } void CheckStraight() { /* 前略 */ UpdateDeckStraight(); } void UpdateDeckPair() { int no; for(int i = 0; i < MAX_CARDS; i++){ no = cards[i]%MAX_NUMBERS; // ハイカード→ワンペアへのリーチ(すべて色を付ける) if( r_pair ){ for(int j = 0; j < MAX_MARKS; j++){ if( !Drawed( no + MAX_NUMBERS*j ) ){ wantNum[ no + MAX_NUMBERS*j ].pair++; } } } else { // スリーカード以上へのリーチ(役強化) // ペアを構成していたら色を付ける if( r_over3 ){ if( numbersCount[ no ] > 1 ){ Debug.Log("r_over3 no = " + no + " numbersCount = " + numbersCount[no] ); for(int j = 0; j < MAX_MARKS; j++){ if( !Drawed( no + MAX_NUMBERS*j ) ){ wantNum[ no + MAX_NUMBERS*j ].over3++; } } } } // 役追加 // ワンペアまたはスリーカードの場合、ツーペアまたはフルハウスへのリーチ if( r_over2pair ){ if( numbersCount[ no ] == 1 ){ if( pair_num == 1 ){ for(int j = 0; j < MAX_MARKS; j++){ if( !Drawed( no + MAX_NUMBERS*j ) ){ wantNum[ no + MAX_NUMBERS*j ].over2pair++; } } } } } } } } bool CheckPair() { /* 前略 */ UpdateDeckPair(); // 戻り値 if( (max_paircount == 0) && (pair_num == 0) ){ return false; } else { return true; } } public void ChangeCards( bool force ) { /* 前略 */ // リーチを山札にも表示(追加) drawCtrl.UpdateDeckHints(); } }
山札の色分け(DrawControl.cs)
public class DrawControl : MonoBehaviour { // 追加した関数 public void UpdateDeckHints() { for( int i = 0; i < TOTAL_CARDS; i++ ){ if( !cardSuit.Drawed( i ) ){ WantNumber wn = cardSuit.GetWantNumber( i ); if( wn.straightFlush != 0 ){ GoStraightFlushHinted( i ); } else if( wn.flush != 0 ){ GoFlushHinted( i ); } else if( wn.straight != 0 ){ GoStraightHinted( i ); } else if( wn.over3 != 0 ){ GoOver3OfAKindHinted( i ); } else if( wn.over2pair != 0 ){ GoOver2PairHinted( i ); } else if( wn.pair != 0 ){ GoPairHinted( i ); } else { GoNotDrawed( i ); } } } } }
プレイ画面
(前回までと同じURLです。最新の状態に更新されています)
tomo-mana.hatenablog.com
次回
リーチがかかった時の表示に、どの役へのリーチを表示ししてて、その発生確率は何パーセントか(何通りあるか)を表示します。ここまで来ると、ポーカーの役が決まるまでのダイナミックな動きが見られるようになります。
思っていたよりも、乱数調整への道は長い!