rubyでドローポーカーを作ってみる～実装編2（役）～

## 概要

[rubyでドローポーカーを作ってみる～準備編～](https://qiita.com/r12tkmt/items/56a60f2d69226b2bdcf2)
↓
[rubyでドローポーカーを作ってみる～test-unit準備編～](https://qiita.com/r12tkmt/items/48940d0e09a77d210bf2)
↓
[rubyでドローポーカーを作ってみる～実装編1（カード）～](https://qiita.com/r12tkmt/items/175aa66fd91995f13524)

に続いて。

ソース: https://github.com/rytkmt/ruby_poker

今回は役について実装を行っていきます。

## 役の実装

改めて要件を整理してみます。
- 役は5枚のカードをもとに判定を行う
- プレイヤーは役をもとに勝敗を比較する
  - 役の比較をするために「役」クラスを作り`Comparable`を使えば比較ができそう
- 役の中でも、同じ役の際に比較するカードを保持する（1枚）
- 役は英語で「 **hand** 」 ここ大事。

ということでいざ実装。

### ファイル作成

まずは普通にファイル作成

```ruby:ruby_poker/hand.rb
module RubyPoker
  class Hand
    def intialize(cards:)
    end
  end
end
```

```ruby:ruby_poker.rb
require "ruby_poker/hand"
```

### 役判定

「役」のクラスを生成しているということは、そのタイミングで判定まで行うべきですよね。。

なので、生成時に判定メソッドを使用し種別と同役時の比較用カードをセットするようにする

#### 生成

```ruby:ruby_poker/hand.rb
    attr_reader :hand_type, :comparison_card

    def intialize(cards:)
      raise(ArgumentError) if cards.size != 5
      @hand_type, @comparison_card = judge(cards: cards)
    end

  private

    def judge(cards:)
    end
```

役の判定にcardsを使うだけで保持する必要がないためインスタンス変数にはしないようにする。

役判定をどのように実装するか・・・

役は多いので1つ1つメソッドを用意して・・とやるのもなんか切り分けが出来ていない感が残る。。
なので、判定moduleにして切り出すことにしましょう。

そして、判定は強い順番に行っていき初めに一致したものをセットすればよいため、
まずはカードの時と同様に、定義を行います。

#### タイプの定義

英語名はこちらを参考に・・笑
https://en.wikipedia.org/wiki/List_of_poker_hands

```ruby:ruby_poker.rb
  HAND_TYPES = %i[
    royal_straight_flush
    straight_flush
    four_of_a_kind
    full_house
    flush
    straight
    three_of_a_kind
    two_pair
    one_pair
    high_card
  ].reverse.freeze
```

動的にモジュールのクラスを取得したいため、スネークケースからキャメルケースに変換を行って`const_get`で取得したい。。
なので、そこの文言変換のために`active_support`のgemを使用しましょう
（普段rails触ってるとどうしてもこの辺りのかゆいところに手が届くのが便利で使いたくなります）

```ruby:Gemfile
gem "activesupport"
```

```shell
$ bundle install
```

これでインストール完了

```ruby:ruby_poker.rb
require "active_support/all"
```

読込も忘れず行う

#### モジュールに判定を移譲

```ruby:ruby_poker/hand.rb
Dir[__dir__ + "/hand/hand_type_judgers/*.rb"].each { |p| require p }

module RubyPoker
  class Hand
```

```ruby:ruby_poker/hand.rb
  private

    def judge(cards:)
      matched_cards = nil
      matched_type = RubyPoker::HAND_TYPES.find do |type|
        judger = RubyPoker::Hand::HandTypeJudgers.const_get(type.to_s.classify) # <= ここのためにactive_support
        matched_cards = judger.judge(cards: cards)
      end

      [matched_type, matched_cards.max]
    end
```

handの内部処理を切り分ける形で、あくまでhandの責務内だと思ったので、handのインナーモジュールとして実装するようにします
Judgerの`.judge`は一致した場合、同役判定の対象となるカードを必ず返すようにし、一致しなかったらnilを返すようにする

#### 役判定モジュール実装

##### ロイヤルストレートフラッシュ

```ruby:ruby_poker/lib/ruby_poker/hand/hand_type_judgers/royal_straight_flush.rb
module RubyPoker
  class Hand
    module HandTypeJudgers
      module RoyalStraightFlush
        def self.judge(cards:)
          return nil unless cards.map(&:suit).uniq.size == 1
          return nil unless cards.sort.map(&:number) == [10, 11, 12, 13, 1]
          cards
        end
      end
    end
  end
end
```

##### ストレートフラッシュ

```ruby:ruby_poker/lib/ruby_poker/hand/hand_type_judgers/straight_flush.rb
module RubyPoker
  class Hand
    module HandTypeJudgers
      module StraightFlush
        def self.judge(cards:)
          return nil unless cards.map(&:suit).uniq.size == 1
          min_number_level = cards.min.number_level
          expected = [*min_number_level..min_number_level + 4]
          return nil unless cards.sort.map(&:number_level) == expected
          cards
        end
      end
    end
  end
end
```

##### フォーカード

```ruby:ruby_poker/lib/ruby_poker/hand/hand_type_judgers/four_of_a_kind.rb
module RubyPoker
  class Hand
    module HandTypeJudgers
      module FourOfAKind
        def self.judge(cards:)
          cards.group_by(&:number).detect { |k, v| v.size == 4 }&.second
        end
      end
    end
  end
end
```

##### フルハウス

```ruby:ruby_poker/lib/ruby_poker/hand/hand_type_judgers/full_house.rb
module RubyPoker
  class Hand
    module HandTypeJudgers
      module FullHouse
        def self.judge(cards:)
          grouped_number_cards = cards.group_by(&:number)
          three_card_number, three_cards = grouped_number_cards.detect { |k, v| v.size == 3 }
          return nil unless three_card_number
          grouped_number_cards.delete(three_card_number)
          return nil unless grouped_number_cards.detect { |k, v| v.size == 2 }
          three_cards
        end
      end
    end
  end
end
```

##### フラッシュ

```ruby:ruby_poker/lib/ruby_poker/hand/hand_type_judgers/flush.rb
module RubyPoker
  class Hand
    module HandTypeJudgers
      module Flush
        def self.judge(cards:)
          return nil unless cards.map(&:suit).uniq.size == 1
          cards
        end
      end
    end
  end
end
```

##### ストレート

```ruby:ruby_poker/lib/ruby_poker/hand/hand_type_judgers/straight.rb
module RubyPoker
  class Hand
    module HandTypeJudgers
      module Straight
        def self.judge(cards:)
          min_number_level = cards.min.number_level
          expected = [*min_number_level..min_number_level + 4]
          return nil unless cards.sort.map(&:number_level) == expected
          cards
        end
      end
    end
  end
end
```

##### スリーカード

```ruby:ruby_poker/lib/ruby_poker/hand/hand_type_judgers/three_of_a_kind.rb
module RubyPoker
  class Hand
    module HandTypeJudgers
      module ThreeOfAKind
        def self.judge(cards:)
          cards.group_by(&:number).detect { |k, v| v.size == 3 }&.second
        end
      end
    end
  end
end
```

##### ツーペア

```ruby:ruby_poker/lib/ruby_poker/hand/hand_type_judgers/two_pair.rb
module RubyPoker
  class Hand
    module HandTypeJudgers
      module TwoPair
        def self.judge(cards:)
          pairs = cards.group_by(&:number_level).select { |k, v| v.size == 2 }
          return nil unless pairs.size == 2
          pairs.max.second
        end
      end
    end
  end
end
```

##### ワンペア

```ruby:ruby_poker/lib/ruby_poker/hand/hand_type_judgers/one_pair.rb
module RubyPoker
  class Hand
    module HandTypeJudgers
      module OnePair
        def self.judge(cards:)
          cards.group_by(&:number_level).detect { |k, v| v.size == 2 }&.second
        end
      end
    end
  end
end
```

##### ハイカード（豚）

```ruby:ruby_poker/lib/ruby_poker/hand/hand_type_judgers/high_card.rb
module RubyPoker
  class Hand
    module HandTypeJudgers
      module HighCard
        def self.judge(cards:)
          [cards.max]
        end
      end
    end
  end
end
```

テストの実装も含めて少し大変でしたが実装完了。これで役判定と、同役時の判定用カードの返却までしっかりと行うことができるようになりました。

#### Comparable用の判定追加

```diff:ruby_poker/hand.rb
+    include Comparable
```

```ruby:ruby_poker/hand.rb
    def hand_level
      RubyPoker::HAND_TYPES.reverse.index(@hand_type)
    end

    def <=>(other)
      hand_comparison = hand_level <=> other.hand_level
      hand_comparison.zero? ? @comparison_card <=> other.comparison_card : hand_comparison
    end
```

これで役の勝敗を`>`などで比較することができるようになりました

## 終わりに

今回の内容にて、カード、役　などゲームに大切な処理は一通り終わったかと思います。
次回は、これらを使うプレイヤーやゲーム進行回りを作っていきたいと思います。

## 続き

↓
[rubyでドローポーカーを作ってみる～実装編3（プレイヤー）～](https://qiita.com/r12tkmt/items/ae44086d9bf7d01e7572)