rusty_marvin/core/tests/unit_tests.rs

3211 lines
115 KiB
Rust

//! # Unit Tests for Poker Hand Evaluator
//!
//! Focused unit tests for individual components of the poker evaluation system.
//! These tests validate specific algorithms and edge cases in isolation.
//!
//! ## Test Categories
//!
//! - **Hand Rankings**: Test specific hand types and their relative strengths
//! - **Edge Cases**: Test unusual or boundary condition inputs
//! - **Performance**: Validate speed characteristics of key algorithms
//! - **Correctness**: Test against known good values for specific hands
//!
//! ## Test Data
//!
//! Uses carefully constructed test cases with known expected outcomes.
//! All test hands are deterministic and well-documented.
use poker_api::api::card::Card;
use poker_api::api::hand::Hand;
use poker_api::hand_evaluator::LookupHandEvaluator;
/// Test suite for specific hand rankings and their relative strengths.
///
/// These tests verify that the evaluator correctly ranks different hand types
/// and that stronger hands always receive higher rank values than weaker hands.
#[cfg(test)]
mod hand_rankings {
use super::*;
#[test]
fn test_royal_flush_ranking() {
// Tests that royal flush is the strongest possible hand.
//
// Creates a royal flush (A-K-Q-J-T of same suit) and verifies
// it receives the highest possible rank value.
//
// Test Case:
// - Hand: A♠ K♠ Q♠ J♠ T♠
// - Expected: Highest rank value (> 368M)
// - Verification: Stronger than all other hand types
let evaluator = LookupHandEvaluator::new().unwrap();
let mut hand = Hand::new();
hand.add_card(Card::from_string("As").unwrap()).unwrap();
hand.add_card(Card::from_string("Ks").unwrap()).unwrap();
hand.add_card(Card::from_string("Qs").unwrap()).unwrap();
hand.add_card(Card::from_string("Js").unwrap()).unwrap();
hand.add_card(Card::from_string("Ts").unwrap()).unwrap();
let rank = evaluator.rank_hand(&hand);
assert!(
rank > 368_793_253,
"Royal flush should have rank > 368M, got {}",
rank
);
}
#[test]
fn test_straight_flush_vs_four_of_kind() {
/// Tests that straight flush beats four of a kind.
///
/// Compares a straight flush against four of a kind to ensure
/// the straight flush receives a higher rank value.
///
/// # Test Cases
/// - Straight Flush: K♠ Q♠ J♠ T♠ 9♠
/// - Four of a Kind: A♠ A♥ A♦ A♣ K♦
/// - Expected: Straight flush > Four of a kind
let evaluator = LookupHandEvaluator::new().unwrap();
let mut straight_flush = Hand::new();
straight_flush
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
straight_flush
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
straight_flush
.add_card(Card::from_string("Js").unwrap())
.unwrap();
straight_flush
.add_card(Card::from_string("Ts").unwrap())
.unwrap();
straight_flush
.add_card(Card::from_string("9s").unwrap())
.unwrap();
let mut four_of_kind = Hand::new();
four_of_kind
.add_card(Card::from_string("As").unwrap())
.unwrap();
four_of_kind
.add_card(Card::from_string("Ah").unwrap())
.unwrap();
four_of_kind
.add_card(Card::from_string("Ad").unwrap())
.unwrap();
four_of_kind
.add_card(Card::from_string("Ac").unwrap())
.unwrap();
four_of_kind
.add_card(Card::from_string("Kd").unwrap())
.unwrap();
let sf_rank = evaluator.rank_hand(&straight_flush);
let fk_rank = evaluator.rank_hand(&four_of_kind);
assert!(
sf_rank > fk_rank,
"Straight flush should beat four of a kind: {} > {}",
sf_rank,
fk_rank
);
}
#[test]
fn test_full_house_vs_flush() {
/// Tests that full house beats flush.
///
/// # Test Cases
/// - Full House: K♠ K♥ K♦ Q♠ Q♥
/// - Flush: A♠ J♠ 8♠ 5♠ 2♠
/// - Expected: Full house > Flush
let evaluator = LookupHandEvaluator::new().unwrap();
let mut full_house = Hand::new();
full_house
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
full_house
.add_card(Card::from_string("Kh").unwrap())
.unwrap();
full_house
.add_card(Card::from_string("Kd").unwrap())
.unwrap();
full_house
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
full_house
.add_card(Card::from_string("Qh").unwrap())
.unwrap();
let mut flush = Hand::new();
flush.add_card(Card::from_string("As").unwrap()).unwrap();
flush.add_card(Card::from_string("Js").unwrap()).unwrap();
flush.add_card(Card::from_string("8s").unwrap()).unwrap();
flush.add_card(Card::from_string("5s").unwrap()).unwrap();
flush.add_card(Card::from_string("2s").unwrap()).unwrap();
let fh_rank = evaluator.rank_hand(&full_house);
let f_rank = evaluator.rank_hand(&flush);
assert!(
fh_rank > f_rank,
"Full house should beat flush: {} > {}",
fh_rank,
f_rank
);
}
#[test]
fn test_all_hand_types_basic() {
/// Tests all 9 basic hand types with proper ranking verification.
///
/// This test ensures that all hand types are correctly identified and ranked
/// according to poker hand strength hierarchy.
let evaluator = LookupHandEvaluator::new().unwrap();
// High Card: A K Q J 9 (different suits)
let mut high_card = Hand::new();
high_card
.add_card(Card::from_string("As").unwrap())
.unwrap();
high_card
.add_card(Card::from_string("Kd").unwrap())
.unwrap();
high_card
.add_card(Card::from_string("Qh").unwrap())
.unwrap();
high_card
.add_card(Card::from_string("Jc").unwrap())
.unwrap();
high_card
.add_card(Card::from_string("9s").unwrap())
.unwrap();
// Pair: A A K Q J
let mut pair = Hand::new();
pair.add_card(Card::from_string("As").unwrap()).unwrap();
pair.add_card(Card::from_string("Ah").unwrap()).unwrap();
pair.add_card(Card::from_string("Kd").unwrap()).unwrap();
pair.add_card(Card::from_string("Qh").unwrap()).unwrap();
pair.add_card(Card::from_string("Jc").unwrap()).unwrap();
// Two Pair: A A K K Q
let mut two_pair = Hand::new();
two_pair.add_card(Card::from_string("As").unwrap()).unwrap();
two_pair.add_card(Card::from_string("Ah").unwrap()).unwrap();
two_pair.add_card(Card::from_string("Ks").unwrap()).unwrap();
two_pair.add_card(Card::from_string("Kh").unwrap()).unwrap();
two_pair.add_card(Card::from_string("Qc").unwrap()).unwrap();
// Three of a Kind: A A A K Q
let mut three_kind = Hand::new();
three_kind
.add_card(Card::from_string("As").unwrap())
.unwrap();
three_kind
.add_card(Card::from_string("Ah").unwrap())
.unwrap();
three_kind
.add_card(Card::from_string("Ad").unwrap())
.unwrap();
three_kind
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
three_kind
.add_card(Card::from_string("Qh").unwrap())
.unwrap();
// Straight: A K Q J T (different suits)
let mut straight = Hand::new();
straight.add_card(Card::from_string("As").unwrap()).unwrap();
straight.add_card(Card::from_string("Kd").unwrap()).unwrap();
straight.add_card(Card::from_string("Qh").unwrap()).unwrap();
straight.add_card(Card::from_string("Jc").unwrap()).unwrap();
straight.add_card(Card::from_string("Ts").unwrap()).unwrap();
// Flush: A J 8 5 2 (same suit)
let mut flush = Hand::new();
flush.add_card(Card::from_string("As").unwrap()).unwrap();
flush.add_card(Card::from_string("Js").unwrap()).unwrap();
flush.add_card(Card::from_string("8s").unwrap()).unwrap();
flush.add_card(Card::from_string("5s").unwrap()).unwrap();
flush.add_card(Card::from_string("2s").unwrap()).unwrap();
// Full House: A A A K K
let mut full_house = Hand::new();
full_house
.add_card(Card::from_string("As").unwrap())
.unwrap();
full_house
.add_card(Card::from_string("Ah").unwrap())
.unwrap();
full_house
.add_card(Card::from_string("Ad").unwrap())
.unwrap();
full_house
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
full_house
.add_card(Card::from_string("Kh").unwrap())
.unwrap();
// Four of a Kind: A A A A K
let mut four_kind = Hand::new();
four_kind
.add_card(Card::from_string("As").unwrap())
.unwrap();
four_kind
.add_card(Card::from_string("Ah").unwrap())
.unwrap();
four_kind
.add_card(Card::from_string("Ad").unwrap())
.unwrap();
four_kind
.add_card(Card::from_string("Ac").unwrap())
.unwrap();
four_kind
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
// Straight Flush: A K Q J T (same suit)
let mut straight_flush = Hand::new();
straight_flush
.add_card(Card::from_string("As").unwrap())
.unwrap();
straight_flush
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
straight_flush
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
straight_flush
.add_card(Card::from_string("Js").unwrap())
.unwrap();
straight_flush
.add_card(Card::from_string("Ts").unwrap())
.unwrap();
// Get ranks for all hands
let hc_rank = evaluator.rank_hand(&high_card);
let p_rank = evaluator.rank_hand(&pair);
let tp_rank = evaluator.rank_hand(&two_pair);
let tk_rank = evaluator.rank_hand(&three_kind);
let s_rank = evaluator.rank_hand(&straight);
let f_rank = evaluator.rank_hand(&flush);
let fh_rank = evaluator.rank_hand(&full_house);
let fk_rank = evaluator.rank_hand(&four_kind);
let sf_rank = evaluator.rank_hand(&straight_flush);
// Verify hand type hierarchy (higher rank = stronger hand)
assert!(
sf_rank > fk_rank,
"Straight flush should beat four of a kind"
);
assert!(fk_rank > fh_rank, "Four of a kind should beat full house");
assert!(fh_rank > f_rank, "Full house should beat flush");
assert!(f_rank > s_rank, "Flush should beat straight");
assert!(s_rank > tk_rank, "Straight should beat three of a kind");
assert!(tk_rank > tp_rank, "Three of a kind should beat two pair");
assert!(tp_rank > p_rank, "Two pair should beat pair");
assert!(p_rank > hc_rank, "Pair should beat high card");
}
#[test]
fn test_hand_type_edge_cases() {
/// Tests edge cases for hand types including wheel straight and Broadway.
let evaluator = LookupHandEvaluator::new().unwrap();
// Wheel Straight: A 2 3 4 5 (different suits)
let mut wheel_straight = Hand::new();
wheel_straight
.add_card(Card::from_string("As").unwrap())
.unwrap();
wheel_straight
.add_card(Card::from_string("2d").unwrap())
.unwrap();
wheel_straight
.add_card(Card::from_string("3h").unwrap())
.unwrap();
wheel_straight
.add_card(Card::from_string("4c").unwrap())
.unwrap();
wheel_straight
.add_card(Card::from_string("5s").unwrap())
.unwrap();
// Broadway Straight: A K Q J T (different suits)
let mut broadway_straight = Hand::new();
broadway_straight
.add_card(Card::from_string("Ad").unwrap())
.unwrap();
broadway_straight
.add_card(Card::from_string("Kh").unwrap())
.unwrap();
broadway_straight
.add_card(Card::from_string("Qc").unwrap())
.unwrap();
broadway_straight
.add_card(Card::from_string("Js").unwrap())
.unwrap();
broadway_straight
.add_card(Card::from_string("Ts").unwrap())
.unwrap();
// Wheel Straight Flush: A 2 3 4 5 (same suit)
let mut wheel_straight_flush = Hand::new();
wheel_straight_flush
.add_card(Card::from_string("As").unwrap())
.unwrap();
wheel_straight_flush
.add_card(Card::from_string("2s").unwrap())
.unwrap();
wheel_straight_flush
.add_card(Card::from_string("3s").unwrap())
.unwrap();
wheel_straight_flush
.add_card(Card::from_string("4s").unwrap())
.unwrap();
wheel_straight_flush
.add_card(Card::from_string("5s").unwrap())
.unwrap();
// Get ranks
let wheel_rank = evaluator.rank_hand(&wheel_straight);
let broadway_rank = evaluator.rank_hand(&broadway_straight);
let wheel_sf_rank = evaluator.rank_hand(&wheel_straight_flush);
// Both straights should be valid (wheel is lowest straight)
assert!(wheel_rank > 0, "Wheel straight should be valid");
assert!(broadway_rank > 0, "Broadway straight should be valid");
// Broadway should beat wheel straight
assert!(
broadway_rank > wheel_rank,
"Broadway should beat wheel straight"
);
// Wheel straight flush should be valid and strong
assert!(wheel_sf_rank > 0, "Wheel straight flush should be valid");
}
#[test]
fn test_hand_type_boundaries() {
/// Tests exact boundaries between hand types to ensure correct ranking.
let evaluator = LookupHandEvaluator::new().unwrap();
// Test boundary between High Card and Pair
let mut high_card = Hand::new();
high_card
.add_card(Card::from_string("As").unwrap())
.unwrap();
high_card
.add_card(Card::from_string("Kd").unwrap())
.unwrap();
high_card
.add_card(Card::from_string("Qh").unwrap())
.unwrap();
high_card
.add_card(Card::from_string("Jc").unwrap())
.unwrap();
high_card
.add_card(Card::from_string("9s").unwrap())
.unwrap();
let mut weak_pair = Hand::new();
weak_pair
.add_card(Card::from_string("2s").unwrap())
.unwrap();
weak_pair
.add_card(Card::from_string("2d").unwrap())
.unwrap();
weak_pair
.add_card(Card::from_string("3h").unwrap())
.unwrap();
weak_pair
.add_card(Card::from_string("4c").unwrap())
.unwrap();
weak_pair
.add_card(Card::from_string("5s").unwrap())
.unwrap();
let hc_rank = evaluator.rank_hand(&high_card);
let wp_rank = evaluator.rank_hand(&weak_pair);
assert!(wp_rank < hc_rank, "Even weak pair should beat high card");
// Test boundary between Pair and Two Pair
let mut pair = Hand::new();
pair.add_card(Card::from_string("As").unwrap()).unwrap();
pair.add_card(Card::from_string("Ah").unwrap()).unwrap();
pair.add_card(Card::from_string("Kd").unwrap()).unwrap();
pair.add_card(Card::from_string("Qh").unwrap()).unwrap();
pair.add_card(Card::from_string("Jc").unwrap()).unwrap();
let mut two_pair = Hand::new();
two_pair.add_card(Card::from_string("2s").unwrap()).unwrap();
two_pair.add_card(Card::from_string("2d").unwrap()).unwrap();
two_pair.add_card(Card::from_string("3h").unwrap()).unwrap();
two_pair.add_card(Card::from_string("3c").unwrap()).unwrap();
two_pair.add_card(Card::from_string("4s").unwrap()).unwrap();
let p_rank = evaluator.rank_hand(&pair);
let tp_rank = evaluator.rank_hand(&two_pair);
assert!(tp_rank < p_rank, "Two pair should beat pair");
// Test boundary between Three of a Kind and Straight
let mut three_kind = Hand::new();
three_kind
.add_card(Card::from_string("As").unwrap())
.unwrap();
three_kind
.add_card(Card::from_string("Ah").unwrap())
.unwrap();
three_kind
.add_card(Card::from_string("Ad").unwrap())
.unwrap();
three_kind
.add_card(Card::from_string("Kc").unwrap())
.unwrap();
three_kind
.add_card(Card::from_string("Qh").unwrap())
.unwrap();
let mut weak_straight = Hand::new();
weak_straight
.add_card(Card::from_string("2s").unwrap())
.unwrap();
weak_straight
.add_card(Card::from_string("3d").unwrap())
.unwrap();
weak_straight
.add_card(Card::from_string("4h").unwrap())
.unwrap();
weak_straight
.add_card(Card::from_string("5c").unwrap())
.unwrap();
weak_straight
.add_card(Card::from_string("6s").unwrap())
.unwrap();
let tk_rank = evaluator.rank_hand(&three_kind);
let ws_rank = evaluator.rank_hand(&weak_straight);
assert!(ws_rank < tk_rank, "Straight should beat three of a kind");
}
#[test]
fn test_steel_wheel_vs_wheel_straight() {
/// Tests steel wheel (A 2 3 4 5 same suit) vs regular wheel straight.
let evaluator = LookupHandEvaluator::new().unwrap();
// Steel wheel (wheel straight flush)
let mut steel_wheel = Hand::new();
steel_wheel
.add_card(Card::from_string("As").unwrap())
.unwrap();
steel_wheel
.add_card(Card::from_string("2s").unwrap())
.unwrap();
steel_wheel
.add_card(Card::from_string("3s").unwrap())
.unwrap();
steel_wheel
.add_card(Card::from_string("4s").unwrap())
.unwrap();
steel_wheel
.add_card(Card::from_string("5s").unwrap())
.unwrap();
// Regular wheel straight
let mut wheel_straight = Hand::new();
wheel_straight
.add_card(Card::from_string("As").unwrap())
.unwrap();
wheel_straight
.add_card(Card::from_string("2d").unwrap())
.unwrap();
wheel_straight
.add_card(Card::from_string("3h").unwrap())
.unwrap();
wheel_straight
.add_card(Card::from_string("4c").unwrap())
.unwrap();
wheel_straight
.add_card(Card::from_string("5s").unwrap())
.unwrap();
let sw_rank = evaluator.rank_hand(&steel_wheel);
let ws_rank = evaluator.rank_hand(&wheel_straight);
assert!(sw_rank < ws_rank, "Steel wheel should beat wheel straight");
assert!(sw_rank < 1000, "Steel wheel should be very strong hand");
}
#[test]
fn test_royal_flush_variations() {
/// Tests different royal flush variations and their relative strengths.
let evaluator = LookupHandEvaluator::new().unwrap();
// Spades royal flush
let mut rf_spades = Hand::new();
rf_spades
.add_card(Card::from_string("As").unwrap())
.unwrap();
rf_spades
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
rf_spades
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
rf_spades
.add_card(Card::from_string("Js").unwrap())
.unwrap();
rf_spades
.add_card(Card::from_string("Ts").unwrap())
.unwrap();
// Hearts royal flush
let mut rf_hearts = Hand::new();
rf_hearts
.add_card(Card::from_string("Ah").unwrap())
.unwrap();
rf_hearts
.add_card(Card::from_string("Kh").unwrap())
.unwrap();
rf_hearts
.add_card(Card::from_string("Qh").unwrap())
.unwrap();
rf_hearts
.add_card(Card::from_string("Jh").unwrap())
.unwrap();
rf_hearts
.add_card(Card::from_string("Th").unwrap())
.unwrap();
let rf_s_rank = evaluator.rank_hand(&rf_spades);
let rf_h_rank = evaluator.rank_hand(&rf_hearts);
// All royal flushes should be equally strong (same rank)
assert_eq!(
rf_s_rank, rf_h_rank,
"All royal flushes should have same rank"
);
assert!(rf_s_rank < 100, "Royal flush should be very strong");
}
#[test]
fn test_flush_vs_straight_comparison() {
/// Tests that flush beats straight and verifies ranking within types.
let evaluator = LookupHandEvaluator::new().unwrap();
// Strong Flush: A K Q J 9 (same suit)
let mut strong_flush = Hand::new();
strong_flush
.add_card(Card::from_string("As").unwrap())
.unwrap();
strong_flush
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
strong_flush
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
strong_flush
.add_card(Card::from_string("Js").unwrap())
.unwrap();
strong_flush
.add_card(Card::from_string("9s").unwrap())
.unwrap();
// Weak Flush: K Q J 8 7 (same suit)
let mut weak_flush = Hand::new();
weak_flush
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
weak_flush
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
weak_flush
.add_card(Card::from_string("Js").unwrap())
.unwrap();
weak_flush
.add_card(Card::from_string("8s").unwrap())
.unwrap();
weak_flush
.add_card(Card::from_string("7s").unwrap())
.unwrap();
// Strong Straight: K Q J T 9 (different suits)
let mut strong_straight = Hand::new();
strong_straight
.add_card(Card::from_string("Kd").unwrap())
.unwrap();
strong_straight
.add_card(Card::from_string("Qh").unwrap())
.unwrap();
strong_straight
.add_card(Card::from_string("Jc").unwrap())
.unwrap();
strong_straight
.add_card(Card::from_string("Ts").unwrap())
.unwrap();
strong_straight
.add_card(Card::from_string("9s").unwrap())
.unwrap();
// Get ranks
let sf_rank = evaluator.rank_hand(&strong_flush);
let wf_rank = evaluator.rank_hand(&weak_flush);
let s_rank = evaluator.rank_hand(&strong_straight);
// Flush should beat straight
assert!(sf_rank > s_rank, "Strong flush should beat strong straight");
// Stronger flush should beat weaker flush
assert!(sf_rank > wf_rank, "Strong flush should beat weak flush");
}
}
/// Test suite for Java compatibility verification.
///
/// These tests ensure that the Rust implementation matches the Java Meerkat API
/// in terms of card encoding, key generation, and ranking values.
#[cfg(test)]
mod java_compatibility {
use super::*;
#[test]
fn test_java_card_encoding_format() {
/// Tests that card encoding matches Java's 8-bit rrrr-sss format.
///
/// Java uses: rrrr-sss where rrrr = rank (1-13), sss = suit (1-4)
/// This should match our Card implementation exactly.
// Test specific card encodings
let ace_spades = Card::from_string("As").unwrap();
assert_eq!(ace_spades.rank(), 13); // Ace = 13 in Java
assert_eq!(ace_spades.suit(), 4); // Spades = 4 in Java
let deuce_clubs = Card::from_string("2c").unwrap();
assert_eq!(deuce_clubs.rank(), 1); // Deuce = 1 in Java
assert_eq!(deuce_clubs.suit(), 1); // Clubs = 1 in Java
let king_hearts = Card::from_string("Kh").unwrap();
assert_eq!(king_hearts.rank(), 12); // King = 12 in Java
assert_eq!(king_hearts.suit(), 3); // Hearts = 3 in Java
// Test index calculation (should match Java's card ordering)
assert_eq!(ace_spades.index(), 51); // Last card in deck
assert_eq!(deuce_clubs.index(), 0); // First card in deck
assert_eq!(king_hearts.index(), 37); // King of hearts position
}
#[test]
fn test_java_key_generation() {
/// Tests that 64-bit key generation matches Java implementation.
///
/// Java creates keys by packing 5 cards as: card1|card2|card3|card4|card5
/// Each card is 8 bits: rrrr-sss (rank 1-13, suit 1-4)
let evaluator = LookupHandEvaluator::new().unwrap();
// Create a royal flush and verify key generation
let mut hand = Hand::new();
hand.add_card(Card::from_string("As").unwrap()).unwrap();
hand.add_card(Card::from_string("Ks").unwrap()).unwrap();
hand.add_card(Card::from_string("Qs").unwrap()).unwrap();
hand.add_card(Card::from_string("Js").unwrap()).unwrap();
hand.add_card(Card::from_string("Ts").unwrap()).unwrap();
// Test the rank_hand5 method which uses Java-style key generation
let rank = evaluator.rank_hand5(&hand);
// Should return a valid rank (non-zero)
assert!(
rank > 0,
"Java-style key generation should produce valid rank"
);
// Test key generation for specific cards
let mut key: u64 = 0;
for i in 0..5 {
if let Some(card) = hand.get_card(i + 1) {
let rank_val = card.rank() as u64;
let suit_val = card.suit() as u64;
let encoded_card = (rank_val << 3) | suit_val;
key |= (encoded_card as u64) << (i * 8);
}
}
// Key should be non-zero for valid hand
assert!(key > 0, "64-bit key should be non-zero for valid hand");
// Test table index calculation
let table_index = (key % evaluator.hand_ranks.len() as u64) as usize;
assert!(
table_index < evaluator.hand_ranks.len(),
"Table index should be valid"
);
// Verify the rank from table matches direct evaluation
assert_eq!(
rank, evaluator.hand_ranks[table_index],
"Table lookup should match direct evaluation"
);
}
#[test]
fn test_java_ranking_direction() {
/// Tests that ranking direction matches Java (1 = best, 7462 = worst).
///
/// In Java:
/// - Royal flush = 1 (best possible hand)
/// - 7-5-4-3-2 high card = 7462 (worst possible hand)
let evaluator = LookupHandEvaluator::new().unwrap();
// Royal flush should be very close to 1 (best hand)
let mut royal_flush = Hand::new();
royal_flush
.add_card(Card::from_string("As").unwrap())
.unwrap();
royal_flush
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
royal_flush
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
royal_flush
.add_card(Card::from_string("Js").unwrap())
.unwrap();
royal_flush
.add_card(Card::from_string("Ts").unwrap())
.unwrap();
let rf_rank = evaluator.rank_hand(&royal_flush);
// Royal flush should have a very low rank number (close to 1)
// Note: Exact value depends on table implementation, but should be < 1000
assert!(
rf_rank < 1000,
"Royal flush should have very low rank number, got {}",
rf_rank
);
// Worst high card hand should have high rank number (close to 7462)
let mut worst_hand = Hand::new();
worst_hand
.add_card(Card::from_string("7c").unwrap())
.unwrap();
worst_hand
.add_card(Card::from_string("5d").unwrap())
.unwrap();
worst_hand
.add_card(Card::from_string("4h").unwrap())
.unwrap();
worst_hand
.add_card(Card::from_string("3s").unwrap())
.unwrap();
worst_hand
.add_card(Card::from_string("2c").unwrap())
.unwrap();
let wh_rank = evaluator.rank_hand(&worst_hand);
// Worst hand should have high rank number
assert!(
wh_rank > 7000,
"Worst hand should have high rank number, got {}",
wh_rank
);
// Royal flush should be better than worst hand
assert!(
rf_rank < wh_rank,
"Royal flush should be better than worst hand"
);
}
#[test]
fn test_java_specific_hand_rankings() {
/// Tests specific hand rankings that should match Java implementation.
///
/// These are known good hands with expected Java ranking values.
let evaluator = LookupHandEvaluator::new().unwrap();
// Test case 1: Royal Flush (should be very close to 1)
let mut royal_flush = Hand::new();
royal_flush
.add_card(Card::from_string("As").unwrap())
.unwrap();
royal_flush
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
royal_flush
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
royal_flush
.add_card(Card::from_string("Js").unwrap())
.unwrap();
royal_flush
.add_card(Card::from_string("Ts").unwrap())
.unwrap();
let rf_rank = evaluator.rank_hand(&royal_flush);
assert!(rf_rank < 100, "Royal flush should be in top 100 hands");
// Test case 2: Straight Flush (should be very good but not royal)
let mut straight_flush = Hand::new();
straight_flush
.add_card(Card::from_string("9s").unwrap())
.unwrap();
straight_flush
.add_card(Card::from_string("8s").unwrap())
.unwrap();
straight_flush
.add_card(Card::from_string("7s").unwrap())
.unwrap();
straight_flush
.add_card(Card::from_string("6s").unwrap())
.unwrap();
straight_flush
.add_card(Card::from_string("5s").unwrap())
.unwrap();
let sf_rank = evaluator.rank_hand(&straight_flush);
assert!(
sf_rank > rf_rank,
"Straight flush should be worse than royal flush"
);
assert!(sf_rank < 5000, "Straight flush should be in top 5000 hands");
// Test case 3: Four of a Kind (should be good but not straight flush)
let mut four_kind = Hand::new();
four_kind
.add_card(Card::from_string("Ah").unwrap())
.unwrap();
four_kind
.add_card(Card::from_string("Ad").unwrap())
.unwrap();
four_kind
.add_card(Card::from_string("Ac").unwrap())
.unwrap();
four_kind
.add_card(Card::from_string("As").unwrap())
.unwrap();
four_kind
.add_card(Card::from_string("Kd").unwrap())
.unwrap();
let fk_rank = evaluator.rank_hand(&four_kind);
assert!(
fk_rank > sf_rank,
"Four of kind should be worse than straight flush"
);
assert!(fk_rank < 10000, "Four of kind should be in top 10000 hands");
}
#[test]
fn test_java_known_good_hands() {
/// Tests specific hands with known Java rankings for compatibility verification.
let evaluator = LookupHandEvaluator::new().unwrap();
// Known good hands with expected rank ranges (based on Java implementation)
let known_hands = vec![
// (hand_cards, expected_rank_range)
(vec!["As", "Ks", "Qs", "Js", "Ts"], (1, 100)), // Royal flush
(vec!["9s", "8s", "7s", "6s", "5s"], (1000, 5000)), // 9-high straight flush
(vec!["Ah", "Ad", "Ac", "As", "Kd"], (5000, 10000)), // Four Aces
(vec!["Kh", "Kd", "Kc", "Ks", "Ad"], (10000, 15000)), // Four Kings
(vec!["As", "Ah", "Ad", "Ks", "Kh"], (15000, 20000)), // AAAKK full house
(vec!["As", "Ks", "Qs", "Js", "9s"], (20000, 25000)), // AKQJ9 flush
(vec!["As", "Kd", "Qh", "Jc", "Ts"], (25000, 30000)), // AKQJT straight
(vec!["As", "Ah", "Ad", "Ks", "Qs"], (30000, 35000)), // AAAKQ three of kind
(vec!["As", "Ah", "Ks", "Kd", "Qs"], (35000, 40000)), // AAKKQ two pair
(vec!["As", "Ah", "Ks", "Qs", "Js"], (40000, 45000)), // AAKQJ pair
(vec!["As", "Kd", "Qh", "Jc", "9s"], (45000, 50000)), // AKQJ9 high card
];
for (cards, (min_rank, max_rank)) in known_hands {
let mut hand = Hand::new();
for card_str in &cards {
hand.add_card(Card::from_string(card_str).unwrap()).unwrap();
}
let rank = evaluator.rank_hand(&hand);
assert!(
rank >= min_rank && rank <= max_rank,
"Hand {:?} should have rank in range {}-{}, got {}",
cards,
min_rank,
max_rank,
rank
);
}
}
#[test]
fn test_java_card_encoding_comprehensive() {
/// Comprehensive test of Java-compatible card encoding for all cards.
let evaluator = LookupHandEvaluator::new().unwrap();
// Test all card ranks and suits
let ranks = [
"A", "K", "Q", "J", "T", "9", "8", "7", "6", "5", "4", "3", "2",
];
let suits = ["s", "h", "d", "c"];
for rank in &ranks {
for suit in &suits {
let card_str = format!("{}{}", rank, suit);
let card = Card::from_string(&card_str).unwrap();
// Verify Java-style encoding
let expected_rank = match *rank {
"A" => 13,
"K" => 12,
"Q" => 11,
"J" => 10,
"T" => 9,
"9" => 8,
"8" => 7,
"7" => 6,
"6" => 5,
"5" => 4,
"4" => 3,
"3" => 2,
"2" => 1,
_ => panic!("Invalid rank"),
};
let expected_suit = match *suit {
"s" => 4,
"h" => 3,
"d" => 2,
"c" => 1,
_ => panic!("Invalid suit"),
};
assert_eq!(
card.rank(),
expected_rank,
"Card {} should have rank {}",
card_str,
expected_rank
);
assert_eq!(
card.suit(),
expected_suit,
"Card {} should have suit {}",
card_str,
expected_suit
);
// Test that card can be used in hand evaluation
let mut hand = Hand::new();
hand.add_card(card).unwrap();
// Add dummy cards to make 5-card hand
hand.add_card(Card::from_string("2s").unwrap()).unwrap();
hand.add_card(Card::from_string("3s").unwrap()).unwrap();
hand.add_card(Card::from_string("4s").unwrap()).unwrap();
hand.add_card(Card::from_string("5s").unwrap()).unwrap();
let rank = evaluator.rank_hand(&hand);
assert!(
rank > 0,
"Hand with card {} should produce valid rank",
card_str
);
}
}
}
#[test]
fn test_java_key_generation_comprehensive() {
/// Comprehensive test of Java-compatible 64-bit key generation.
let evaluator = LookupHandEvaluator::new().unwrap();
// Test key generation for various hand types
let test_hands = vec![
vec!["As", "Ks", "Qs", "Js", "Ts"], // Royal flush
vec!["9s", "8s", "7s", "6s", "5s"], // Straight flush
vec!["Ah", "Ad", "Ac", "As", "Kd"], // Four of a kind
vec!["As", "Ah", "Ad", "Ks", "Kh"], // Full house
vec!["As", "Ks", "Qs", "Js", "9s"], // Flush
vec!["As", "Kd", "Qh", "Jc", "Ts"], // Straight
vec!["As", "Ah", "Ad", "Ks", "Qs"], // Three of a kind
vec!["As", "Ah", "Ks", "Kd", "Qs"], // Two pair
vec!["As", "Ah", "Ks", "Qs", "Js"], // Pair
vec!["As", "Kd", "Qh", "Jc", "9s"], // High card
];
for cards in &test_hands {
let mut hand = Hand::new();
for card_str in cards {
hand.add_card(Card::from_string(card_str).unwrap()).unwrap();
}
// Test rank_hand5 method (Java-style key generation)
let rank5 = evaluator.rank_hand5(&hand);
let rank_general = evaluator.rank_hand(&hand);
// Both methods should produce same result for 5-card hands
assert_eq!(
rank5, rank_general,
"rank_hand5 and rank_hand should match for 5-card hands"
);
// Generate manual 64-bit key for verification
let mut key: u64 = 0;
for (i, card_str) in cards.iter().enumerate() {
let card = Card::from_string(card_str).unwrap();
let rank_val = card.rank() as u64;
let suit_val = card.suit() as u64;
let encoded_card = (rank_val << 3) | suit_val;
key |= (encoded_card as u64) << (i * 8);
}
// Key should be non-zero and properly formatted
assert!(key > 0, "64-bit key should be non-zero");
assert!(
key < 2u64.pow(40),
"Key should fit in 40 bits (5 * 8-bit cards)"
);
// Table lookup should work
let table_index = (key % evaluator.hand_ranks.len() as u64) as usize;
assert!(
table_index < evaluator.hand_ranks.len(),
"Table index should be valid"
);
assert_eq!(
rank5, evaluator.hand_ranks[table_index],
"Table lookup should match direct evaluation"
);
}
}
}
/// Test suite for multi-card hand evaluation.
///
/// These tests verify correct evaluation of 5, 6, and 7-card hands,
/// including best 5-card combination selection for larger hands.
#[cfg(test)]
mod multi_card_hands {
use super::*;
#[test]
fn test_five_card_evaluation() {
/// Tests basic 5-card hand evaluation.
let evaluator = LookupHandEvaluator::new().unwrap();
let mut hand = Hand::new();
hand.add_card(Card::from_string("As").unwrap()).unwrap();
hand.add_card(Card::from_string("Ks").unwrap()).unwrap();
hand.add_card(Card::from_string("Qs").unwrap()).unwrap();
hand.add_card(Card::from_string("Js").unwrap()).unwrap();
hand.add_card(Card::from_string("Ts").unwrap()).unwrap();
let rank = evaluator.rank_hand(&hand);
assert!(rank > 0, "5-card hand should produce valid rank");
}
#[test]
fn test_six_card_best_five() {
/// Tests 6-card hand evaluation finds the best 5-card combination.
let evaluator = LookupHandEvaluator::new().unwrap();
// 6 cards: royal flush in spades + extra card
let mut six_cards = Hand::new();
six_cards
.add_card(Card::from_string("As").unwrap())
.unwrap();
six_cards
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
six_cards
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
six_cards
.add_card(Card::from_string("Js").unwrap())
.unwrap();
six_cards
.add_card(Card::from_string("Ts").unwrap())
.unwrap();
six_cards
.add_card(Card::from_string("2d").unwrap())
.unwrap(); // Extra card
let rank = evaluator.rank_hand(&six_cards);
assert!(rank > 0, "6-card hand should produce valid rank");
// Should be able to find the royal flush within the 6 cards
let mut royal_flush_only = Hand::new();
royal_flush_only
.add_card(Card::from_string("As").unwrap())
.unwrap();
royal_flush_only
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
royal_flush_only
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
royal_flush_only
.add_card(Card::from_string("Js").unwrap())
.unwrap();
royal_flush_only
.add_card(Card::from_string("Ts").unwrap())
.unwrap();
let rf_rank = evaluator.rank_hand(&royal_flush_only);
// The 6-card hand should find the royal flush and get same rank
assert_eq!(
rank, rf_rank,
"6-card hand should find the royal flush within"
);
}
#[test]
fn test_seven_card_best_five() {
/// Tests 7-card hand evaluation finds the best 5-card combination.
let evaluator = LookupHandEvaluator::new().unwrap();
// 7 cards: royal flush in spades + two extra cards
let mut seven_cards = Hand::new();
seven_cards
.add_card(Card::from_string("As").unwrap())
.unwrap();
seven_cards
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
seven_cards
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
seven_cards
.add_card(Card::from_string("Js").unwrap())
.unwrap();
seven_cards
.add_card(Card::from_string("Ts").unwrap())
.unwrap();
seven_cards
.add_card(Card::from_string("2d").unwrap())
.unwrap(); // Extra card 1
seven_cards
.add_card(Card::from_string("3c").unwrap())
.unwrap(); // Extra card 2
let rank = evaluator.rank_hand(&seven_cards);
assert!(rank > 0, "7-card hand should produce valid rank");
// Should be able to find the royal flush within the 7 cards
let mut royal_flush_only = Hand::new();
royal_flush_only
.add_card(Card::from_string("As").unwrap())
.unwrap();
royal_flush_only
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
royal_flush_only
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
royal_flush_only
.add_card(Card::from_string("Js").unwrap())
.unwrap();
royal_flush_only
.add_card(Card::from_string("Ts").unwrap())
.unwrap();
let rf_rank = evaluator.rank_hand(&royal_flush_only);
// The 7-card hand should find the royal flush and get same rank
assert_eq!(
rank, rf_rank,
"7-card hand should find the royal flush within"
);
}
#[test]
fn test_incremental_hand_building() {
/// Tests incremental hand building and evaluation.
let evaluator = LookupHandEvaluator::new().unwrap();
// Start with empty key
let key: u64 = 0;
// Add cards incrementally
let card1 = Card::from_string("As").unwrap();
let card2 = Card::from_string("Ks").unwrap();
let card3 = Card::from_string("Qs").unwrap();
let card4 = Card::from_string("Js").unwrap();
let card5 = Card::from_string("Ts").unwrap();
// Build hand incrementally
let _result1 = evaluator.rank_hand_increment_single(key, card1.index() as u32);
let _result2 = evaluator.rank_hand_increment_single(key, card2.index() as u32);
let _result3 = evaluator.rank_hand_increment_single(key, card3.index() as u32);
let _result4 = evaluator.rank_hand_increment_single(key, card4.index() as u32);
let _result5 = evaluator.rank_hand_increment_single(key, card5.index() as u32);
// For this test, we'll just verify the method can be called without panicking
// The actual incremental functionality would need the proper implementation
// Should have valid key after adding 5 cards
assert!(key > 0, "Key should be non-zero after adding cards");
// Final rank should match direct evaluation
let mut hand = Hand::new();
hand.add_card(card1).unwrap();
hand.add_card(card2).unwrap();
hand.add_card(card3).unwrap();
hand.add_card(card4).unwrap();
hand.add_card(card5).unwrap();
let direct_rank = evaluator.rank_hand(&hand);
let table_index = (key % evaluator.hand_ranks.len() as u64) as usize;
let incremental_rank = evaluator.hand_ranks[table_index];
assert_eq!(
direct_rank, incremental_rank,
"Incremental and direct evaluation should match"
);
}
#[test]
fn test_hand_with_hole_cards() {
/// Tests evaluation of hands with hole cards and community cards.
let evaluator = LookupHandEvaluator::new().unwrap();
// Community cards (board)
let mut board = Hand::new();
board.add_card(Card::from_string("As").unwrap()).unwrap();
board.add_card(Card::from_string("Ks").unwrap()).unwrap();
board.add_card(Card::from_string("Qs").unwrap()).unwrap();
board.add_card(Card::from_string("Js").unwrap()).unwrap();
board.add_card(Card::from_string("2d").unwrap()).unwrap();
// Hole cards that complete royal flush
let hole1 = Card::from_string("Ts").unwrap();
// Evaluate with hole cards
let rank = evaluator.rank_hand_with_hole_cards(
hole1.index() as u32,
0, // No second hole card for this test
&board,
);
assert!(rank > 0, "Hand with hole cards should produce valid rank");
}
#[test]
fn test_comprehensive_hole_card_scenarios() {
/// Tests various hole card scenarios with different board textures.
let evaluator = LookupHandEvaluator::new().unwrap();
// Scenario 1: Hole cards complete royal flush
let mut board1 = Hand::new();
board1.add_card(Card::from_string("As").unwrap()).unwrap();
board1.add_card(Card::from_string("Ks").unwrap()).unwrap();
board1.add_card(Card::from_string("Qs").unwrap()).unwrap();
board1.add_card(Card::from_string("Js").unwrap()).unwrap();
board1.add_card(Card::from_string("2d").unwrap()).unwrap();
let hole1 = Card::from_string("Ts").unwrap();
let rank1 = evaluator.rank_hand_with_hole_cards(hole1.index() as u32, 0, &board1);
assert!(
rank1 < 1000,
"Royal flush with hole cards should be very strong"
);
// Scenario 2: Hole cards make full house
let mut board2 = Hand::new();
board2.add_card(Card::from_string("As").unwrap()).unwrap();
board2.add_card(Card::from_string("Ah").unwrap()).unwrap();
board2.add_card(Card::from_string("Kd").unwrap()).unwrap();
board2.add_card(Card::from_string("Kc").unwrap()).unwrap();
board2.add_card(Card::from_string("2d").unwrap()).unwrap();
let hole2 = Card::from_string("Ad").unwrap();
let rank2 = evaluator.rank_hand_with_hole_cards(hole2.index() as u32, 0, &board2);
assert!(rank2 < 20000, "Full house with hole cards should be strong");
// Scenario 3: Two hole cards make straight
let mut board3 = Hand::new();
board3.add_card(Card::from_string("9s").unwrap()).unwrap();
board3.add_card(Card::from_string("8d").unwrap()).unwrap();
board3.add_card(Card::from_string("7h").unwrap()).unwrap();
board3.add_card(Card::from_string("6c").unwrap()).unwrap();
board3.add_card(Card::from_string("2s").unwrap()).unwrap();
let hole3a = Card::from_string("Ts").unwrap();
let hole3b = Card::from_string("Jd").unwrap();
let rank3 = evaluator.rank_hand_with_hole_cards(
hole3a.index() as u32,
hole3b.index() as u32,
&board3,
);
assert!(rank3 < 30000, "Straight with hole cards should be decent");
}
#[test]
fn test_incremental_hand_building_comprehensive() {
/// Comprehensive test of incremental hand building with various scenarios.
let evaluator = LookupHandEvaluator::new().unwrap();
// Test incremental building of royal flush
let royal_cards = vec![
Card::from_string("As").unwrap(),
Card::from_string("Ks").unwrap(),
Card::from_string("Qs").unwrap(),
Card::from_string("Js").unwrap(),
Card::from_string("Ts").unwrap(),
];
// Build hand incrementally and verify improving strength
let mut current_best = 0u32;
for (i, _card) in royal_cards.iter().enumerate() {
let mut hand = Hand::new();
for &c in &royal_cards[0..=i] {
hand.add_card(c).unwrap();
}
if hand.size() >= 5 {
let rank = evaluator.rank_hand(&hand);
assert!(rank > 0, "Hand should produce valid rank");
// As we add more cards, the hand should get stronger or stay strong
if i > 0 {
assert!(
rank <= current_best || rank > 0,
"Hand should maintain strength or improve"
);
}
current_best = rank;
}
}
// Final royal flush should be very strong
assert!(
current_best < 1000,
"Final royal flush should be very strong"
);
}
#[test]
fn test_omaha_hand_evaluation() {
/// Tests Omaha-style hand evaluation (4 hole cards, 5 community cards).
let evaluator = LookupHandEvaluator::new().unwrap();
// Hole cards
let mut hole_cards = Hand::new();
hole_cards
.add_card(Card::from_string("As").unwrap())
.unwrap();
hole_cards
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
hole_cards
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
hole_cards
.add_card(Card::from_string("Js").unwrap())
.unwrap();
// Community cards
let mut board = Hand::new();
board.add_card(Card::from_string("Ts").unwrap()).unwrap();
board.add_card(Card::from_string("9d").unwrap()).unwrap();
board.add_card(Card::from_string("8h").unwrap()).unwrap();
board.add_card(Card::from_string("7c").unwrap()).unwrap();
board.add_card(Card::from_string("2s").unwrap()).unwrap();
// Test different 2-hole-card combinations
let hole_indices: Vec<u32> = hole_cards
.get_card_array()
.iter()
.map(|&idx| idx as u32)
.collect();
let mut best_rank = u32::MAX;
// Try all combinations of 2 hole cards with 3 community cards
for i in 0..4 {
for j in (i + 1)..4 {
let rank =
evaluator.rank_hand_with_hole_cards(hole_indices[i], hole_indices[j], &board);
best_rank = best_rank.min(rank);
}
}
assert!(
best_rank < 30000,
"Omaha hand should find good 5-card combination"
);
}
#[test]
fn test_hand_evaluation_with_duplicates() {
/// Tests hand evaluation when duplicate cards are present (edge case).
let evaluator = LookupHandEvaluator::new().unwrap();
// Create hand with some duplicate cards (shouldn't happen in real poker)
let mut hand = Hand::new();
hand.add_card(Card::from_string("As").unwrap()).unwrap();
hand.add_card(Card::from_string("As").unwrap()).unwrap(); // Duplicate
hand.add_card(Card::from_string("Ks").unwrap()).unwrap();
hand.add_card(Card::from_string("Qs").unwrap()).unwrap();
hand.add_card(Card::from_string("Js").unwrap()).unwrap();
// Should still produce a valid rank without panicking
let rank = evaluator.rank_hand(&hand);
assert!(rank > 0, "Hand with duplicates should still evaluate");
// Test 6-card hand with duplicates
let mut six_card = Hand::new();
six_card.add_card(Card::from_string("As").unwrap()).unwrap();
six_card.add_card(Card::from_string("As").unwrap()).unwrap(); // Duplicate
six_card.add_card(Card::from_string("Ks").unwrap()).unwrap();
six_card.add_card(Card::from_string("Qs").unwrap()).unwrap();
six_card.add_card(Card::from_string("Js").unwrap()).unwrap();
six_card.add_card(Card::from_string("Ts").unwrap()).unwrap();
let rank6 = evaluator.rank_hand(&six_card);
assert!(
rank6 > 0,
"6-card hand with duplicates should still evaluate"
);
}
}
/// Test suite for regression testing.
///
/// These tests verify specific hands that were problematic in the original
/// implementation and ensure consistency between different evaluation methods.
#[cfg(test)]
mod regression_tests {
use super::*;
#[test]
fn test_problematic_hands() {
/// Tests specific hands that were problematic in original implementation.
let evaluator = LookupHandEvaluator::new().unwrap();
// Test case 1: A-2-3-4-5 straight (wheel straight)
let mut wheel_straight = Hand::new();
wheel_straight
.add_card(Card::from_string("As").unwrap())
.unwrap();
wheel_straight
.add_card(Card::from_string("2d").unwrap())
.unwrap();
wheel_straight
.add_card(Card::from_string("3h").unwrap())
.unwrap();
wheel_straight
.add_card(Card::from_string("4c").unwrap())
.unwrap();
wheel_straight
.add_card(Card::from_string("5s").unwrap())
.unwrap();
let wheel_rank = evaluator.rank_hand(&wheel_straight);
assert!(wheel_rank > 0, "Wheel straight should be valid");
// Test case 2: Duplicate suits in flush (should not be flush)
let mut not_flush = Hand::new();
not_flush
.add_card(Card::from_string("As").unwrap())
.unwrap();
not_flush
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
not_flush
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
not_flush
.add_card(Card::from_string("Js").unwrap())
.unwrap();
not_flush
.add_card(Card::from_string("Ts").unwrap())
.unwrap();
let not_flush_rank = evaluator.rank_hand(&not_flush);
assert!(not_flush_rank > 0, "Non-flush should still be valid hand");
// Test case 3: Four of a kind vs full house distinction
let mut four_kind = Hand::new();
four_kind
.add_card(Card::from_string("As").unwrap())
.unwrap();
four_kind
.add_card(Card::from_string("Ah").unwrap())
.unwrap();
four_kind
.add_card(Card::from_string("Ad").unwrap())
.unwrap();
four_kind
.add_card(Card::from_string("Ac").unwrap())
.unwrap();
four_kind
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
let mut full_house = Hand::new();
full_house
.add_card(Card::from_string("As").unwrap())
.unwrap();
full_house
.add_card(Card::from_string("Ah").unwrap())
.unwrap();
full_house
.add_card(Card::from_string("Ad").unwrap())
.unwrap();
full_house
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
full_house
.add_card(Card::from_string("Kh").unwrap())
.unwrap();
let fk_rank = evaluator.rank_hand(&four_kind);
let fh_rank = evaluator.rank_hand(&full_house);
assert!(fk_rank < fh_rank, "Four of kind should beat full house");
}
#[test]
fn test_additional_problematic_hands() {
/// Tests additional hands that have been problematic in poker evaluators.
let evaluator = LookupHandEvaluator::new().unwrap();
// Problematic hand 1: Straight vs straight flush edge case
let mut straight = Hand::new();
straight.add_card(Card::from_string("As").unwrap()).unwrap();
straight.add_card(Card::from_string("Kd").unwrap()).unwrap();
straight.add_card(Card::from_string("Qh").unwrap()).unwrap();
straight.add_card(Card::from_string("Jc").unwrap()).unwrap();
straight.add_card(Card::from_string("Ts").unwrap()).unwrap();
let mut straight_flush = Hand::new();
straight_flush
.add_card(Card::from_string("As").unwrap())
.unwrap();
straight_flush
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
straight_flush
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
straight_flush
.add_card(Card::from_string("Js").unwrap())
.unwrap();
straight_flush
.add_card(Card::from_string("Ts").unwrap())
.unwrap();
let s_rank = evaluator.rank_hand(&straight);
let sf_rank = evaluator.rank_hand(&straight_flush);
assert!(sf_rank < s_rank, "Straight flush should beat straight");
// Problematic hand 2: Full house vs three of a kind
let mut three_kind = Hand::new();
three_kind
.add_card(Card::from_string("As").unwrap())
.unwrap();
three_kind
.add_card(Card::from_string("Ah").unwrap())
.unwrap();
three_kind
.add_card(Card::from_string("Ad").unwrap())
.unwrap();
three_kind
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
three_kind
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
let mut full_house = Hand::new();
full_house
.add_card(Card::from_string("As").unwrap())
.unwrap();
full_house
.add_card(Card::from_string("Ah").unwrap())
.unwrap();
full_house
.add_card(Card::from_string("Ad").unwrap())
.unwrap();
full_house
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
full_house
.add_card(Card::from_string("Kh").unwrap())
.unwrap();
let tk_rank = evaluator.rank_hand(&three_kind);
let fh_rank = evaluator.rank_hand(&full_house);
assert!(fh_rank < tk_rank, "Full house should beat three of a kind");
// Problematic hand 3: Two pair vs pair
let mut pair = Hand::new();
pair.add_card(Card::from_string("As").unwrap()).unwrap();
pair.add_card(Card::from_string("Ah").unwrap()).unwrap();
pair.add_card(Card::from_string("Kd").unwrap()).unwrap();
pair.add_card(Card::from_string("Qs").unwrap()).unwrap();
pair.add_card(Card::from_string("Js").unwrap()).unwrap();
let mut two_pair = Hand::new();
two_pair.add_card(Card::from_string("As").unwrap()).unwrap();
two_pair.add_card(Card::from_string("Ah").unwrap()).unwrap();
two_pair.add_card(Card::from_string("Ks").unwrap()).unwrap();
two_pair.add_card(Card::from_string("Kh").unwrap()).unwrap();
two_pair.add_card(Card::from_string("Qs").unwrap()).unwrap();
let p_rank = evaluator.rank_hand(&pair);
let tp_rank = evaluator.rank_hand(&two_pair);
assert!(tp_rank < p_rank, "Two pair should beat pair");
}
#[test]
fn test_consistency_across_evaluation_methods() {
/// Tests consistency between different evaluation methods and scenarios.
let evaluator = LookupHandEvaluator::new().unwrap();
let test_hands = vec![
vec!["As", "Ks", "Qs", "Js", "Ts"], // Royal flush
vec!["9s", "8s", "7s", "6s", "5s"], // Straight flush
vec!["Ah", "Ad", "Ac", "As", "Kd"], // Four of a kind
vec!["As", "Ah", "Ad", "Ks", "Kh"], // Full house
vec!["As", "Ks", "Qs", "Js", "9s"], // Flush
vec!["As", "Kd", "Qh", "Jc", "Ts"], // Straight
vec!["As", "Ah", "Ad", "Ks", "Qs"], // Three of a kind
vec!["As", "Ah", "Ks", "Kd", "Qs"], // Two pair
vec!["As", "Ah", "Ks", "Qs", "Js"], // Pair
vec!["As", "Kd", "Qh", "Jc", "9s"], // High card
];
for cards in &test_hands {
let mut hand = Hand::new();
for card_str in cards {
hand.add_card(Card::from_string(card_str).unwrap()).unwrap();
}
// Test consistency between rank_hand and rank_hand5 for 5-card hands
let general_rank = evaluator.rank_hand(&hand);
let specific_rank = evaluator.rank_hand5(&hand);
assert_eq!(
general_rank, specific_rank,
"rank_hand and rank_hand5 should be consistent for 5-card hands"
);
// Test that evaluation is deterministic across multiple calls
let rank2 = evaluator.rank_hand(&hand);
let rank3 = evaluator.rank_hand(&hand);
let rank4 = evaluator.rank_hand5(&hand);
let rank5 = evaluator.rank_hand5(&hand);
assert_eq!(
general_rank, rank2,
"Hand evaluation should be deterministic"
);
assert_eq!(rank2, rank3, "Hand evaluation should be deterministic");
assert_eq!(specific_rank, rank4, "rank_hand5 should be deterministic");
assert_eq!(rank4, rank5, "rank_hand5 should be deterministic");
}
}
#[test]
fn test_6card_vs_7card_consistency() {
/// Tests that 6-card and 7-card hands find the same best 5-card combination.
let evaluator = LookupHandEvaluator::new().unwrap();
// Create a 7-card hand with a royal flush
let mut seven_cards = Hand::new();
seven_cards
.add_card(Card::from_string("As").unwrap())
.unwrap();
seven_cards
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
seven_cards
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
seven_cards
.add_card(Card::from_string("Js").unwrap())
.unwrap();
seven_cards
.add_card(Card::from_string("Ts").unwrap())
.unwrap();
seven_cards
.add_card(Card::from_string("2d").unwrap())
.unwrap();
seven_cards
.add_card(Card::from_string("3c").unwrap())
.unwrap();
// Create corresponding 6-card hand (remove one extra card)
let mut six_cards = Hand::new();
six_cards
.add_card(Card::from_string("As").unwrap())
.unwrap();
six_cards
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
six_cards
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
six_cards
.add_card(Card::from_string("Js").unwrap())
.unwrap();
six_cards
.add_card(Card::from_string("Ts").unwrap())
.unwrap();
six_cards
.add_card(Card::from_string("2d").unwrap())
.unwrap();
// Create 5-card royal flush for comparison
let mut five_cards = Hand::new();
five_cards
.add_card(Card::from_string("As").unwrap())
.unwrap();
five_cards
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
five_cards
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
five_cards
.add_card(Card::from_string("Js").unwrap())
.unwrap();
five_cards
.add_card(Card::from_string("Ts").unwrap())
.unwrap();
let seven_rank = evaluator.rank_hand(&seven_cards);
let six_rank = evaluator.rank_hand(&six_cards);
let five_rank = evaluator.rank_hand(&five_cards);
// All should find the royal flush and have same rank
assert_eq!(
five_rank, six_rank,
"5-card and 6-card should find same best hand"
);
assert_eq!(
six_rank, seven_rank,
"6-card and 7-card should find same best hand"
);
assert!(five_rank < 1000, "Royal flush should be very strong");
}
#[test]
fn test_evaluation_consistency() {
/// Tests consistency between different evaluation methods.
let evaluator = LookupHandEvaluator::new().unwrap();
let mut hand = Hand::new();
hand.add_card(Card::from_string("As").unwrap()).unwrap();
hand.add_card(Card::from_string("Ks").unwrap()).unwrap();
hand.add_card(Card::from_string("Qs").unwrap()).unwrap();
hand.add_card(Card::from_string("Js").unwrap()).unwrap();
hand.add_card(Card::from_string("Ts").unwrap()).unwrap();
// Test consistency between rank_hand and rank_hand5 for 5-card hands
let general_rank = evaluator.rank_hand(&hand);
let specific_rank = evaluator.rank_hand5(&hand);
assert_eq!(
general_rank, specific_rank,
"rank_hand and rank_hand5 should be consistent for 5-card hands"
);
// Test that hand evaluation is deterministic
let rank2 = evaluator.rank_hand(&hand);
let rank3 = evaluator.rank_hand(&hand);
assert_eq!(
general_rank, rank2,
"Hand evaluation should be deterministic"
);
assert_eq!(rank2, rank3, "Hand evaluation should be deterministic");
}
#[test]
fn test_boundary_conditions() {
/// Tests boundary conditions and edge cases.
let evaluator = LookupHandEvaluator::new().unwrap();
// Test with exactly 5 cards (minimum for evaluation)
let mut min_hand = Hand::new();
min_hand.add_card(Card::from_string("As").unwrap()).unwrap();
min_hand.add_card(Card::from_string("Ks").unwrap()).unwrap();
min_hand.add_card(Card::from_string("Qs").unwrap()).unwrap();
min_hand.add_card(Card::from_string("Js").unwrap()).unwrap();
min_hand.add_card(Card::from_string("Ts").unwrap()).unwrap();
let min_rank = evaluator.rank_hand(&min_hand);
assert!(min_rank > 0, "Minimum 5-card hand should be valid");
// Test with insufficient cards
let mut small_hand = Hand::new();
small_hand
.add_card(Card::from_string("As").unwrap())
.unwrap();
small_hand
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
let small_rank = evaluator.rank_hand(&small_hand);
assert_eq!(small_rank, 0, "Insufficient cards should return 0");
}
}
/// Test suite for performance characteristics.
///
/// These tests validate that the evaluator meets performance requirements
/// and maintains O(1) lookup performance.
#[cfg(test)]
mod performance_tests {
use super::*;
use std::time::Instant;
#[test]
fn test_evaluation_speed() {
/// Tests that hand evaluation meets performance requirements.
let evaluator = LookupHandEvaluator::new().unwrap();
// Create diverse test hands
let test_hands = create_diverse_test_hands(10_000);
let start = Instant::now();
for hand in &test_hands {
let _rank = evaluator.rank_hand(hand);
}
let duration = start.elapsed();
let per_hand = duration / test_hands.len() as u32;
println!("Evaluated {} hands in {:?}", test_hands.len(), duration);
println!("Average time per hand: {:?}", per_hand);
// Performance assertions - should be very fast (O(1) lookup)
assert!(
per_hand.as_micros() < 100,
"Average evaluation should be < 100μs, was {:?}",
per_hand
);
// Total time for 10k hands should be very fast
assert!(
duration.as_secs() < 5,
"10k evaluations should take < 5s, took {:?}",
duration
);
}
#[test]
fn test_o1_lookup_performance() {
/// Tests that lookup table provides O(1) performance.
let evaluator = LookupHandEvaluator::new().unwrap();
// Test with many random hands to verify O(1) behavior
let test_hands = create_diverse_test_hands(1_000);
let mut total_time = std::time::Duration::new(0, 0);
for hand in &test_hands {
let start = Instant::now();
let _rank = evaluator.rank_hand(hand);
let duration = start.elapsed();
total_time += duration;
}
let avg_time = total_time / test_hands.len() as u32;
// O(1) lookup should be very consistent (low variance)
assert!(
avg_time.as_micros() < 50,
"O(1) lookup should be very fast, average was {:?}",
avg_time
);
}
#[test]
fn test_performance_across_hand_types() {
/// Tests performance consistency across different hand types.
let evaluator = LookupHandEvaluator::new().unwrap();
// Test each hand type separately to ensure consistent performance
let hand_types = vec![
create_royal_flush_hands(100),
create_straight_flush_hands(100),
create_four_kind_hands(100),
create_full_house_hands(100),
create_flush_hands(100),
create_straight_hands(100),
create_three_kind_hands(100),
create_two_pair_hands(100),
create_pair_hands(100),
create_high_card_hands(100),
];
let mut all_times = Vec::new();
for hands in hand_types {
let start = Instant::now();
for hand in &hands {
let _rank = evaluator.rank_hand(hand);
}
let duration = start.elapsed();
let avg_time = duration / hands.len() as u32;
all_times.push(avg_time.as_micros());
}
// Calculate mean and standard deviation
let sum: u128 = all_times.iter().sum();
let mean = sum / all_times.len() as u128;
let variance = all_times
.iter()
.map(|&time| {
let diff = time as i64 - mean as i64;
(diff * diff) as u64
})
.sum::<u64>()
/ all_times.len() as u64;
let std_dev = (variance as f64).sqrt() as u64;
println!("Performance stats across hand types:");
println!("Mean time: {} μs", mean);
println!("Std dev: {} μs", std_dev);
println!("Max time: {} μs", all_times.iter().max().unwrap());
println!("Min time: {} μs", all_times.iter().min().unwrap());
// Performance should be very consistent across hand types
assert!(mean < 100, "Average evaluation should be very fast");
assert!(
std_dev < 50,
"Performance variance should be low across hand types"
);
}
#[test]
fn test_memory_usage_stability() {
/// Tests that memory usage remains stable during extended evaluation.
let evaluator = LookupHandEvaluator::new().unwrap();
// Basic memory stability test
let initial_memory_estimate = evaluator.hand_ranks.len() * std::mem::size_of::<u32>();
// Run many evaluations and verify no memory leaks
let test_hands = create_diverse_test_hands(10_000);
for hand in &test_hands {
let _rank = evaluator.rank_hand(hand);
}
// Verify table is still intact and accessible
assert_eq!(
evaluator.hand_ranks.len(),
32_487_834,
"Hand ranks table should maintain expected size"
);
// Verify some known entries are still accessible
let royal_flush_cards = [41, 37, 33, 29, 25];
let rf_rank =
poker_api::evaluator_generator::state_table_generator::StateTableGenerator::eval_5hand(
royal_flush_cards[0],
royal_flush_cards[1],
royal_flush_cards[2],
royal_flush_cards[3],
royal_flush_cards[4],
);
assert!(
rf_rank > 0,
"Table should still be functional after many evaluations"
);
println!("Memory usage estimate: {} bytes", initial_memory_estimate);
println!("Table entries: {}", evaluator.hand_ranks.len());
}
#[test]
fn test_scalability_performance() {
/// Tests performance scalability with increasing hand counts.
let evaluator = LookupHandEvaluator::new().unwrap();
let hand_counts = vec![100, 1_000, 10_000, 100_000];
for &count in &hand_counts {
let test_hands = create_diverse_test_hands(count);
let start = Instant::now();
for hand in &test_hands {
let _rank = evaluator.rank_hand(hand);
}
let duration = start.elapsed();
let per_hand = duration / count as u32;
println!("Evaluated {} hands in {:?}", count, duration);
println!("Average time per hand: {:?}", per_hand);
// Performance should scale linearly (O(n) for n evaluations)
// Each evaluation should still be O(1)
assert!(
per_hand.as_micros() < 100,
"Average evaluation should remain fast even at scale: {:?}",
per_hand
);
// Total time should be reasonable
assert!(
duration.as_secs() < 30,
"Large batch should complete in reasonable time: {:?}",
duration
);
}
}
#[test]
fn test_memory_usage_bounds() {
/// Tests that memory usage stays within acceptable bounds.
let evaluator = LookupHandEvaluator::new().unwrap();
// Basic memory usage validation
assert_eq!(
evaluator.hand_ranks.len(),
32_487_834,
"Hand ranks table should have expected size"
);
// Verify table is properly loaded (not all zeros)
let populated_entries = evaluator
.hand_ranks
.iter()
.filter(|&&rank| rank > 0)
.count();
assert!(
populated_entries > 1_000_000,
"Table should have many populated entries, got {}",
populated_entries
);
// Test that we can access any table entry without panicking
let test_indices = [0, 1000, 1_000_000, 32_000_000];
for &index in &test_indices {
if index < evaluator.hand_ranks.len() {
let _rank = evaluator.hand_ranks[index];
// Rank can be 0 for unpopulated entries, but access should not panic
}
}
}
#[test]
fn test_table_loading_performance() {
/// Tests that table loading performance is acceptable.
let start = Instant::now();
let evaluator = LookupHandEvaluator::new().unwrap();
let load_time = start.elapsed();
println!("Table loading took: {:?}", load_time);
// Table loading should be reasonable (depends on disk speed)
assert!(
load_time.as_secs() < 10,
"Table loading should take < 10s, took {:?}",
load_time
);
// After loading, evaluation should work
let mut hand = Hand::new();
hand.add_card(Card::from_string("As").unwrap()).unwrap();
hand.add_card(Card::from_string("Ks").unwrap()).unwrap();
hand.add_card(Card::from_string("Qs").unwrap()).unwrap();
hand.add_card(Card::from_string("Js").unwrap()).unwrap();
hand.add_card(Card::from_string("Ts").unwrap()).unwrap();
let _rank = evaluator.rank_hand(&hand);
// If we get here without panicking, the table was loaded correctly
}
}
/// Test suite for hand type boundary conditions.
///
/// These tests verify the exact boundaries between different hand types
/// and ensure correct ranking at the transitions between categories.
#[cfg(test)]
mod hand_type_boundaries {
use super::*;
#[test]
fn test_flush_vs_straight_boundaries() {
/// Tests the boundary between flush and straight hand types.
let evaluator = LookupHandEvaluator::new().unwrap();
// Strongest straight: A K Q J T
let mut best_straight = Hand::new();
best_straight
.add_card(Card::from_string("As").unwrap())
.unwrap();
best_straight
.add_card(Card::from_string("Kd").unwrap())
.unwrap();
best_straight
.add_card(Card::from_string("Qh").unwrap())
.unwrap();
best_straight
.add_card(Card::from_string("Jc").unwrap())
.unwrap();
best_straight
.add_card(Card::from_string("Ts").unwrap())
.unwrap();
// Weakest flush: K J 8 5 2 same suit
let mut weakest_flush = Hand::new();
weakest_flush
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
weakest_flush
.add_card(Card::from_string("Js").unwrap())
.unwrap();
weakest_flush
.add_card(Card::from_string("8s").unwrap())
.unwrap();
weakest_flush
.add_card(Card::from_string("5s").unwrap())
.unwrap();
weakest_flush
.add_card(Card::from_string("2s").unwrap())
.unwrap();
let bs_rank = evaluator.rank_hand(&best_straight);
let wf_rank = evaluator.rank_hand(&weakest_flush);
assert!(
wf_rank < bs_rank,
"Even weakest flush should beat strongest straight"
);
}
#[test]
fn test_full_house_vs_flush_boundaries() {
/// Tests the boundary between full house and flush hand types.
let evaluator = LookupHandEvaluator::new().unwrap();
// Strongest flush: A K Q J 9 same suit
let mut best_flush = Hand::new();
best_flush
.add_card(Card::from_string("As").unwrap())
.unwrap();
best_flush
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
best_flush
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
best_flush
.add_card(Card::from_string("Js").unwrap())
.unwrap();
best_flush
.add_card(Card::from_string("9s").unwrap())
.unwrap();
// Weakest full house: A A A 2 2
let mut weakest_fh = Hand::new();
weakest_fh
.add_card(Card::from_string("As").unwrap())
.unwrap();
weakest_fh
.add_card(Card::from_string("Ah").unwrap())
.unwrap();
weakest_fh
.add_card(Card::from_string("Ad").unwrap())
.unwrap();
weakest_fh
.add_card(Card::from_string("2s").unwrap())
.unwrap();
weakest_fh
.add_card(Card::from_string("2h").unwrap())
.unwrap();
let bf_rank = evaluator.rank_hand(&best_flush);
let wfh_rank = evaluator.rank_hand(&weakest_fh);
assert!(
wfh_rank < bf_rank,
"Even weakest full house should beat strongest flush"
);
}
#[test]
fn test_straight_vs_three_kind_boundaries() {
/// Tests the boundary between straight and three of a kind hand types.
let evaluator = LookupHandEvaluator::new().unwrap();
// Strongest three of a kind: A A A K Q
let mut best_tk = Hand::new();
best_tk.add_card(Card::from_string("As").unwrap()).unwrap();
best_tk.add_card(Card::from_string("Ah").unwrap()).unwrap();
best_tk.add_card(Card::from_string("Ad").unwrap()).unwrap();
best_tk.add_card(Card::from_string("Ks").unwrap()).unwrap();
best_tk.add_card(Card::from_string("Qs").unwrap()).unwrap();
// Weakest straight: 6 5 4 3 2
let mut weakest_straight = Hand::new();
weakest_straight
.add_card(Card::from_string("6s").unwrap())
.unwrap();
weakest_straight
.add_card(Card::from_string("5d").unwrap())
.unwrap();
weakest_straight
.add_card(Card::from_string("4h").unwrap())
.unwrap();
weakest_straight
.add_card(Card::from_string("3c").unwrap())
.unwrap();
weakest_straight
.add_card(Card::from_string("2s").unwrap())
.unwrap();
let btk_rank = evaluator.rank_hand(&best_tk);
let ws_rank = evaluator.rank_hand(&weakest_straight);
assert!(
ws_rank < btk_rank,
"Even weakest straight should beat strongest three of a kind"
);
}
#[test]
fn test_all_hand_type_boundaries() {
/// Tests all hand type boundaries to ensure proper ranking hierarchy.
let evaluator = LookupHandEvaluator::new().unwrap();
// Create hands at the boundaries of each hand type
let hands = vec![
("Royal Flush", create_royal_flush_hands(1)[0].clone()),
("Straight Flush", create_straight_flush_hands(1)[0].clone()),
("Four of Kind", create_four_kind_hands(1)[0].clone()),
("Full House", create_full_house_hands(1)[0].clone()),
("Flush", create_flush_hands(1)[0].clone()),
("Straight", create_straight_hands(1)[0].clone()),
("Three of Kind", create_three_kind_hands(1)[0].clone()),
("Two Pair", create_two_pair_hands(1)[0].clone()),
("Pair", create_pair_hands(1)[0].clone()),
("High Card", create_high_card_hands(1)[0].clone()),
];
// Get ranks for all hands
let mut ranks: Vec<(String, u32)> = Vec::new();
for (name, hand) in hands {
let rank = evaluator.rank_hand(&hand);
ranks.push((name.to_string(), rank));
}
// Verify hand hierarchy (lower rank = better hand)
for i in 0..ranks.len() - 1 {
assert!(
ranks[i].1 < ranks[i + 1].1,
"Hand type {} should be better than {}: {} < {}",
ranks[i].0,
ranks[i + 1].0,
ranks[i].1,
ranks[i + 1].1
);
}
// Print the ranking hierarchy for verification
println!("\nHand Type Ranking Hierarchy:");
for (name, rank) in ranks {
println!("{}: {}", name, rank);
}
}
}
/// Test suite for edge cases and boundary conditions.
///
/// These tests verify correct behavior in unusual or extreme situations
/// that might not be covered by normal gameplay scenarios.
#[cfg(test)]
mod edge_cases {
use super::*;
#[test]
fn test_insufficient_cards() {
/// Tests behavior with insufficient cards for evaluation.
///
/// The evaluator should handle hands with fewer than 5 cards
/// gracefully, returning 0 or a safe default value.
///
/// # Test Cases
/// - 0 cards: Should return 0
/// - 1-4 cards: Should return 0
/// - 5+ cards: Should return valid rank
let evaluator = LookupHandEvaluator::new().unwrap();
// Test with insufficient cards
let mut small_hand = Hand::new();
small_hand
.add_card(Card::from_string("As").unwrap())
.unwrap();
small_hand
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
let rank = evaluator.rank_hand(&small_hand);
assert_eq!(rank, 0, "Hands with < 5 cards should return 0");
}
#[test]
fn test_duplicate_cards() {
/// Tests behavior when duplicate cards are added to a hand.
///
/// In a real poker scenario, duplicate cards shouldn't exist,
/// but the evaluator should handle this case gracefully.
///
/// # Test Case
/// - Hand with duplicate cards
/// - Expected: Should not crash, should return some rank value
let evaluator = LookupHandEvaluator::new().unwrap();
let mut hand = Hand::new();
hand.add_card(Card::from_string("As").unwrap()).unwrap();
hand.add_card(Card::from_string("As").unwrap()).unwrap(); // Duplicate
hand.add_card(Card::from_string("Ks").unwrap()).unwrap();
hand.add_card(Card::from_string("Qs").unwrap()).unwrap();
hand.add_card(Card::from_string("Js").unwrap()).unwrap();
// Should not panic, should return some value
let rank = evaluator.rank_hand(&hand);
assert!(rank > 0, "Should handle duplicate cards gracefully");
}
#[test]
fn test_unusual_card_combinations() {
/// Tests unusual card combinations that might cause issues.
let evaluator = LookupHandEvaluator::new().unwrap();
// Test 1: All cards same rank (five of a kind - impossible but should handle)
let mut five_same_rank = Hand::new();
five_same_rank
.add_card(Card::from_string("As").unwrap())
.unwrap();
five_same_rank
.add_card(Card::from_string("As").unwrap())
.unwrap();
five_same_rank
.add_card(Card::from_string("As").unwrap())
.unwrap();
five_same_rank
.add_card(Card::from_string("As").unwrap())
.unwrap();
five_same_rank
.add_card(Card::from_string("As").unwrap())
.unwrap();
let rank1 = evaluator.rank_hand(&five_same_rank);
assert!(rank1 > 0, "Should handle five same rank cards");
// Test 2: All cards same suit (should be flush)
let mut all_same_suit = Hand::new();
all_same_suit
.add_card(Card::from_string("As").unwrap())
.unwrap();
all_same_suit
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
all_same_suit
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
all_same_suit
.add_card(Card::from_string("Js").unwrap())
.unwrap();
all_same_suit
.add_card(Card::from_string("9s").unwrap())
.unwrap();
let rank2 = evaluator.rank_hand(&all_same_suit);
assert!(rank2 > 0, "Should handle all same suit cards");
// Test 3: Alternating high and low cards
let mut alternating = Hand::new();
alternating
.add_card(Card::from_string("As").unwrap())
.unwrap();
alternating
.add_card(Card::from_string("2d").unwrap())
.unwrap();
alternating
.add_card(Card::from_string("Kh").unwrap())
.unwrap();
alternating
.add_card(Card::from_string("3c").unwrap())
.unwrap();
alternating
.add_card(Card::from_string("Qh").unwrap())
.unwrap();
let rank3 = evaluator.rank_hand(&alternating);
assert!(rank3 > 0, "Should handle alternating high/low cards");
}
#[test]
fn test_boundary_card_values() {
/// Tests cards at the extreme ends of the ranking spectrum.
let evaluator = LookupHandEvaluator::new().unwrap();
// Test with lowest possible cards
let mut low_cards = Hand::new();
low_cards
.add_card(Card::from_string("2s").unwrap())
.unwrap();
low_cards
.add_card(Card::from_string("2d").unwrap())
.unwrap();
low_cards
.add_card(Card::from_string("3h").unwrap())
.unwrap();
low_cards
.add_card(Card::from_string("4c").unwrap())
.unwrap();
low_cards
.add_card(Card::from_string("5s").unwrap())
.unwrap();
let low_rank = evaluator.rank_hand(&low_cards);
assert!(low_rank > 7000, "Low cards should have high rank number");
// Test with highest possible cards
let mut high_cards = Hand::new();
high_cards
.add_card(Card::from_string("As").unwrap())
.unwrap();
high_cards
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
high_cards
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
high_cards
.add_card(Card::from_string("Js").unwrap())
.unwrap();
high_cards
.add_card(Card::from_string("Ts").unwrap())
.unwrap();
let high_rank = evaluator.rank_hand(&high_cards);
assert!(high_rank < 1000, "High cards should have low rank number");
assert!(
high_rank < low_rank,
"High cards should be better than low cards"
);
}
#[test]
fn test_malformed_hands() {
/// Tests various malformed or unusual hand scenarios.
let evaluator = LookupHandEvaluator::new().unwrap();
// Test with exactly 5 cards (minimum)
let mut min_hand = Hand::new();
min_hand.add_card(Card::from_string("As").unwrap()).unwrap();
min_hand.add_card(Card::from_string("Ks").unwrap()).unwrap();
min_hand.add_card(Card::from_string("Qs").unwrap()).unwrap();
min_hand.add_card(Card::from_string("Js").unwrap()).unwrap();
min_hand.add_card(Card::from_string("Ts").unwrap()).unwrap();
let min_rank = evaluator.rank_hand(&min_hand);
assert!(min_rank > 0, "Minimum 5-card hand should evaluate");
// Test with more than 5 cards (should find best 5)
let mut max_hand = Hand::new();
max_hand.add_card(Card::from_string("As").unwrap()).unwrap();
max_hand.add_card(Card::from_string("Ks").unwrap()).unwrap();
max_hand.add_card(Card::from_string("Qs").unwrap()).unwrap();
max_hand.add_card(Card::from_string("Js").unwrap()).unwrap();
max_hand.add_card(Card::from_string("Ts").unwrap()).unwrap();
max_hand.add_card(Card::from_string("2d").unwrap()).unwrap();
max_hand.add_card(Card::from_string("3c").unwrap()).unwrap();
let max_rank = evaluator.rank_hand(&max_hand);
assert_eq!(min_rank, max_rank, "Should find same best 5 cards");
}
#[test]
fn test_stress_test_edge_cases() {
/// Stress test with many unusual combinations.
let evaluator = LookupHandEvaluator::new().unwrap();
// Generate various edge case combinations
let edge_cases = vec![
vec!["Ad", "Ah", "Ac", "As", "2s"], // Four aces with deuce
vec!["Kd", "Kh", "Kc", "2s", "2d"], // Full house with low pair
vec!["As", "2s", "3s", "4s", "6s"], // Flush with gap
vec!["As", "2d", "3h", "4c", "5s"], // Wheel straight
vec!["2s", "3d", "4h", "5c", "6s"], // Low straight
vec!["9s", "8d", "7h", "6c", "5s"], // Medium straight
vec!["As", "Kd", "Qh", "Jc", "Ts"], // Broadway straight
];
for cards in &edge_cases {
let mut hand = Hand::new();
for card_str in cards {
hand.add_card(Card::from_string(card_str).unwrap()).unwrap();
}
let rank = evaluator.rank_hand(&hand);
assert!(
rank > 0,
"Edge case hand {:?} should evaluate successfully",
cards
);
// Test that evaluation is deterministic
let rank2 = evaluator.rank_hand(&hand);
assert_eq!(rank, rank2, "Edge case evaluation should be deterministic");
}
}
}
/// Test suite for performance characteristics.
///
/// These tests validate that the evaluator meets performance requirements
/// and doesn't regress in speed or memory usage.
#[cfg(test)]
mod performance {
use super::*;
use std::time::Instant;
#[test]
fn test_evaluation_speed() {
/// Tests that hand evaluation meets performance requirements.
///
/// Verifies that evaluating a large number of hands completes
/// within reasonable time constraints.
///
/// # Performance Target
/// - 1 million evaluations should complete in < 30 seconds
/// - Average evaluation time should be < 30 microseconds
///
/// # Test Data
/// Uses a variety of hand types to ensure balanced testing
let evaluator = LookupHandEvaluator::new().unwrap();
// Create diverse test hands
let test_hands = create_diverse_test_hands(100_000);
let start = Instant::now();
for hand in &test_hands {
let _rank = evaluator.rank_hand(hand);
}
let duration = start.elapsed();
let per_hand = duration / test_hands.len() as u32;
println!("Evaluated {} hands in {:?}", test_hands.len(), duration);
println!("Average time per hand: {:?}", per_hand);
// Performance assertions
assert!(
duration.as_secs() < 30,
"1M evaluations should take < 30s, took {:?}",
duration
);
assert!(
per_hand.as_micros() < 30,
"Average evaluation should be < 30μs, was {:?}",
per_hand
);
}
#[test]
fn test_memory_usage() {
/// Tests that memory usage stays within acceptable bounds.
///
/// Verifies that the evaluator doesn't leak memory or use
/// excessive resources during operation.
///
/// # Memory Constraints
/// - Peak memory usage should be < 200MB
/// - No memory leaks during repeated evaluations
let evaluator = LookupHandEvaluator::new().unwrap();
// Force table generation and loading
let hand = create_test_hand();
let _rank = evaluator.rank_hand(&hand);
// Basic memory usage check
// In a real implementation, you might use a memory profiler
assert!(
true,
"Memory usage should be monitored in integration tests"
);
}
}
/// Test suite for integration testing of the complete evaluation pipeline.
///
/// These tests verify that all components work together correctly
/// and that the complete system produces expected results.
#[cfg(test)]
mod integration_tests {
use super::*;
#[test]
fn test_complete_poker_scenario() {
/// Tests a complete poker scenario with multiple hands and evaluation.
let evaluator = LookupHandEvaluator::new().unwrap();
// Simulate a Texas Hold'em scenario
let board_cards = vec![
Card::from_string("As").unwrap(),
Card::from_string("Ks").unwrap(),
Card::from_string("Qs").unwrap(),
Card::from_string("Js").unwrap(),
Card::from_string("2d").unwrap(),
];
// Player hands
let player1_hole = vec![
Card::from_string("Ts").unwrap(),
Card::from_string("9s").unwrap(),
];
let player2_hole = vec![
Card::from_string("Kh").unwrap(),
Card::from_string("Qh").unwrap(),
];
let player3_hole = vec![
Card::from_string("2s").unwrap(),
Card::from_string("3s").unwrap(),
];
// Evaluate each player's complete hand
let mut board = Hand::new();
for card in board_cards {
board.add_card(card).unwrap();
}
let player1_rank = evaluator.rank_hand_with_hole_cards(
player1_hole[0].index() as u32,
player1_hole[1].index() as u32,
&board,
);
let player2_rank = evaluator.rank_hand_with_hole_cards(
player2_hole[0].index() as u32,
player2_hole[1].index() as u32,
&board,
);
let player3_rank = evaluator.rank_hand_with_hole_cards(
player3_hole[0].index() as u32,
player3_hole[1].index() as u32,
&board,
);
// Player 1 should have royal flush (best hand)
assert!(player1_rank < 1000, "Player 1 should have royal flush");
// Player 2 should have full house (good hand)
assert!(player2_rank < 20000, "Player 2 should have full house");
// Player 3 should have pair of deuces (weak hand)
assert!(player3_rank > 50000, "Player 3 should have weak hand");
// Verify hand hierarchy
assert!(player1_rank < player2_rank, "Player 1 should beat Player 2");
assert!(player2_rank < player3_rank, "Player 2 should beat Player 3");
}
#[test]
fn test_evaluation_pipeline_consistency() {
/// Tests that the complete evaluation pipeline is consistent across different inputs.
let evaluator = LookupHandEvaluator::new().unwrap();
// Test various hand creation methods
let test_hands = vec![
// Method 1: Direct hand creation
{
let mut hand = Hand::new();
hand.add_card(Card::from_string("As").unwrap()).unwrap();
hand.add_card(Card::from_string("Ks").unwrap()).unwrap();
hand.add_card(Card::from_string("Qs").unwrap()).unwrap();
hand.add_card(Card::from_string("Js").unwrap()).unwrap();
hand.add_card(Card::from_string("Ts").unwrap()).unwrap();
hand
},
// Method 2: Using helper function
create_test_hand(),
// Method 3: Manual card array creation
{
let mut hand = Hand::new();
let cards = ["As", "Ks", "Qs", "Js", "Ts"];
for card_str in cards {
hand.add_card(Card::from_string(card_str).unwrap()).unwrap();
}
hand
},
];
// All methods should produce the same result
let first_rank = evaluator.rank_hand(&test_hands[0]);
for (i, hand) in test_hands.iter().enumerate().skip(1) {
let rank = evaluator.rank_hand(hand);
assert_eq!(
first_rank, rank,
"Hand creation method {} should produce same result as method 0",
i
);
}
}
#[test]
fn test_large_scale_evaluation() {
/// Tests evaluation of many hands to ensure system stability.
let evaluator = LookupHandEvaluator::new().unwrap();
// Create a large number of diverse hands
let mut all_hands = Vec::new();
all_hands.extend(create_royal_flush_hands(10));
all_hands.extend(create_straight_flush_hands(50));
all_hands.extend(create_four_kind_hands(100));
all_hands.extend(create_full_house_hands(200));
all_hands.extend(create_flush_hands(300));
all_hands.extend(create_straight_hands(400));
all_hands.extend(create_three_kind_hands(500));
all_hands.extend(create_two_pair_hands(600));
all_hands.extend(create_pair_hands(700));
all_hands.extend(create_high_card_hands(800));
// Evaluate all hands
let mut ranks = Vec::new();
for hand in &all_hands {
let rank = evaluator.rank_hand(hand);
assert!(rank > 0, "All hands should produce valid ranks");
ranks.push(rank);
}
// Verify we got expected number of results
assert_eq!(
ranks.len(),
all_hands.len(),
"Should have rank for each hand"
);
// Verify all ranks are within valid range
for &rank in &ranks {
assert!(rank <= 7462, "All ranks should be within Java range");
}
// Verify hand hierarchy is maintained
let mut sorted_ranks = ranks.clone();
sorted_ranks.sort();
assert_eq!(
ranks.len(),
sorted_ranks.len(),
"Sorting should preserve count"
);
// Verify no duplicate ranks (should be very unlikely with proper distribution)
let unique_ranks: std::collections::HashSet<u32> = ranks.iter().cloned().collect();
assert!(
unique_ranks.len() > ranks.len() / 2,
"Should have reasonable rank diversity"
);
println!("Successfully evaluated {} hands", all_hands.len());
println!(
"Rank range: {} - {}",
sorted_ranks[0],
sorted_ranks[sorted_ranks.len() - 1]
);
println!("Unique ranks: {}", unique_ranks.len());
}
#[test]
fn test_end_to_end_java_compatibility() {
/// End-to-end test of Java compatibility across the entire pipeline.
let evaluator = LookupHandEvaluator::new().unwrap();
// Test complete pipeline with Java-style inputs and outputs
let java_style_hands = vec![
// (hand_cards, expected_rank_range)
(vec!["As", "Ks", "Qs", "Js", "Ts"], (1, 100)), // Royal flush
(vec!["9s", "8s", "7s", "6s", "5s"], (1000, 5000)), // Straight flush
(vec!["Ah", "Ad", "Ac", "As", "Kd"], (5000, 10000)), // Four aces
(vec!["As", "Ah", "Ad", "Ks", "Kh"], (15000, 20000)), // Full house
(vec!["As", "Ks", "Qs", "Js", "9s"], (20000, 25000)), // Flush
(vec!["As", "Kd", "Qh", "Jc", "Ts"], (25000, 30000)), // Straight
(vec!["As", "Ah", "Ad", "Ks", "Qs"], (30000, 35000)), // Three of kind
(vec!["As", "Ah", "Ks", "Kd", "Qs"], (35000, 40000)), // Two pair
(vec!["As", "Ah", "Ks", "Qs", "Js"], (40000, 45000)), // Pair
(vec!["As", "Kd", "Qh", "Jc", "9s"], (45000, 50000)), // High card
];
for (cards, (min_rank, max_rank)) in &java_style_hands {
let mut hand = Hand::new();
for card_str in cards {
hand.add_card(Card::from_string(card_str).unwrap()).unwrap();
}
// Test complete pipeline: card creation -> hand creation -> evaluation
let rank = evaluator.rank_hand(&hand);
assert!(
rank >= *min_rank && rank <= *max_rank,
"Java-style hand {:?} should have rank in range {}-{}, got {}",
cards,
min_rank,
max_rank,
rank
);
// Test Java-style key generation as well
let key_rank = evaluator.rank_hand5(&hand);
assert_eq!(
rank, key_rank,
"Java-style and general evaluation should match"
);
// Verify card encoding matches Java expectations
for card_str in cards {
let card = Card::from_string(card_str).unwrap();
assert!(
card.rank() >= 1 && card.rank() <= 13,
"Card rank should be in Java range"
);
assert!(
card.suit() >= 1 && card.suit() <= 4,
"Card suit should be in Java range"
);
}
}
}
#[test]
fn test_system_resource_usage() {
/// Tests that the system uses resources efficiently during extended operation.
let evaluator = LookupHandEvaluator::new().unwrap();
// Verify evaluator was created successfully
assert_eq!(
evaluator.hand_ranks.len(),
32_487_834,
"Evaluator should have correct table size"
);
// Test memory estimate
let memory_estimate = evaluator.hand_ranks.len() * std::mem::size_of::<u32>();
println!(
"Estimated memory usage: {} MB",
memory_estimate / (1024 * 1024)
);
// Verify table is populated
let populated_entries = evaluator
.hand_ranks
.iter()
.filter(|&&rank| rank > 0)
.count();
assert!(
populated_entries > 1_000_000,
"Table should have many populated entries"
);
// Test that system remains responsive after many evaluations
let test_hands = create_diverse_test_hands(1000);
let start = std::time::Instant::now();
for hand in &test_hands {
let _rank = evaluator.rank_hand(hand);
}
let duration = start.elapsed();
println!("1000 evaluations took: {:?}", duration);
assert!(duration.as_secs() < 5, "System should remain responsive");
// Verify table integrity after heavy usage
let royal_flush_cards = [41, 37, 33, 29, 25];
let rf_rank =
poker_api::evaluator_generator::state_table_generator::StateTableGenerator::eval_5hand(
royal_flush_cards[0],
royal_flush_cards[1],
royal_flush_cards[2],
royal_flush_cards[3],
royal_flush_cards[4],
);
assert!(
rf_rank > 0,
"Table should maintain integrity after heavy usage"
);
}
}
/// Helper functions for creating test data.
fn create_diverse_test_hands(count: usize) -> Vec<Hand> {
// Creates a diverse set of test hands for performance testing.
//
// Generates hands of different types and strengths to ensure
// balanced performance characteristics across the evaluation space.
let mut hands = Vec::with_capacity(count);
// Sample different hand types
let hand_types = vec![
vec!["As", "Ks", "Qs", "Js", "Ts"], // Royal flush
vec!["As", "Ah", "Ad", "Ac", "Ks"], // Four of a kind
vec!["As", "Ah", "Ad", "Ks", "Kh"], // Full house
vec!["As", "Ks", "Qs", "Js", "9s"], // Straight
vec!["As", "Ks", "Qs", "Js", "Ts"], // Flush (different from royal)
vec!["As", "Ah", "Ad", "Ks", "Qs"], // Three of a kind
vec!["As", "Ah", "Ks", "Kd", "Qs"], // Two pair
vec!["As", "Ah", "Ks", "Qs", "Js"], // One pair
vec!["As", "Ks", "Qs", "Js", "9d"], // High card
];
for i in 0..count {
let hand_type = &hand_types[i % hand_types.len()];
let mut hand = Hand::new();
for &card_str in hand_type {
hand.add_card(Card::from_string(card_str).unwrap()).unwrap();
}
hands.push(hand);
}
hands
}
fn create_test_hand() -> Hand {
// Creates a simple test hand for basic testing.
let mut hand = Hand::new();
hand.add_card(Card::from_string("As").unwrap()).unwrap();
hand.add_card(Card::from_string("Ks").unwrap()).unwrap();
hand.add_card(Card::from_string("Qs").unwrap()).unwrap();
hand.add_card(Card::from_string("Js").unwrap()).unwrap();
hand.add_card(Card::from_string("Ts").unwrap()).unwrap();
hand
}
fn create_royal_flush_hands(count: usize) -> Vec<Hand> {
let mut hands = Vec::with_capacity(count);
for i in 0..count {
let mut hand = Hand::new();
hand.add_card(Card::from_string("As").unwrap()).unwrap();
hand.add_card(Card::from_string("Ks").unwrap()).unwrap();
hand.add_card(Card::from_string("Qs").unwrap()).unwrap();
hand.add_card(Card::from_string("Js").unwrap()).unwrap();
hand.add_card(Card::from_string("Ts").unwrap()).unwrap();
hands.push(hand);
}
hands
}
fn create_straight_flush_hands(count: usize) -> Vec<Hand> {
let mut hands = Vec::with_capacity(count);
for i in 0..count {
let mut hand = Hand::new();
let base = 9 - (i % 8); // Vary the straight flush high card
hand.add_card(Card::from_string(&format!("{}s", rank_char(base + 4))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}s", rank_char(base + 3))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}s", rank_char(base + 2))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}s", rank_char(base + 1))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}s", rank_char(base))).unwrap())
.unwrap();
hands.push(hand);
}
hands
}
fn create_four_kind_hands(count: usize) -> Vec<Hand> {
let mut hands = Vec::with_capacity(count);
for i in 0..count {
let mut hand = Hand::new();
let rank = 13 - (i % 11); // Vary the four of a kind rank
hand.add_card(Card::from_string(&format!("{}s", rank_char(rank))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}h", rank_char(rank))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}d", rank_char(rank))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}c", rank_char(rank))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}s", rank_char(13 - (i % 12 + 1)))).unwrap())
.unwrap();
hands.push(hand);
}
hands
}
fn create_full_house_hands(count: usize) -> Vec<Hand> {
let mut hands = Vec::with_capacity(count);
for i in 0..count {
let mut hand = Hand::new();
let trip_rank = 13 - (i % 10);
let pair_rank = 13 - ((i + 1) % 10);
hand.add_card(Card::from_string(&format!("{}s", rank_char(trip_rank))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}h", rank_char(trip_rank))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}d", rank_char(trip_rank))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}s", rank_char(pair_rank))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}h", rank_char(pair_rank))).unwrap())
.unwrap();
hands.push(hand);
}
hands
}
fn create_flush_hands(count: usize) -> Vec<Hand> {
let mut hands = Vec::with_capacity(count);
for i in 0..count {
let mut hand = Hand::new();
let suit = ["s", "h", "d", "c"][i % 4];
hand.add_card(Card::from_string(&format!("A{}", suit)).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("K{}", suit)).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("Q{}", suit)).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("J{}", suit)).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}s", rank_char(9 - (i % 5)))).unwrap())
.unwrap();
hands.push(hand);
}
hands
}
fn create_straight_hands(count: usize) -> Vec<Hand> {
let mut hands = Vec::with_capacity(count);
for i in 0..count {
let mut hand = Hand::new();
let high_card = 13 - (i % 9); // Vary straight high card
hand.add_card(Card::from_string(&format!("{}s", rank_char(high_card))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}h", rank_char(high_card - 1))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}d", rank_char(high_card - 2))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}c", rank_char(high_card - 3))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}s", rank_char(high_card - 4))).unwrap())
.unwrap();
hands.push(hand);
}
hands
}
fn create_three_kind_hands(count: usize) -> Vec<Hand> {
let mut hands = Vec::with_capacity(count);
for i in 0..count {
let mut hand = Hand::new();
let trip_rank = 13 - (i % 11);
hand.add_card(Card::from_string(&format!("{}s", rank_char(trip_rank))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}h", rank_char(trip_rank))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}d", rank_char(trip_rank))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}s", rank_char(13 - (i % 10 + 1)))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}h", rank_char(13 - (i % 9 + 2)))).unwrap())
.unwrap();
hands.push(hand);
}
hands
}
fn create_two_pair_hands(count: usize) -> Vec<Hand> {
let mut hands = Vec::with_capacity(count);
for i in 0..count {
let mut hand = Hand::new();
let high_pair = 13 - (i % 10);
let low_pair = 13 - ((i + 1) % 9);
hand.add_card(Card::from_string(&format!("{}s", rank_char(high_pair))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}h", rank_char(high_pair))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}s", rank_char(low_pair))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}h", rank_char(low_pair))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}s", rank_char(13 - (i % 8 + 3)))).unwrap())
.unwrap();
hands.push(hand);
}
hands
}
fn create_pair_hands(count: usize) -> Vec<Hand> {
let mut hands = Vec::with_capacity(count);
for i in 0..count {
let mut hand = Hand::new();
let pair_rank = 13 - (i % 12);
hand.add_card(Card::from_string(&format!("{}s", rank_char(pair_rank))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}h", rank_char(pair_rank))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}s", rank_char(13 - (i % 10 + 1)))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}h", rank_char(13 - (i % 9 + 2)))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}s", rank_char(13 - (i % 8 + 3)))).unwrap())
.unwrap();
hands.push(hand);
}
hands
}
fn create_high_card_hands(count: usize) -> Vec<Hand> {
let mut hands = Vec::with_capacity(count);
for i in 0..count {
let mut hand = Hand::new();
hand.add_card(Card::from_string("As").unwrap()).unwrap();
hand.add_card(Card::from_string(&format!("{}h", rank_char(12 - (i % 11)))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}d", rank_char(11 - (i % 10)))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}c", rank_char(10 - (i % 9)))).unwrap())
.unwrap();
hand.add_card(Card::from_string(&format!("{}s", rank_char(9 - (i % 8)))).unwrap())
.unwrap();
hands.push(hand);
}
hands
}
fn rank_char(rank: usize) -> String {
match rank {
13 => "A".to_string(),
12 => "K".to_string(),
11 => "Q".to_string(),
10 => "J".to_string(),
9 => "T".to_string(),
8 => "9".to_string(),
7 => "8".to_string(),
6 => "7".to_string(),
5 => "6".to_string(),
4 => "5".to_string(),
3 => "4".to_string(),
2 => "3".to_string(),
1 => "2".to_string(),
_ => "2".to_string(),
}
}