Class: PokerHand

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/ruby-poker.rb

Constant Summary collapse

OPS =
[
  ['Royal Flush',     :royal_flush? ],
  ['Straight Flush',  :straight_flush? ],
  ['Four of a kind',  :four_of_a_kind? ],
  ['Full house',      :full_house? ],
  ['Flush',           :flush? ],
  ['Straight',        :straight? ],
  ['Three of a kind', :three_of_a_kind?],
  ['Two pair',        :two_pair? ],
  ['Pair',            :pair? ],
  ['Highest Card',    :highest_card? ],
]
RESOLVING_METHODS =

Resolving methods are just passed directly down to the @hand array

[:size, :+, :-]
@@allow_duplicates =

true by default

true

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(cards = []) ⇒ PokerHand

Returns a new PokerHand object. Accepts the cards represented in a string or an array

PokerHand.new("3d 5c 8h Ks")   # => #<PokerHand:0x5c673c ...
PokerHand.new(["3d", "5c", "8h", "Ks"])  # => #<PokerHand:0x5c2d6c ...


16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/ruby-poker.rb', line 16

def initialize(cards = [])
  if cards.is_a? Array
    @hand = cards.map do |card|
      if card.is_a? Card
        card
      else
        Card.new(card.to_s)
      end
    end
  elsif cards.respond_to?(:to_str)
    @hand = cards.scan(/\S{2,3}/).map { |str| Card.new(str) }
  else
    @hand = cards
  end
  
  check_for_duplicates if !@@allow_duplicates
end

Instance Attribute Details

#handObject (readonly)

Returns the value of attribute hand.



5
6
7
# File 'lib/ruby-poker.rb', line 5

def hand
  @hand
end

Class Method Details

.allow_duplicatesObject



8
# File 'lib/ruby-poker.rb', line 8

def self.allow_duplicates; @@allow_duplicates; end

.allow_duplicates=(v) ⇒ Object



9
# File 'lib/ruby-poker.rb', line 9

def self.allow_duplicates=(v); @@allow_duplicates = v; end

Instance Method Details

#<<(new_cards) ⇒ Object

Add a card to the hand

hand = PokerHand.new("5d")
hand << "6s"          # => Add a six of spades to the hand by passing a string
hand << ["7h", "8d"]  # => Add multiple cards to the hand using an array


300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/ruby-poker.rb', line 300

def << new_cards
  if new_cards.is_a?(Card) || new_cards.is_a?(String)
    new_cards = [new_cards]
  end
  
  new_cards.each do |nc|
    unless @@allow_duplicates
      raise "A card with the value #{nc} already exists in this hand. Set PokerHand.allow_duplicates to true if you want to be able to add a card more than once." if self =~ /#{nc}/
    end
    
    @hand << Card.new(nc)
  end
end

#<=>(other_hand) ⇒ Object



291
292
293
# File 'lib/ruby-poker.rb', line 291

def <=> other_hand
  self.score[0].compact <=> other_hand.score[0].compact
end

#=~(re) ⇒ Object

The =~ method does a regular expression match on the cards in this hand. This can be useful for many purposes. A common use is the check if a card exists in a hand.

PokerHand.new("3d 4d 5d") =~ /8h/           # => nil
PokerHand.new("3d 4d 5d") =~ /4d/           # => #<MatchData:0x615e18>


73
74
75
# File 'lib/ruby-poker.rb', line 73

def =~ (re)
  re.match(just_cards)
end

#by_faceObject

Returns a new PokerHand object with the cards sorted by value with the highest value first.

PokerHand.new("3d 5c 8h Ks").by_face.just_cards   # => "Ks 8h 5c 3d"


46
47
48
# File 'lib/ruby-poker.rb', line 46

def by_face
  PokerHand.new(@hand.sort_by { |c| [c.face, c.suit] }.reverse)
end

#by_suitObject

Returns a new PokerHand object with the cards sorted by suit The suit order is spades, hearts, diamonds, clubs

PokerHand.new("3d 5c 8h Ks").by_suit.just_cards   # => "Ks 8h 3d 5c"


38
39
40
# File 'lib/ruby-poker.rb', line 38

def by_suit
  PokerHand.new(@hand.sort_by { |c| [c.suit, c.face] }.reverse)
end

#delete(card) ⇒ Object

Remove a card from the hand.

