見出し画像

ChatGPTとPoker(Mixgame)入門~NL 2-7 Single Drawを題材に~


はじめに

概要

この記事ではChatGPTがMixgameの戦略を考える手助けにならないのかということについての記事となっています。昨今テキサスホールデムではGTOツールや計算ツールが発展しており、今更ChatGPTを利用するまでもないと感じますが、Mixgameなら利用する価値があるではないかと思い、実験してみることにしました。

無料で全部読めます!対価がいただけるならやりがい感じれて嬉しいなということで購入設定してみました。正直このnoteの価値もいまいち不明なので、読んで判断してください!

今回は入門ですし、長くなってしまうので、確率の計算に終始しています。実際に確率の計算をしたうえで、今回感じた問題点と解決策をまとめました。
データ分析をさせることもできるので、戦略を提案させることはできないのかということついては今後研究してみたいと思います。

読んでほしい人

・Mixgameの戦略を考える上で人の頭のみでは限界があると思っていた人
・ChatGPTでどんなことができるかよく分かっていないポーカー好きな人

前者の方はもちろん、ChatGPTで今どんなことができるのかよく分かっていない後者の方も、好きなポーカーを通じてChatGPTこんなことができるんだと知ることができると思います。
長くなってしまったんですけど、本当に頑張ったので読んでほしいです!

前提知識はいるか

なるべく知識がなくても内容が分かるようにしたつもりですが、知識があった方が分かりやすいと思います。ただ分からない部分があってもどんなことができるかは分かると思うので、note読んで興味が出た人は、Pythonの勉強を少しで大丈夫だと思うので、やってみてください!
僕自体は文系です。仕事ではwebアプリの開発を半年したことがあるのですが、それはpythonとはあんまり関係なく(reactやGoなど)、数か月独学したことある程度なのでそんなに身構えないで大丈夫です!

ChatGPTの機能

ChatGPTは簡単に言えばAIチャットサービスです。発表当初は大きな話題となりましたし、一度は触ったことのある方は多いのではないでしょうか。当初からプログラムを作ろうと思ったとき、どんなプログラムを作りたいか言えばコードを答えてくれました。ただ実行は自分で行う必要がありましたし、コードにエラーが起きた場合には、それをChatGPTに教えて、また修正してもらう必要がありました。

しかし今年の7月にAdvanced Data Analysis(旧Code Interpreter)の登場によりデータ分析などがチャットを打つだけで、コードを作ってくれ、プログラムの実行ができるようになりました。この機能は知らなかったという方も多いのではないかと思い、こちらの機能の紹介にもなると思っています。

ただ有料版(月20ドル)の機能になるのでそこはご了承ください。無料版だと自分でコードを実行することになります。その場合ChatGPTの出力を自分でコードを実行することになります。Pythonならクラウド上(Google Colab)で実行もできるので、環境を用意する必要はないと思います。ただ、Advanced Data Analysis(旧Code Interpreter)はコードにエラーが起きた場合、なぜ起きたかを分析して自分で修正してくれるので、楽ですし、必要な知識も減ると思います。

NL 2-7 Single Drawを題材に

NL 2-7 Single Drawとは

今回はNL 2-7 Single Drawを題材にしますが、それは何という方もいると思うので、せっかくですからこれもChatGPTに答えてもらいましょう。

NL 2-7 Single Draw(No Limit Deuce to Seven Single Draw)はドローポーカーの一種で、次のようなルールで行われます:

1. **カードのランク**: このゲームの目的は、最も低いランクの5枚の手を持つことです。最良の手は2, 3, 4, 5, 7の異なるスートです。エースは常に高いカードとしてカウントされ、ストレートやフラッシュは不利に働きます。つまり、 2, 3, 4, 5,6や任意のフラッシュは悪い手と見なされます。

2. **ブラインドとアンティ**: ゲームはアンティと2つのブラインド(スモールブラインドとビッグブラインド)を使って開始されます。

3. **ディール**: 各プレイヤーには5枚のプライベートカードが配られます。

4. **最初のベッティングラウンド**: ビッグブラインドの左側から始めて、プレイヤーはフォールド、コール、レイズを選べます。

