ゲーム化!tomo_manaのブログ

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

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

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

f:id:tomo_mana:20211220104617p:plain

今回は、第4回のリーチの認識に続いて、役またはリーチのカードに輪郭(アウトライン)を付ける処理を追加します。

輪郭(アウトライン)を追加する

複雑な形状に輪郭を追加する場合、shaderを使った方法などがあるようですが、今回輪郭を追加するのは単純な形状(角丸の四角)なので、Image を重ね合わせて実現します。

f:id:tomo_mana:20211216233516p:plain
輪郭線の追加イメージ


RectTransformオブジェクトを重ねる時は、いくつか制限がありそうでした。

●RectTransformの場合、表示順は z軸 ではなくHierarchyの階層で行う。
●子は親より手前に表示される。
●同じ階層(たとえば親同士)の場合、Hierarchyに沿う。

(以下を参考にしました)
blog.cfm-art.net


子が親より手前に表示されますので、輪郭を親、カードイメージを子にします。(2枚の Image は移動させる場合に座標を合わせたいので、親子にしています)

f:id:tomo_mana:20211219005859p:plain
Hierarchy - カード部(輪郭用Image追加)

これによって、カードイメージの階層は1段階、深くなります。

// gameObject.transform.GetChild(no).GetChild(0).GetComponent<Image>();
   gameObject.transform.GetChild(no).GetChild(0).GetChild(0).GetComponent<Image>();


カードイメージの Anchor はcenter/centerにします。あとは輪郭のサイズ(Width/Height)を変更すれば、輪郭線の太さを調節できます。

f:id:tomo_mana:20211219010305p:plain
Inspector - Outline, Card

輪郭に任意の色を付ける処理は以下の通りです。

void 輪郭表示(int no, Color color)
{
    gameObject.GetComponent<Image>().color = color;
}

優先順位

輪郭表示で表現する場合、前回のシグナルのように、あまり複数の状態を表示するのには向いていません。どちらかというと、今一番どの役に近いのかを表示してくれた方が便利です。

f:id:tomo_mana:20211220094229p:plain
シグナル表示と輪郭表示(イメージ)

基本的に、ツーペア、スリーカード以上の役は、その役を崩して次の役を狙いに行くことは少ないと思います。また、ペアは常にリーチ状態のため、輪郭でリーチを表現しないものとします。

ワンペアだけは役が揃う確率が高い(42%)だけに、より高い役を狙いに行ける時には、ワンペアを崩して狙いに行く選択肢もあると思います。そこで、ワンペアの場合、ストレートまたはフラッシュのリーチとワンペアの両方を表示するようにします。


そこで、以下の順に表示することにします。

f:id:tomo_mana:20211217171934p:plain
役、リーチの決定

(1) ワンペア以外の役は、出来上がった役を優先(役を成しているカードだけ色を付ける)
(2) ワンペアの場合、
 あと1枚でフラッシュ、またはストレートの場合、ワンペア+リーチ表示
 それ以外はワンペア
(3) ハイカードの場合、
 フラッシュまたはストレートのリーチの場合、リーチしているカードだけ色を付ける


判別式は以下のようになります。

void 輪郭表示()
{
    役の名前 優先輪郭;
    
    switch ( 役 )
    {
        case ワンペア:
        case ハイカード:
        {
            if( リーチ_フラッシュ ){
            	輪郭優先 = フラッシュリーチ;
            } else
            if( リーチ_ストレート ){
            	輪郭優先 = ストレートリーチ;
            } else {
                輪郭優先 = リーチ;
            }
        }
        default:
            輪郭優先 == 役;
            break;
    }
}

リーチしているカードだけ色を付ける

フラッシュのリーチ

フラッシュのリーチは、最も高い頻度で出ているマークの数が3以上でリーチが確定するのでした。

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

カードの各マークがこの最頻値に一致した時に、カードの色を付けることにします。

void フラッシュ条件()
{
    int i;
    
    for( i = 0; i < 手持ちカード枚数; i++ ){
        if( カード[i]/13 == 最頻マーク ){
            輪郭表示( i, 赤 );
        } else {
            輪郭表示( i, 透明 );
        }
    }
}

ストレートのリーチ

ストレートのリーチは、{A, 2, 3, 4, 5} から {10, J, Q, K, A} までの10パターンについて、3つ以上一致したらリーチが確定するのでした。

f:id:tomo_mana:20211213072742p:plain
ストレートの全パターン(10パターン)

その中で、最も強い役の組み合わせを待っているものとします。

下の表では、下の行ほど強いパターンになります。この場合、{5, 6, 7, 8, 9} が最も強いパターンになります。

