use crate::{deck::{CardMask, Rank, RankMask, Suit}, hand::{Hand, Hand5}};
pub struct RefHand5 {}
impl RefHand5 {
pub fn new() -> Self {
Self { }
}
fn check_straight(ranks: RankMask) -> Option<Rank> {
let rbits = ranks.bits();
let sb4 = rbits << 0;
let sb3 = rbits << 1;
let sb2 = rbits << 2;
let sb1 = rbits << 3;
let sb0 = (rbits << 4) | (rbits >> (13 - 4));
let rbits_conv5 = sb4 & sb3 & sb2 & sb1 & sb0;
if rbits_conv5 != 0 {
let top_rank_index = (16 - 1 - rbits_conv5.leading_zeros()) as u8;
Some(Rank::from_index(top_rank_index))
} else {
None
}
}
fn check_flush(cards: CardMask) -> Option<RankMask> {
Suit::ALL.iter()
.map(|&suit| cards.of_suit(suit).top5())
.max()
}
}
impl Hand5 for RefHand5 {
fn hand5(&self, cards: CardMask) -> Hand {
let ranks = cards.unsuited();
let mut have_suited5 = false;
let mut suits_counts = [0; Suit::NUM];
for suit in Suit::ALL {
suits_counts[suit.index() as usize] = cards.of_suit(suit).count();
if suits_counts[suit.index() as usize] >= 5 {
have_suited5 = true;
}
}
let mut found_straight: Option<Rank> = None;
let mut checked_straight: bool = false;
let mut found_flush: Option<RankMask> = None;
if have_suited5 {
found_straight = Self::check_straight(ranks);
checked_straight = true;
if let Some(_) = found_straight {
let mut best_straight_flush: Option<Rank> = None;
for suit in Suit::ALL {
if suits_counts[suit.index() as usize] >= 5 {
if let Some(top_rank) = Self::check_straight(cards.of_suit(suit)) {
if best_straight_flush.is_none() || top_rank > best_straight_flush.unwrap() {
best_straight_flush = Some(top_rank);
}
}
}
}
if let Some(top_rank) = best_straight_flush {
return Hand::StraightFlush { top: top_rank };
}
}
found_flush = Self::check_flush(cards);
}
let mut best_trip: Option<Rank> = None;
let mut best_pair1: Option<Rank> = None;
let mut best_pair2: Option<Rank> = None;
for rank in Rank::ALL.iter().rev() {
let cards_of_rank_count = cards.of_rank_count(*rank);
if cards_of_rank_count >= 4 {
return Hand::FourOfAKind { quad: *rank, kickers: (cards.unsuited() & RankMask::from(*rank).inverse()).top1() };
} else if cards_of_rank_count >= 3 {
match best_pair1 {
Some(pair) => {
return Hand::FullHouse { trip: *rank, pair: pair };
}
None => {
best_trip = Some(*rank);
}
}
} else if cards_of_rank_count >= 2 {
match best_trip {
Some(trip) => {
return Hand::FullHouse { trip: trip, pair: *rank };
}
_ => {}
}
match best_pair1 {
Some(pair) => {
match best_pair2 {
Some(pair2) => { }
_ => {
best_pair2 = Some(*rank);
}
}
}
_ => {
best_pair1 = Some(*rank);
}
}
}
}
if let Some(flush) = found_flush {
return Hand::Flush { ranks: flush };
}
if !checked_straight {
found_straight = Self::check_straight(ranks);
}
if let Some(straight) = found_straight {
return Hand::Straight { top: straight };
}
if let Some(trip) = best_trip {
assert!(best_pair1.is_none(), "should not have a pair if we have a trip, that should have been caught earlier as a full house");
return Hand::ThreeOfAKind { trip: trip, kickers: (cards.unsuited() & RankMask::from(trip).inverse()).top2() };
}
if let Some(pair1) = best_pair1 {
match best_pair2 {
Some(pair2) => {
return Hand::TwoPair { pairs: RankMask::from_many(&[pair1, pair2]), kickers: (cards.unsuited() & RankMask::from_many(&[pair1, pair2]).inverse()).top1() };
}
_ => {
return Hand::OnePair { pair: pair1, kickers: (cards.unsuited() & RankMask::from(pair1).inverse()).top3() };
}
}
}
Hand::HighCard { kickers: ranks.top5() }
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! assert_hand_eq {
($cards:expr, $expected:expr) => {
let _cards = $cards.into();
let _hr = RefHand5{}.hand5(_cards);
assert_eq!(_hr, $expected);
};
}
macro_rules! assert_hand_ne {
($cards:expr, $expected:expr) => {
let _cards = $cards.into();
let _hr = RefHand5{}.hand5(_cards);
assert_ne!(_hr, $expected);
};
}
macro_rules! assert_hand_lt {
($cards1:expr, $cards2:expr) => {
let _cards1 = $cards1.into();
let _cards2 = $cards2.into();
let _hr1 = RefHand5{}.hand5(_cards1);
let _hr2 = RefHand5{}.hand5(_cards2);
if !(_hr1.1 < _hr2.1) {
panic!("failed assertion: (lhs < rhs)\n\tlhs: {:?}\n\trhs: {:?}", _hr1.1, _hr2.1);
}
};
}
#[test]
fn test_highcard() {
let cards = "2s3h4s5h7s".into();
let _hr = RefHand5{}.hand5(cards);
assert_eq!(_hr, Hand::HighCard { kickers: cards.unsuited() });
assert_hand_eq!("2s3h4s5h7s", Hand::HighCard { kickers: "23457".into() });
assert_hand_ne!("2s3h4s5h7s", Hand::HighCard { kickers: "2345".into() });
assert_hand_ne!("2s3h4s5h7s", Hand::HighCard { kickers: "2".into() });
assert_hand_eq!("2sAh4sJh7s", Hand::HighCard { kickers: "2A4J7".into() });
assert_hand_ne!("2sAh4sJh7s", Hand::HighCard { kickers: "2A4J".into() });
assert_hand_ne!("2sAh4sJh7s", Hand::HighCard { kickers: "2".into() });
assert_hand_ne!("2s2h3c4d5s", Hand::HighCard { kickers: "22345".into() });
}
#[test]
fn test_onepair() {
assert_hand_eq!("2s2h3c4d5s", Hand::OnePair { pair: Rank::Two, kickers: "345".into() });
assert_hand_ne!("2s2h3c4d5s", Hand::OnePair { pair: Rank::Two, kickers: "34".into() });
assert_hand_eq!("AsAh3c4d5s", Hand::OnePair { pair: Rank::Ace, kickers: "345".into() });
assert_hand_ne!("AsAh3c4d5s", Hand::OnePair { pair: Rank::Ace, kickers: "34".into() });
assert_hand_ne!("AsAh3c3d5s", Hand::OnePair { pair: Rank::Ace, kickers: "345".into() });
assert_hand_ne!("AsAh3cAd5s", Hand::OnePair { pair: Rank::Ace, kickers: "34".into() });
}
#[test]
fn test_twopair() {
assert_hand_eq!("2s2h3c3d5s", Hand::TwoPair { pairs: "23".into(), kickers: "5".into() });
assert_hand_ne!("2s2h3c3d5s", Hand::TwoPair { pairs: "23".into(), kickers: "56".into() });
}
#[test]
fn test_threeofakind() {
assert_hand_eq!("2s2h2c3d5s", Hand::ThreeOfAKind { trip: Rank::Two, kickers: "35".into() });
assert_hand_ne!("2s2h2c3d5s", Hand::ThreeOfAKind { trip: Rank::Two, kickers: "356".into() });
}
#[test]
fn test_straight() {
assert_hand_eq!("AsKhQsJhTs", Hand::Straight { top: Rank::Ace });
assert_hand_eq!("2s3h4s5h6s", Hand::Straight { top: Rank::Six });
assert_hand_eq!("2s3h4s5hAs", Hand::Straight { top: Rank::Five });
assert_hand_eq!("2s3h4s5hAcKhQdJhTc", Hand::Straight { top: Rank::Ace });
assert_hand_eq!("2s3h4s5hAsKhQsJh", Hand::Straight { top: Rank::Five });
assert_hand_eq!("2s3h4d5hAsKdQdJh6s", Hand::Straight { top: Rank::Six });
}
#[test]
fn test_flush() {
assert_hand_eq!("2s3s4s5s7s", Hand::Flush { ranks: "23457".into() });
assert_hand_ne!("2s3s4s5s7s", Hand::Flush { ranks: "2345".into() });
assert_hand_eq!("2s3s4s5s7sTsQs", Hand::Flush { ranks: "QT754".into() });
assert_hand_eq!("2c5c8c7c7s9cTcTs", Hand::Flush { ranks: "T9785".into() });
}
#[test]
fn test_fullhouse() {
assert_hand_eq!("2s2h2c3d3s", Hand::FullHouse { trip: Rank::Two, pair: Rank::Three });
assert_hand_ne!("2s2h2c3d3s", Hand::FullHouse { trip: Rank::Two, pair: Rank::Four });
}
#[test]
fn test_fourofakind() {
assert_hand_eq!("2s2h2c2d5s", Hand::FourOfAKind { quad: Rank::Two, kickers: "5".into() });
assert_hand_ne!("2s2h2c2d5s", Hand::FourOfAKind { quad: Rank::Two, kickers: "56".into() });
}
#[test]
fn test_straightflush() {
assert_hand_eq!("2s3s4s5s6s", Hand::StraightFlush { top: Rank::Six });
assert_hand_eq!("6s7s8s9sTs", Hand::StraightFlush { top: Rank::Ten });
assert_hand_eq!("As2s3s4s5s", Hand::StraightFlush { top: Rank::Five });
assert_hand_eq!("KsQsJsTs9s", Hand::StraightFlush { top: Rank::King });
assert_hand_eq!("AsKsQsJsTs2s3s4s5s6s", Hand::StraightFlush { top: Rank::Ace });
assert_hand_eq!("AsQsJsTs2s3s4s5s", Hand::StraightFlush { top: Rank::Five });
assert_hand_eq!("AsQsJsTs2s3s4s5s6s", Hand::StraightFlush { top: Rank::Six });
}
}