5. **ドロー**: ベッティングラウンドの後、残っているプレイヤーは0枚から5枚までのカードを捨てて新しいカードを引くことができます。目的は、より良い(低い)手を作成することです。

6. **最終的なベッティングラウンド**: ドローの後、もう一度ベッティングが行われます。再びビッグブラインドの左から始まります。

7. **ショーダウン**: 最後のベッティングラウンドの後、残ったプレイヤーがカードを公開します。最も低い手を持つプレイヤーがポットを獲得します。

このゲームは戦略的な深さがあり、プレイヤーの読み取りやブラフが重要な要素となります。

ChatGPTの出力

読みづらいと思った方は、分かりやすいものがネットにあると思うので、調べてみてください。

ちなみにNL 2-7 Single Drawを題材にした理由はルールがシンプルかつ1回しかチェンジしないので最初の題材として適切だと思ったからというのと、初手については日本で人気のある2-7 triple drawと共通するからですね。他にもMixGameのルールも聞いてみましたが、少なくともメジャーなものは知ってそうです。

初手の確率を計算してみよう

まずはTロー以上の役がでる確率、9ロー以上の役が出る確率、8ロー以上の役が出る確率を計算させたところ、約 4.75%、約 2.04%、約 0.71%と結果を出力してきました。とりあえずこれは既に知られている確率と一致していそうです。

実際にどんなプログラムが動いているか見てみましょう。(これはTロー以上の役が出る確率を求めるコードです)チャットでお願いするだけでこんなコード書いて実行してくれるのすごくないですか!

from itertools import combinations

# Define the cards and their ranks
cards = [
    '2C', '2D', '2H', '2S',
    '3C', '3D', '3H', '3S',
    '4C', '4D', '4H', '4S',
    '5C', '5D', '5H', '5S',
    '6C', '6D', '6H', '6S',
    '7C', '7D', '7H', '7S',
    '8C', '8D', '8H', '8S',
    '9C', '9D', '9H', '9S',
    'TC', 'TD', 'TH', 'TS',
    'JC', 'JD', 'JH', 'JS',
    'QC', 'QD', 'QH', 'QS',
    'KC', 'KD', 'KH', 'KS',
    'AC', 'AD', 'AH', 'AS'
]

rank_dict = {
    'A': 14, '2': 2, '3': 3, '4': 4, '5': 5, 
    '6': 6, '7': 7, '8': 8, '9': 9, 'T': 10, 
    'J': 11, 'Q': 12, 'K': 13
}

# Check if the hand has a pair or higher
def has_pair_or_higher(hand):
    values = [card[:-1] for card in hand]
    return any(values.count(v) > 1 for v in values)

# Check if the hand is a straight using rank values
def is_straight_using_rank(hand):
    values = sorted([rank_dict[card[:-1]] for card in hand])
    return max(values) - min(values) == 4 and len(set(values)) == 5

# Check if the hand is a flush
def is_flush(hand):
    suits = [card[-1] for card in hand]
    return len(set(suits)) == 1

# Get all the 5 card combinations
all_hands = list(combinations(cards, 5))

# Filter out hands that are 10 low
ten_low_hands = [
    hand for hand in all_hands
    if (
        not (is_straight_using_rank(hand) or is_flush(hand) or has_pair_or_higher(hand)) and
        max([rank_dict[card[:-1]] for card in hand]) <= 10
    )
]

# Calculate the probability
probability_ten_low = len(ten_low_hands) / len(all_hands)
probability_ten_low

一応コードを簡単に説明すると、まずトランプ52枚と数字の強さを定義しています。ポーカーにおいてAはKよりも強いわけですからAは14と定義されています。
次にペアがあるか、ストレートがあるか、フラッシュがあるかを確認する3つの関数を作りました。
ここで一旦5枚のカードの組みあわせ(テキサスホールデムでは「コンボ」という用語が使われています)の数を求めました。
そして5枚のカードが全て10以下の組み合わせのうち、さっきの3つの関数を使って役ができていないと判断される組み合わせの数を求めます。これがつまり10ロー以上の役の組み合わせの数というわけです。
最後に10ロー以上の役の組み合わせの数から5枚のカードの組み合わせの数で割ることにより、初手に10ロー以上の役ができる確率を求めることができるわけです。

