Help us understand the problem. What is going on with this article?

rubyでドローポーカーを作ってみる~実装編2(役)~

概要

rubyでドローポーカーを作ってみる~準備編~

rubyでドローポーカーを作ってみる~test-unit準備編~

rubyでドローポーカーを作ってみる~実装編1(カード)~

に続いて。

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

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

役の実装

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

ということでいざ実装。

ファイル作成

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

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

役判定

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

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

生成

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_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触ってるとどうしてもこの辺りのかゆいところに手が届くのが便利で使いたくなります)

Gemfile
gem "activesupport"
$ bundle install

これでインストール完了

ruby_poker.rb
require "active_support/all"

読込も忘れず行う

モジュールに判定を移譲

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

module RubyPoker
  class Hand
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_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_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_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_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_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_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_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_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_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_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用の判定追加

ruby_poker/hand.rb
+    include Comparable
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(プレイヤー)~

r12tkmt
vimとruby関連が多いかと思います :)
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away