I may be wrong about some of the rules concerning the determination of the winning hand in five-card poker (draw? stud? other?), but the code below can be easily modified to correct any errors in my assumptions.
The "kicker" is the tie-breaker. For example, if two hands both contain straights, the "kicker" is the card with the highest rank (for 3, 4, 5, 6, and 7, 7 is the "kicker"). If both hands have the same kicker, it's a tie. For a hand with, say, two 5's and two queens, the first kicker is the higher of the ranks (the queen), and if that rank is the same for another hand with two pairs, 5 becomes the kicker. If both hands have pairs of queens and a pair of 5's, it's a tie. Note the possibility of multiple "winners".
Hands are compared by associated arrays, which I've called "hand values". The first element of the array is a non-negative integer representing the type of hand, with 0 denoting "high card" (all cards have different ranks and not all suits are the same), 1 denoting one pair, and so on, up to 8 for a straight-flush. The second element of the array is the kicker (if there is one) and in one case (two pair) there is a third element that is the second kicker, should the first one not decide the winner. Arrays are ordered lexicographically in Ruby (see Array#<=>), so we can determine the winner (or winners in the case of a tie) by finding which array is greatest.
I've assumed a hand is represented like so:
[['J', :clubs], ['2', :spades], ['J', :diamonds], ['2', :hearts], ['J', :spades]]
This may entail a conversion from some other format (that I've not discussed).
Code
RANK_STR_TO_RANK = %w| 2 3 4 5 6 7 8 9 J Q K A |.
each_with_index.with_object({}) { |(s,i),h| h[s] = i }
#=> {"2"=>0, "3"=>1, "4"=>2, "5"=>3, "6"=>4, "7"=>5,
# "8"=>6, "9"=>7, "J"=>8, "Q"=>9, "K"=>10, "A"=>11}
HIGH_CARD = 0
TWO_OF_KIND = 1
TWO_PAIR = 2
THREE_OF_KIND = 3
STRAIGHT = 4
FLUSH = 5
FULL_HOUSE = 6
FOUR_OF_KIND = 7
STRAIGHT_FlUSH = 8
def winners(hands)
hands_and_values = hands.map { |hand| [hand, hand_value(hand)] }
winning_value = hands_and_values.map(&:last).max
hands_and_values.select { |_, value| value == winning_value }.map(&:first)
end
def hand_value(hand)
ranks, suits = convert(hand).transpose
case
when straight_flush?(ranks, suits) then [STRAIGHT_FlUSH, ranks.max]
when (kicker = four_of_kind?(ranks)) then [FOUR_OF_KIND, kicker]
when (kicker = full_house?(ranks)) then [FULL_HOUSE, kicker]
when flush?(suits) then [FLUSH]
when straight?(ranks) then [STRAIGHT, ranks.max]
when (kicker = three_of_kind?(ranks)) then [THREE_OF_KIND, kicker]
when (kickers = two_pair?(ranks)) then [TWO_PAIR, *kickers]
else [HIGH_CARD, ranks.max]
end
end
def convert(hand)
hand.map { |rank_str, suit| [RANK_STR_TO_RANK[rank_str], suit] }
end
def straight_flush?(ranks, suits)
straight?(ranks) && flush?(suits)
end
def four_of_kind?(ranks)
h = group_by_rank(ranks)
h.key?(4) ? h[4].first : false
end
def full_house?(ranks)
h = group_by_rank(ranks)
(h.key?(3) && h.key?(2)) ? h[3].first : false
end
def flush?(suits)
suits.uniq.size == 1
end
def straight?(ranks)
smallest, largest = ranks.minmax
largest - smallest == 4 && ranks.uniq.size == 5
end
def three_of_kind?(ranks)
h = group_by_rank(ranks)
h.key?(3) ? h[3].first : false
end
def two_pair?(ranks)
h = group_by_rank(ranks)
h.key?(2) && h[2].size==2 ? h[2].sort.reverse : false
end
def one_pair?(ranks)
h = group_by_rank(ranks)
h.key?(2) ? h[2].first : false
end
def group_by_rank(ranks)
ranks.group_by(&:itself).each_with_object({}) { |(k,v),h| (h[v.size] ||= []) << k }
end
Example
hands = [[['J', :clubs], ['2', :spades], ['J', :diamonds], ['2', :hearts], ['J', :spades]],
[['Q', :hearts], ['3', :clubs], ['Q', :clubs], ['A', :hearts], ['3', :spades]],
[['4', :hearts], ['5', :spades], ['6', :hearts], ['7', :hearts], ['8', :spades]],
[['5', :clubs], ['9', :clubs], ['K', :clubs], ['A', :clubs], ['8', :clubs]]]
winners(hands)
#=> [
# [["J", :clubs], ["2", :spades], ["J", :diamonds], ["2", :hearts], ["J", :spades]]
# ]
Notes
In winners, the intermediate values are as follows.
hands_and_values
#=> [[[["J", :clubs], ["2", :spades], ["J", :diamonds], ["2", :hearts], ["J", :spades]],
# [6, 8]],
# [[["Q", :hearts], ["3", :clubs], ["Q", :clubs], ["A", :hearts], ["3", :spades]],
# [2, 9, 1]],
# [[["4", :hearts], ["5", :spades], ["6", :hearts], ["7", :hearts], ["8", :spades]],
# [4, 6]],
# [[["5", :clubs], ["9", :clubs], ["K", :clubs], ["A", :clubs], ["8", :clubs]],
# [5]]
# ]
winning_value
#=> [6, 8]
The value of the first hand is 6, 8, 0, which means that it is a FULL_HOUSE (6) with 3 jacks (rank=>8) and 2 2's (rank=>0). If two hands have full houses, the rank of the three-of-a-kind is the "kicker", or tie-breaker.
"10 of ♣"