Ruby2.7ではパターンマッチが入るらしいと聞いたので、Ruby2.7.0-devでポーカーの役のチェックを書いてみました。
このコードは今の所Ruby2.7.0-devでしか動きませんし、もしかすると将来的にもRuby2.7.0-devでしか動かないかもしれません。あしからず。
コード
出来上がったコードはこんな感じで、
class PokerChecker
def check(cards)
case cards.sort { @1[1] <=> @2[1] }
in [[m, a], [^m, b], [^m, c], [^m, d], [^m, e]] if a != 1 && [a, b, c, d].zip([b, c, d, e]).map { @2 - @1 }.all? { @1 == 1 } ; :straight_flush
in [[m, 1], [^m, 10], [^m, 11], [^m, 12], [^m, 13]]; :straight_flush
in [[_, a], [_, ^a], [_, ^a], [_, ^a], [_, _]]; :four_of_kind
in [[_, _], [_, a], [_, ^a], [_, ^a], [_, ^a]]; :four_of_kind
in [[_, a], [_, b], [_, c], [_, d], [_, e]] if a != 1 && [a, b, c, d].zip([b, c, d, e]).map { @2 - @1 }.all? { @1 == 1 } ; :straight
in [[_, 1], [_, 10], [_, 11], [_, 12], [_, 13]]; :straight
in [[m, _], [^m, _], [^m, _], [^m, _], [^m, _]]; :flush
in [[_, a], [_, ^a], [_, b], [_, ^b], [_, ^b]]; :full_house
in [[_, a], [_, ^a], [_, ^a], [_, b], [_, ^b]]; :full_house
in [[_, a], [_, ^a], [_, ^a], [_, _], [_, _]]; :three_of_kind
in [[_, _], [_, a], [_, ^a], [_, ^a], [_, _]]; :three_of_kind
in [[_, _], [_, _], [_, a], [_, ^a], [_, ^a]]; :three_of_kind
in [[_, a], [_, ^a], [_, b], [_, ^b], [_, _]]; :two_pair
in [[_, _], [_, a], [_, ^a], [_, b], [_, ^b]]; :two_pair
in [[_, a], [_, ^a], [_, _], [_, b], [_, ^b]]; :two_pair
in [[_, a], [_, ^a], [_, _], [_, _], [_, _]]; :one_pair
in [[_, _], [_, a], [_, ^a], [_, _], [_, _]]; :one_pair
in [[_, _], [_, _], [_, a], [_, ^a], [_, _]]; :one_pair
in [[_, _], [_, _], [_, _], [_, a], [_, ^a]]; :one_pair
else; :buta
end
end
end
使うときはこんな感じです。
irb(main):003:0> PokerChecker.new.check([[:s, 2], [:d, 2], [:h, 2], [:c, 8], [:h, 8]])
=> :full_house
checkメソッドに配列の配列を渡していて、中身の配列の1つ目がトランプのマーク、2つ目がトランプの数値を表しています。上の例だと、スペードの2、ダイヤの2、ハートの2、クラブの8、ハートの8でフルハウスとなっています。
ストレートフラッシュの判定でいきなり if
の力に頼っているのがいささか残念ですが、20行ちょっとで書けていることからパターンマッチの強力さが伺えます。(テストも書いたので多分バグは無いと思いますが、バグってたらすみません)
パターンマッチの記法に慣れないうちは面食らうと思いますが、慣れてくれば気にならなくなるのではないでしょうか(きっと)。
書いていてなかなか楽しかったので、早く仕事でも使えるようになると良いですね。
その他所感
ストレート(とストレートフラッシュ)の判定について
本当は
in [[_, a], [_, ^a + 1], [_, ^a + 2], [_, ^a + 3], [_, ^a + 4]]; :straight
のように書きたかったのですが、これはSyntaxErrorになってしまいました。
SyntaxError ((irb):9: syntax error, unexpected '+', expecting ']')
in [[_, a], [_, ^a + 1], [_, ^a + 2], [_, ^a + 3]...
^
^a + 1
を括弧で囲んでも変わらなかったので、諦めて連続しているかの判定を if
で書いています。
Arrayパターンで変数へ代入しつつ複数条件書く時
パターンマッチでは、 |
区切りで複数のパターンが書けます。
irb(main):018:0> case 3; in 1 | 2 | 3; :match; end
=> :match
これはArrayパターンでも同様に使えます。
irb(main):016:0> case [1, 2, 2]; in [1, 2, 2] | [2, 2, 1]; :match; end
=> :match
が、変数への代入も合わせて書くと…
irb(main):017:0> case [1, 2, 2]; in [_, a, ^a] | [a, ^a, _]; :match; end
Traceback (most recent call last):
1: from (irb)
SyntaxError ((irb):17: illegal variable in alternative pattern (a))
(irb):17: illegal variable in alternative pattern (a)
と、これまたSyntaxErrorになってしまいました。
irb(main):019:0> case [1, 2, 2]; in [_, a, ^a]; :match; end
=> :match
↑は動くので意図的な物なのか何なのかよく分かりません。
ただし、分解して別のin
にすれば良いですし、かえって分解したほうが読みやすくなったので結果的には良かったです。
Numbered arguments
ついでにNumbered argumentsも使ってみました。一行に収まる程度のブロック(またはProc)であれば、可読性を保ったまま短くかけそうです。
おまけ
本文中では読みやすさのために消していますが、パターンマッチを書くと warning: Pattern matching is experimental, and the behavior may change in future versions of Ruby!
というありがたい警告が出ました。