ゲーム化!tomo_manaのブログ

ゲーム化!tomo-manaのブログ

Unityでゲームを作る方法について紹介しています

そろいやすいポーカー#4 リーチの認識(Unity2019.4.4f1)

f:id:tomo_mana:20211212232551p:plain

今回は、どの役に近いか(リーチしたか)を認識できる機能を実装します。

概要

ポーカーでの役は、大きく分けるとフラッシュ、ストレート、そしてペア系になります。

f:id:tomo_mana:20211212233446p:plain
役のリーチ

リーチは、常に今の手持ちより強い役にかかるものとします。

ストレートとフラッシュは、条件を満たすカードが3枚あったらリーチとします。ペアは、2枚以上で常に役になるため、常に何らかのリーチがかかっています。


それぞれ、リーチの条件を見ていきます。

リーチの条件

フラッシュのリーチ

フラッシュはすべてのマークの個数を数え、一番多いマーク数が3以上であればリーチになります。


たとえば、A, 3, 5, 7, 8 を持っていたとして、それぞれのマークを調べると、ハートが1枚、ダイヤが3枚、スペードが1枚でした。この場合、全てのマークで最も枚数が多いのはダイヤで、3枚以上の条件を満たすのでリーチとなります。

f:id:tomo_mana:20211213190923p:plain
フラッシュのリーチ走査

これを処理にすると、以下のようになります。

手持ちカード枚数 = 5;
マークの種類の数 = 4;
リーチ条件数 = 3;

void CheckFlush()
{
    // マーク出現数を数える
    for( i = 0; i < 手持ちカード枚数; i++ ){
        マーク出現数[ カード[i]/13 ]++;
    }
    // 最大マーク出現数の確認
    for( i = 0; i < マークの種類の数; i++ ){
        if( 最大マーク出現数 < マーク出現数[i] ){
            最大マーク出現数 = マーク出現数[i];
        }
    }
    // リーチ条件を満たしているか?
    if( 最大マーク出現数 == 手持ちカード枚数 ){
        // フラッシュ確定
    } else
    if( 最大マーク出現数 >= リーチ条件数 ){
        // フラッシュのリーチ
    }
}

心持ち、前回よりスッキリした気がします。

ストレートのリーチ

第2回では、ストレートの判定条件を少し複雑に考えていたのですが、よく考えると、ストレートになる条件は10パターンしかないので、どのパターンに近いかで判定すればよいのでは、と考えました。

f:id:tomo_mana:20211213072742p:plain
ストレートのリーチ条件数

先ほどと同じく、A, 3, 5, 7, 8 を持っていたとします。A, 2, 3, 4, 5 のパターンから 10, J, Q, K, A までの 10パターンにどれくらいあてはまっているか調べると、{A, x, 3, x, 5} {3, x, 5, x, 7} {x, 5, x, 7, 8} {5, x, 7, 8, x} でそれぞれ3枚該当があり、ストレートのリーチであることが分かります。

f:id:tomo_mana:20211213191009p:plain
ストレートのリーチ走査

これを処理にすると、以下のようになります。

void CheckStraight()
{
    int i, j;
    
    // ナンバー出現数を数える
    for( i = 0; i < 手持ちカード枚数; i++ ){
        ナンバー出現数[ カード[i]%13 ]++;
    }
    for( i = 0; i < ストレートパターン数; i++ ){
        if( ナンバー出現数[i] != 0 ){
            for( j = 0; j < 手持ちカード枚数; j++){
                if( ナンバー出現数[(i+j)%13] != 0 ){
                    ストレート条件一致数[i]++;
                }
            }
            if( ストレート条件一致数[i] == 手持ちカード枚数 ){
                // ストレート確定
            } else 
            if( ストレート条件一致数[i] >= リーチ条件数 ){
                // ストレートのリーチ
            }
        }
    }
}

前回の処理に比べると、非常にスッキリしました。

ペアのリーチ

ペアは常にどれかのリーチ状態になります。そのため、いつも次の1枚を待つ状態と考えます。

この記事の冒頭にあった図から、ペアのリーチだけを抜き出すと、以下のようになります。図の青線は、冒頭の図に足りないところを少し足しています。

f:id:tomo_mana:20211213193750p:plain
ペアのリーチ条件


それぞれの役から伸びている矢印を表にすると、以下のようになります。緑はペア数が1、青はペア数が2です。

f:id:tomo_mana:20211213203221p:plain
ペアのリーチ条件(表)

イカード(役なし)を除いて、もっとも確率の高いワンペアから、フルハウスまで、ほぼ緑と青が点灯することになります。