※#の後に英語でどんな処理をしているか説明してくれてるので、簡単な英語は読めるといいかもしれませんね。またこの処理が終わった後に改めて役判定をする関数の完成版をつくりました。このコードは簡易版といった感じですね。

これくらいのコードは作ることはそれほど難しくはないですが、相当スキルのある人でないとかなり時間がかかると思います。これをChatGPTにお願いするだけで作れるのはすごいことです。これなら気になったことでいちいち1からコードを作る必要がないので、自分が気になったことを調べていくこともできそうです。

実際のやり取り

イメージが湧きやすいと思うので一応どんなやりとりをしているのかについて説明します。

ポーカーのNL 2-7 Single Drawについて質問します。初手に10ロー以上の役ができる確率を計算してください。

入力

10ロー以上のハンドになるための条件は以下の通りです:
1.カードの中で最も高いものが10またはそれ以下。
2.エースは最も高いカードとして扱われるため、ハンドに含めることはできません。
3.ペアやそれ以上の組み合わせ(2ペア、3オブアカインドなど)がない。
4.ストレートやフラッシュが形成されていない。
これらの条件を元に、10ロー以上のハンドが出る確率を計算します。
[Finished working Show work v]
10ロー以上のハンドが出る確率を計算した結果、約 4.75%です。

出力

何だこれと思われた方もいると思うので、[Finished working Show work v]の部分について説明すると、vを押すと上記のコード出てきてます。Finished workingは実行が終わったよということですね。

ではChatGPTのなんとなくの使い方が分かったところで他の確率も計算してみましょう。

捨てる手札が与える影響を計算してみよう

シチュエーション1
自分の手: 2♡,3♤,4♡,7♧,Q♧ → Q♧を交換
相手の手: 2♧,5♡,6♤,T♢,K♢ → K♢を交換

シチュエーション2
自分の手: 2♡,3♤,3♧,4♡,7♧ → 3♧を交換
相手の手: 2♧,5♡,6♤,T♢,K♢ → K♢を交換

こんな状況を想定してみたいと思います。手札を1枚捨てた時点では自分も相手も手札はまったく同じですが、引ける可能性のあるカードが変わります。当然3♧が引けない方が相手は強い役を作れなくなります。また自分も3♧はペアになるカードなので、3♧を引くよりはQ♧が引けた方が役が強くなります。
よってシチュエーション2の方が勝率は高くなりそうです。

ちょっとだけ苦戦しましたが、以下のように出力してきました。

シチュエーション1の結果は以下の通りです:
勝ち: 1,025回
負け: 697回
引き分け: 0回
勝率: 約59.52%

シチュエーション2の結果は以下の通りです:
勝ち: 1,047回
負け: 675回
引き分け: 0回
勝率: 約60.80%

出力

ということでシチュエーション2の方が1.3%ほど自分の勝率が高いですね。勝ち負けの組み合わせの違いで言うと22通りです。一応これはブロッカー的な話の1つになるんでしょうか。1.3%の違いでどのような影響を与えるかを考察すれば、シチュエーション1と2でどのようにプレイするか考えることができるかもしれないですね。

ここからはこの結果が正しいかを判断するために、少し数学的なお話になります。結果だけ知りたい人は完全に無視してしまってよいです。

手札の交換はデッキの52枚からそれぞれの手札5枚ずつを除いた42枚から行う事になります。よってそれぞれの手札の組み合わせとしてありうるのは42×41になりますから、勝ち負けの組み合わせの数は1722であるべきです。とりあえずそれは満たしていますね。

シチュエーション1とシチュエーション2で異なる組み合わせとなっているのは自分と相手のそれぞれが3♧を引いているか、Q♧を引いているかという41×2の82通りとなります。(自分が3♧を引いた場合に相手の手札のパターンは41通りなので、それがQ♧に変わった場合41個組み合わせが変化します。相手の場合も同様ですね。)

実際の変化は自分が3ペアになってしまうか、Qローとなるかという違い、相手が10ローとなるか、Qローとなるかの違いであるということを考えると82通りのうち22個だけ勝敗が変わるというのはなんとなくあっている気がします。

