ポーカー・テキサスホールデム開発ロジック①

スクリーンショット 2015-12-01 11.16.42
LINEで送る
Pocket

スクリーンショット 2015-12-01 11.16.42

こんにちは、ゲームプログラマーのきょるかんです。

今回はUnityで開発したポーカーのシステムについてご紹介したいと思います。
日本ではポーカーというとクローズドポーカーという、互いの手札が完全に見えないタイプのルールが主流です。
ですが海外のカジノでは、見せる手札と見せない手札の組み合わせで役を作るタイプのルールが主流です。
今回は後者のルールでも特に世界的に普及しているテキサスホールデムというポーカーを開発しましたので、その開発手法を複数回に分けてお話しさせていただければと思います。

 

+++++++++++++++++++++++++++++++++++

 

まずはテキサスホールデムをご存じない方のためにルールをご紹介したいと思います。
基本的なルールとしては、各プレイヤーは他の人に見せない自分だけに配られた2枚のカードと、場に最高5枚配られる共通のカードの合わせて7枚までのカードから、好きな5枚を選んで役を作って勝負します。
場札は全員に見える状態で配られ、全員が共通して同じ場札を用いて役を作っていくことになります。

■ ホールカード

最初に各プレイヤーに2枚の手札が配られます。これはホールカードと呼ばれています。
この状態で最初のベットを行います。これはプリフロップと呼ばれています。
最初に賭ける人が賭け金の相場を決めます。(この相場額をブラインドと呼びます)
このブラインドでの勝負に同意した場合はコールを選びます。
ブラインドをもっと上げたい場合はレイズを選び、上げたい額を告げます。誰かがレイズすると一度コールしていた場合でも再度コールするかの選択を迫られます。
逆に勝負を降りる場合はフォールドを選びます。この時それまでベットしていた賭け金は返ってきません。

■ フロップ

全員のベットが終わると、次に場札が3枚配られます。これはフロップと呼ばれています。
プレイヤーは手札の2枚と場札の3枚で役を確認します。
この状態で再度ベットを行います。
全員の賭け金は一つに集められており、これをポットと呼びます。
勝者はこのポットを全て得ることができます。

■ ターン

次に場札が1枚配られます。これはターンと呼ばれます。
ここでまたベットを行います。
途中で皆がフォールドして一人になったらその時点でも勝負終了になります。この時残った一人がポットを全て手に入れます。

■ リバー

最後に場札が1枚配られます。これはリバーと呼ばれます。
ここで最後のベットを行います。

■ ショーダウン

最後にそれぞれの役を見せます。
手札2枚と場札5枚から好きな5枚を選んで役にします。合計7枚あっても7枚全てを使って役を作ることはできません。
一番強い役を作った人が勝者となりポットを全て手に入れます。

以上の繰り返しがテキサスホールデムのルールになります。

 

+++++++++++++++++++++++++++++++++++

 

さて、ポーカーを作るにあたってまずはトランプカードのシステムを作らねばなりません。
後々カードゲームをさらに作ることも視野に入れて、汎用的に応用できるカードシステムを構築しておきます。
そこでCardsクラスというものを作りました。

public class Cards : SingletonMonoBehaviour<Cards> {
    [SerializeFieldprivate Vector2[] OffSet;
    [SerializeFieldprivate int jokerNum = 1;
    private static string[] kind = new string[]{
        "SA""S2""S3""S4""S5""S6""S7""S8""S9""S10""SJ""SQ""SK",
        "HA""H2""H3""H4""H5""H6""H7""H8""H9""H10""HJ""HQ""HK",
        "DA""D2""D3""D4""D5""D6""D7""D8""D9""D10""DJ""DQ""DK",
        "CA""C2""C3""C4""C5""C6""C7""C8""C9""C10""CJ""CQ""CK",
        "J0""J1"
    };
    public static Vector2[] OffSetsget { return Instance.OffSet; } }
    public static string[] Kindget { return kind; } }
    private static List<intnumber = new List<int>();
    public static List<intNumberget { return number; } }
    private static List<intsuits = new List<int>();
    public static List<intSuitsget { return suits; } }
    public static CardKind[] Kinds;

    public static List<intStock = new List<int>();
    public static List<List<int>> Players = new List<List<int>>();
    public static List<intLayout = new List<int>();
    public static List<intDiscarded = new List<int>();

    Cards(){
        Kinds = new CardKind[]{
            new CardKind(Suit.Spade1),
            new CardKind(Suit.Spade2),
            new CardKind(Suit.Spade3),
            new CardKind(Suit.Spade4),
            new CardKind(Suit.Spade5),
            new CardKind(Suit.Spade6),
            new CardKind(Suit.Spade7),
            new CardKind(Suit.Spade8),
            new CardKind(Suit.Spade9),
            new CardKind(Suit.Spade10),
            new CardKind(Suit.Spade11),
            new CardKind(Suit.Spade12),
            new CardKind(Suit.Spade13),
            new CardKind(Suit.Heart1),
            new CardKind(Suit.Heart2),
            new CardKind(Suit.Heart3),
            new CardKind(Suit.Heart4),
            new CardKind(Suit.Heart5),
            new CardKind(Suit.Heart6),
            new CardKind(Suit.Heart7),
            new CardKind(Suit.Heart8),
            new CardKind(Suit.Heart9),
            new CardKind(Suit.Heart10),
            new CardKind(Suit.Heart11),
            new CardKind(Suit.Heart12),
            new CardKind(Suit.Heart13),
            new CardKind(Suit.Diamond1),
            new CardKind(Suit.Diamond2),
            new CardKind(Suit.Diamond3),
            new CardKind(Suit.Diamond4),
            new CardKind(Suit.Diamond5),
            new CardKind(Suit.Diamond6),
            new CardKind(Suit.Diamond7),
            new CardKind(Suit.Diamond8),
            new CardKind(Suit.Diamond9),
            new CardKind(Suit.Diamond10),
            new CardKind(Suit.Diamond11),
            new CardKind(Suit.Diamond12),
            new CardKind(Suit.Diamond13),
            new CardKind(Suit.Club1),
            new CardKind(Suit.Club2),
            new CardKind(Suit.Club3),
            new CardKind(Suit.Club4),
            new CardKind(Suit.Club5),
            new CardKind(Suit.Club6),
            new CardKind(Suit.Club7),
            new CardKind(Suit.Club8),
            new CardKind(Suit.Club9),
            new CardKind(Suit.Club10),
            new CardKind(Suit.Club11),
            new CardKind(Suit.Club12),
            new CardKind(Suit.Club13),
            new CardKind(Suit.Joker0),
            new CardKind(Suit.Joker0)
        };
    }

    protected override void Awake(){
        base.Awake();
        number = kind.Where((itemindex) => index < 52).Select((itemindex) => index % 13 + 1).ToList<int>();
        number.AddRange(kind.Where((itemindex) => index >= 52).Select(item => 0));
        suits = kind.Select((itemindex) => index / 13).ToList<int>();
    }

    public static void InitJoker(int jokerNum){
        if (jokerNum > 2jokerNum = 2;
        Instance.jokerNum = jokerNum;
    }

    public static void InitHands(int handsNum){
        Players.Clear();
        for (int i = 0;i < handsNum;i++) Players.Add(new List<int>());
    }
    
    public static void InitShuffleStock(){
        Stock.Clear();
        Layout.Clear();
        Discarded.Clear();
        for (int i = 0;i < 52 + Instance.jokerNum;i++){
            int insertNum = Random.Range(0i + 1);
            Stock.Insert(insertNumi);
        }
    }

    public static void ResetDiscarded(){
        for (int i = 0;i < Discarded.Count;i++){
            int insertNum = Random.Range(0i + 1);
            Stock.Insert(insertNumDiscarded[i]);
        }
        Discarded.Clear();
    }

    public static bool InitLayout(int layoutNum){
        Discarded.AddRange(Layout);
        Layout.Clear();
        Layout = Stock.Take(layoutNum).ToList<int>();
        Stock = Stock.Skip(layoutNum).ToList<int>();
        if (Layout.Count == layoutNumreturn true;
        else return false;
    }

    public static bool AddLayout(int addNum = 1){
        int layoutRange = Layout.Count;
        Layout.AddRange(Stock.Take(addNum));
        Stock = Stock.Skip(addNum).ToList<int>();
        if (Layout.Count == layoutRange + addNumreturn true;
        else return false;
    }

    public static bool InitDistribute(int distributeNum){
        foreach (List<inthn in Players){
            Discarded.AddRange(hn);
            hn.Clear();
        }
        Players = Players.Select((itemindex) => Stock.Skip(distributeNum * index).Take(distributeNum).ToList<int>()).ToList<List<int>>();
        Stock = Stock.Skip(distributeNum * Players.Count).ToList<int>();
        if (distributeNum * Players.Count > Stock.Countreturn false;
        return true;
    }
    
    public static bool AddDistribute(int handNumint addNum = 1){
        int handsRange = Players[handNum].Count;
        Players[handNum].AddRange(Stock.Take(addNum));
        Stock = Stock.Skip(addNum).ToList<int>();
        if (Players[handNum].Count != handsRange + addNumreturn false;
        return true;
    }
    
    public static void InitDistributeAll(){
        foreach (List<inthn in Players){
            Discarded.AddRange(hn);
            hn.Clear();
        }

        Players = Players.Select((itemindex) => 
                             Stock.Where((item0index0) => index % Players.Count < Stock.Count % Players.Count ? 
                    index0 / Mathf.CeilToInt((float)Stock.Count / (float)Players.Count) == index : 
                    index0 / Mathf.FloorToInt((float)Stock.Count / (float)Players.Count) == index).ToList<int>()).ToList<List<int>>();
        Stock.Clear();
    }
    
    public static bool Discard(int handNumint discardNum){
        if (discardNum >= Players[handNum].Countreturn false;
        Discarded.AddRange(Players[handNum].Where((itemindex) => index == discardNum));
        Players[handNum] = Players[handNum].Where((itemindex) => index != discardNum).ToList<int>();
        return true;
    }

    public static void DiscardAll(int handNum){
        Discarded.AddRange(Players[handNum]);
        Players[handNum].Clear();
    }
    
}

[System.Serializable]
public class CardKind{
    public Suit suit;
    public int number;
    public CardKind(Suit suitint number){
        this.suit = suit;
        this.number = number;
    }
}

public enum Suit{
    Spade,
    Heart,
    Diamond,
    Club,
    Joker,
}

別途マークの種類を示すSuit列挙型と、スートと数値の情報を持つ各カードのデータを表すCardKindクラスを用意しています。
そしてCardKindクラスのインスタンスを52枚+ジョーカー分を持つKindsというクラス変数が基本的なカードデータとしてCardsクラス内に宣言してあります。
さらにStockPlayersLayoutDiscardedという変数も用意しました。
これらはそれぞれ山札、プレイヤーの手札、場札、捨札を表します。
基本的な流れとしては、山札からカードを取って手札や場札として配り、捨てたカードは捨札として集める、という処理を行えるようにしています。

■ 主なメソッド

InitJoker … 使用するジョーカーの枚数を初期化します。
InitHands … 参加プレイヤー数を初期化します。
InitShuffleStock … 山札をシャッフルして初期化します。
InitLayout … 場札を初期化して配ります。すでに配置してある場札は捨てられます。
AddLayout … 場札に追加して配ります。
InitDistribute … プレイヤーの手札を初期化して指定した枚数配ります。すでに配られてある手札は捨てられます。
AddDistribute … 指定したプレイヤーに手札を配ります。
InitDistributeAll … 山札を全てプレイヤーに配ります。
Discard … 指定したプレイヤーの指定した手札を捨てます。
DiscardAll … 指定したプレイヤーの全ての手札を捨てます。

今回はカードシステムのご紹介までということにさせていただこうと思います。
次回、いよいよテキサスホールデムシステムのロジックについて踏み込んでご紹介させていただこうと思います。
ここまでお読みいただきありがとうございました。