Last active
February 3, 2021 12:14
-
-
Save baweaver/e89846ca73a7874c9ac8c9d1d21d40fc to your computer and use it in GitHub Desktop.
Computing poker hand scores using Ruby
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Card = Struct.new(:suite, :rank) do | |
include Comparable | |
def precedence() = [SUITES_SCORES[self.suite], RANKS_SCORES[self.rank]] | |
def rank_precedence() = RANKS_SCORES[self.rank] | |
def suite_precedence() = SUITES_SCORES[self.rank] | |
def <=>(other) = self.precedence <=> other.precedence | |
def to_s() = "#{self.suite}#{self.rank}" | |
end | |
Hand = Struct.new(:cards) do | |
def sort() = Hand[self.cards.sort] | |
def sort_by_rank() = Hand[self.cards.sort_by(&:rank_precedence)] | |
def to_s() = self.cards.map(&:to_s).join(', ') | |
end | |
SUITES = %w(S H D C).freeze | |
SUITES_SCORES = SUITES.each_with_index.to_h | |
RANKS = [*2..10, *%w(J Q K A)].map(&:to_s).freeze | |
RANKS_SCORES = RANKS.each_with_index.to_h | |
SCORES = %i( | |
royal_flush | |
straight_flush | |
four_of_a_kind | |
full_house | |
flush | |
straight | |
three_of_a_kind | |
two_pair | |
one_pair | |
high_card | |
).reverse_each.with_index(1).to_h.freeze | |
CARDS = SUITES.flat_map { |s| RANKS.map { |r| Card[s, r] } }.freeze | |
def hand_score(unsorted_hand) | |
hand = Hand[unsorted_hand].sort_by_rank.cards | |
is_straight = -> hand { | |
hand | |
.map { RANKS_SCORES[_1.rank] } | |
.sort | |
.each_cons(2) | |
.all? { |a, b| b - a == 1 } | |
} | |
return SCORES[:royal_flush] if hand in [ | |
Card[s, '10'], Card[^s, 'J'], Card[^s, 'Q'], Card[^s, 'K'], Card[^s, 'A'] | |
] | |
return SCORES[:straight_flush] if is_straight[hand] && hand in [ | |
Card[s, *], Card[^s, *], Card[^s, *], Card[^s, *], Card[^s, *] | |
] | |
return SCORES[:four_of_a_kind] if hand in [ | |
*, Card['S', *], Card['H', *], Card['D', *], Card['C', *], * | |
] | |
return SCORES[:full_house] if hand in [ | |
Card[*, r1], Card[*, ^r1], Card[*, ^r1], Card[*, r2], Card[*, ^r2] | |
] | |
return SCORES[:full_house] if hand in [ | |
Card[*, r1], Card[*, ^r1], Card[*, r2], Card[*, ^r2], Card[*, ^r2] | |
] | |
return SCORES[:flush] if hand in [ | |
Card[s, *], Card[^s, *], Card[^s, *], Card[^s, *], Card[^s, *] | |
] | |
return SCORES[:straight] if is_straight[hand] | |
return SCORES[:three_of_a_kind] if hand in [ | |
*, Card[*, r], Card[*, ^r], Card[*, ^r], * | |
] | |
return SCORES[:two_pair] if hand in [ | |
*, Card[*, r1], Card[*, ^r1], Card[*, r2], Card[*, ^r2], * | |
] | |
return SCORES[:two_pair] if hand in [ | |
Card[*, r1], Card[*, ^r1], *, Card[*, r2], Card[*, ^r2] | |
] | |
return SCORES[:one_pair] if hand in [ | |
*, Card[*, r], Card[*, ^r], * | |
] | |
SCORES[:high_card] | |
end | |
# --- Testing ------ | |
EXAMPLES = { | |
royal_flush: | |
RANKS.last(5).map { Card['S', _1] }, | |
straight_flush: | |
RANKS.first(5).map { Card['S', _1] }, | |
four_of_a_kind: | |
[CARDS[0], *SUITES.map { Card[_1, 'A'] }], | |
full_house: | |
SUITES.first(3).map { Card[_1, 'A'] } + | |
SUITES.first(2).map { Card[_1, 'K'] }, | |
flush: | |
(0..RANKS.size).step(2).first(5).map { Card['S', RANKS[_1]] }, | |
straight: | |
[Card['H', RANKS.first], *RANKS[1..4].map { Card['S', _1] }], | |
three_of_a_kind: | |
CARDS.first(2) + | |
SUITES.first(3).map { Card[_1, 'A'] }, | |
two_pair: | |
CARDS.first(1) + | |
SUITES.first(2).flat_map { [Card[_1, 'A'], Card[_1, 'K']] }, | |
one_pair: | |
[CARDS[10], CARDS[15], CARDS[20], *SUITES.first(2).map { Card[_1, 'A'] }], | |
high_card: | |
[CARDS[10], CARDS[15], CARDS[20], CARDS[5], Card['S', 'A']] | |
}.freeze | |
SCORE_MAP = SCORES.invert | |
EXAMPLES.each do |hand_type, hand| | |
score = hand_score(hand) | |
correct_text = hand_type == SCORE_MAP[score] ? 'correct' : 'incorrect' | |
puts <<~OUT | |
Hand: #{Hand[hand]} (#{hand_type}) | |
Score: #{score} (#{correct_text}) | |
OUT | |
puts | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
But this will only work for 5 cards. If you have 7 cards (eg. texas holdem -> 5 community + 2 in the hole) I think this becomes a lot more complicated, especially as ruby doesn't appear to allow you to have a wildcard in the middle and at the ends in one pattern.
For example the following seems tricky to match:
A K K Q J J 10
I suspect at this point using something like:
@cards.group_by { _1.rank_score }
and then looking for groups of the correct number becomes easier to understand.