ホールデムポーカー ハンド評価 JavaScript

# 目的
ポーカー（ホールデム）でのハンドを評価する際の考慮事項の整理。

以下の二つを作る

- `handInfo`: 7枚のカードから、作られうる最大の役を計算し、結果を`Object`で返却
    - `handInfoRepr`: 上記のサマリ文字列を返却
- `handcmp`: 二つの `handInfo` を比較してどちらが強いのかを計算する

```
> handInfoRepr([0,1,2,3,4,5,6])
'STRAIGHT_FLUSH (3♥ 4♥ 5♥ 6♥ 7♥) out of A♥ 2♥ 3♥ 4♥ 5♥ 6♥ 7♥'
> handInfoRepr([0,1,2,15,28,41,42])
'FOUR_OF_A_KIND (3♥ 3♠ 3♣ 3♦) out of A♥ 2♥ 3♥ 3♠ 3♣ 3♦ 4♦'
> handInfoRepr([0,1,2,14,28,49,50])
'TWO_PAIR (3♥ 3♣ 2♥ 2♠) out of A♥ 2♥ 3♥ 2♠ 3♣ J♦ Q♦'
```

# カードのコード
内部的にはカードに 0～51 の番号を付ける。 13 で割った余りがランクで、商がスートになる。表示用の関数を実装してもよい。

```js
const CARDS = R.range(0, 52);
const suit = card => Math.floor(card / 13);
const rank = card => card % 13;
const repr = (card) => {
  const SUIT = ['♥', '♠', '♣', '♦'];
  const RANK = ['A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K'];
  return `${RANK[rank(card)]}${SUIT[suit(card)]}`;
};
```

```
> [repr(0), repr(31), repr(51)]
[ 'A♥', '6♣', 'K♦' ]
```

カードの強さ(コードではレートと命名)の比較をする関数を定義すると後々はかどる。A が一番強い。レートは小さい方が強いとしたので注意(特に cardcmp の実装をみよ)。

```js
const RATE = [0, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1];
const rate = card => RATE[rank(card)];
const cardcmp = (card1, card2) => rate(card2) - rate(card1);
```

# 役名の決定
ハンドの役を決めるには、強い順に（つまりストレートフラッシュから順に）役にマッチするかを判定していけばよい。判定結果がOKなら、その場で評価を打ち切る。

下記のサンプルでは、マッチャーのインターフェースを定めている。マッチャーは返り値として単一のオブジェクトを返し、成功した場合は `result` フィールドに `true`, 失敗した場合は `false` を入れる。

```js
const applyMatchers = matchers => hand => {
  let ret;
  for (const m of matchers) {
    ret = m(hand);
    if (ret.result) return ret;
  }
  return { hand, result: false };
};
// どこかで matchers を定義して、、以下のように rankInfo を作る。
const handInfo = applyMatchers(matchers);
```

# ハンドの強さ
ハンドの強さは、3 つの要素からなる。優先順位は上から順。

- **役の名前**: ストレート、フルハウスなど
- **役を構成するカードのランク**: （例：ツーペアの場合）2と6のペア
- **キッカー**: 役を構成しない部分の数字

すなわち、ハンドの強さをコード化するためには、役名を特定するだけではだめだということ。

私は以下のように名前を付けた

- **handCode** (役の名前) :: Number
- **matchPartCodes** (役を構成するカードのランク) :: [Number]
- **kickerCodes** (キッカー) :: [Number]

こんな感じの実装でよいだろう。

```js
const handcmp = (info1, info2) => {
  const cmp = (a, b) => a - b;
  // 1. compare with hand name.
  const result = cmp(info1.handCode, info2.handCode);
  if (!(result === 0)) return result;
  // 2. compare with matchPartCodes
  for (let i = 0; i < info1.matchPartCodes.length; i += 1) {
    const compared = cmp(info1.matchPartCodes[i], info2.matchPartCodes[i]);
    if (!(compared === 0)) return compared;
  }
  // 3. compare with kickers
  for (let i = 0; i < info1.kickers; i += 1) {
    const compared = cmp(rate(info1.kicker[i]), rate(info2.kicker[i]));
    if (!(compared === 0)) return compared;
  }
  return 0;
};
```

# 例: フラッシュのマッチャー

コードのほとんどの部分はマッチャーの実装である。具体例として、フラッシュのマッチャーを示す。

- ハンドをスートでグルーピングして
- 5 枚以上含まれるグループがあれば抽出し（なければリターン）
- そのグループの中で、強い順に5枚をとってきて、それを `matchPart` にする
- 1番強いカードのレートを `matchPartCode` に設定する。

```js
const flush = (hand) => {
  const grouped = Object.values(R.groupBy(suit, hand));
  const target = grouped.filter(arr => arr.length > 4);
  if (target.length === 0) return { result: false };
  const matchPart = target[0].sort(cardcmp).reverse().slice(0, 5);
  const matchPartCode = [rate(matchPart[0])];
  return { result: true, handCode: hr.FLUSH, matchPart, matchPartCode };
};
```

# おわりに

- ストレートのマッチャーの実装が予想以上に難しかった
- そのうち github で公開するかも。
- ハンドをハッシュ化して評価する方法があるらしいので調べてみよう
    - [HenryRLee/PokerHandEvaluator](https://github.com/HenryRLee/PokerHandEvaluator)