バドゥーギにおけるコンボ数の分布、及びそのブロッカーによる影響


## 概要
- プリドローにおけるBadugi, Triの確率を計算した
- Badugiが成立している場合の各役のコンボ数を計算し、分布を確かめた
- blockerによってコンボ数にどのような影響を及ぼすか確かめた

## 環境
ruby 2.6.3p62
Google Spreadsheet
Badugiのルール説明に関しては割愛します。

## プリドローにおけるBadugiの成立確率

まず、プリドローの段階でBadugiが成立している確率を求めます。
同時に、主なバリューハンドである8-badugiの確率、及びtriハンドが配られる確率を求めます。
この確率の感覚を掴み、ゲームにおける敵のアクションの頻度と照らし合わせることが目的です。

```ruby
ranks = ['K', 'Q', 'J', 'T', '9', '8', '7', '6', '5', '4', '3', '2', 'A']
suits = ['s', 'h', 'd', 'c']
# トランプ1セットを下準備
decks = ranks.flat_map{ |rank| suits.map { |suit| {rank: rank, suit: suit}}}

# デッキから任意の4枚を引くコンビネーション (オマハ同様270725通り)
total_holes = decks.combination(4).to_a
# badugiハンド
badugi_holes = []
# 8以下のbadugiハンド(バリュー)
under_8_badugi_holes = []
# triハンド(日本風に言うと○○ダイ)
tri_holes = []

total_holes.each do |hole|
	# ハンド内の数字のパターン数
	rank_length = hole.map { |h| h[:rank]}.uniq.length
	# ハンド内の絵柄のパターン数
	suit_length = hole.map { |h| h[:suit]}.uniq.length
	
	if rank_length == 4 && suit_length == 4 # badugi
		badugi_holes.push(hole)
		# 8以下のbadugiのみ抽出
		under_8_badugi_holes.push(hole) if hole.map { |h| h[:rank] }.all?{ |h| ['8', '7', '6', '5', '4', '3', '2', 'A'].include?(h) }
	elsif rank_length == 3 || suit_length == 3 # tri hand
		tri_holes.push(hole)
	end
end

p "badugiのハンドが配られる確率"
p badugi_holes.length.to_f / total_holes.length.to_f * 100
p "8以下のbadugiハンドが配られる確率"
p under_8_badugi_holes.length.to_f / total_holes.length.to_f * 100
p "triハンドが配られる確率"
p tri_holes.length.to_f / total_holes.length.to_f * 100
```
**実行結果**

```
"badugiのハンドが配られる確率"
6.3385354141656665
"8以下のbadugiハンドが配られる確率"
0.6205559146735617
"triハンドが配られる確率"
69.83913565426171
```

Badugiが成立する確率は、 **6.34％** であることがわかります。
また、このゲームで主なバリュー役として用いられる8-hi badugiよりも強い役が配られる確率が **0.6％** であるという事実も覚えておくべき値だと感じました。


## Badugiハンドのコンボ数の分布

次に、同じBadugiハンドでもK-badugiと4-badugiではどのくらい価値が異なるのかを、各コンボ数を計算することで確かめます。

```ruby
p "badugiのコンボ数を、ランクごとに算出"
badugi_holes.group_by {|h| h[0][:rank]}
	.map {|k, v| [k, v.size] }
 	.each { |k, v| puts [k, v].join("\t") }	# spreadsheetに貼り付けやすいようにtab区切りでoutput
```

**実行結果**

```
"badugiのコンボ数を、ランクごとに算出"
K	5280
Q	3960
J	2880
T	2016
9	1344
8	840
7	480
6	240
5	96
4	24
```

![スクリーンショット 2022-02-25 23.03.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21406/d62d69c7-e1fb-d114-003f-c01169e7d2d3.png)

Badugiのコンボ数は、二次関数的に下がっていくことがわかります。
1つランクが落ちるだけで、役としての価値が大きく変わることがわかります。