f:id:tomo_mana:20211213191009p:plain
ストレートパターン毎の一致枚数の走査
void ストレート条件(){
    int i, j;
    
    for( i = 0; i < 手持ちカード枚数; i++ ){
        ストレートのリーチカード = false;
        // 最大ストレート条件のテーブルを作成して確認(総当たり)
        for( j = 0; j < 手持ちカード枚数; j++ ){
            if( カード[i]%13 == (最強ストレートパターンの最小値 + j)%13 ){
                ストレートのリーチカード = true;
            }
        }
        if( ストレートのリーチカード ){
            輪郭表示( i, 橙 );
        } else {
            輪郭表示( i, 透明 );
        }
    }
}

ペアのリーチ

ペアについては、いつもリーチがかかった状態になるので、アウトラインでは確定した役だけを表示するようにします。


なお、ペアについても、どのカードで役が成立しているかを識別できるようにするために、認識方法に少し修正を入れます。

bool CheckPair()
{
    最大カウント = ペア数 = 0;
    for( int i = 0; i < 手持ちカード枚数; i++ ){
        if( ナンバー出現数[i] >= 2 ){
            ペア数++;
            if( 最大カウント < ナンバー出現数[i] ){
                最大カウント = ナンバー出現数[i];
            }
        }
    }
    if( (最大カウント == 0) && (ペア数 == 0) ){
        return false;
    } else {
        return true;
    }
}

ナンバー出現数(int[13])は、前回(第4回)の「ストレートのリーチ」で実装したものです。


このナンバー出現数を使用すると、ペア条件での輪郭表示処理は以下になります。

bool ペア条件( int i, Color 指定色 ){
    bool ret = false;

    if( ナンバー出現数[ cards[i]%13 ] >= 2 ){
        輪郭表示( i, 指定色 );
        ret = true;
    } else {
        輪郭表示( i, 透明 );
    }
    return ret;
}
void ペア条件( Color 指定色 ){
    for( i = 0; i < 手持ちカード枚数; i++ ){
        ペア条件( i, 指定色 );
    }
}

ペア条件だけ、便宜的にforループ文と各カードの判定条件を分けておきます。


さて、

これまでは確定済みの役だったのですが、ここからは、確定済みの役とリーチとの合わせ技になります。

ワンペア+フラッシュ or ストレートのリーチ

ワンペアのリーチの場合、フラッシュまたはストレートの走査中に、ペアの判定条件を追加します。(しれっと書きましたが、この辺りはトライ&エラーでした)


ワンペア+フラッシュのリーチ
ワンペア+フラッシュのリーチの場合、先ほどのフラッシュの走査条件を、以下のように書き換えます。

void フラッシュ条件()
{
    int i;
    
    for( i = 0; i < 手持ちカード枚数; i++ ){
        if( !ペア条件( i, 紫 ) ){   // 追加
            if( カード[i]/13 == 最頻マーク ){
                輪郭表示( i, 赤 );
            } else {
                輪郭表示( i, 透明 );
            }
        }
    }
}


ワンペア+ストレートのリーチ
ストレートも同様の修正で処理できます。

void ストレート条件(){
    int i, j;
    
    for( i = 0; i < 手持ちカード枚数; i++ ){
//        ストレートのリーチカード = false;
        if( !ペア条件( i, 紫 ) ){
            // 最大ストレート条件のテーブルを作成して確認(総当たり)
            for( j = 0; j < 手持ちカード枚数; j++ ){
                if( カード[i]%13 == (最強ストレートパターンの最小値 + j)%13 ){
//                  ストレートのリーチカード = true;
                    輪郭表示( i, 橙 );
                }
            }
        }
//        if( ストレートのリーチカード ){
//            輪郭表示( i, 橙 );
//        } else {
//            輪郭表示( i, 透明 );
//        }
    }
}

いくつかコメントアウトしてあるのは、ペア条件の判定を追加したことで、透明輪郭表示を省略できるためです。

イカード+フラッシュ or ストレートのリーチ

イカードとワンペアでの違いは、リーチを認識する枚数です。これは、冒頭の輪郭を決める処理で吸収します。

void 輪郭表示()
{
    int しきい値;
    優先輪郭 = 役;
    
    switch( 役 ){
        case ワンペア:
        case ハイカード:
        {
            if( リーチ_フラッシュ ){
                しきい値 = フラッシュリーチしきい値;
                if( 役 == ワンペア ){
                    しきい値++;
                }
                if( 最頻マーク >= しきい値 ){
                    優先輪郭 = フラッシュ;
                }
            } else
            if( リーチ_ストレート ){
                しきい値 = ストレートリーチしきい値;
                if( 役 == ワンペア ){
                    しきい値++;
                }
                if( ストレート該当数 >= しきい値 ){
                    優先輪郭 = ストレート;
                }
            }
            break;
        }
        default:
            break;
    }
}

コード

前回からの修正部分のみ抜粋。

public class CardSuit : MonoBehaviour
{
    /* 略 */
    