hand = PokerHand.new("5d Jd")
hand.delete("Jd")           # => #<Card:0x5d0674 @value=23, @face=10, @suit=1>
hand.just_cards             # => "5d"


319
320
321
# File 'lib/ruby-poker.rb', line 319

def delete card
  @hand.delete(Card.new(card))
end

#face_valuesObject

Returns an array of the card values in the hand. The values returned are 1 less than the value on the card. For example: 2’s will be shown as 1.

PokerHand.new(["3c", "Kh"]).face_values     # => [2, 12]


63
64
65
# File 'lib/ruby-poker.rb', line 63

def face_values
  @hand.map { |c| c.face }
end

#flush?Boolean

Returns:

  • (Boolean)


129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/ruby-poker.rb', line 129

def flush?
  if (md = (by_suit =~ /(.)(.) (.)\2 (.)\2 (.)\2 (.)\2/))
    [
      [
        6,
        Card::face_value(md[1]),
        *(md[3..6].map { |f| Card::face_value(f) })
      ],
      arrange_hand(md)
    ]
  else
    false
  end
end

#four_of_a_kind?Boolean

Returns:

  • (Boolean)


96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/ruby-poker.rb', line 96

def four_of_a_kind?
  if (md = (by_face =~ /(.). \1. \1. \1./))
    # get kicker
    (md.pre_match + md.post_match).match(/(\S)/)
    [
      [8, Card::face_value(md[1]), Card::face_value($1)],
      arrange_hand(md)
    ]
  else
    false
  end
end

#full_house?Boolean

Returns:

  • (Boolean)


109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/ruby-poker.rb', line 109

def full_house?
  if (md = (by_face =~ /(.). \1. \1. (.*)(.). \3./))
    arranged_hand = arrange_hand(md[0] + ' ' +
        md.pre_match + ' ' + md[2] + ' ' + md.post_match)
    [
      [7, Card::face_value(md[1]), Card::face_value(md[3])],
      arranged_hand
    ]
  elsif (md = (by_face =~ /((.). \2.) (.*)((.). \5. \5.)/))
    arranged_hand = arrange_hand(md[4] + ' '  + md[1] + ' ' +
        md.pre_match + ' ' + md[3] + ' ' + md.post_match)
    [
      [7, Card::face_value(md[5]), Card::face_value(md[2])],
      arranged_hand
    ]
  else
    false
  end
end

#hand_ratingObject Also known as: rank

Returns the verbose hand rating

PokerHand.new("4s 5h 6c 7d 8s").hand_rating     # => "Straight"


252
253
254
255
256
# File 'lib/ruby-poker.rb', line 252

def hand_rating
  OPS.map { |op|
    (method(op[1]).call()) ? op[0] : false
  }.find { |v| v }
end

#highest_card?Boolean

Returns:

  • (Boolean)


231
232
233
234
# File 'lib/ruby-poker.rb', line 231

def highest_card?
  result = by_face
  [[1, *result.face_values[0..4]], result.hand.join(' ')]
end

#just_cardsObject Also known as: cards

Returns string representation of the hand without the rank

PokerHand.new(["3c", "Kh"]).just_cards     # => "3c Kh"


53
54
55
# File 'lib/ruby-poker.rb', line 53

def just_cards
  @hand.join(" ")
end

#pair?Boolean

Returns:

  • (Boolean)


211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/ruby-poker.rb', line 211

def pair?
  if (md = (by_face =~ /(.). \1./))
    # get kicker
    arranged_hand = arrange_hand(md)
    arranged_hand.match(/(?:\S\S ){2}(\S)\S\s+(\S)\S\s+(\S)/)
    [
      [
        2,
        Card::face_value(md[1]),
        Card::face_value($1),
        Card::face_value($2),
        Card::face_value($3)
      ],
      arranged_hand
    ]
  else
    false
  end
end

#royal_flush?Boolean

Returns:

  • (Boolean)


77
78
79
80
81
82
83
# File 'lib/ruby-poker.rb', line 77

def royal_flush?
  if (md = (by_suit =~ /A(.) K\1 Q\1 J\1 T\1/))
    [[10], arrange_hand(md)]
  else
    false
  end
end

#scoreObject



260
261
262
263
264
# File 'lib/ruby-poker.rb', line 260

