Skip to content

Instantly share code, notes, and snippets.

@msimpson msimpson/poker
Created Jul 24, 2011

Embed
What would you like to do?
A library to rank and compare poker hands.
#!/usr/bin/env ruby
# encoding: UTF-8
# - Poker -
# A library to rank and compare poker hands.
module Poker
ACE = 14
KING = 13
QUEEN = 12
JACK = 11
SPADE = 1
HEART = 2
DIAMOND = 3
CLUB = 4
STRFLUSH = 9
FOUR = 8
FULL = 7
FLUSH = 6
STR = 5
THREE = 4
TWO = 3
PAIR = 2
HIGH = 1
class Card
attr_accessor :value, :suit
def initialize(value = nil, suit = nil)
@value = value if (0..ACE).include?value
@suit = suit if (SPADE..CLUB).include?suit
end
def > (card); @value > card.value; end
def >= (card); @value >= card.value; end
def < (card); @value < card.value; end
def <= (card); @value < card.value; end
def == (card); @value == card.value &&
@suit == card.suit; end
end
class Hand < Array
def > (hand); value > hand.value; end
def >= (hand); value >= hand.value; end
def < (hand); value < hand.value; end
def <= (hand); value < hand.value; end
def == (hand); value == hand.value; end
def sort_by_value
sort do |card_a, card_b|
card_b.value <=> card_a.value
end
end
def cards_by_value(value)
Hand[*select{ |card| card.value == value }]
end
def cards_by_suit(suit)
Hand[*select{ |card| card.suit == suit }]
end
def ranked; rank; @ranked; end
def value; rank; @value; end
def type; rank; @type; end
def valid?
not self.empty?
end
def to_s
faces = ["T", "J", "Q", "K", "A"]
suits = ["\u2660", "\u2665", "\u2666", "\u2663"]
hand = ""
self.each do |card|
value = card.value > 9 ? faces[card.value - 10] : card.value.to_s
suit = suits[card.suit - 1]
hand += "#{value}#{suit} "
end
hand.strip
end
private
def rank
pack = Proc.new do |hand, type, value|
@ranked, @type, @value = hand, type, value; self
end
# 221 - 230
if (hand = find_str_flush).valid?
return pack.call hand, STRFLUSH, hand.first.value + 216 + kick(hand)
end
# 208 - 220
if (hand = find_four).valid?
return pack.call hand, FOUR, hand.first.value + 206 + kick(hand)
end
# 130 - 207
if (hand = find_full).valid?
three = hand.first.value
pair = hand[3].value
return pack.call(
hand, FULL,
((three - 3) ** 2 + (three - 3)) / 2 + (pair - 2) + 130 + kick(hand)
)
end
# 129
if (hand = find_flush).valid?
return pack.call hand, FLUSH, 129 + kick(hand)
end
# 119 - 128
if (hand = find_str).valid?
return pack.call hand, STR, hand.first.value + 114 + kick(hand)
end
# 106 - 118
if (hand = find_three).valid?
return pack.call hand, THREE, hand.first.value + 104 + kick(hand)
end
# 028 - 105
if (hand = find_two).valid?
pair_1 = hand.first.value
pair_2 = hand[2].value
return pack.call(
hand, TWO,
((pair_1 - 3) ** 2 + (pair_1 - 3)) / 2 + (pair_2 - 2) + 28 + kick(hand)
)
end
# 015 - 027
if (hand = find_pair).valid?
return pack.call hand, PAIR, hand.first.value + 13 + kick(hand)
end
# 002 - 014
card = sort_by_value.first
return pack.call hand, HIGH, card.value + kick(Hand[card])
end
def unique_cards(cards = self)
Hand[*cards].sort_by_value.uniq { |card| card.value }
end
def values_by_count(limit)
counts = Array.new(13, 0)
values = []
each { |card| counts[card.value - 2] += 1 }
counts.each_with_index do |count, value|
values << value + 2 if count == limit
end
values.reverse
end
def cards_by_count(limit)
values = values_by_count limit
Hand[*sort_by_value.select{ |card| values.include? card.value }]
end
def find_pair
cards_by_value values_by_count(2).first
end
def find_two
cards = cards_by_count(2)
cards.count > 3 ? cards[0..3] : Hand[]
end
def find_three
cards_by_value values_by_count(3).first
end
def find_four
cards_by_value values_by_count(4).first
end
def find_full
pair = find_pair
three = cards_by_count 3
if three.count > 3
three[0..4]
elsif not (pair.empty? || three.empty?)
Hand[*three + pair]
else
Hand[]
end
end
def find_str(cards = self, limit = 5)
cards = unique_cards Hand[*cards]
return Hand[] if cards.count < limit
(cards.count - (limit - 1)).times do |i|
if cards[i].value - cards[i + (limit - 1)].value == limit - 1
return cards[i..(i + limit - 1)]
end
end
if cards.first.value == ACE &&
cards[1 - limit].value == limit &&
cards[1 - limit].value - cards[-1].value == limit - 2
return Hand[*cards[(1 - limit)..-1] + [cards.first]]
end
return Hand[]
end
def flush?(limit = 5)
counts = Array.new(4, 0)
each do |card|
suit = card.suit - 1
counts[suit] += 1
return card.suit if counts[suit] == limit
end
false
end
def find_raw_flush(limit = 5)
suit = flush?
return suit ? cards_by_suit(suit) : Hand[]
end
def find_flush(limit = 5)
flush = find_raw_flush
flush.sort_by_value[0..(limit - 1)]
end
def find_str_flush(limit = 5)
find_str(find_raw_flush)
end
def kick(cards = self, hand)
diff = Hand[]
cards.each do |card_1|
same = false
hand.each do |card_2|
same = true if card_1 == card_2
end
diff << card_1 if not same
end
value = '0.'
diff.sort_by_value.each do |card|
value += card.value > 9 ? card.value.to_s : "0#{card.value}"
end
return value.empty? ? 0.0 : value.to_f
end
end
end
# Test
if __FILE__ == $0
hand_1 = Poker::Hand[
Poker::Card.new(Poker::JACK, Poker::CLUB),
Poker::Card.new(Poker::JACK, Poker::DIAMOND),
Poker::Card.new(Poker::ACE, Poker::SPADE),
Poker::Card.new(Poker::ACE, Poker::CLUB),
Poker::Card.new(Poker::ACE, Poker::DIAMOND),
Poker::Card.new(5, Poker::CLUB),
Poker::Card.new(4, Poker::DIAMOND)
]
hand_2 = Poker::Hand[
Poker::Card.new(5, Poker::CLUB),
Poker::Card.new(4, Poker::DIAMOND),
Poker::Card.new(3, Poker::SPADE),
Poker::Card.new(2, Poker::CLUB),
Poker::Card.new(Poker::ACE, Poker::CLUB),
Poker::Card.new(Poker::JACK, Poker::CLUB),
Poker::Card.new(Poker::JACK, Poker::DIAMOND)
]
puts "[#{hand_1.ranked.to_s}] > [#{hand_2.ranked.to_s}] = #{hand_1 > hand_2}"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.