ホールデムポーカ simulation & visualization ～ハンドの勝利確率とプレイ人数に非依存な特徴的なハンドの存在示唆～

# 1. 基本 ～役の成立確率～

自分の手札 2枚とコミュニティカード 5枚の合わせて 7枚が与えられた際に、役ができる確率は厳密に計算できる。[Wikipedia - ポーカー・ハンドの一覧](https://ja.wikipedia.org/wiki/%E3%83%9D%E3%83%BC%E3%82%AB%E3%83%BC%E3%83%BB%E3%83%8F%E3%83%B3%E3%83%89%E3%81%AE%E4%B8%80%E8%A6%A7#7%E6%9E%9A%E3%81%AE%E3%81%A8%E3%81%8D%E3%81%AE%E7%A2%BA%E7%8E%87) の値を少し加工すると以下の表を得る。

```
役 組み合わせ 確率     累積確率  1-累積確率 役の名称
HC 23294460  17.4%    17.4%    82.6%     ハイカード/ブタ
1P 58627800  43.8%    61.2%    38.8%     ワンペア
2P 31433400  23.5%    84.7%    15.2%     ツーペア
3K  6461620   4.8%    89.5%    10.5%     スリーカード
ST  6180020   4.6%    94.1%     5.9%     ストレート
F   4047644   3.0%    97.1%     2.9%     フラッシュ
FH  3473184   2.6%    99.7%     0.3%     フルハウス
4K   224848   0.17%   99.9%     0.2%     フォーカード
SF    41584   0.031%  99.9%     0.0%     ストレートフラッシュ

合計	133784560	100%
```

# 2. 課題

上記で出てきた 合計組み合わせ数 `133784560` は `C(52, 7)` であり、python などのインタプリタで簡単に計算できる。一応、32bit にぎりぎり収まる程度の大きさである。

```py
>>> 52*51*50*49*48*47*46/7/6/5/4/3/2/1
133784560.0
>>> 1 << 32
4294967296
```


それでは、二人の場合はどうだろうか？この場合の組み合わせ数は `C(52, 9)` であり、まだ計算の余地はありそうだ。36億。1000万/sec の評価関数を使っても6分くらいかかる。さて、通常ホールデムは 9人でプレイする。その場合は、 `C(52, 23)` である。もう計算はできない。352兆。

```py
>>> 52*51*50*49*48*47*46*45*44/9/8/7/6/5/4/3/2/1
3679075400.0
>>> 1 << 32
4294967296

>>> a = [ 52-i for i in range(23) ]
>>> b = range(1,24)
>>> x = 1
>>> for i in a: x = x * i
>>> for i in b: x = x / i
>>> x
352870329957600.06
```

したがって、シミュレーションをする必要がある。

# 3.シミュレーション概要

- `n = 2,5,9` 人のプレーヤーでゲームを 100万回実施する
    - 全員がショーダウンすると仮定
- 各回で勝利したハンドを記録し、頻度グラフ（累積密度関数）を生成する
- 横軸はハンドの強さ、縦軸は頻度(100% でノーマライズ)
    - ハンドの強さは "Cactus Kev's Poker Hand Evaluator" のアルゴリズムで生成した生成値(1～7462)
- バランスハンドを計算する
    - バランスハンドは、「勝利確率」と「勝利ハンドの累積関数」が一致したハンドとする
    - 例: 2-player では、0.5 の確率で勝利可能と考えられるので、累積関数が 50% となるハンドを調べる



# 4.シミュレーション結果

![ポーカーハンド.png](https://qiita-image-store.s3.amazonaws.com/0/127424/79131061-475a-3195-615d-c95fda5dd932.png)

# 5.考察

- single player の結果は冒頭のWikipedia の確率をよく再現できた

プログラムが正常に動作したと考える。また、Wikipediaの粒度よりも細かい粒度で累積関数を再現できた。


- プレイヤーが増えるとグラフが右にシフトする

この挙動は合理的である。同じハンドでも、プレイヤー数が多くなれば勝利確率は減る。これは x 軸（ハンド）を固定したときに、プレイヤー数が多くなると、累積関数値が下振れするという形で現れる。別の言い方をすると、グラフが右にシフトするともいえる。


- 弱めのツーペア以上の役をつくれそうなときにエントリするべき

これは non-trivial な結果だ。駆け引きをしない場合、ポーカーの勝利確率は `1/n` である。弱いハンドでは `1/n` の確率より負けやすく、強いハンドでは `1/n` の確率より勝ちやすいだろう。それでは、その分岐点となるハンドの強さはどれくらいだろうか？と考えるのは合理的である。今回のシミュレーションでは、どんなハンドが勝利したか？を記録し、`1/n` の確率以上で勝つハンドを異なる `n = 2,5,9` で計測した。その結果、若干のブレはあるにせよ、**n によらず弱めのツーペアを作れるハンドがオッズにあう**ことが示唆された。

「ホールデムとは、ツーペア以上の役を作れそうなときにエントリするゲームである」という命題は、より深く検証する価値があるように思う。

# 6.参考文献

ハンドの強さを整数値に変換するというアルゴリズムが本質であった。これに関しては、[私の前の記事](https://qiita.com/41semicolon/items/13d4b61703f4dd876ce4)にサマリを用意した。英語が読める人は、[発見者のページ](http://suffe.cool/poker/evaluator.html)にいくのがよい。

ただし、上記のアルゴリズムはホールデムでは使用できない。整数値に変換するアルゴリズムのみが利用できる。7枚のカードで、上述の整数値にマッピングするアルゴリズムは、[HenryRLee/PokerHandEvaluator](https://github.com/HenryRLee/PokerHandEvaluator) のアルゴリズムである。これを私が JavaScript にポートして利用した。

グラフの可視化には、D3.js を利用した。今回のようにグリッドがかなり複雑なグラフを作成する場合、既存のグラフライブラリでヴィジュアライズするのは無理。JavaScriptのプログラミングパラダイムでSVGを直にいじれる D3.js が最適であった。

バランスハンドという概念は私のオリジナルであるので、何か間違いがあるかもしれない。というかそもそも、勝利確率と、勝利ハンドの累積関数は別の概念であるので、これが一致するだろうというのはかなり大胆な仮定であるように思う。にもかかわらず、比較的よい一致したのは興味深い結果であるように思う。