これは手作業で検算できそうなので実際にやってみました。
交換後の自分の手札が2347Qの場合、23347の時に比べて自分が勝つ組み合わせは13通り多かったです。また交換後の相手の手札が256TQの場合、2356Tの時に比べて自分が勝つ組みあわせは9通り多かったです。なので自分の初手の手札が2♡,3♤,3♧,4♡,7♧のときは2♡,3♤,4♡,7♧,Q♧の時より自分が勝つ組みあわせは22通りおおいということになります。(これは地道に試すだけなので数学的な話ではありません。)
よってこの結果は正しいと証明出来ました。

初手の確率を計算してみよう(発展編)

では次にプレーヤーが6人いるときに初手で10ロー以上の役を誰かしら持っている確率について計算してみましょう。
この場合6人の手札はの組み合わせはめちゃめちゃ多いです。52枚から30枚選ぶ組み合わせとそれをさらに5枚ずつ6人に配る組みあわせを掛け合わせると考えれば多いことは想像できるでしょう。
ChatGPTは組み合わせが多すぎて時間がかかりすぎてしまうと言ってきました。よってここではシミュレーションによって推定するという手法になりました。

10,000回のシミュレーションの結果、6人のプレイヤーがいる場合、誰かが10ロー以上の役を持っている確率は約26.69%となりました。

この結果は、6人のプレイヤー全員がランダムにカードを受け取る場合の確率を示しています。したがって、6人のプレイヤーの中で誰かが10ロー以上の役を持っている可能性は、約4回に1回程度となります。

出力1

10万回のシミュレーションの結果、6人のプレイヤーがいる場合、誰かが10ロー以上の役を持っている確率は約26.826%となりました。

シミュレーション回数を増やすことで、結果の信頼性が向上しました。この確率は、前回の結果と非常に近い値となっています。

出力2

ということで確率はは27%弱になるという推定ができます。これは例えば自分がUTGでJローを持っている時にどうするという判断の基礎になるでしょうか。(もちろん自分がJローを持っていると仮定したときに5人が10ロー以上の役を持っているかどうかをシミュレーションしないと正確な数値ではないわけですが、それもChatGPTはやってくれると思います。)

ここは自分も混乱しているところなので、確率の計算に自信のある方に教えてほしいです。つまり読み飛ばしてよいです。
1人が10ロー以上の役を引く確率は約4.75%でした。よってある1人がJロー以下の役である確率は約95.25%ということになります。よって誰も10ロー以上の役を持っていない確率は0.9525^6が約0.7468であることから約74.68%ということになる。つまり誰かが10ロー以上の役を持っているのは25.32%であるという計算ってこういう場合にも使える計算ですかね?
例えばだれも当たりくじを引かない確率はこうやって計算できると思いますが、トランプ52枚と役判定のロジックは複雑に組み合わさっているため、使えないんでしょうか?
使えなさそうな気もしますが、よく分からなくなったので誰か教えてください…。

感想

役判定の関数が固まってからは結構簡単に計算できました。役判定の関数は今回は普通のポーカーと逆だよって教えてから、できたものを修正していけばよかったのですが、そうじゃないMixgameは役判定の関数作るのちょっと大変かもしれません。最初作ればいいだけなのでそんなに問題じゃないですけどね。
色々な確率の計算は少なくともできそうですね!長くなってしまったので、さらにデータを分析させるとかはまた次回にします。
とりあえず使ってみて分かったことがたくさんあるので、次の章でまとめます。

使う上での問題点と解決策

ミスを疑い結果を鵜呑みにしない

実際のやりとりのところで後述すると言ったところについて触れます。実はChatGPTが意外と間違えるので、そのミスを修正しながら、コードを完成させました。例えばAを10以下に含めてしまっていたので、AはKより強いですよということを指摘しました。

ルールの説明を見る限り、知ってはいるはずなんですが、意外と人間みたいな凡ミスをします。なので最初に出た結果を鵜呑みにすることはできません。

ただし修正といってもコードを1から自分で作り上げるよりは大きな手間ではなく、圧倒的に楽であるということは強調しておきます。

入力文を工夫してミスを抑制する

これはChatGPTを使うときの一般的な注意点なのですが、ChatGPTのミスを減らしたり、出力の精度を上げたりするためには入力文を工夫する必要があります。例えばAはKより強いよということをあらかじめ入力文で教えてあげれば考慮してくれる可能性が高いです。ここで可能性が高いと言ったのはたまに入力文の一部を考慮しない場合があるときです。そのときは改めて指定する必要があります。

同じMixgameについて調べていればChatGPTがよく犯すミスが見えてくると思うので、そこについて先回りして予め入力文に打ち込んでおけば修正の時間はさらに短縮できるということですね。
ちなみにもちろん同じチャット内では今までのやり取りを踏まえてくれるので、新しくチャットを始める場合ということになります。

ChatGPTを使いこなす補助をChatGPTにさせる

ミスをしていないかを判断するためには結果が大体あってそうかの感覚、確率の知識、処理の流れを確認する論理の力、Pythonの知識が全くない状態では厳しいかもしれません。ただ例えばコードで分からないところがあれば、どんな処理をしているのかまたChatGPTに聞くことができるので、深い知識が必要だとは思いません。ChatGPT自体が補助もしてくれるのでそんなに身構える必要はないと思います。

以下がChatGPTに聞いてみた結果です。

<ここにコードを張り付けた上で>
上はNL 2-7 single drawというポーカーの初手で10ロー以上の役ができる確率を求めるコードです。どんな処理をしているか教えてください。

入力

もちろん、コードを詳細に解説いたします。
1. カードと役の定義
まず、52枚のカードをリスト形式で定義しています。各カードは、値とスートから成り立っており、例として '2C' はクラブの2を意味します。
次に、各カードの数値を整数で表すための辞書 rank_dict を定義しています。ここでは、'A'(エース)は14、'T'(10)は10としています。

2. 役の確認関数
次に、いくつかの役を確認するための関数を定義しています。has_pair_or_higher(手札)
手札にペア以上の役(ペア、スリーカード、フォーカード)があるかを確認します。
is_straight_using_rank(手札)
手札がストレートかどうかを確認します。この関数は、手札のカードの数値を rank_dict を使用して取得し、それらをソートしてストレートであるかどうかを確認します。
is_flush(手札)
手札がフラッシュ(同じスートの5枚のカード)であるかどうかを確認します。

3. 手札の組み合わせ
combinations 関数を使用して、52枚のカードから5枚のカードのすべての組み合わせをリストとして取得しています。これにより、すべての可能な手札の組み合わせが得られます。

4. 10ローの手札のフィルタリング
次に、条件に合った手札(10ロー)のみをフィルタリングしています。条件は以下の通りです:ストレート、フラッシュ、ペア以上の役がない。
手札の中で最も高いカードの数値が10以下である。

5. 確率の計算
最後に、10ローの手札の数をすべての手札の組み合わせの数で割ることで、10ローの役ができる確率を計算しています。

このコードを実行すると、NL 2-7 single drawというポーカーの初手で10ロー以上の役ができる確率が得られます。

出力

もちろんさらに聞きたいことがあればより詳細に答えてくれます。今回はストレートを判定する関数について聞いてみましょう。

def is_straight_using_rank(hand):
    values = sorted([rank_dict[card[:-1]] for card in hand])
    return max(values) - min(values) == 4 and len(set(values)) == 5
これはどんな処理をしているのでしょうか。

入力

`is_straight_using_rank` 関数は、与えられた手札がストレート(連続する5枚のカード)であるかどうかを判断する関数です。この関数の内部処理を詳しく解説します。

1. **カードの数値を取得**
```[rank_dict[card[:-1]] for card in hand]```
このリスト内包表記を使って、与えられた手札の各カードの数値(例:'A' は 14、'2' は 2)を取得します。`card[:-1]` は、カードの最後の文字(スートを示す文字)を除外して数値部分のみを取得するためのものです。

2. **ソート**
```values = sorted(...)```
手札のカードの数値をソートします。これにより、数値が小さい順に並び替えられます。

