Holdem basic core structures from scratch.
This commit is contained in:
parent
f02c35bef3
commit
e94bf42dce
53 changed files with 6316 additions and 11906 deletions
400
holdem_core/tests/integration_tests.rs
Normal file
400
holdem_core/tests/integration_tests.rs
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
//! Integration tests for poker data structures
|
||||
//!
|
||||
//! These tests verify that the holdem_core poker structures work together correctly,
|
||||
//! including serialization round-trips and performance benchmarks.
|
||||
|
||||
use holdem_core::{board, card, deck, hand, hole_cards};
|
||||
use std::time::Instant;
|
||||
|
||||
#[test]
|
||||
fn test_integration_hand_from_hole_cards_and_board() {
|
||||
// Test creating hands from various hole card and board combinations
|
||||
|
||||
// Test with preflop (no board cards)
|
||||
let hole_cards = hole_cards::HoleCards::new(
|
||||
card::Card::new(12, 0).unwrap(), // As
|
||||
card::Card::new(11, 1).unwrap(), // Kh
|
||||
)
|
||||
.unwrap();
|
||||
let empty_board = board::Board::new();
|
||||
let preflop_hand = hand::Hand::from_hole_cards_and_board(&hole_cards, &empty_board).unwrap();
|
||||
assert_eq!(preflop_hand.len, 2);
|
||||
assert_eq!(preflop_hand.cards()[0], card::Card::new(12, 0).unwrap());
|
||||
assert_eq!(preflop_hand.cards()[1], card::Card::new(11, 1).unwrap());
|
||||
|
||||
// Test with flop
|
||||
let mut flop_board = board::Board::new();
|
||||
flop_board
|
||||
.deal_flop(vec![
|
||||
card::Card::new(10, 2).unwrap(), // Qd
|
||||
card::Card::new(9, 3).unwrap(), // Js
|
||||
card::Card::new(8, 0).unwrap(), // Tc
|
||||
])
|
||||
.unwrap();
|
||||
let flop_hand = hand::Hand::from_hole_cards_and_board(&hole_cards, &flop_board).unwrap();
|
||||
assert_eq!(flop_hand.len, 5);
|
||||
assert_eq!(flop_hand.cards()[0], card::Card::new(12, 0).unwrap()); // As
|
||||
assert_eq!(flop_hand.cards()[1], card::Card::new(11, 1).unwrap()); // Kh
|
||||
assert_eq!(flop_hand.cards()[2], card::Card::new(10, 2).unwrap()); // Qd
|
||||
assert_eq!(flop_hand.cards()[3], card::Card::new(9, 3).unwrap()); // Js
|
||||
assert_eq!(flop_hand.cards()[4], card::Card::new(8, 0).unwrap()); // Tc
|
||||
|
||||
// Test with turn
|
||||
let mut turn_board = flop_board.clone();
|
||||
turn_board
|
||||
.deal_turn(card::Card::new(7, 1).unwrap())
|
||||
.unwrap(); // 9h
|
||||
let turn_hand = hand::Hand::from_hole_cards_and_board(&hole_cards, &turn_board).unwrap();
|
||||
assert_eq!(turn_hand.len, 6);
|
||||
|
||||
// Test with river
|
||||
let mut river_board = turn_board.clone();
|
||||
river_board
|
||||
.deal_river(card::Card::new(6, 2).unwrap())
|
||||
.unwrap(); // 8d
|
||||
let river_hand = hand::Hand::from_hole_cards_and_board(&hole_cards, &river_board).unwrap();
|
||||
assert_eq!(river_hand.len, 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_integration_hand_from_hole_cards_and_board_edge_cases() {
|
||||
// Test edge cases for hand creation from hole cards and board
|
||||
|
||||
// Test with pocket aces and board
|
||||
let pocket_aces = hole_cards::HoleCards::new(
|
||||
card::Card::new(12, 0).unwrap(), // As
|
||||
card::Card::new(12, 1).unwrap(), // Ah
|
||||
)
|
||||
.unwrap();
|
||||
let mut board = board::Board::new();
|
||||
board
|
||||
.deal_flop(vec![
|
||||
card::Card::new(10, 2).unwrap(), // Qd
|
||||
card::Card::new(9, 3).unwrap(), // Js
|
||||
card::Card::new(8, 0).unwrap(), // Tc
|
||||
])
|
||||
.unwrap();
|
||||
let hand = hand::Hand::from_hole_cards_and_board(&pocket_aces, &board).unwrap();
|
||||
assert_eq!(hand.len, 5);
|
||||
|
||||
// Test with suited connectors
|
||||
let suited_connectors = hole_cards::HoleCards::new(
|
||||
card::Card::new(11, 0).unwrap(), // Ks
|
||||
card::Card::new(10, 0).unwrap(), // Qs
|
||||
)
|
||||
.unwrap();
|
||||
let hand = hand::Hand::from_hole_cards_and_board(&suited_connectors, &board).unwrap();
|
||||
assert_eq!(hand.len, 5);
|
||||
|
||||
// Test with empty board (should work)
|
||||
let empty_board = board::Board::new();
|
||||
let hand = hand::Hand::from_hole_cards_and_board(&pocket_aces, &empty_board).unwrap();
|
||||
assert_eq!(hand.len, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_integration_serialization_round_trips() {
|
||||
// Test that all structures can be serialized and deserialized correctly
|
||||
|
||||
// Test Card serialization round-trip
|
||||
let original_card = card::Card::new(12, 3).unwrap(); // Ace of Spades
|
||||
let json = serde_json::to_string(&original_card).unwrap();
|
||||
let deserialized_card: card::Card = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(original_card, deserialized_card);
|
||||
|
||||
// Test HoleCards serialization round-trip
|
||||
let original_hole_cards = hole_cards::HoleCards::new(
|
||||
card::Card::new(12, 0).unwrap(),
|
||||
card::Card::new(11, 1).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let json = serde_json::to_string(&original_hole_cards).unwrap();
|
||||
let deserialized_hole_cards: hole_cards::HoleCards = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(original_hole_cards, deserialized_hole_cards);
|
||||
|
||||
// Test Board serialization round-trip
|
||||
let mut original_board = board::Board::new();
|
||||
original_board
|
||||
.deal_flop(vec![
|
||||
card::Card::new(10, 2).unwrap(),
|
||||
card::Card::new(9, 3).unwrap(),
|
||||
card::Card::new(8, 0).unwrap(),
|
||||
])
|
||||
.unwrap();
|
||||
original_board
|
||||
.deal_turn(card::Card::new(7, 1).unwrap())
|
||||
.unwrap();
|
||||
original_board
|
||||
.deal_river(card::Card::new(6, 2).unwrap())
|
||||
.unwrap();
|
||||
|
||||
let json = serde_json::to_string(&original_board).unwrap();
|
||||
let deserialized_board: board::Board = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(original_board, deserialized_board);
|
||||
|
||||
// Test Hand serialization round-trip
|
||||
let original_hand =
|
||||
hand::Hand::from_hole_cards_and_board(&original_hole_cards, &original_board).unwrap();
|
||||
let json = serde_json::to_string(&original_hand).unwrap();
|
||||
let deserialized_hand: hand::Hand = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(original_hand, deserialized_hand);
|
||||
|
||||
// Test Deck serialization round-trip
|
||||
let original_deck = deck::Deck::new();
|
||||
let json = serde_json::to_string(&original_deck).unwrap();
|
||||
let deserialized_deck: deck::Deck = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(original_deck.cards(), deserialized_deck.cards());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_integration_serialization_round_trips_comprehensive() {
|
||||
// Test serialization round-trips with various data formats and edge cases
|
||||
|
||||
// Test with TOML serialization
|
||||
let card = card::Card::new(12, 3).unwrap();
|
||||
let toml = toml::to_string(&card).unwrap();
|
||||
let deserialized: card::Card = toml::from_str(&toml).unwrap();
|
||||
assert_eq!(card, deserialized);
|
||||
|
||||
// Test complex hand serialization
|
||||
let hole_cards = hole_cards::HoleCards::new(
|
||||
card::Card::new(12, 0).unwrap(),
|
||||
card::Card::new(11, 1).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut board = board::Board::new();
|
||||
board
|
||||
.deal_flop(vec![
|
||||
card::Card::new(10, 2).unwrap(),
|
||||
card::Card::new(9, 3).unwrap(),
|
||||
card::Card::new(8, 0).unwrap(),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let hand = hand::Hand::from_hole_cards_and_board(&hole_cards, &board).unwrap();
|
||||
|
||||
// JSON round-trip
|
||||
let json = serde_json::to_string(&hand).unwrap();
|
||||
let deserialized: hand::Hand = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(hand, deserialized);
|
||||
|
||||
// TOML round-trip
|
||||
let toml = toml::to_string(&hand).unwrap();
|
||||
let deserialized: hand::Hand = toml::from_str(&toml).unwrap();
|
||||
assert_eq!(hand, deserialized);
|
||||
|
||||
// Test empty structures
|
||||
let empty_hand = hand::Hand::new(vec![]).unwrap();
|
||||
let json = serde_json::to_string(&empty_hand).unwrap();
|
||||
let deserialized: hand::Hand = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(empty_hand, deserialized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_integration_performance_benchmarks() {
|
||||
// Performance benchmarks for common operations
|
||||
|
||||
let iterations = 1000;
|
||||
|
||||
// Benchmark hand creation from hole cards and board
|
||||
let hole_cards = hole_cards::HoleCards::new(
|
||||
card::Card::new(12, 0).unwrap(),
|
||||
card::Card::new(11, 1).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut board = board::Board::new();
|
||||
board
|
||||
.deal_flop(vec![
|
||||
card::Card::new(10, 2).unwrap(),
|
||||
card::Card::new(9, 3).unwrap(),
|
||||
card::Card::new(8, 0).unwrap(),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let start = Instant::now();
|
||||
for _ in 0..iterations {
|
||||
let _hand = hand::Hand::from_hole_cards_and_board(&hole_cards, &board).unwrap();
|
||||
}
|
||||
let duration = start.elapsed();
|
||||
println!("Hand creation ({} iterations): {:?}", iterations, duration);
|
||||
assert!(
|
||||
duration.as_millis() < 100,
|
||||
"Hand creation too slow: {:?}",
|
||||
duration
|
||||
);
|
||||
|
||||
// Benchmark serialization round-trips
|
||||
let hand = hand::Hand::from_hole_cards_and_board(&hole_cards, &board).unwrap();
|
||||
let start = Instant::now();
|
||||
for _ in 0..iterations {
|
||||
let json = serde_json::to_string(&hand).unwrap();
|
||||
let _: hand::Hand = serde_json::from_str(&json).unwrap();
|
||||
}
|
||||
let duration = start.elapsed();
|
||||
println!(
|
||||
"Serialization round-trip ({} iterations): {:?}",
|
||||
iterations, duration
|
||||
);
|
||||
assert!(
|
||||
duration.as_millis() < 500,
|
||||
"Serialization too slow: {:?}",
|
||||
duration
|
||||
);
|
||||
|
||||
// Benchmark card creation and operations
|
||||
let start = Instant::now();
|
||||
let mut cards = Vec::new();
|
||||
for i in 0..iterations {
|
||||
cards.push(card::Card::new((i % 13) as u8, (i % 4) as u8));
|
||||
}
|
||||
let duration = start.elapsed();
|
||||
println!("Card creation ({} iterations): {:?}", iterations, duration);
|
||||
assert!(
|
||||
duration.as_millis() < 50,
|
||||
"Card creation too slow: {:?}",
|
||||
duration
|
||||
);
|
||||
|
||||
// Benchmark deck operations
|
||||
let start = Instant::now();
|
||||
for _ in 0..(iterations / 10) {
|
||||
// Fewer iterations for deck operations
|
||||
let mut deck = deck::Deck::new();
|
||||
let _ = deck.shuffle(&mut rand::rng());
|
||||
let _ = deck.deal(5);
|
||||
}
|
||||
let duration = start.elapsed();
|
||||
println!(
|
||||
"Deck shuffle and deal ({} iterations): {:?}",
|
||||
iterations / 10,
|
||||
duration
|
||||
);
|
||||
assert!(
|
||||
duration.as_millis() < 200,
|
||||
"Deck operations too slow: {:?}",
|
||||
duration
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_integration_complex_scenarios() {
|
||||
// Test complex real-world scenarios
|
||||
|
||||
// Test multiple hands from same board
|
||||
let mut board = board::Board::new();
|
||||
board
|
||||
.deal_flop(vec![
|
||||
card::Card::new(12, 2).unwrap(), // Ad
|
||||
card::Card::new(11, 3).unwrap(), // Ks
|
||||
card::Card::new(10, 0).unwrap(), // Qc
|
||||
])
|
||||
.unwrap();
|
||||
board.deal_turn(card::Card::new(9, 1).unwrap()).unwrap(); // Jh
|
||||
board.deal_river(card::Card::new(8, 2).unwrap()).unwrap(); // Td
|
||||
|
||||
let hole_cards1 = hole_cards::HoleCards::new(
|
||||
card::Card::new(12, 0).unwrap(), // As
|
||||
card::Card::new(12, 1).unwrap(), // Ah
|
||||
)
|
||||
.unwrap(); // Pocket aces
|
||||
|
||||
let hole_cards2 = hole_cards::HoleCards::new(
|
||||
card::Card::new(11, 0).unwrap(), // Kc
|
||||
card::Card::new(11, 1).unwrap(), // Kh
|
||||
)
|
||||
.unwrap(); // Pocket kings
|
||||
|
||||
let hand1 = hand::Hand::from_hole_cards_and_board(&hole_cards1, &board).unwrap();
|
||||
let hand2 = hand::Hand::from_hole_cards_and_board(&hole_cards2, &board).unwrap();
|
||||
|
||||
assert_eq!(hand1.len, 7);
|
||||
assert_eq!(hand2.len, 7);
|
||||
|
||||
// Both hands should contain all board cards plus their hole cards
|
||||
assert!(hand1.cards().contains(&card::Card::new(12, 0).unwrap())); // As in hand1
|
||||
assert!(hand1.cards().contains(&card::Card::new(12, 1).unwrap())); // Ah in hand1
|
||||
assert!(hand2.cards().contains(&card::Card::new(11, 0).unwrap())); // Kc in hand2
|
||||
assert!(hand2.cards().contains(&card::Card::new(11, 1).unwrap())); // Kh in hand2
|
||||
|
||||
// Both hands should contain all board cards
|
||||
for card in board.visible_cards() {
|
||||
assert!(hand1.cards().contains(card));
|
||||
assert!(hand2.cards().contains(card));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_integration_error_handling() {
|
||||
// Test error handling in integration scenarios
|
||||
|
||||
// Test duplicate cards between hole cards and board
|
||||
let hole_cards = hole_cards::HoleCards::new(
|
||||
card::Card::new(12, 0).unwrap(), // As
|
||||
card::Card::new(11, 1).unwrap(), // Kh
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut board = board::Board::new();
|
||||
board
|
||||
.deal_flop(vec![
|
||||
card::Card::new(12, 0).unwrap(), // Duplicate As
|
||||
card::Card::new(9, 3).unwrap(),
|
||||
card::Card::new(8, 0).unwrap(),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
// This should fail due to duplicate cards
|
||||
assert!(hand::Hand::from_hole_cards_and_board(&hole_cards, &board).is_err());
|
||||
|
||||
// Test serialization of invalid states (should still work)
|
||||
let invalid_hand = hand::Hand::new(vec![
|
||||
card::Card::new(12, 0).unwrap(),
|
||||
card::Card::new(12, 0).unwrap(), // Duplicate
|
||||
]);
|
||||
assert!(invalid_hand.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_integration_data_consistency() {
|
||||
// Test that data remains consistent across operations
|
||||
|
||||
// Create a complex scenario and verify all data stays consistent
|
||||
let hole_cards = hole_cards::HoleCards::new(
|
||||
card::Card::new(12, 0).unwrap(),
|
||||
card::Card::new(11, 1).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut board = board::Board::new();
|
||||
let flop_cards = vec![
|
||||
card::Card::new(10, 2).unwrap(),
|
||||
card::Card::new(9, 3).unwrap(),
|
||||
card::Card::new(8, 0).unwrap(),
|
||||
];
|
||||
board.deal_flop(flop_cards.clone()).unwrap();
|
||||
|
||||
let hand = hand::Hand::from_hole_cards_and_board(&hole_cards, &board).unwrap();
|
||||
|
||||
// Verify hand contains exactly the expected cards
|
||||
let expected_cards = vec![
|
||||
card::Card::new(12, 0).unwrap(), // hole
|
||||
card::Card::new(11, 1).unwrap(), // hole
|
||||
card::Card::new(10, 2).unwrap(), // flop
|
||||
card::Card::new(9, 3).unwrap(), // flop
|
||||
card::Card::new(8, 0).unwrap(), // flop
|
||||
];
|
||||
|
||||
assert_eq!(hand.len, 5);
|
||||
for expected in expected_cards {
|
||||
assert!(hand.cards().contains(&expected));
|
||||
}
|
||||
|
||||
// Test serialization preserves data
|
||||
let json = serde_json::to_string(&hand).unwrap();
|
||||
let deserialized: hand::Hand = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(hand, deserialized);
|
||||
assert_eq!(hand.len, deserialized.len);
|
||||
for i in 0..hand.len {
|
||||
assert_eq!(hand.cards()[i], deserialized.cards()[i]);
|
||||
}
|
||||
}
|
||||
484
holdem_core/tests/property_tests.rs
Normal file
484
holdem_core/tests/property_tests.rs
Normal file
|
|
@ -0,0 +1,484 @@
|
|||
//! Property-based tests for poker data structures
|
||||
//!
|
||||
//! These tests verify fundamental properties of poker structures like cards,
|
||||
//! hands, and decks through comprehensive testing of edge cases and invariants.
|
||||
|
||||
use holdem_core::{card::Card, deck::Deck, hand::Hand};
|
||||
use std::collections::HashSet;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[cfg(test)]
|
||||
mod card_properties {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_card_properties_comprehensive() {
|
||||
// Test all possible card combinations for key properties
|
||||
for rank in 0..=12 {
|
||||
for suit in 0..=3 {
|
||||
let card = Card::new(rank, suit).unwrap();
|
||||
|
||||
// Property: Valid cards are always within valid rank/suit bounds
|
||||
assert!(card.rank() <= 12);
|
||||
assert!(card.suit() <= 3);
|
||||
|
||||
// Property: Card creation is deterministic (same inputs = same card)
|
||||
let card2 = Card::new(rank, suit).unwrap();
|
||||
assert_eq!(card, card2);
|
||||
assert_eq!(card.rank(), rank);
|
||||
assert_eq!(card.suit(), suit);
|
||||
|
||||
// Property: String parsing round-trips correctly
|
||||
let string_repr = format!("{}", card);
|
||||
let parsed = Card::from_str(&string_repr).unwrap();
|
||||
assert_eq!(card, parsed);
|
||||
|
||||
// Property: Card serialization round-trips correctly
|
||||
let json = serde_json::to_string(&card).unwrap();
|
||||
let deserialized: Card = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(card, deserialized);
|
||||
|
||||
let toml = toml::to_string(&card).unwrap();
|
||||
let deserialized: Card = toml::from_str(&toml).unwrap();
|
||||
assert_eq!(card, deserialized);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_card_ordering_properties() {
|
||||
// Test card ordering with various combinations
|
||||
let test_cases = vec![
|
||||
((12, 3), (11, 3)), // Ace > King, same suit
|
||||
((12, 3), (12, 0)), // Ace spades > Ace hearts (Spades=4 > Hearts=3)
|
||||
((12, 0), (12, 1)), // Ace hearts > Ace diamonds (Hearts=3 > Diamonds=2)
|
||||
((12, 1), (12, 2)), // Ace diamonds > Ace clubs (Diamonds=2 > Clubs=1)
|
||||
((5, 3), (5, 0)), // Five spades > Five hearts
|
||||
((5, 0), (5, 1)), // Five hearts > Five diamonds
|
||||
((5, 1), (5, 2)), // Five diamonds > Five clubs
|
||||
];
|
||||
|
||||
for ((rank1, suit1), (rank2, suit2)) in test_cases {
|
||||
let card1 = Card::new(rank1, suit1).unwrap();
|
||||
let card2 = Card::new(rank2, suit2).unwrap();
|
||||
|
||||
// Property: Cards are properly ordered (higher rank first, then higher suit)
|
||||
if rank1 != rank2 {
|
||||
if rank1 > rank2 {
|
||||
assert!(
|
||||
card1 > card2,
|
||||
"Card {:?} should be > Card {:?}",
|
||||
card1,
|
||||
card2
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
card1 < card2,
|
||||
"Card {:?} should be < Card {:?}",
|
||||
card1,
|
||||
card2
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// If ranks are equal, higher suit order should be greater
|
||||
// Suit order: Spades(4) > Hearts(3) > Diamonds(2) > Clubs(1)
|
||||
let suit_order1 = match suit1 {
|
||||
3 => 4, // Spades
|
||||
0 => 3, // Hearts
|
||||
1 => 2, // Diamonds
|
||||
2 => 1, // Clubs
|
||||
_ => 0,
|
||||
};
|
||||
let suit_order2 = match suit2 {
|
||||
3 => 4, // Spades
|
||||
0 => 3, // Hearts
|
||||
1 => 2, // Diamonds
|
||||
2 => 1, // Clubs
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
if suit_order1 > suit_order2 {
|
||||
assert!(
|
||||
card1 > card2,
|
||||
"Card {:?} should be > Card {:?}",
|
||||
card1,
|
||||
card2
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
card1 < card2,
|
||||
"Card {:?} should be < Card {:?}",
|
||||
card1,
|
||||
card2
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_cards_unique_property() {
|
||||
let mut seen = HashSet::new();
|
||||
|
||||
// Property: All 52 possible cards are unique
|
||||
for rank in 0..=12 {
|
||||
for suit in 0..=3 {
|
||||
let card = Card::new(rank, suit).unwrap();
|
||||
assert!(seen.insert(card), "Card {} was not unique", card);
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(seen.len(), 52, "Should have exactly 52 unique cards");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod hand_properties {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_hand_properties_comprehensive() {
|
||||
// Test hands of various sizes for key properties
|
||||
for size in 0..=7 {
|
||||
if size < 2 {
|
||||
continue; // Skip very small hands for some tests
|
||||
}
|
||||
|
||||
// Generate distinct cards for this hand size
|
||||
let mut cards = Vec::new();
|
||||
let mut card_set = HashSet::new();
|
||||
|
||||
for i in 0..size {
|
||||
let rank = (i % 13) as u8;
|
||||
let suit = (i % 4) as u8;
|
||||
let card = Card::new(rank, suit).unwrap();
|
||||
|
||||
// Ensure uniqueness
|
||||
if card_set.insert(card) {
|
||||
cards.push(card);
|
||||
}
|
||||
|
||||
if cards.len() == size {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if cards.len() == size {
|
||||
let hand = Hand::new(cards.clone()).unwrap();
|
||||
|
||||
// Property: Hands never contain duplicate cards
|
||||
let hand_cards = hand.cards();
|
||||
for i in 0..hand_cards.len() {
|
||||
for j in (i + 1)..hand_cards.len() {
|
||||
assert_ne!(hand_cards[i], hand_cards[j]);
|
||||
}
|
||||
}
|
||||
|
||||
// Property: Hands are always sorted correctly (rank descending)
|
||||
for i in 1..hand_cards.len() {
|
||||
assert!(hand_cards[i].rank() <= hand_cards[i - 1].rank());
|
||||
}
|
||||
|
||||
// Property: All cards in hand are valid cards
|
||||
for card in hand.iter() {
|
||||
assert!(card.rank() <= 12);
|
||||
assert!(card.suit() <= 3);
|
||||
}
|
||||
|
||||
// Property: Hand serialization round-trips correctly
|
||||
let json = serde_json::to_string(&hand).unwrap();
|
||||
let deserialized: Hand = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(hand, deserialized);
|
||||
|
||||
let toml = toml::to_string(&hand).unwrap();
|
||||
let deserialized: Hand = toml::from_str(&toml).unwrap();
|
||||
assert_eq!(hand, deserialized);
|
||||
|
||||
// Property: Hand length matches actual card count
|
||||
assert_eq!(hand.len, cards.len());
|
||||
assert_eq!(hand_cards.len(), cards.len());
|
||||
|
||||
// Property: Best five cards returns exactly 5 cards (if hand large enough)
|
||||
if hand.len >= 5 {
|
||||
let best_five = hand.best_five_cards();
|
||||
assert_eq!(best_five.len(), 5);
|
||||
|
||||
// Property: Best five cards are subset of hand cards
|
||||
for &card in &best_five {
|
||||
assert!(hand_cards.contains(&card));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hand_length_properties() {
|
||||
// Property: Hand length is always <= 7
|
||||
for size in 0..=10 {
|
||||
let mut cards = Vec::new();
|
||||
for i in 0..size {
|
||||
let rank = (i % 13) as u8;
|
||||
let suit = (i % 4) as u8;
|
||||
if let Ok(card) = Card::new(rank, suit) {
|
||||
cards.push(card);
|
||||
}
|
||||
}
|
||||
|
||||
if size <= 7 {
|
||||
let hand = Hand::new(cards);
|
||||
assert!(hand.is_ok(), "Hand of size {} should be valid", size);
|
||||
} else {
|
||||
let hand = Hand::new(cards);
|
||||
assert!(hand.is_err(), "Hand of size {} should be invalid", size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hand_display_properties() {
|
||||
// Test various hand sizes for display format consistency
|
||||
for size in 0..=7 {
|
||||
let mut cards = Vec::new();
|
||||
for i in 0..size {
|
||||
let rank = (i % 13) as u8;
|
||||
let suit = (i % 4) as u8;
|
||||
if let Ok(card) = Card::new(rank, suit) {
|
||||
cards.push(card);
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(hand) = Hand::new(cards) {
|
||||
let display = format!("{}", hand);
|
||||
|
||||
// Property: Hand display format is consistent
|
||||
assert!(display.starts_with("Hand("));
|
||||
assert!(display.ends_with(")"));
|
||||
assert!(display.contains("cards:"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod deck_properties {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_deck_properties_comprehensive() {
|
||||
let deck = Deck::new();
|
||||
|
||||
// Property: Fresh decks always contain exactly 52 cards
|
||||
assert_eq!(deck.remaining(), 52);
|
||||
assert!(!deck.is_empty());
|
||||
|
||||
// Property: All cards in deck are unique
|
||||
let cards = deck.cards();
|
||||
let mut seen = HashSet::new();
|
||||
for &card in cards {
|
||||
assert!(seen.insert(card), "Duplicate card in deck: {}", card);
|
||||
}
|
||||
assert_eq!(seen.len(), 52);
|
||||
|
||||
// Property: All cards in deck are valid cards
|
||||
for &card in cards {
|
||||
assert!(card.rank() <= 12);
|
||||
assert!(card.suit() <= 3);
|
||||
}
|
||||
|
||||
// Property: Deck serialization round-trips correctly
|
||||
let json = serde_json::to_string(&deck).unwrap();
|
||||
let deserialized: Deck = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(deck.cards(), deserialized.cards());
|
||||
|
||||
let toml = toml::to_string(&deck).unwrap();
|
||||
let deserialized: Deck = toml::from_str(&toml).unwrap();
|
||||
assert_eq!(deck.cards(), deserialized.cards());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deck_dealing_properties() {
|
||||
let mut deck = Deck::new();
|
||||
let initial_remaining = deck.remaining();
|
||||
|
||||
// Property: Dealing cards reduces remaining count
|
||||
if initial_remaining > 0 {
|
||||
let dealt = deck.deal(1);
|
||||
assert_eq!(dealt.len(), 1);
|
||||
assert_eq!(deck.remaining(), initial_remaining - 1);
|
||||
}
|
||||
|
||||
// Property: Dealing more cards than available returns empty
|
||||
let mut empty_deck = Deck::new();
|
||||
while empty_deck.remaining() > 0 {
|
||||
empty_deck.deal(1);
|
||||
}
|
||||
|
||||
assert_eq!(empty_deck.remaining(), 0);
|
||||
let dealt_none = empty_deck.deal(1);
|
||||
assert!(dealt_none.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod integration_properties {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_hand_from_hole_cards_and_board_properties() {
|
||||
// Test various combinations of hole cards and board cards
|
||||
for num_hole in 2..=2 {
|
||||
for num_board in 0..=5 {
|
||||
let mut hole_cards = Vec::new();
|
||||
let mut board_cards = Vec::new();
|
||||
let mut all_cards_set = HashSet::new();
|
||||
|
||||
// Generate unique hole cards
|
||||
for i in 0..num_hole {
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
let rank = (i % 13) as u8;
|
||||
let suit = (i % 4) as u8;
|
||||
let card = Card::new(rank, suit).unwrap();
|
||||
if all_cards_set.insert(card) {
|
||||
hole_cards.push(card);
|
||||
break;
|
||||
}
|
||||
attempts += 1;
|
||||
if attempts > 50 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate unique board cards
|
||||
for i in 0..num_board {
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
let rank = ((i + 2) % 13) as u8;
|
||||
let suit = ((i + 1) % 4) as u8;
|
||||
let card = Card::new(rank, suit).unwrap();
|
||||
if all_cards_set.insert(card) {
|
||||
board_cards.push(card);
|
||||
break;
|
||||
}
|
||||
attempts += 1;
|
||||
if attempts > 50 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hole_cards.len() == num_hole && board_cards.len() == num_board {
|
||||
use holdem_core::{board::Board, hole_cards::HoleCards};
|
||||
|
||||
let hole_cards_struct = HoleCards::new(hole_cards[0], hole_cards[1]).unwrap();
|
||||
let mut board = Board::new();
|
||||
|
||||
if board_cards.len() >= 3 {
|
||||
// For testing purposes, just add the first 3 cards as flop
|
||||
board
|
||||
.deal_flop(board_cards.iter().take(3).cloned().collect())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Property: Hand creation from hole cards and board never exceeds 7 cards
|
||||
let hand = Hand::from_hole_cards_and_board(&hole_cards_struct, &board);
|
||||
assert!(hand.is_ok());
|
||||
|
||||
let hand = hand.unwrap();
|
||||
assert!(hand.len <= 7);
|
||||
|
||||
// The hand should contain hole cards + any successfully added board cards
|
||||
let expected_min_len = num_hole; // At least hole cards
|
||||
let expected_max_len = num_hole + num_board; // At most hole cards + all board cards
|
||||
assert!(hand.len >= expected_min_len);
|
||||
assert!(hand.len <= expected_max_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hand_notation_parsing_properties() {
|
||||
// Test hand notation parsing with various valid combinations
|
||||
for size in 2..=7 {
|
||||
let mut cards = Vec::new();
|
||||
let mut card_set = HashSet::new();
|
||||
|
||||
for i in 0..size {
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
let rank = (i % 13) as u8;
|
||||
let suit = (i % 4) as u8;
|
||||
let card = Card::new(rank, suit).unwrap();
|
||||
if card_set.insert(card) {
|
||||
cards.push(card);
|
||||
break;
|
||||
}
|
||||
attempts += 1;
|
||||
if attempts > 50 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cards.len() == size {
|
||||
let mut notation_parts = Vec::new();
|
||||
for card in &cards {
|
||||
notation_parts.push(format!("{}", card));
|
||||
}
|
||||
let notation = notation_parts.join(" ");
|
||||
|
||||
// Property: Hand notation parsing works for valid hands
|
||||
let parsed = Hand::from_notation(¬ation);
|
||||
if size <= 7 {
|
||||
assert!(parsed.is_ok(), "Failed to parse notation: {}", notation);
|
||||
let parsed_hand = parsed.unwrap();
|
||||
assert_eq!(parsed_hand.len, cards.len());
|
||||
} else {
|
||||
assert!(
|
||||
parsed.is_err(),
|
||||
"Should fail to parse hand with {} cards",
|
||||
size
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hand_strength_properties() {
|
||||
// Test that hand strength is deterministic
|
||||
for size in 5..=7 {
|
||||
let mut cards = Vec::new();
|
||||
let mut card_set = HashSet::new();
|
||||
|
||||
for i in 0..size {
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
let rank = (i % 13) as u8;
|
||||
let suit = (i % 4) as u8;
|
||||
let card = Card::new(rank, suit).unwrap();
|
||||
if card_set.insert(card) {
|
||||
cards.push(card);
|
||||
break;
|
||||
}
|
||||
attempts += 1;
|
||||
if attempts > 50 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cards.len() == size {
|
||||
// Property: Hand strength is deterministic (same hand = same strength)
|
||||
let hand1 = Hand::new(cards.clone()).unwrap();
|
||||
let hand2 = Hand::new(cards).unwrap();
|
||||
|
||||
// For now, strength is always 0 (placeholder), but should be consistent
|
||||
assert_eq!(hand1.strength(), hand2.strength());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue