use crate::core::{CardBitSet, CardIter, Hand, RSPokerError, Rank, Rankable};
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct OmahaHand {
hole: CardBitSet,
board: CardBitSet,
}
impl OmahaHand {
pub fn new(hole: CardBitSet, board: CardBitSet) -> Result<Self, RSPokerError> {
let hole_count = hole.count();
if !(2..=7).contains(&hole_count) {
return Err(RSPokerError::OmahaHoleCardCount(hole_count));
}
let board_count = board.count();
if !(3..=5).contains(&board_count) {
return Err(RSPokerError::OmahaBoardCardCount(board_count));
}
if !(hole & board).is_empty() {
return Err(RSPokerError::OmahaOverlappingCards);
}
Ok(Self { hole, board })
}
pub fn new_from_str(hole_str: &str, board_str: &str) -> Result<Self, RSPokerError> {
let hole: CardBitSet = Hand::new_from_str(hole_str)?.into();
let board: CardBitSet = Hand::new_from_str(board_str)?.into();
Self::new(hole, board)
}
pub fn hole(&self) -> CardBitSet {
self.hole
}
pub fn board(&self) -> CardBitSet {
self.board
}
}
impl Rankable for OmahaHand {
fn rank(&self) -> Rank {
CardIter::new(self.hole, 2)
.flat_map(|hole| CardIter::new(self.board, 3).map(move |board| (hole | board).rank()))
.max()
.unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{Card, CoreRank, Hand, Rank, Rankable, Suit, Value};
#[test]
fn test_new_valid() {
let hand = OmahaHand::new_from_str("AhAsKhKs", "QhJhTh9h8h");
assert!(hand.is_ok());
}
#[test]
fn test_new_too_few_hole_cards() {
let mut hole = CardBitSet::new();
hole.insert(Card::new(Value::Ace, Suit::Spade));
let mut board = CardBitSet::new();
for c in [
Card::new(Value::Two, Suit::Heart),
Card::new(Value::Three, Suit::Heart),
Card::new(Value::Four, Suit::Heart),
] {
board.insert(c);
}
assert!(matches!(
OmahaHand::new(hole, board),
Err(RSPokerError::OmahaHoleCardCount(1))
));
}
#[test]
fn test_new_too_few_board_cards() {
let mut hole = CardBitSet::new();
hole.insert(Card::new(Value::Ace, Suit::Spade));
hole.insert(Card::new(Value::King, Suit::Spade));
let mut board = CardBitSet::new();
board.insert(Card::new(Value::Two, Suit::Heart));
board.insert(Card::new(Value::Three, Suit::Heart));
assert!(matches!(
OmahaHand::new(hole, board),
Err(RSPokerError::OmahaBoardCardCount(2))
));
}
#[test]
fn test_new_overlapping_cards() {
let shared = Card::new(Value::Ace, Suit::Spade);
let mut hole = CardBitSet::new();
hole.insert(shared);
hole.insert(Card::new(Value::King, Suit::Spade));
let mut board = CardBitSet::new();
board.insert(shared);
board.insert(Card::new(Value::Two, Suit::Heart));
board.insert(Card::new(Value::Three, Suit::Heart));
assert!(matches!(
OmahaHand::new(hole, board),
Err(RSPokerError::OmahaOverlappingCards)
));
}
#[test]
fn test_new_from_str_duplicate_in_hole() {
let result = OmahaHand::new_from_str("AhAhKsQs", "2h3h4h5h6h");
assert!(matches!(result, Err(RSPokerError::DuplicateCardInHand(_))));
}
#[test]
fn test_issue_33_example() {
let hand = OmahaHand::new_from_str("AhAsKhKs", "QhJhTh9h8h").unwrap();
let rank = hand.rank();
let best_hand: CardBitSet = Hand::new_from_str("AhKhQhJhTh").unwrap().into();
assert_eq!(rank, best_hand.rank());
}
#[test]
fn test_straight_flush() {
let hand = OmahaHand::new_from_str("9s8s7c6c", "Ts7s6s5d4d").unwrap();
let rank = hand.rank();
assert_eq!(CoreRank::StraightFlush, CoreRank::from(rank));
}
#[test]
fn test_four_of_a_kind() {
let hand = OmahaHand::new_from_str("AsAh9c2c", "AcAdKs3h4h").unwrap();
let rank = hand.rank();
assert_eq!(CoreRank::FourOfAKind, CoreRank::from(rank));
}
#[test]
fn test_full_house() {
let hand = OmahaHand::new_from_str("AsAhKsQd", "AdKdKc3h4h").unwrap();
let rank = hand.rank();
assert_eq!(CoreRank::FullHouse, CoreRank::from(rank));
}
#[test]
fn test_flush() {
let hand = OmahaHand::new_from_str("AhKh2s3s", "Qh9h5h4d6d").unwrap();
let rank = hand.rank();
assert_eq!(CoreRank::Flush, CoreRank::from(rank));
}
#[test]
fn test_straight() {
let hand = OmahaHand::new_from_str("AhKs2c3d", "QdJhTc9s4h").unwrap();
let rank = hand.rank();
assert_eq!(CoreRank::Straight, CoreRank::from(rank));
let wheel = OmahaHand::new_from_str("Ah2s9c8c", "3d4d5hKcQc")
.unwrap()
.rank();
assert!(rank > wheel);
}
#[test]
fn test_must_use_exactly_two_hole_cards() {
let hand = OmahaHand::new_from_str("AhAdAcAs", "KhKd2c3c4c").unwrap();
let rank = hand.rank();
assert_eq!(CoreRank::TwoPair, CoreRank::from(rank));
}
#[test]
fn test_must_use_exactly_three_board_cards() {
let hand = OmahaHand::new_from_str("AhKh2s3s", "QhJhTh9h8d").unwrap();
let rank = hand.rank();
assert_eq!(rank.category(), CoreRank::StraightFlush);
}
#[test]
fn test_board_flush_not_player_flush() {
let hand = OmahaHand::new_from_str("2s3s4d5d", "AhKhQhJh9h").unwrap();
let rank = hand.rank();
assert_ne!(CoreRank::Flush, CoreRank::from(rank));
assert_ne!(CoreRank::StraightFlush, CoreRank::from(rank));
}
#[test]
fn test_plo5_five_hole_cards() {
let hand = OmahaHand::new_from_str("AhAsKhKs9d", "QhJhTh2c3c").unwrap();
assert_eq!(hand.hole().count(), 5);
let rank = hand.rank();
assert_eq!(rank.category(), CoreRank::StraightFlush);
}
#[test]
fn test_plo6_six_hole_cards() {
let hand = OmahaHand::new_from_str("AhAsKhKs9d8d", "QhJhTh2c3c").unwrap();
assert_eq!(hand.hole().count(), 6);
let rank = hand.rank();
assert_eq!(rank.category(), CoreRank::StraightFlush);
}
#[test]
fn test_flop_three_board_cards() {
let hand = OmahaHand::new_from_str("AhAsKhKs", "QhJhTh").unwrap();
let rank = hand.rank();
assert_eq!(rank.category(), CoreRank::StraightFlush);
}
#[test]
fn test_turn_four_board_cards() {
let hand = OmahaHand::new_from_str("AhAsKhKs", "QhJhTh2c").unwrap();
let rank = hand.rank();
assert_eq!(rank.category(), CoreRank::StraightFlush);
}
#[test]
fn test_accessors() {
let hand = OmahaHand::new_from_str("AhAsKhKs", "QhJhTh9h8h").unwrap();
assert_eq!(hand.hole().count(), 4);
assert_eq!(hand.board().count(), 5);
}
#[test]
fn test_wheel_straight() {
let hand = OmahaHand::new_from_str("Ah2s9c8c", "3d4d5hKcQc").unwrap();
let rank = hand.rank();
assert_eq!(rank.category(), CoreRank::Straight);
let six_high = OmahaHand::new_from_str("2s3s9c8c", "4d5d6hKcQc")
.unwrap()
.rank();
assert!(rank < six_high);
}
#[test]
fn test_high_card_only() {
let hand = OmahaHand::new_from_str("2s7d4c9h", "AsKdQh3cJh").unwrap();
let rank = hand.rank();
assert_eq!(CoreRank::HighCard, CoreRank::from(rank));
}
#[test]
fn test_one_pair() {
let hand = OmahaHand::new_from_str("AhKs2c3d", "Ad9h8c7s4d").unwrap();
let rank = hand.rank();
assert_eq!(CoreRank::OnePair, CoreRank::from(rank));
}
#[test]
fn test_rankable_trait_impl() {
let hand = OmahaHand::new_from_str("AhAsKhKs", "QhJhTh9h8h").unwrap();
let rank = hand.rank();
assert_eq!(rank.category(), CoreRank::StraightFlush);
fn rank_any(hand: &impl Rankable) -> Rank {
hand.rank()
}
assert_eq!(rank_any(&hand).category(), CoreRank::StraightFlush);
}
#[test]
fn test_new_too_many_hole_cards() {
let hand = OmahaHand::new_from_str("AhAsKhKs9d8d7c6c", "QhJhTh2s3s");
assert!(matches!(hand, Err(RSPokerError::OmahaHoleCardCount(8))));
}
#[test]
fn test_new_too_many_board_cards() {
let hand = OmahaHand::new_from_str("AhAsKhKs", "QhJhTh9h8h2c");
assert!(matches!(hand, Err(RSPokerError::OmahaBoardCardCount(6))));
}
#[test]
fn test_plo7_seven_hole_cards() {
let hand = OmahaHand::new_from_str("AhAsKhKs9d8d7c", "QdJdTd2s3s");
assert!(hand.is_ok());
assert_eq!(hand.unwrap().hole().count(), 7);
}
#[test]
fn test_rank_comparison_between_hands() {
let hand_a = OmahaHand::new_from_str("AhKh2s3s", "Qh9h5h4d6d").unwrap();
let hand_b = OmahaHand::new_from_str("AhKs2c3d", "QdJhTc9s4h").unwrap();
assert!(hand_a.rank() > hand_b.rank());
}
}