LoginSignup
1
1

More than 5 years have passed since last update.

Ruby2.7.0-devのパターンマッチでポーカーの役を判定してみた

Last updated at Posted at 2019-04-19

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! というありがたい警告が出ました。

1
1
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1