    // 輪郭線の色
    Color[] outlineColor = new Color[] {
        new Color(0.8f, 0.8f, 0.3f, 1.0f),  // STRAIGHT_FLUSH,      // 茶色
        Color.red,                          // FLUSH,
        new Color(1.0f, 0.5f, 0.0f, 1.0f),  // STRAIGHT,
        Color.green,                        // FOUR_OF_A_KIND,
        Color.green,                        // THREE_OF_A_KIND,
        Color.blue,                         // TWO_PAIR,
        new Color(0.8f, 0.0f, 0.8f, 1.0f),  // PAIR,                // 紫
        Color.blue,                         // FULL_HOUSE,
        new Color(0.0f, 0.0f, 0.0f, 0.0f),  // HIGH_CARD,           // 透明
        new Color(0.0f, 0.0f, 0.0f, 0.0f),  // BUG,                 // 透明
                                            // HAND_MAX
    };
    
    void SetOutlineColor(int no, Color color)
    {
        if( ReferenceEquals( color, null ) ){
            return;
        }
        gameObject.transform.GetChild(no).GetChild(0).GetComponent<Image>().color = color;
    }
    
    void SetOutlineColor(Color color)
    {
        if( ReferenceEquals( color, null ) ){
            return;
        }
        for( int i = 0; i < MAX_CARDS; i++ ){
            SetOutlineColor(i, color);
        }
    }
    
    void CheckFlushOutline( HAND_NAME specHand )
    {
        int i;
        
        for( i = 0; i < MAX_CARDS; i++ ){
            if( !CheckPairOutline( i, outlineColor[(int)HAND_NAME.PAIR] ) ){
                if( cards[i]/13 == maxMarkId ){
                    SetOutlineColor( i, outlineColor[(int)specHand] );
                }
            }
        }
    }

    void CheckStraightOutline(){
        int i, j;
        
        for( i = 0; i < MAX_CARDS; i++ ){
            // 最大ストレート条件のテーブルを作成して確認(総当たり)
            for( j = 0; j < MAX_CARDS; j++ ){
                if( !CheckPairOutline( i, outlineColor[(int)HAND_NAME.PAIR] ) ){
                    if( cards[i]%13 == (maxStraightPtnCountIdx + j)%13 ){
                        SetOutlineColor( i, outlineColor[(int)HAND_NAME.STRAIGHT] );
                        break;
                    }
                }
            }
        }
    }

    bool CheckPairOutline( int i, Color specColor ){
        bool ret = false;

        if( numbersCount[ cards[i]%13 ] >= 2 ){
            SetOutlineColor( i, specColor );
            ret = true;
        } else {
            SetOutlineColor( i, outlineColor[(int)HAND_NAME.HIGH_CARD] );
        }
        return ret;
    }
    void CheckPairOutline( Color specColor ){
        for( int i = 0; i < MAX_CARDS; i++ ){
            CheckPairOutline( i, specColor );
        }
    }
    
    HAND_NAME recommend;
    
    // 揃っている役を表示する
    public void RecommendHand(HAND_NAME hand)
    {
        // 輪郭表示の決定
        switch( hand ){
            case HAND_NAME.PAIR:
            case HAND_NAME.HIGH_CARD:
            {
                if( r_flush ){
                    recommend = HAND_NAME.FLUSH;
                } else
                if( r_straight ){
                    recommend = HAND_NAME.STRAIGHT;
                } else {
                    recommend = hand;
                }
                break;
            }
            default:
                recommend = hand;
                break;
        }
        // 表示方式を指定
        switch( recommend ){
            case HAND_NAME.STRAIGHT_FLUSH:
            case HAND_NAME.FLUSH:
                CheckFlushOutline( recommend );
                break;
            case HAND_NAME.STRAIGHT:
                CheckStraightOutline();
                break;
            case HAND_NAME.FOUR_OF_A_KIND:
            case HAND_NAME.THREE_OF_A_KIND:
            case HAND_NAME.FULL_HOUSE:
            case HAND_NAME.TWO_PAIR:
            case HAND_NAME.PAIR:
                CheckPairOutline( outlineColor[(int)recommend] );
                break;
            default:
                SetOutlineColor( outlineColor[(int)HAND_NAME.HIGH_CARD] );
                break;
        }
    }

    // トリガ
    public void ChangeCards( bool force )
    {
        /* 略 */
        :
        // リーチ表示
        CheckWaitingHands( hand );      // ここまで前回
        
        // カードのアウトラインに表示    // ここから追加
        RecommendHand( hand );
        // debug
        if( !ReferenceEquals(handText, null) ){
            if( (hand == HAND_NAME.PAIR) || (hand == HAND_NAME.HIGH_CARD) ){
                if( hand != recommend ){
                    handText.text += "\n(reaching " + hand_name[(int)recommend] + ")";
                }
            }
        }
        /* 以下略 */
    }
}

単独のクラスにできそうですが、既存のコード(CardSuit.cs)と共有している領域も多いため、今回は切り離し作業はしませんでした。

プレイ画面

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


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



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