## 8-badugi以下の分布

続いて、バリューハンドとして主に用いられる8-badugi以下の役の分布を、セカンドキッカーごとに調べます。

```ruby
p "8以下のbadugiハンドのコンボ数を、セカンドキッカーごとに算出"
under_8_badugi_holes.group_by {|h| "#{h[0][:rank]}-#{h[1][:rank]}"}
	.map {|k, v| [k, v.size] }
 	.each { |k, v| puts [k, v].join("\t") }	# spreadsheetに貼り付けやすいようにtab区切りで
```

**実行結果**

```
"8以下のbadugiハンドのコンボ数を、セカンドキッカーごとに算出"
8-7	360
8-6	240
8-5	144
8-4	72
8-3	24
7-6	240
7-5	144
7-4	72
7-3	24
6-5	144
6-4	72
6-3	24
5-4	72
5-3	24
4-3	24
```
![スクリーンショット 2022-02-27 18.52.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21406/781a59c1-7836-82e6-68e0-050ca08a399b.png)

ランクが1つ下がるたびにコンボ数が大きく下がることは前述しましたが、8-7-badugiのコンボ数が6-badugiのコンボ数を上回るなど、セカンドキッカーによっても役の価値が大きく変わるということがわかります。


## Blockerによる影響

ポーカーというゲームは数々のルールがありますが、Bluff/Bluff-Catchの意思決定で重要になるのがブロッカーです。
最後に、ブロッカーの種類によってこのゲームではコンボ数がどのように変化していくのかを確かめます。


### ①ブロッカーの価値はカードごとに変わるか

A♠がない場合と、2♠がない場合で比較し、コンボ数に変化が現れるのかを確かめます。
今回はA♠ ~ 5♠までを確かめました。

```ruby
p "blockerを効かせたときのコンボ数の変化"
blockers = [['As'], ['2s'], ['3s'], ['4s'], ['5s']]
blockers.map { |blocker|
	under_8_badugi_holes
	.select { |h| h.map{ |v| v[:rank] + v[:suit] }.all?{ |v| !blocker.include?(v) } }
	.group_by { |h| "#{h[0][:rank]}-#{h[1][:rank]}" }
}.each_with_index { |blocked, index|
	p "blocked:#{blockers[index].join("")}"
	output_combo(blocked)
}

```

<details><summary>実行結果</summary><div>

```
"blocked:As"
8-7	330
8-6	216
8-5	126
8-4	60
8-3	18
7-6	216
7-5	126
7-4	60
7-3	18
6-5	126
6-4	60
6-3	18
5-4	60
5-3	18
4-3	18
"blocked:2s"
8-7	330
8-6	216
8-5	126
8-4	60
8-3	18
7-6	216
7-5	126
7-4	60
7-3	18
6-5	126
6-4	60
6-3	18
5-4	60
5-3	18
4-3	18
"blocked:3s"
8-7	330
8-6	216
8-5	126
8-4	60
8-3	18
7-6	216
7-5	126
7-4	60
7-3	18
6-5	126
6-4	60
6-3	18
5-4	60
5-3	18
4-3	18
"blocked:4s"
8-7	330
8-6	216
8-5	126
8-4	54
8-3	24
7-6	216
7-5	126
7-4	54
7-3	24
6-5	126
6-4	54
6-3	24
5-4	54
5-3	24
4-3	18
"blocked:5s"
8-7	330
8-6	216
8-5	108
8-4	72
8-3	24
7-6	216
7-5	108
7-4	72
7-3	24
6-5	108
6-4	72
6-3	24
5-4	54
5-3	18
4-3	24

```
</div></details>

![スクリーンショット 2022-03-02 3.19.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21406/38929dda-dbd1-66c6-9e38-d62f52739ea6.png)

結果から読み解けることとして、