3. **ストレートの条件の確認**
```max(values) - min(values) == 4```
ストレートの条件の1つとして、最大値と最小値の差が4であるかどうかを確認します。例えば、[2, 3, 4, 5, 6] の場合、6(最大値) - 2(最小値) = 4 となり、この条件を満たします。

4. **重複の確認**
```len(set(values)) == 5```
ストレートのもう1つの条件として、5枚のカードが全て異なる数値である必要があります。`set(values)` は、`values` リストのユニークな要素のみを取得するためのものです。その後、`len(...)` でその要素数を取得し、5と等しいかどうかを確認します。5枚のカードが全て異なる数値であれば、この条件を満たします。

5. **戻り値**
上記の2つの条件を両方とも満たす場合、この関数は `True` を返します。それ以外の場合は `False` を返します。

この関数を使用すると、与えられた手札がストレートであるかどうかを簡単に確認することができます。

出力

どうですか。こんなに詳しく説明してくれるならそんなに知識がなくても使える気がしませんか?
実はこの方法には分からないところを解消するというメリットだけでなく、ChatGPTにChatGPTの検算や見直しをさせるというメリットもあります。またミスの修正にも使えるわけですね。正しいと思いますか?ときくと、自ら具体例をあてはめて、自ら作り上げた関数の検算をするなんてこともできます。

前の結果を引き継ぐためにコードを保存する

手札を交換した場合の勝率を作る際、役判定をする関数を作るのに苦労しました。前の結果は今の時点では引き継げないですが、いちいち1から作るのは大変ですから、保存したうえで、次回からはそれを読み込ませたうえで使いましょう。実はファイルのアップロードも可能なので、コードやデータが蓄積されるたび何かのファイルに保存しておくと、スムーズだと思います。

必要な処理を1つずつ作りミスの発生源を分かりやすくする

例えば、ポーカーにおいて役判定というのは大事な処理で、これができなければ勝率を計算することも当然できません。よって少なくとも勝率を計算するために、役判定の関数を作ったうえで、勝率を計算する関数を作ることになります。しかし前述の通り、ミスが発生する可能性が高いのに、複数の処理があるとどこでミスが発生しているのか分かりづらいです。これで今回最初の方は苦戦しました。なので目的のために必要な処理を細かく区切ったうえで、1つずつ作っていく方が、結果的には時間も短縮できるでしょう。

おわりに

今回はNL 2-7 Single Drawを題材に確率の計算を行い、問題点と解決策についてまとめました。自分でもやってみたい方はぜひ!

この記事の正確性についてですが、そんなに大きな間違いはしていないと思っていますが、1つ1つ確実だと言い切ることは正直できないので、間違っているという所があると思ったらぜひ教えてほしいです。

今回は入門編ですし、意外と長くなってしまったので、深い内容とはなっていませんが、これを応用すれば他のMixgameでも戦略をより深めていくことができるかもしれませんし、今後はデータに基づいて戦略を提案させるみたいなこともやってみたいと思っています。
使う人が使えば戦略を考える上でラストピースとなってもおかしくないと思います。それくらいの可能性は秘めてると思いますね。今後もChatGPTは進化すると思いますし。


そもそも僕がMixgameは大方ルールを知って遊びでプレイしたことがある程度で、そもそも大して知らないので、適切な深め方が分からなかった部分もあるので、好きなMixgameが出来たり、他のプレーヤーの方と話したりした場合に考察を深めてみたいと思います。Mixgame好きの方はお願いします!こんな確率が計算できたらいいなとかあるんですかね?既に確率の計算は大体できているでしょうか。それなら戦略を提案させるとこまでやらないとダメですよねー。

NL 2-7 single drawはチェンジ枚数が1回なこともあって結構深いところまで研究しやすいと思っているので、ひとまずはNL 2-7 single drawを研究するのもいいなと思っています。初手だけは2-7 triple drawにも応用できますしねー。

以上です。この記事がどれくらい価値のあるものなのか正直あんまり分からないですけど、面白いと思ってくれた方、スキや購入してくれると、やりがいを感じるしうれしいです!

ここから先は

0字

¥ 500

期間限定 PayPay支払いすると抽選でお得に!

この記事が気に入ったらサポートをしてみませんか?