3211 lines
115 KiB
Rust
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(¬_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(),
|
|
}
|
|
}
|