**X-badugiにおいて、5♠よりもA♠のほうが、セカンドキッカーで区切ったときにより強い役のコンボ数を下げる効果はあるが、X-badugi全体のコンボ数に変化はない**
ということがわかります。
よく考えればこれは当たり前でした。
(例えば5-badugiにおいて、3♠がないことによって5-3badugiのコンボ数が減ることと、4♠がないことによって5-4badugiのコンボ数が減ることは互いに対称だからです。)

### ② 「同じ色がいっぱいブロックされる」と「下の方のカードがいっぱいブロックされる」ではどちらがブロッカーとしての価値が高いか

1. A♠2♥3◆4♣5♠6♥.....というように、高い役を完成させるために必要な下の方のカードが、色が満遍なくブロックされる
2. A♠2♠3♠4♠5♠6♠.....というように、特定の同じ色がブロックされる場合
3. A♠A♥A◆A♣2♠2♥.....というように、高い役を完成させるために必須となる下の方のカードが多くブロックされる場合

「高い役を完成させるために必須となる下の方のカード」と「特定の色」のどちらが多くブロックされたほうがブロッカーとしての効果が高いのかを比較しました。

```ruby
p "blockerを効かせたときのコンボ数の変化"
blockers = [
			# blockするカードを下のほうから満遍なく拾っていく
			['As'],
			['As', '2h'],
			['As', '2h', '3d'],
			['As', '2h', '3d', '4c'],
			['As', '2h', '3d', '4c', '5s'],
			['As', '2h', '3d', '4c', '5s', '6c'],
			# blockするカードを特定の色ごとに増やしていく
			['As'],
			['As', '2s'],
			['As', '2s', '3s'],
			['As', '2s', '3s', '4s'],
			['As', '2s', '3s', '4s', '5s'],
			['As', '2s', '3s', '4s', '5s', '6s'],
			# blockするカードを下の方の重要なランクごとに増やしていく
			['As'],
			['As', 'Ah'],
			['As', 'Ah', 'Ad'],
			['As', 'Ah', 'Ad', 'Ac'],
			['As', 'Ah', 'Ad', 'Ac', '2s'],
			['As', 'Ah', 'Ad', 'Ac', '2s', '2h'],
			]

blockers.map { |blocker|
	under_8_badugi_holes
	.select { |h| h.map{ |v| v[:rank] + v[:suit] }.all?{ |v| !blocker.include?(v) } }
	.group_by { |h| "#{h[0][:rank]}-#{h[1][:rank]}" }
}.each_with_index { |blocked, index|
	p "blocked:#{blockers[index].join("")}"
	output_combo(blocked)
}
```

<details><summary>実行結果</summary><div>

```
"blocked:As"
8-7	330
8-6	216
8-5	126
8-4	60
8-3	18
7-6	216
7-5	126
7-4	60
7-3	18
6-5	126
6-4	60
6-3	18
5-4	60
5-3	18
4-3	18
"blocked:As2h"
8-7	302
8-6	194
8-5	110
8-4	50
8-3	14
7-6	194
7-5	110
7-4	50
7-3	14
6-5	110
6-4	50
6-3	14
5-4	50
5-3	14
4-3	14
"blocked:As2h3d"
8-7	276
8-6	174
8-5	96
8-4	42
8-3	11
7-6	174
7-5	96
7-4	42
7-3	11
6-5	96
6-4	42
6-3	11
5-4	42
5-3	11
4-3	11
"blocked:As2h3d4c"
8-7	252
8-6	156
8-5	84
8-4	33
8-3	11
7-6	156
7-5	84
7-4	33
7-3	11
6-5	84
6-4	33
6-3	11
5-4	33
5-3	11
4-3	9
"blocked:As2h3d4c5s"
8-7	228
8-6	138
8-5	63
8-4	33
8-3	11
7-6	138
7-5	63
7-4	33
7-3	11
6-5	63
6-4	33
6-3	11
5-4	25
5-3	8
4-3	9
"blocked:As2h3d4c5s6c"
8-7	206
8-6	105
8-5	63
8-4	33
8-3	11
7-6	105
7-5	63
7-4	33
7-3	11
6-5	49
6-4	24
6-3	9
5-4	25
5-3	8
4-3	9
"blocked:As"
8-7	330
8-6	216
8-5	126
8-4	60
8-3	18
7-6	216
7-5	126
7-4	60
7-3	18
6-5	126
6-4	60
6-3	18
5-4	60
5-3	18
4-3	18
"blocked:As2s"
8-7	300
8-6	192
8-5	108
8-4	48
8-3	12
7-6	192
7-5	108
7-4	48
7-3	12
6-5	108
6-4	48
6-3	12
5-4	48
5-3	12
4-3	12
"blocked:As2s3s"
8-7	270
8-6	168
8-5	90
8-4	36
8-3	6
7-6	168
7-5	90
7-4	36
7-3	6
6-5	90
6-4	36
6-3	6
5-4	36
5-3	6
4-3	6
"blocked:As2s3s4s"
8-7	240
8-6	144
8-5	72
8-4	18
8-3	6
7-6	144
7-5	72
7-4	18
7-3	6
6-5	72
6-4	18
6-3	6
5-4	18
5-3	6
"blocked:As2s3s4s5s"
8-7	210
8-6	120
8-5	36
8-4	18
8-3	6
7-6	120
7-5	36
7-4	18
7-3	6
6-5	36
6-4	18
6-3	6
"blocked:As2s3s4s5s6s"
8-7	180
8-6	60
8-5	36
8-4	18
8-3	6
7-6	60
7-5	36
7-4	18
7-3	6
"blocked:As"
8-7	330
8-6	216
8-5	126
8-4	60
8-3	18
7-6	216
7-5	126
7-4	60
7-3	18
6-5	126
6-4	60
6-3	18
5-4	60
5-3	18
4-3	18
"blocked:AsAh"
8-7	300
8-6	192
8-5	108
8-4	48
8-3	12
7-6	192
7-5	108
7-4	48
7-3	12
6-5	108
6-4	48
6-3	12
5-4	48
5-3	12
4-3	12
"blocked:AsAhAd"
8-7	270
8-6	168
8-5	90
8-4	36
8-3	6
7-6	168
7-5	90
7-4	36
7-3	6
6-5	90
6-4	36
6-3	6
5-4	36
5-3	6
4-3	6
"blocked:AsAhAdAc"
8-7	240
8-6	144
8-5	72
8-4	24
7-6	144
7-5	72
7-4	24
6-5	72
6-4	24
5-4	24
"blocked:AsAhAdAc2s"
8-7	216
8-6	126
8-5	60
8-4	18
7-6	126
7-5	60
7-4	18
6-5	60
6-4	18
5-4	18
"blocked:AsAhAdAc2s2h"
8-7	192
8-6	108
8-5	48
8-4	12
7-6	108
7-5	48
7-4	12
6-5	48
6-4	12
5-4	12

```
</div></details>

この結果を、8-badugiのコンボ数で比較しました。
結果は以下です。

![スクリーンショット 2022-03-02 3.41.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21406/65cd2ac2-8847-cec1-3019-c6862e2b7f6a.png)

![スクリーンショット 2022-03-02 3.40.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21406/9501664e-8e11-7ff4-2b76-751779a21ae3.png)


この結果から読み解けるところとして

- A♠2♥3◆... のように、「下の方のカードが、様々な色でブロックされる」ことよりも、「下の方のカードから順にブロックされていく」「特定の色がブロックされる」ことのほうがコンボ数に及ぼす影響が大きい
- 4枚目までは、「下の方のカードから順にブロックされていく」と「特定の色がブロックされる」のコンボ数への影響は同じ
- 5枚目以降、「特定の色がブロックされる」ことのほうが「下の方のカードから順にブロックされていく」ことよりも影響が大きくなる


## おわりに

<details><summary>ソースコードまとめ</summary><div>

```ruby
ranks = ['K', 'Q', 'J', 'T', '9', '8', '7', '6', '5', '4', '3', '2', 'A']
suits = ['s', 'h', 'd', 'c']
# トランプ1セットを下準備
decks = ranks.flat_map{ |rank| suits.map { |suit| {rank: rank, suit: suit}}}

# デッキから任意の4枚を引くコンビネーション (オマハ同様270725通り)
total_holes = decks.combination(4).to_a
# badugiハンド
badugi_holes = []
# 8以下のbadugiハンド(バリュー)
under_8_badugi_holes = []
# triハンド(日本風に言うと○○ダイ)
tri_holes = []

# 上記holesをgroup_byしたときのコンボ数を吐き出す
def output_combo(grouped)
 	grouped.map {|k, v| [k, v.size] }
 		.each { |k, v| puts [k, v].join("\t") }	# spreadsheetに貼り付けやすいようにtab区切りでoutput
end

total_holes.each do |hole|
	# ハンド内の数字のパターン数
	rank_length = hole.map { |h| h[:rank]}.uniq.length
	# ハンド内の絵柄のパターン数
	suit_length = hole.map { |h| h[:suit]}.uniq.length
	
	if rank_length == 4 && suit_length == 4 # badugi
		badugi_holes.push(hole)

		# 8以下のbadugiのみ抽出
		under_8_badugi_holes.push(hole) if hole.map { |h| h[:rank] }.all?{ |h| ['8', '7', '6', '5', '4', '3', '2', 'A'].include?(h) }
	elsif rank_length == 3 || suit_length == 3 # tri hand
		tri_holes.push(hole)
	end
end

p "badugiのハンドが配られる確率"
p badugi_holes.length.to_f / total_holes.length.to_f * 100

p "8以下のbadugiハンドが配られる確率"
p under_8_badugi_holes.length.to_f / total_holes.length.to_f * 100

p "triハンドが配られる確率"
p tri_holes.length.to_f / total_holes.length.to_f * 100

p "badugiのコンボ数を、ランクごとに算出"
output_combo(badugi_holes.group_by {|h| h[0][:rank]})

p "8以下のbadugiハンドのコンボ数を、セカンドキッカーごとに算出"
output_combo(under_8_badugi_holes.group_by {|h| "#{h[0][:rank]}-#{h[1][:rank]}"})

p "blockerを効かせたときのコンボ数の変化"
blockers = [
			# blockするカードのランクをあげていく
			['As'],
			['2s'],
			['3s'],
			['4s'],
			['5s'],
			# blockするカードを下のほうから満遍なく拾っていく
			['As'],
			['As', '2h'],
			['As', '2h', '3d'],
			['As', '2h', '3d', '4c'],
			['As', '2h', '3d', '4c', '5s'],
			['As', '2h', '3d', '4c', '5s', '6c'],
			# blockするカードを特定の色ごとに増やしていく
			['As'],
			['As', '2s'],
			['As', '2s', '3s'],
			['As', '2s', '3s', '4s'],
			['As', '2s', '3s', '4s', '5s'],
			['As', '2s', '3s', '4s', '5s', '6s'],
			# blockするカードを重要なランクごとに増やしていく
			['As'],
			['As', 'Ah'],
			['As', 'Ah', 'Ad'],
			['As', 'Ah', 'Ad', 'Ac'],
			['As', 'Ah', 'Ad', 'Ac', '2s'],
			['As', 'Ah', 'Ad', 'Ac', '2s', '2h'],
			]

blockers.map { |blocker|
	under_8_badugi_holes
	.select { |h| h.map{ |v| v[:rank] + v[:suit] }.all?{ |v| !blocker.include?(v) } }
	.group_by { |h| "#{h[0][:rank]}-#{h[1][:rank]}" }
}.each_with_index { |blocked, index|
	p "blocked:#{blockers[index].join("")}"
	output_combo(blocked)
}
```
</div></details>