f:id:tomo_mana:20211213231619p:plain
ペア条件(まとめ)
void ペアのリーチ判定(現在の役)
{
    // 1組系
    switch( 現在の役 ){
        case フルハウス:
        case スリーカード:
        case ツーペア:
        case ワンペア:
            // リーチ
            break;
    }
    // 2組系
    switch( 現在の役 ){
        case スリーカード:
        case ツーペア:
        case ワンペア:
        case ハイカード:
            // リーチ
            break;
    }
    // ワンペア
    if( 現在の役 == ハイカード ){
        // ワンペアリーチ
    }
}

リーチの表示

現在のリーチ状況を表示する画面を追加します。

f:id:tomo_mana:20211214060551p:plain
リーチ表示の追加

どの役をリーチと見ているのか、各リーチの状況を、それぞれランプを点灯させて分かるようにします。
●(赤)フラッシュ
●(橙)ストレート
●(青)2組系
●(緑)1組系:スリーカード以上
●(紫)1組系:ワンペア


Hierarchy
前回追加した山札表示(Deck)の右に、野球のストライクボードのような表示を追加します。名前はReachSignalsとしておきます。

f:id:tomo_mana:20211214075002p:plain
Layout - ボード表示


表示は、Panel と Text を Emtpyオブジェクト(Signal)で括った簡易的なものです。

f:id:tomo_mana:20211214080035p:plain
Hierarchy - ボード表示


表示用のクラス(ReachSignals.cs)を作って、リーチの情報が来たら点灯・消灯をするようにします(SetSignal)。
コード

// シグナルID
public enum SIGNAL_ID {
    SIG_FLASH,
    SIG_STRAIGHT,
    SIG_OVER_2PAIR,
    SIG_OVER_3_OF_A_KIND,
    SIG_PAIR,
    SIG_OFF,
};
public class ReachSignals : MonoBehaviour
{
    List<Image> signals = new List<Image>();
    
    List<Color> signalColor = new List<Color> {
        // 色指定
    };
    public void SetSignal( SIGNAL_ID id, bool on )
    {
        if( on ){
            signals[(int)id].color = signalColor[(int)id];
        } else {
            signals[(int)id].color = signalColor[(int)SIGNAL_ID.SIG_OFF];
        }
    }
}

コード

カード管理(CardSuit.cs)

public class CardSuit : MonoBehaviour
{
    /* 略 */
    
    // フラッシュ判定(修正)
    int MAX_MARKS = 4;              // マークの種類の数
    int[] markCount = new int[4];   // マーク出現数
    int maxMarkCount;               // 最大マーク出現数
    int maxMarkId;                  // 最大出現マーク
    
    int FLUSH_REACHING_THRESHOLD = 3;   // フラッシュリーチしきい値
    
    bool r_flush;                   // フラッシュのリーチ
    
    void CheckFlush()
    {
        int i;
        
        // マーク出現数クリア
        for( i = 0; i < MAX_MARKS; i++ ){
            markCount[i] = 0;
        }
        maxMarkCount = 0;
        // マーク出現数を数える
        for( i = 0; i < MAX_CARDS; i++ ){
            markCount[ cards[i]/13 ]++;
        }
        // 最大マーク出現数の確認
        for( i = 0; i < MAX_MARKS; i++ ){
            if( maxMarkCount < markCount[i] ){
                maxMarkCount = markCount[i];
                maxMarkId = i;
            }
        }
        // フラッシュ判定
        flush = false;
        r_flush = false;
        if( maxMarkCount == MAX_CARDS ){
            flush = true;
        } else
        if( maxMarkCount >= FLUSH_REACHING_THRESHOLD ){
            r_flush = true;
        }
    }
    
    // ストレート判定(修正)
    int MAX_NUMBERS = 13;
    int[] numbersCount = new int[13];           // ナンバー出現数
    
    int MAX_STRAIGHT_COMBINATION_NUMBER = 10;   // ストレートパターン数
    int[] straightPtnCount = new int[10];       // ストレート条件一致数
    
    int STRAIGHT_REACHING_THRESHOLD = 3;        // ストレートリーチしきい値
    
    bool r_straight;                            // ストレートのリーチ
    
