use crate::core::card::Card;
use super::{FlatHand, Hand};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Copy)]
pub enum Rank {
HighCard(u32),
OnePair(u32),
TwoPair(u32),
ThreeOfAKind(u32),
Straight(u32),
Flush(u32),
FullHouse(u32),
FourOfAKind(u32),
StraightFlush(u32),
}
const WHEEL: u32 = 0b1_0000_0000_1111;
fn rank_straight(value_set: u32) -> Option<u32> {
let left =
value_set & (value_set << 1) & (value_set << 2) & (value_set << 3) & (value_set << 4);
let idx = left.leading_zeros();
if idx < 32 {
Some(32 - 4 - idx)
} else if value_set & WHEEL == WHEEL {
Some(0)
} else {
None
}
}
fn keep_highest(rank: u32) -> u32 {
1 << (32 - rank.leading_zeros() - 1)
}
fn keep_n(rank: u32, to_keep: u32) -> u32 {
let mut result = rank;
while result.count_ones() > to_keep {
result &= result - 1;
}
result
}
fn find_flush(suit_value_sets: &[u32]) -> Option<usize> {
suit_value_sets.iter().position(|sv| sv.count_ones() >= 5)
}
pub trait Rankable {
fn cards(&self) -> impl Iterator<Item = Card>;
fn rank(&self) -> Rank {
let mut value_to_count: [u8; 13] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let mut count_to_value: [u32; 5] = [0, 0, 0, 0, 0];
let mut suit_value_sets: [u32; 4] = [0, 0, 0, 0];
let mut value_set: u32 = 0;
for c in self.cards() {
let v = c.value as u8;
let s = c.suit as u8;
value_set |= 1 << v;
value_to_count[v as usize] += 1;
suit_value_sets[s as usize] |= 1 << v;
}
for (value, &count) in value_to_count.iter().enumerate() {
count_to_value[count as usize] |= 1 << value;
}
let flush: Option<usize> = find_flush(&suit_value_sets);
if let Some(flush_idx) = flush {
if let Some(rank) = rank_straight(suit_value_sets[flush_idx]) {
Rank::StraightFlush(rank)
} else {
let rank = keep_n(suit_value_sets[flush_idx], 5);
Rank::Flush(rank)
}
} else if count_to_value[4] != 0 {
let high = keep_highest(value_set ^ count_to_value[4]);
Rank::FourOfAKind((count_to_value[4] << 13) | high)
} else if count_to_value[3] != 0 && count_to_value[3].count_ones() == 2 {
let set = keep_highest(count_to_value[3]);
let pair = count_to_value[3] ^ set;
Rank::FullHouse((set << 13) | pair)
} else if count_to_value[3] != 0 && count_to_value[2] != 0 {
let set = count_to_value[3];
let pair = keep_highest(count_to_value[2]);
Rank::FullHouse((set << 13) | pair)
} else if let Some(s_rank) = rank_straight(value_set) {
Rank::Straight(s_rank)
} else if count_to_value[3] != 0 {
let low = keep_n(value_set ^ count_to_value[3], 2);
Rank::ThreeOfAKind((count_to_value[3] << 13) | low)
} else if count_to_value[2].count_ones() >= 2 {
let pairs = keep_n(count_to_value[2], 2);
let low = keep_highest(value_set ^ pairs);
Rank::TwoPair((pairs << 13) | low)
} else if count_to_value[2] == 0 {
Rank::HighCard(keep_n(value_set, 5))
} else {
let pair = count_to_value[2];
let low = keep_n(value_set ^ count_to_value[2], 3);
Rank::OnePair((pair << 13) | low)
}
}
fn rank_five(&self) -> Rank {
let mut suit_set: u32 = 0;
let mut value_set: u32 = 0;
let mut value_to_count: [u8; 13] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let mut count_to_value: [u32; 5] = [0, 0, 0, 0, 0];
for c in self.cards() {
let v = c.value as u8;
let s = c.suit as u8;
suit_set |= 1 << s;
value_set |= 1 << v;
value_to_count[v as usize] += 1;
}
for (value, &count) in value_to_count.iter().enumerate() {
count_to_value[count as usize] |= 1 << value;
}
let unique_card_count = value_set.count_ones();
match unique_card_count {
5 => {
let suit_count = suit_set.count_ones();
let is_flush = suit_count == 1;
match (rank_straight(value_set), is_flush) {
(None, false) => Rank::HighCard(value_set),
(Some(rank), false) => Rank::Straight(rank),
(None, true) => Rank::Flush(value_set),
(Some(rank), true) => Rank::StraightFlush(rank),
}
}
4 => {
let major_rank = count_to_value[2];
let minor_rank = value_set ^ major_rank;
Rank::OnePair((major_rank << 13) | minor_rank)
}
3 => {
let three_value = count_to_value[3];
if three_value > 0 {
let major_rank = three_value;
let minor_rank = value_set ^ major_rank;
Rank::ThreeOfAKind((major_rank << 13) | minor_rank)
} else {
let major_rank = count_to_value[2];
let minor_rank = value_set ^ major_rank;
Rank::TwoPair((major_rank << 13) | minor_rank)
}
}
2 => {
let three_value = count_to_value[3];
if three_value > 0 {
let major_rank = three_value;
let minor_rank = value_set ^ major_rank;
Rank::FullHouse((major_rank << 13) | minor_rank)
} else {
let major_rank = count_to_value[4];
let minor_rank = value_set ^ major_rank;
Rank::FourOfAKind((major_rank << 13) | minor_rank)
}
}
_ => unreachable!(),
}
}
}
impl Rankable for FlatHand {
fn cards(&self) -> impl Iterator<Item = Card> {
self.iter().copied()
}
}
impl Rankable for Vec<Card> {
fn cards(&self) -> impl Iterator<Item = Card> {
self.iter().copied()
}
}
impl Rankable for [Card] {
fn cards(&self) -> impl Iterator<Item = Card> {
self.iter().copied()
}
}
impl Rankable for &[Card] {
fn cards(&self) -> impl Iterator<Item = Card> {
self.iter().copied()
}
}
impl Rankable for Hand {
fn cards(&self) -> impl Iterator<Item = Card> {
self.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::card::*;
use crate::core::flat_hand::*;
#[test]
fn test_keep_highest() {
assert_eq!(0b100, keep_highest(0b111));
}
#[test]
fn test_keep_n() {
assert_eq!(3, keep_n(0b1111, 3).count_ones());
}
#[test]
fn test_cmp() {
assert!(Rank::HighCard(0) < Rank::StraightFlush(0));
assert!(Rank::HighCard(0) < Rank::FourOfAKind(0));
assert!(Rank::HighCard(0) < Rank::ThreeOfAKind(0));
}
#[test]
fn test_cmp_high() {
assert!(Rank::HighCard(0) < Rank::HighCard(100));
}
#[test]
fn test_high_card_hand() {
let hand = FlatHand::new_from_str("Ad8h9cTc5c").unwrap();
let rank = (1 << Value::Ace as u32)
| (1 << Value::Eight as u32)
| (1 << Value::Nine as u32)
| (1 << Value::Ten as u32)
| (1 << Value::Five as u32);
assert!(Rank::HighCard(rank) == hand.rank_five());
}
#[test]
fn test_can_rank_two_card_hand() {
let hand = FlatHand::new_from_str("Ad8h").unwrap();
let rank = (1 << Value::Ace as u32) | (1 << Value::Eight as u32);
assert!(Rank::HighCard(rank) == hand.rank());
}
#[test]
fn test_flush() {
let hand = FlatHand::new_from_str("Ad8d9dTd5d").unwrap();
let rank = (1 << Value::Ace as u32)
| (1 << Value::Eight as u32)
| (1 << Value::Nine as u32)
| (1 << Value::Ten as u32)
| (1 << Value::Five as u32);
assert!(Rank::Flush(rank) == hand.rank_five());
}
#[test]
fn test_full_house() {
let hand = FlatHand::new_from_str("AdAc9d9c9s").unwrap();
let rank = ((1 << (Value::Nine as u32)) << 13) | (1 << (Value::Ace as u32));
assert!(Rank::FullHouse(rank) == hand.rank_five());
}
#[test]
fn test_two_pair() {
let hand = FlatHand::new_from_str("AdAc9D9cTs").unwrap();
let rank = (((1 << Value::Ace as u32) | (1 << Value::Nine as u32)) << 13)
| (1 << Value::Ten as u32);
assert!(Rank::TwoPair(rank) == hand.rank_five());
}
#[test]
fn test_one_pair() {
let hand = FlatHand::new_from_str("AdAc9d8cTs").unwrap();
let rank = ((1 << Value::Ace as u32) << 13)
| (1 << Value::Nine as u32)
| (1 << Value::Eight as u32)
| (1 << Value::Ten as u32);
assert!(Rank::OnePair(rank) == hand.rank_five());
}
#[test]
fn test_four_of_a_kind() {
let hand = FlatHand::new_from_str("AdAcAsAhTs").unwrap();
assert!(
Rank::FourOfAKind((1 << (Value::Ace as u32) << 13) | (1 << (Value::Ten as u32)))
== hand.rank_five()
);
}
#[test]
fn test_wheel() {
let hand = FlatHand::new_from_str("Ad2c3s4h5s").unwrap();
assert!(Rank::Straight(0) == hand.rank_five());
}
#[test]
fn test_straight() {
let hand = FlatHand::new_from_str("2c3s4h5s6d").unwrap();
assert!(Rank::Straight(1) == hand.rank_five());
}
#[test]
fn test_three_of_a_kind() {
let hand = FlatHand::new_from_str("2c2s2h5s6d").unwrap();
let rank = ((1 << (Value::Two as u32)) << 13)
| (1 << (Value::Five as u32))
| (1 << (Value::Six as u32));
assert!(Rank::ThreeOfAKind(rank) == hand.rank_five());
}
#[test]
fn test_rank_seven_straight_flush() {
let h = FlatHand::new_from_str("AdKdQdJdTd9d8d").unwrap();
assert_eq!(Rank::StraightFlush(9), h.rank());
}
#[test]
fn test_rank_seven_straight_flush_wheel() {
let h = FlatHand::new_from_str("2d3d4d5d6h7cAd").unwrap();
assert_eq!(Rank::StraightFlush(0), h.rank());
}
#[test]
fn test_rank_seven_straights() {
let straights = [
"2h3c4s5d6dTsKh",
"3c4s5d6d7hTsKh",
"4s5d6d7h8cTsKh",
"5c6c7h8h9dAhAd",
"6c7c8h9hTsKc6s",
"7c8h9hTsKc6sJh",
"8h9hTsQc6sJhAs",
"9hTsQc6sJhKsKc",
"TsQc6sJhKsAc5h",
];
for (idx, s) in straights.iter().enumerate() {
assert_eq!(
Rank::Straight(idx as u32 + 1),
FlatHand::new_from_str(s).unwrap().rank()
);
}
}
#[test]
fn test_rank_seven_find_best_with_wheel() {
let h = FlatHand::new_from_str("6dKdAd2d5d4d3d").unwrap();
assert_eq!(Rank::StraightFlush(1), h.rank());
}
#[test]
fn test_rank_seven_four_kind() {
let h = FlatHand::new_from_str("2s2h2d2cKd9h4s").unwrap();
let four_rank = (1 << Value::Two as u32) << 13;
let low_rank = 1 << Value::King as u32;
assert_eq!(Rank::FourOfAKind(four_rank | low_rank), h.rank());
}
#[test]
fn test_rank_seven_four_plus_set() {
let h = FlatHand::new_from_str("2s2h2d2c8d8s8c").unwrap();
let four_rank = (1 << Value::Two as u32) << 13;
let low_rank = 1 << Value::Eight as u32;
assert_eq!(Rank::FourOfAKind(four_rank | low_rank), h.rank());
}
#[test]
fn test_rank_seven_full_house_two_sets() {
let h = FlatHand::new_from_str("As2h2d2c8d8s8c").unwrap();
let set_rank = (1 << Value::Eight as u32) << 13;
let low_rank = 1 << Value::Two as u32;
assert_eq!(Rank::FullHouse(set_rank | low_rank), h.rank());
}
#[test]
fn test_rank_seven_full_house_two_pair() {
let h = FlatHand::new_from_str("2h2d2c8d8sKdKs").unwrap();
let set_rank = (1 << Value::Two as u32) << 13;
let low_rank = 1 << Value::King as u32;
assert_eq!(Rank::FullHouse(set_rank | low_rank), h.rank());
}
#[test]
fn test_two_pair_from_three_pair() {
let h = FlatHand::new_from_str("2h2d8d8sKdKsTh").unwrap();
let pair_rank = ((1 << Value::King as u32) | (1 << Value::Eight as u32)) << 13;
let low_rank = 1 << Value::Ten as u32;
assert_eq!(Rank::TwoPair(pair_rank | low_rank), h.rank());
}
#[test]
fn test_rank_seven_two_pair() {
let h = FlatHand::new_from_str("2h2d8d8sKd6sTh").unwrap();
let pair_rank = ((1 << Value::Two as u32) | (1 << Value::Eight as u32)) << 13;
let low_rank = 1 << Value::King as u32;
assert_eq!(Rank::TwoPair(pair_rank | low_rank), h.rank());
}
}