def score
  OPS.map { |op|
    method(op[1]).call()
  }.find([0]) { |score| score }
end

#sort_using_rankObject

Returns a string of the hand arranged based on its rank. Usually this will be the same as by_face but there are some cases where it makes a difference.

ph = PokerHand.new("AS 3S 5S 2S 4S")
ph.sort_using_rank        # => "5s 4s 3s 2s As"
ph.by_face.just_cards       # => "As 5s 4s 3s 2s"


272
273
274
# File 'lib/ruby-poker.rb', line 272

def sort_using_rank
  score[1]
end

#straight?Boolean

Returns:

  • (Boolean)


144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/ruby-poker.rb', line 144

def straight?
  result = false
  if hand.size >= 5
    transform = delta_transform
    # note we can have more than one delta 0 that we
    # need to shuffle to the back of the hand
    i = 0
    until transform.match(/^\S{3}( [1-9x]\S\S)+( 0\S\S)*$/) or i >= hand.size  do
      # only do this once per card in the hand to avoid entering an
      # infinite loop if all of the cards in the hand are the same
      transform.gsub!(/(\s0\S\S)(.*)/, "\\2\\1")    # moves the front card to the back of the string
      i += 1
    end
    if (md = (/.(.). 1.. 1.. 1.. 1../.match(transform)))
      high_card = Card::face_value(md[1])
      arranged_hand = fix_low_ace_display(md[0] + ' ' + md.pre_match + ' ' + md.post_match)
      result = [[5, high_card], arranged_hand]
    end
  end
end

#straight_flush?Boolean

Returns:

  • (Boolean)


85
86
87
88
89
90
91
92
93
94
# File 'lib/ruby-poker.rb', line 85

def straight_flush?
  if (md = (/.(.)(.)(?: 1.\2){4}/.match(delta_transform(true))))
    high_card = Card::face_value(md[1])
    arranged_hand = fix_low_ace_display(md[0] + ' ' +
        md.pre_match + ' ' + md.post_match)
    [[9, high_card], arranged_hand]
  else
    false
  end
end

#three_of_a_kind?Boolean

Returns:

  • (Boolean)


165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/ruby-poker.rb', line 165

def three_of_a_kind?
  if (md = (by_face =~ /(.). \1. \1./))
    # get kicker
    arranged_hand = arrange_hand(md)
    arranged_hand.match(/(?:\S\S ){3}(\S)\S (\S)/)
    [
      [
        4,
        Card::face_value(md[1]),
        Card::face_value($1),
        Card::face_value($2)
      ],
      arranged_hand
    ]
  else
    false
  end
end

#to_aObject Also known as: to_ary

Returns an array of Card objects that make up the PokerHand.



285
286
287
# File 'lib/ruby-poker.rb', line 285

def to_a
  @hand
end

#to_sObject

Returns string with a listing of the cards in the hand followed by the hand’s rank.

h = PokerHand.new("8c 8s")
h.to_s                      # => "8c 8s (Pair)"


280
281
282
# File 'lib/ruby-poker.rb', line 280

def to_s
  just_cards + " (" + hand_rating + ")"
end

#two_pair?Boolean

Returns:

  • (Boolean)


184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/ruby-poker.rb', line 184

def two_pair?
  # \1 is the face value of the first pair
  # \2 is the card in between the first pair and the second pair
  # \3 is the face value of the second pair
  if (md = (by_face =~ /(.). \1.(.*) (.). \3./))
    # to get the kicker this does the following
    # md[0] is the regex matched above which includes the first pair and
    # the second pair but also some cards in the middle so we sub them out
    # then we add on the cards that came before the first pair, the cards that
    # we in between, and the cards that came after.
    arranged_hand = arrange_hand(md[0].sub(md[2], '') + ' ' +
        md.pre_match + ' ' + md[2] + ' ' + md.post_match)
    arranged_hand.match(/(?:\S\S ){4}(\S)/)
    [
      [
        3,
        Card::face_value(md[1]),    # face value of the first pair
        Card::face_value(md[3]),    # face value of the second pair
        Card::face_value($1)        # face value of the kicker
      ],
      arranged_hand
    ]
  else
    false
  end
end

#uniqObject

Same concept as Array#uniq



324
325
326
# File 'lib/ruby-poker.rb', line 324

def uniq
  PokerHand.new(@hand.uniq)
end