概要
rubyでドローポーカーを作ってみる~準備編~
↓
rubyでドローポーカーを作ってみる~test-unit準備編~
↓
rubyでドローポーカーを作ってみる~実装編1(カード)~
に続いて。
ソース: https://github.com/rytkmt/ruby_poker
今回は役について実装を行っていきます。
役の実装
改めて要件を整理してみます。
- 役は5枚のカードをもとに判定を行う
- プレイヤーは役をもとに勝敗を比較する
- 役の比較をするために「役」クラスを作り
Comparable
を使えば比較ができそう
- 役の比較をするために「役」クラスを作り
- 役の中でも、同じ役の際に比較するカードを保持する(1枚)
- 役は英語で「 hand 」 ここ大事。
ということでいざ実装。
ファイル作成
まずは普通にファイル作成
module RubyPoker
class Hand
def intialize(cards:)
end
end
end
require "ruby_poker/hand"
役判定
「役」のクラスを生成しているということは、そのタイミングで判定まで行うべきですよね。。
なので、生成時に判定メソッドを使用し種別と同役時の比較用カードをセットするようにする
生成
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
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触ってるとどうしてもこの辺りのかゆいところに手が届くのが便利で使いたくなります)
gem "activesupport"
$ bundle install
これでインストール完了
require "active_support/all"
読込も忘れず行う
モジュールに判定を移譲
Dir[__dir__ + "/hand/hand_type_judgers/*.rb"].each { |p| require p }
module RubyPoker
class Hand
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を返すようにする
役判定モジュール実装
ロイヤルストレートフラッシュ
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
ストレートフラッシュ
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
フォーカード
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
フルハウス
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
フラッシュ
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
ストレート
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
スリーカード
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
ツーペア
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
ワンペア
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
ハイカード(豚)
module RubyPoker
class Hand
module HandTypeJudgers
module HighCard
def self.judge(cards:)
[cards.max]
end
end
end
end
end
テストの実装も含めて少し大変でしたが実装完了。これで役判定と、同役時の判定用カードの返却までしっかりと行うことができるようになりました。
Comparable用の判定追加
+ include Comparable
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
これで役の勝敗を>
などで比較することができるようになりました
終わりに
今回の内容にて、カード、役 などゲームに大切な処理は一通り終わったかと思います。
次回は、これらを使うプレイヤーやゲーム進行回りを作っていきたいと思います。