    void CheckStraight()
    {
        int i, j;
        
        // ナンバー出現数クリア
        for( i = 0; i < MAX_NUMBERS; i++ ){
            numbersCount[i] = 0;
        }
        // ナンバー出現数を数える
        for( i = 0; i < MAX_CARDS; i++ ){
            numbersCount[ cards[i]%MAX_NUMBERS ]++;
        }
        // ストレート判定
        straight = false;
        r_straight = false;
        for( i = 0; i < MAX_STRAIGHT_COMBINATION_NUMBER; i++ ){
            
            straightPtnCount[i] = 0;
            
            if( numbersCount[i] != 0 ){
                for( j = 0; j < MAX_CARDS; j++){
                    if( numbersCount[(i+j)%MAX_NUMBERS] != 0 ){
                        straightPtnCount[i]++;
                    }
                }
                if( straightPtnCount[i] == MAX_CARDS ){
                    straight = true;
                } else 
                if( straightPtnCount[i] >= STRAIGHT_REACHING_THRESHOLD ){
                    r_straight = true;
                }
            }
        }
    }
    
    // リーチ判定(追加)
    void CheckWaitingHands(HAND_NAME hand)
    {
        if( ReferenceEquals( reachSignals, null ) )
        {
            return;
        }
        // フラッシュ点灯
        reachSignals.SetSignal( SIGNAL_ID.SIG_FLASH, r_flush );
        // ストレート点灯
        reachSignals.SetSignal( SIGNAL_ID.SIG_STRAIGHT, r_straight );
        // ペア1組系点灯
        switch( hand ){
            case HAND_NAME.THREE_OF_A_KIND: // four card, fullhouse
            case HAND_NAME.PAIR:    // three card, two pair
            case HAND_NAME.FULL_HOUSE:  // four card
            case HAND_NAME.TWO_PAIR:    // three card, fullhouse
                reachSignals.SetSignal( SIGNAL_ID.SIG_OVER_3_OF_A_KIND, true );
                break;
            default:
                reachSignals.SetSignal( SIGNAL_ID.SIG_OVER_3_OF_A_KIND, false );
                break;
        }
        // ペア2組系点灯
        switch( hand ){
            case HAND_NAME.PAIR:    // three card, two pair
            case HAND_NAME.TWO_PAIR:    // three card, fullhouse
                reachSignals.SetSignal( SIGNAL_ID.SIG_OVER_2PAIR, true );
                break;
            default:
                reachSignals.SetSignal( SIGNAL_ID.SIG_OVER_2PAIR, false );
                break;
        }
        // ワンペア点灯
        if( hand == HAND_NAME.HIGH_CARD ){
            reachSignals.SetSignal( SIGNAL_ID.SIG_PAIR, true );
        } else {
            reachSignals.SetSignal( SIGNAL_ID.SIG_PAIR, false );
        }
    }
    // トリガ(リーチ処理を追加しました)
    public void ChangeCards( bool force )
    {   
        // 第2回、CheckHands() の後に以下追加しました
        
        // リーチ表示
        CheckWaitingHands( hand );
        
        /* 以下 略 */
    }
}

リーチ表示(ReachSignals.cs)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public enum SIGNAL_ID {
    SIG_FLASH,
    SIG_STRAIGHT,
    SIG_OVER_2PAIR,
    SIG_OVER_3_OF_A_KIND,
    SIG_PAIR,
    SIG_OFF,
};

public class ReachSignals : MonoBehaviour
{
    List<Image> signals = new List<Image>();
    
    List<Color> signalColor = new List<Color> {
        Color.red,
        new Color(1.0f, 0.5f, 0.0f, 1.0f),  // orange
        Color.blue,
        Color.green,
        new Color(0.8f, 0.0f, 0.8f, 1.0f),  // purple
        Color.gray,
    };
    
    public void SetSignal( SIGNAL_ID id, bool on )
    {
        if( on ){
            signals[(int)id].color = signalColor[(int)id];
        } else {
            signals[(int)id].color = signalColor[(int)SIGNAL_ID.SIG_OFF];
        }
    }
    
    // Start is called before the first frame update
    void Start()
    {
        Image image;
        
        foreach( Transform child in gameObject.transform ){
            foreach( Transform gc in child ){
                image = gc.gameObject.GetComponent<Image>();
                if( !ReferenceEquals( image, null ) ){
                    signals.Add( image );
                }
            }
        }
        int i = 0;
        foreach( Image im in signals ){
            SetSignal( (SIGNAL_ID)i, false );
            i++;
        }
    }
}

プレイ画面

(前回までと同じURLです。最新の状態に更新されています)
tomo-mana.hatenablog.com


次回
●どのカードがリーチしているのか(表示)
●どのカードが来たら揃うのか(処理、表示)


本記事に使用しているトランプの絵柄は、いらすとやさんの素材を使用させていただいています。ありがとうございます。
トランプのイラスト(54枚まとめ) | かわいいフリー素材集 いらすとや