Skip to content

Instantly share code, notes, and snippets.

@petertseng
Last active August 29, 2015 14:25
Show Gist options
  • Save petertseng/184e82bed2200ecefc47 to your computer and use it in GitHub Desktop.
Save petertseng/184e82bed2200ecefc47 to your computer and use it in GitHub Desktop.
poker hands in rust and ruby
class Card
attr_reader :rank, :suit
def initialize(rank, suit)
@rank = rank
@suit = suit
end
def to_s
"#{@rank} of #{@suit}"
end
end
def order(hand)
case hand
when :five_of_a_kind; 100
when :royal_flush; 90
when :straight_flush; 80
when :four_of_a_kind; 70
when :full_house; 60
when :flush; 50
when :straight; 40
when :three_of_a_kind; 30
when :two_pair; 20
when :pair; 10
when :nothing; 0
else; raise "Unknown hand #{hand}"
end
end
def eval_hand(ranks, num_wilds, flush)
rank_freqs = ranks.each_with_object(Hash.new(0)) { |rank, freq| freq[rank] += 1 }
count_freqs = rank_freqs.each_with_object(Hash.new(0)) { |(_, value), freq| freq[value] += 1 }
ranks_distinct = count_freqs[1] == ranks.size
if num_wilds == 0
royal_straight = ranks == [1, 10, 11, 12, 13]
straight = ranks_distinct && ranks.last - ranks.first == 4
else
# Ranks are all distinct, and wild cards can make up all the remaining cards needed.
royal_straight = ranks_distinct && ([1, 10, 11, 12, 13] - ranks).size == num_wilds
gap = ranks.last - ranks.first - 1
# Straight with wilds outside: the ranked cards just need to be consecutive.
# That means ranks.last - ranks.first == ranks.size - 1
# Or to use the gap variable, gap == ranks.size - 2
# Straight with wilds inside: the ranked cards are all distinct,
# and the wild cards can fill the gap (gap <= 3).
# (there may be wild s outside too, that's fine!)
# If gap > 3 (for example, 7 to 2 has 4 gap) we can't make a straight.
straight = ranks_distinct && (gap <= 3 || gap == ranks.size - 2)
end
return :five_of_a_kind if count_freqs[5 - num_wilds] > 0
return :royal_flush if royal_straight && flush
return :straight_flush if straight && flush
return :four_of_a_kind if count_freqs[4 - num_wilds] > 0
# Won't happen with >= 2 wilds (would become 4 of a kind).
return :full_house if count_freqs[3] > 0 && count_freqs[2] > 0
return :full_house if count_freqs[2] == 2 && num_wilds == 1
return :flush if flush
return :straight if straight || royal_straight
return :three_of_a_kind if count_freqs[3 - num_wilds] > 0
# Won't happen with any wilds (would become 3 of a kind).
return :two_pair if count_freqs[2] == 2
return :pair if count_freqs[2 - num_wilds] > 0
:nothing
end
class FreqInfo
attr_reader :total
def initialize
@by_wilds = [0, 0, 0, 0, 0]
@total = 0
end
def incr(wilds)
@by_wilds[wilds] += 1
@total += 1
end
def [](x)
@by_wilds[x]
end
end
class Counter
attr_reader :num_wilds
attr_reader :cache_misses
attr_reader :cache_hits
def initialize(wild_rank, jokers, flushes_allowed)
@cache_misses = 0
@cache_hits = 0
@cache = {true => {}, false => {}}
@flushes_allowed = flushes_allowed
@jokers = jokers
@wild_rank = wild_rank
@num_wilds = wild_rank ? (wild_rank == :joker ? jokers : 4) : 0
end
def count
suits = [:spades, :clubs, :diamonds, :hearts]
deck = (1..13).flat_map { |rank| suits.map { |suit| Card.new(rank, suit) }}
@jokers.times { deck << Card.new(:joker, :joker) }
deck.combination(5).each_with_object(Hash.new { |h, k| h[k] = FreqInfo.new }) { |cards, freq|
if @wild_rank
wilds, non_wilds = cards.partition { |c| c.rank == @wild_rank }
num_wilds = wilds.size
else
num_wilds, non_wilds = 0, cards
end
ranks = non_wilds.map(&:rank)
if @flushes_allowed
first_suit = non_wilds.first.suit
flush = non_wilds.all? { |nw| nw.suit == first_suit }
else
flush = false
end
cache_key = ranks.inject(0) { |key, rank| key * 14 + rank }
if (hand = @cache[flush][cache_key])
@cache_hits += 1
else
hand = eval_hand(ranks, num_wilds, flush)
@cache[flush][cache_key] = hand
@cache_misses += 1
end
freq[hand].incr(num_wilds)
}
end
end
counter = Counter.new(nil, 0, true)
counter.count.to_a.sort_by { |rank, info| -order(rank) }.each { |rank, info|
cells = (0..counter.num_wilds).map { |wilds| '%7d' % info[wilds] }
puts ('%18s ' % rank) + cells.join(' ') + (' %7d' % info.total)
}
puts counter.cache_hits
puts counter.cache_misses
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::fmt::{Display, Error, Formatter};
#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
enum Hand {
FiveOfAKind,
RoyalFlush,
StraightFlush,
FourOfAKind,
FullHouse,
Flush,
Straight,
ThreeOfAKind,
TwoPair,
Pair,
HighCard,
}
impl Display for Hand {
fn fmt(&self, format: &mut Formatter) -> Result<(), Error> {
let string = match *self {
Hand::FiveOfAKind => "Five of a Kind",
Hand::RoyalFlush => "Royal Flush",
Hand::StraightFlush => "Straight Flush",
Hand::FourOfAKind => "Four of a Kind",
Hand::FullHouse => "Full House",
Hand::Flush => "Flush",
Hand::Straight => "Straight",
Hand::ThreeOfAKind => "Three of a Kind",
Hand::TwoPair => "Two Pair",
Hand::Pair => "Pair",
Hand::HighCard => "High Card",
};
format.pad(string)
}
}
type Rank = u8;
#[derive(Copy, Clone, Eq, PartialEq)]
enum Suit {
Clubs,
Diamonds,
Hearts,
Spades,
Joker,
}
#[derive(Copy, Clone)]
struct Card {
suit: Suit,
rank: Rank,
}
struct HandFrequency {
by_num_wilds: Vec<usize>,
total: usize,
}
impl HandFrequency {
fn new() -> HandFrequency {
HandFrequency {
by_num_wilds: vec![0, 0, 0, 0, 0],
total: 0,
}
}
fn add(&mut self, num_wilds: usize) {
self.by_num_wilds[num_wilds] += 1;
self.total += 1;
}
}
type HandKey = u32;
struct HandCounter {
freqs: BTreeMap<Hand, HandFrequency>,
cache: HashMap<HandKey, Hand>,
flush_allowed: bool,
wild_rank: Option<Rank>,
}
impl HandCounter {
fn new() -> HandCounter {
HandCounter {
// TODO: Jokers wild
freqs: BTreeMap::new(),
cache: HashMap::new(),
flush_allowed: true,
wild_rank: None,
}
}
fn num_wilds(&self) -> usize {
match self.wild_rank {
// TODO: Jokers wild
Some(_) => 4,
None => 0,
}
}
fn count(count_ranks: &HashMap<usize, usize>, n: usize) -> usize {
count_ranks.get(&n).map_or(0, |x| *x)
}
fn eval_hand(ranks: Vec<Rank>, num_wilds: usize, flush: bool) -> Hand {
// How many of each rank are there?
let mut rank_freqs = HashMap::new();
for rank in &ranks {
*rank_freqs.entry(*rank).or_insert(0) += 1;
}
// How many N-of-a-kinds do we have?
let mut count_ranks = HashMap::new();
for count in rank_freqs.values() {
*count_ranks.entry(*count).or_insert(0) += 1;
}
let ranks_distinct = count_ranks.get(&1).map_or(false, |x| *x == ranks.len());
let royal_straight = if num_wilds == 0 {
ranks == vec![1, 10, 11, 12, 13]
} else {
ranks_distinct && ranks.iter().all(|x| *x == 1 || *x >= 10)
};
let straight = if num_wilds == 0 {
ranks_distinct && ranks[4] - ranks[0] == 4
} else {
let gap = ranks[ranks.len() - 1] - ranks[0] - 1;
// Straight with wilds outside: The ranked cards must be consecutive.
// That means ranks.last - ranks.first == ranks.size - 1
// Straight with wilds inside: The wild cards can fill the gap.
// There may be wilds outside too, that's fine.
// If gap > 3 (for example, 7 to 2 has 4 gap) we can't make a straight.
ranks_distinct && (gap <= 3 || gap == ((ranks.len() - 2) as Rank))
};
if HandCounter::count(&count_ranks, 5 - num_wilds) > 0 { return Hand::FiveOfAKind }
if royal_straight && flush { return Hand::RoyalFlush }
if straight && flush { return Hand::StraightFlush }
if HandCounter::count(&count_ranks, 4 - num_wilds) > 0 { return Hand::FourOfAKind }
// Won't happen with >= 2 wilds (would become 4 of a kind).
if HandCounter::count(&count_ranks, 3) > 0 && HandCounter::count(&count_ranks, 2) > 0 { return Hand::FullHouse; }
if HandCounter::count(&count_ranks, 2) == 2 && num_wilds == 1 { return Hand::FullHouse; }
if flush { return Hand::Flush }
if straight || royal_straight { return Hand::Straight }
if HandCounter::count(&count_ranks, 3 - num_wilds) > 0 { return Hand::ThreeOfAKind }
// Won't happen with any wilds (would become 3 of a kind)
if HandCounter::count(&count_ranks, 2) == 2 { return Hand::TwoPair }
if HandCounter::count(&count_ranks, 2 - num_wilds) > 0 { return Hand::Pair }
Hand::HighCard
}
fn add_hand(&mut self, hand: [&Card; 5]) {
let (num_wilds, non_wilds) = match self.wild_rank {
Some(x) => {
// No partition(), I don't care what the wilds are.
let mut non_wilds = Vec::new();
let mut num_wilds = 0;
for card in hand.iter() {
if card.rank == x {
num_wilds += 1;
} else {
non_wilds.push(card);
}
}
(num_wilds, non_wilds)
},
None => (0, hand.iter().collect()),
};
let flush = self.flush_allowed && non_wilds.iter().all(|card| card.suit == non_wilds[0].suit);
let hand_key = (hand[0].rank as HandKey) | (hand[1].rank as HandKey) << 4 | (hand[2].rank as HandKey) << 8 | (hand[3].rank as HandKey) << 12 | (hand[4].rank as HandKey) << 16 | if flush { 1 << 20 } else { 0 };
let hand_type = *self.cache.entry(hand_key).or_insert_with(|| HandCounter::eval_hand(non_wilds.iter().map(|card| card.rank).collect(), num_wilds, flush));
self.freqs.entry(hand_type).or_insert(HandFrequency::new()).add(num_wilds);
}
}
fn main() {
let cards: Vec<_> = (1..14).flat_map(|i| vec![
Card {suit: Suit::Clubs, rank: i},
Card {suit: Suit::Diamonds, rank: i},
Card {suit: Suit::Hearts, rank: i},
Card {suit: Suit::Spades, rank: i},
].into_iter()).collect();
let mut hc = HandCounter::new();
// Interesting...
for index0 in 0..cards.len() {
for index1 in (index0 + 1)..cards.len() {
for index2 in (index1 + 1)..cards.len() {
for index3 in (index2 + 1)..cards.len() {
for index4 in (index3 + 1)..cards.len() {
hc.add_hand([&cards[index0], &cards[index1], &cards[index2], &cards[index3], &cards[index4]])
}
}
}
}
}
let num_wilds = hc.num_wilds();
for (hand, frequency) in hc.freqs.iter() {
print!("{: >15}:", hand);
if num_wilds > 0 {
for wilds in 0..(num_wilds + 1) {
print!(" {:>7}", frequency.by_num_wilds[wilds]);
}
}
println!(" {:>7}", frequency.total);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment