class PokerHand
Constants
- OPS
- RESOLVING_METHODS
Resolving methods are just passed directly down to the @hand array
Attributes
Public Class Methods
# File lib/ruby-poker/poker_hand.rb, line 7 def self.allow_duplicates; @@allow_duplicates; end
# File lib/ruby-poker/poker_hand.rb, line 8 def self.allow_duplicates=(v); @@allow_duplicates = v; end
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 ...
# File lib/ruby-poker/poker_hand.rb, line 15 def initialize(cards = []) @hand = case cards when Array cards.map do |card| if card.is_a? Card card else Card.new(card.to_s) end end when String cards.scan(/\S{2}/).map { |str| Card.new(str) } else cards end check_for_duplicates unless allow_duplicates end
Public Instance Methods
# File lib/ruby-poker/poker_hand.rb, line 413 def +(other) cards = @hand.map { |card| Card.new(card) } case other when String cards << Card.new(other) when Card cards << other when PokerHand cards += other.hand else raise ArgumentError, "Invalid argument: #{other.inspect}" end PokerHand.new(cards) end
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
# File lib/ruby-poker/poker_hand.rb, line 304 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
# File lib/ruby-poker/poker_hand.rb, line 295 def <=> other_hand self.score[0].compact <=> other_hand.score[0].compact end
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>
# File lib/ruby-poker/poker_hand.rb, line 73 def =~ (re) re.match(just_cards) end
# File lib/ruby-poker/poker_hand.rb, line 342 def allow_duplicates @@allow_duplicates end
Returns a new PokerHand
object with the cards sorted by face value with the highest value first.
PokerHand.new("3d 5c 8h Ks").by_face.just_cards # => "Ks 8h 5c 3d"
# File lib/ruby-poker/poker_hand.rb, line 46 def by_face PokerHand.new(@hand.sort_by { |c| [c.face, c.suit] }.reverse) end
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"
# File lib/ruby-poker/poker_hand.rb, line 38 def by_suit PokerHand.new(@hand.sort_by { |c| [c.suit, c.face] }.reverse) end
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"
# File lib/ruby-poker/poker_hand.rb, line 323 def delete card @hand.delete(Card.new(card)) end
# File lib/ruby-poker/poker_hand.rb, line 231 def empty_hand? if size == 0 [[0]] end end
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]
# File lib/ruby-poker/poker_hand.rb, line 63 def face_values @hand.map { |c| c.face } end
# File lib/ruby-poker/poker_hand.rb, line 124 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
# File lib/ruby-poker/poker_hand.rb, line 96 def four_of_a_kind? if (md = (by_face =~ /(.). \1. \1. \1./)) # get kicker result = [8, Card::face_value(md[1])] result << Card::face_value($1) if (md.pre_match + md.post_match).match(/(\S)/) return [result, arrange_hand(md)] end false end
# File lib/ruby-poker/poker_hand.rb, line 106 def full_house? if (md = (by_face =~ /(.). \1. \1. (.*)(.). \3./)) arranged_hand = rearrange_full_house(by_face.cards) [ [7, Card::face_value(md[1]), Card::face_value(md[3])], arranged_hand ] elsif (md = (by_face =~ /((.). \2.) (.*)((.). \5. \5.)/)) arranged_hand = rearrange_full_house(by_face.cards) [ [7, Card::face_value(md[5]), Card::face_value(md[2])], arranged_hand ] else false end end
Returns the verbose hand rating
PokerHand.new("4s 5h 6c 7d 8s").hand_rating # => "Straight"
# File lib/ruby-poker/poker_hand.rb, line 254 def hand_rating OPS.map { |op| (method(op[1]).call()) ? op[0] : false }.find { |v| v } end
# File lib/ruby-poker/poker_hand.rb, line 222 def highest_card? if size > 0 result = by_face [[1, *result.face_values[0..result.face_values.length]], result.hand.join(' ')] else false end end
Returns string representation of the hand without the rank
PokerHand.new(["3c", "Kh"]).just_cards # => "3c Kh"
# File lib/ruby-poker/poker_hand.rb, line 53 def just_cards @hand.join(" ") end
Checks whether the hand matches usual expressions like AA, AK, AJ+, 66+, AQs, AQo…
Valid expressions:
-
“AJ”: Matches exact faces (in this case an Ace and a Jack), suited or not
-
“AJs”: Same but suited only
-
“AJo”: Same but offsuit only
-
“AJ+”: Matches an Ace with any card >= Jack, suited or not
-
“AJs+”: Same but suited only
-
“AJo+”: Same but offsuit only
-
“JJ+”: Matches any pair >= “JJ”.
-
“8T+”: Matches connectors (in this case with 1 gap : 8T, 9J, TQ, JK, QA)
-
“8Ts+”: Same but suited only
-
“8To+”: Same but offsuit only
The order of the cards in the expression is important (8T+ is not the same as T8+), but the order of the cards in the hand is not (“AK” will match “Ad Kc” and “Kc Ad”).
The expression can be an array of expressions. In this case the method returns true if any expression matches.
This method only works on hands with 2 cards.
PokerHand.new('Ah Ad').match? 'AA' # => true PokerHand.new('Ah Kd').match? 'AQ+' # => true PokerHand.new('Jc Qc').match? '89s+' # => true PokerHand.new('Ah Jd').match? %w( 22+ A6s+ AJ+ ) # => true PokerHand.new('Ah Td').match? %w( 22+ A6s+ AJ+ ) # => false
# File lib/ruby-poker/poker_hand.rb, line 372 def match? expression raise "Hands with #{@hand.size} cards is not supported" unless @hand.size == 2 if expression.is_a? Array return expression.any? { |e| match?(e) } end faces = @hand.map { |card| card.face }.sort.reverse suited = @hand.map { |card| card.suit }.uniq.size == 1 if expression =~ /^(.)(.)(s|o|)(\+|)$/ face1 = Card.face_value($1) face2 = Card.face_value($2) raise ArgumentError, "Invalid expression: #{expression.inspect}" unless face1 and face2 suit_match = $3 plus = ($4 != "") if plus if face1 == face2 face_match = (faces.first == faces.last and faces.first >= face1) elsif face1 > face2 face_match = (faces.first == face1 and faces.last >= face2) else face_match = ((faces.first - faces.last) == (face2 - face1) and faces.last >= face1) end else expression_faces = [face1, face2].sort.reverse face_match = (expression_faces == faces) end case suit_match when '' face_match when 's' face_match and suited when 'o' face_match and !suited end else raise ArgumentError, "Invalid expression: #{expression.inspect}" end end
# File lib/ruby-poker/poker_hand.rb, line 203 def pair? if (md = (by_face =~ /(.). \1./)) arranged_hand_str = arrange_hand(md) arranged_hand = PokerHand.new(arranged_hand_str) if arranged_hand.hand[0].face == arranged_hand.hand[1].face && arranged_hand.hand[0].suit != arranged_hand.hand[1].suit result = [2, arranged_hand.hand[0].face] result << arranged_hand.hand[2].face if arranged_hand.size > 2 result << arranged_hand.hand[3].face if arranged_hand.size > 3 result << arranged_hand.hand[4].face if arranged_hand.size > 4 return [result, arranged_hand_str] end else false end end
# File lib/ruby-poker/poker_hand.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
# File lib/ruby-poker/poker_hand.rb, line 262 def score # OPS.map returns an array containing the result of calling each OPS method against # the poker hand. The truthy cell closest to the front of the array represents # the highest ranking. OPS.map { |op| method(op[1]).call() }.find { |score| score } end
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"
# File lib/ruby-poker/poker_hand.rb, line 277 def sort_using_rank score[1] end
# File lib/ruby-poker/poker_hand.rb, line 139 def straight? 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) return [[5, high_card], arranged_hand] end end false end
# File lib/ruby-poker/poker_hand.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
# File lib/ruby-poker/poker_hand.rb, line 160 def three_of_a_kind? if (md = (by_face =~ /(.). \1. \1./)) # get kicker arranged_hand = arrange_hand(md) matches = arranged_hand.match(/(?:\S\S ){2}(\S\S)/) if matches result = [4, Card::face_value(md[1])] matches = arranged_hand.match(/(?:\S\S ){3}(\S)/) result << Card::face_value($1) if matches matches = arranged_hand.match(/(?:\S\S ){3}(\S)\S (\S)/) result << Card::face_value($2) if matches return [result, arranged_hand] end end false end
Returns an array of ‘Card` objects that make up the `PokerHand`.
# File lib/ruby-poker/poker_hand.rb, line 290 def to_a @hand end
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)"
# File lib/ruby-poker/poker_hand.rb, line 285 def to_s just_cards + " (" + hand_rating + ")" end
# File lib/ruby-poker/poker_hand.rb, line 177 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 were 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) matches = arranged_hand.match(/(?:\S\S ){3}(\S\S)/) if matches result = [] result << 3 result << Card::face_value(md[1]) # face value of the first pair result << Card::face_value(md[3]) # face value of the second pair matches = arranged_hand.match(/(?:\S\S ){4}(\S)/) result << Card::face_value($1) if matches # face value of the kicker return [result, arranged_hand] end end false end
Same concept as Array#uniq
# File lib/ruby-poker/poker_hand.rb, line 328 def uniq PokerHand.new(@hand.uniq) end
Private Instance Methods
if md is a string, arrange_hand
will remove extra white space if md is a MatchData, arrange_hand
returns the matched segment followed by the pre_match and the post_match
# File lib/ruby-poker/poker_hand.rb, line 439 def arrange_hand(md) hand = if md.respond_to?(:to_str) md else md[0] + ' ' + md.pre_match + md.post_match end hand.strip.squeeze(" ") # remove extra whitespace end
# File lib/ruby-poker/poker_hand.rb, line 430 def check_for_duplicates if @hand.size != @hand.uniq.size && !allow_duplicates raise "Attempting to create a hand that contains duplicate cards. Set PokerHand.allow_duplicates to true if you do not want to ignore this error." end end
delta transform returns a string representation of the cards where the delta between card values is in the string. This is necessary so a regexp can then match a straight and/or straight flush
Examples
PokerHand.new("As Qc Jh Ts 9d 8h") # => '0As 2Qc 1Jh 1Ts 19d 18h' PokerHand.new("Ah Qd Td 5d 4d") # => '0Ah 2Qd 2Td 55d 14d'
# File lib/ruby-poker/poker_hand.rb, line 460 def delta_transform(use_suit = false) # In order to check for both ace high and ace low straights we create low # ace duplicates of all of the high aces. aces = @hand.select { |c| c.face == Card::face_value('A') } aces.map! { |c| Card.new(0, c.suit) } # hack to give the appearance of a low ace base = if (use_suit) (@hand + aces).sort_by { |c| [c.suit, c.face] }.reverse else (@hand + aces).sort_by { |c| [c.face, c.suit] }.reverse end # Insert delta in front of each card result = base.inject(['',nil]) do |(delta_hand, prev_card), card| if (prev_card) delta = prev_card - card.face else delta = 0 end # does not really matter for my needs delta = 'x' if (delta > 9 || delta < 0) delta_hand += delta.to_s + card.to_s + ' ' [delta_hand, card.face] end # we just want the delta transform, not the last cards face too result[0].chop end
# File lib/ruby-poker/poker_hand.rb, line 489 def fix_low_ace_display(arranged_hand) # remove card deltas (this routine is only used for straights) arranged_hand.gsub!(/\S(\S\S)\s*/, "\\1 ") # Fix "low aces" arranged_hand.gsub!(/L(\S)/, "A\\1") # Remove duplicate aces (this will not work if you have # multiple decks or wild cards) arranged_hand.gsub!(/((A\S).*)\2/, "\\1") # cleanup white space arranged_hand.gsub!(/\s+/, ' ') # careful to use gsub as gsub! can return nil here arranged_hand.gsub(/\s+$/, '') end
# File lib/ruby-poker/poker_hand.rb, line 506 def rearrange_full_house(cards) card_array = cards.split.uniq card_hash = Hash[card_array.collect{|c| [c[0], card_array.count{|n| n[0] == c[0]}]}] arranged_hand = card_array.select{|c| c if c[0] == card_hash.key(3)} arranged_hand += card_array.select{|c| c if c[0] == card_hash.key(2)} (arranged_hand + (card_array - arranged_hand)).join(" ") end