Egisonポーカー再考
Egisonコードの例としてよく紹介される(?)例としてポーカーの役判定があります。
http://www.egison.org/demonstrations/poker-hands.html
- コレクションをMultisetとして扱うことにより手札の順序を考慮しなくてよい
- non-linear patternを扱える(value patternを使える)おかげでペアやスリーカード系の判定が直接的に書ける
- カードの数部分をintegerではなくmod 13でマッチすることで、ストレートにおける10-11-12-13-Aのようなケースをうまく扱える(後述するように、実際はこれだけだと11-12-13-A-2とかにマッチしてしまってマズいですが)
など、いろいろな面倒をマッチャーに任せて楽ができるという主旨のものです。
これを題材にいろいろやってみました。
Jokerを加える
Jokerを所謂ワイルドカードとして加えるルールがあります。これをカードに加えたポーカーを考えたい、つまり
(poker-hands {<Card <Club> 12>
<Card <Club> 10>
<Joker>
<Card <Club> 1>
<Card <Club> 11>})
のようにJokerを含んだ手を役判定したいとき、考えられる手段としては
- poker-handのパターンを修正する
- cardマッチャーを修正する
前者は、要は例えばスリーカードのパターン
<cons <card _ $n>
<cons <card _ ,n>
<cons <card _ ,n>
<cons _
<cons _
<nil>>>>>>
を
<cons <card _ $n>
<cons (| <card _ ,n> ,<Joker>)
<cons (| <card _ ,n> ,<Joker>)
<cons _
<cons _
<nil>>>>>>
とか書き直せばいいという話ですが、全パターンにこれをやろうとすると場合分けやらがひどいことになって多分死にます。あと、折角直感的にパターンを書けていたのに煩雑になってしまいます。
後者は<Joker>がワイルドカード、つまり存在する全てのカードとしてマッチするようにマッチャーを書き直すというものです。一から全部書き直してもいいですが、algebraic-data-matcherになんとかしてもらえそうなところは全面的に委託する感じで書いてみます。
このようにすると<Joker>がワイルドカードとしてのふるまいをするようになり、poker-handsは何も変えなくてよいということになります。
役判定アルゴリズムのパターン記述部とカードの実装を分離できている
ので、個人的にはこっちの解決策のほうが好きです。
ストレート(及びストレートフラッシュ)の修正
これは普通に間違ってるなーと前からちょっと思っていて、要はストレート(ストレートフラッシュ)のパターンって微妙に間違っていて、12->13->A->2->3みたいなのはストレートにはならんわけです。
これを修正するのは一瞬で、
でOK。一番小さい数字が11未満であることを保証してやれば、12->13->A->2->3はちゃんと弾かれるわけですね。
個人的にはEgisonやりたてのころはpredicate patternは慣れないと使い方わからないようなところあったので、ちょっと複雑にはなるけど例として入れといたら入門的な意味でいいんじゃねーのと思ったりしました。ANDパターンの使用例にもなりますし。