//! # 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 = 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::() / 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::(); // 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 = 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::(); 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 { // 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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(), } }