rusty_marvin/holdem_core/src/deck.rs
Jan-Peter v. Hunnius 156904f2bf feat: add incremental trie lookup API and Deck::without_cards
- Add TrieCursor struct for partial Supersonic LUT trie traversal
- Add init_lookup/advance_lookup/finish_* methods for 5/6/7-card incremental eval
- Add raw_init/raw_advance/raw_finish_* zero-cost API (no depth tracking)
- Add Deck::without_cards() for constructing remaining decks
- Re-export TrieCursor from evaluator module
- Add comprehensive tests for all new APIs
- Update .gitignore for docs/REFERENCES.md
2026-05-25 19:44:21 +02:00

531 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! # Deck Module
//!
//! This module provides the `Deck` struct for representing a deck of playing cards
//! in the poker AI system. Supports shuffling, dealing, and serialization.
//!
//! ## Deck Representation
//!
//! A Deck contains a vector of cards not yet dealt, starting with all 52 cards.
//!
//! ## Examples
//!
//! ### Creating and Using a Deck
//!
//! ```rust
//! use holdem_core::{Deck, Card};
//!
//! let mut deck = Deck::new();
//! assert_eq!(deck.remaining(), 52);
//!
//! // Shuffle the deck
//! use rand::SeedableRng;
//! let mut rng = rand::rngs::StdRng::from_seed([1; 32]);
//! deck.shuffle(&mut rng);
//!
//! // Deal some cards
//! let hole_cards = deck.deal(2);
//! let flop = deck.deal(3);
//! let turn = deck.deal(1);
//! let river = deck.deal(1);
//!
//! assert_eq!(hole_cards.len(), 2);
//! assert_eq!(flop.len(), 3);
//! assert_eq!(turn.len(), 1);
//! assert_eq!(river.len(), 1);
//! assert_eq!(deck.remaining(), 52 - 7);
//! ```
//!
//! ## Design Decisions
//!
//! - **Full Deck**: Always starts with 52 cards in standard poker order
//! - **Efficient Dealing**: Pop from end for O(1) deal operations
//! - **Serialization Ready**: Full serde support for persistence
//! - **Flexible Shuffling**: Uses rand crate for high-quality randomization
use crate::card::Card;
use serde::{Deserialize, Serialize};
/// Represents a deck of cards not yet dealt
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Deck {
cards: Vec<Card>,
}
impl Deck {
/// Creates a new full deck of 52 cards
///
/// # Examples
///
/// ```rust
/// use holdem_core::Deck;
///
/// let deck = Deck::new();
/// assert_eq!(deck.remaining(), 52);
/// ```
///
/// # Panics
///
/// This method does not panic.
pub fn new() -> Self {
let mut cards = Vec::with_capacity(52);
for suit in 0..4 {
for rank in 0..13 {
cards.push(Card::new(rank, suit).unwrap());
}
}
Self { cards }
}
/// Shuffles the deck using the provided random number generator
///
/// # Examples
///
/// ```rust
/// use holdem_core::Deck;
/// use rand::SeedableRng;
///
/// let mut deck = Deck::new();
/// let mut rng = rand::rngs::StdRng::from_seed([1; 32]);
/// deck.shuffle(&mut rng);
/// ```
///
/// # Panics
///
/// This method does not panic.
pub fn shuffle<R: rand::Rng>(&mut self, rng: &mut R) {
use rand::seq::SliceRandom;
self.cards.shuffle(rng);
}
/// Deals a single card from the top of the deck
///
/// Returns `None` if the deck is empty.
///
/// # Examples
///
/// ```rust
/// use holdem_core::Deck;
///
/// let mut deck = Deck::new();
/// let card = deck.deal_one().unwrap();
/// assert_eq!(deck.remaining(), 51);
/// ```
///
/// # Panics
///
/// This method does not panic, but returns `None` if the deck is empty.
pub fn deal_one(&mut self) -> Option<Card> {
self.cards.pop()
}
/// Deals multiple cards from the top of the deck
///
/// Returns a vector containing up to `count` cards. If fewer than `count` cards
/// remain in the deck, returns all remaining cards.
///
/// # Examples
///
/// ```rust
/// use holdem_core::Deck;
///
/// let mut deck = Deck::new();
/// let cards = deck.deal(5);
/// assert_eq!(cards.len(), 5);
/// assert_eq!(deck.remaining(), 47);
/// ```
///
/// # Panics
///
/// This method does not panic.
pub fn deal(&mut self, count: usize) -> Vec<Card> {
let mut dealt = Vec::new();
for _ in 0..count {
if let Some(card) = self.deal_one() {
dealt.push(card);
} else {
break;
}
}
dealt
}
/// Returns the number of cards remaining in the deck
///
/// # Examples
///
/// ```rust
/// use holdem_core::Deck;
///
/// let mut deck = Deck::new();
/// assert_eq!(deck.remaining(), 52);
///
/// deck.deal(5);
/// assert_eq!(deck.remaining(), 47);
/// ```
///
/// # Panics
///
/// This method does not panic.
pub fn remaining(&self) -> usize {
self.cards.len()
}
/// Returns true if the deck is empty
///
/// # Examples
///
/// ```rust
/// use holdem_core::Deck;
///
/// let mut deck = Deck::new();
/// assert!(!deck.is_empty());
///
/// // Deal all cards
/// deck.deal(52);
/// assert!(deck.is_empty());
/// ```
///
/// # Panics
///
/// This method does not panic.
pub fn is_empty(&self) -> bool {
self.cards.is_empty()
}
/// Returns a reference to the remaining cards
///
/// # Examples
///
/// ```rust
/// use holdem_core::Deck;
///
/// let deck = Deck::new();
/// let cards = deck.cards();
/// assert_eq!(cards.len(), 52);
/// ```
///
/// # Panics
///
/// This method does not panic.
pub fn cards(&self) -> &[Card] {
&self.cards
}
/// Creates a new deck excluding the specified cards.
///
/// Useful for constructing a "remaining deck" that omits known cards
/// (hero's hole cards, board cards) — typically resulting in a 45–47 card
/// deck for enumeration-based equity calculations.
///
/// # Examples
///
/// ```rust
/// use holdem_core::{Deck, Card};
/// use std::str::FromStr;
///
/// let deck = Deck::new();
/// let excluded = vec![
/// Card::from_str("As").unwrap(),
/// Card::from_str("Kh").unwrap(),
/// ];
/// let remaining = deck.without_cards(&excluded);
/// assert_eq!(remaining.remaining(), 50);
/// ```
///
/// # Panics
///
/// This method does not panic.
pub fn without_cards(&self, excluded: &[Card]) -> Deck {
use std::collections::HashSet;
let exclude_set: HashSet<Card> = excluded.iter().copied().collect();
let cards: Vec<Card> = self
.cards
.iter()
.copied()
.filter(|c| !exclude_set.contains(c))
.collect();
Deck { cards }
}
}
impl Default for Deck {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn test_deck_new() {
let deck = Deck::new();
assert_eq!(deck.remaining(), 52);
assert!(!deck.is_empty());
// Check all cards are present
let cards = deck.cards();
let mut card_set = HashSet::new();
for &card in cards {
assert!(card_set.insert(card), "Duplicate card in deck: {}", card);
}
assert_eq!(card_set.len(), 52);
}
#[test]
fn test_deck_shuffle() {
let mut deck1 = Deck::new();
let deck2 = Deck::new();
// Decks should be identical initially
assert_eq!(deck1.cards(), deck2.cards());
// Shuffle one deck
use rand::SeedableRng;
let mut rng = rand::rngs::StdRng::from_seed([1; 32]);
deck1.shuffle(&mut rng);
// After shuffle, order should be different (with very high probability)
assert_ne!(deck1.cards(), deck2.cards());
assert_eq!(deck1.remaining(), 52);
assert_eq!(deck2.remaining(), 52);
}
#[test]
fn test_deck_deal_one() {
let mut deck = Deck::new();
// Deal one card
let card = deck.deal_one().unwrap();
assert_eq!(deck.remaining(), 51);
// Deal another
let card2 = deck.deal_one().unwrap();
assert_eq!(deck.remaining(), 50);
// Cards should be different
assert_ne!(card, card2);
}
#[test]
fn test_deck_deal_multiple() {
let mut deck = Deck::new();
// Deal 5 cards
let cards = deck.deal(5);
assert_eq!(cards.len(), 5);
assert_eq!(deck.remaining(), 47);
// All cards should be unique
let mut card_set = HashSet::new();
for &card in &cards {
assert!(card_set.insert(card), "Duplicate card dealt: {}", card);
}
}
#[test]
fn test_deck_deal_empty() {
let mut deck = Deck::new();
// Deal all cards
let all_cards = deck.deal(52);
assert_eq!(all_cards.len(), 52);
assert_eq!(deck.remaining(), 0);
assert!(deck.is_empty());
// Try to deal one more
assert!(deck.deal_one().is_none());
// Try to deal multiple more
let empty_deal = deck.deal(5);
assert!(empty_deal.is_empty());
}
#[test]
fn test_deck_serialization() {
let deck = Deck::new();
// Test JSON serialization
let json = serde_json::to_string(&deck).unwrap();
let deserialized: Deck = serde_json::from_str(&json).unwrap();
assert_eq!(deck.cards(), deserialized.cards());
// Test TOML serialization
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_shuffle_randomness() {
use rand::SeedableRng;
let mut deck = Deck::new();
let original_order: Vec<Card> = deck.cards().to_vec();
// Shuffle with different seeds and verify randomness
let mut rng1 = rand::rngs::StdRng::from_seed([1; 32]);
deck.shuffle(&mut rng1);
let shuffled1 = deck.cards().to_vec();
// Reset deck
deck = Deck::new();
let mut rng2 = rand::rngs::StdRng::from_seed([2; 32]);
deck.shuffle(&mut rng2);
let shuffled2 = deck.cards().to_vec();
// Different seeds should produce different shuffles
assert_ne!(shuffled1, shuffled2);
assert_ne!(shuffled1, original_order);
assert_ne!(shuffled2, original_order);
// All cards should still be present
let mut set1 = HashSet::new();
let mut set2 = HashSet::new();
for &card in &shuffled1 {
set1.insert(card);
}
for &card in &shuffled2 {
set2.insert(card);
}
assert_eq!(set1.len(), 52);
assert_eq!(set2.len(), 52);
}
#[test]
fn test_deck_deal_edge_cases() {
let mut deck = Deck::new();
// Deal more cards than available
let cards = deck.deal(60);
assert_eq!(cards.len(), 52);
assert_eq!(deck.remaining(), 0);
assert!(deck.is_empty());
// Try dealing from empty deck
let empty_deal = deck.deal(5);
assert!(empty_deal.is_empty());
// Deal one from empty deck
assert!(deck.deal_one().is_none());
}
#[test]
fn test_deck_remaining_cards() {
let mut deck = Deck::new();
assert_eq!(deck.remaining(), 52);
// Deal various amounts and check remaining
deck.deal(5);
assert_eq!(deck.remaining(), 47);
deck.deal_one();
assert_eq!(deck.remaining(), 46);
deck.deal(10);
assert_eq!(deck.remaining(), 36);
// Deal all remaining
deck.deal(36);
assert_eq!(deck.remaining(), 0);
assert!(deck.is_empty());
}
#[test]
fn test_deck_deal_zero() {
let mut deck = Deck::new();
// Deal zero cards
let empty_deal = deck.deal(0);
assert!(empty_deal.is_empty());
assert_eq!(deck.remaining(), 52);
}
#[test]
fn test_deck_default() {
let deck = Deck::default();
assert_eq!(deck.remaining(), 52);
assert!(!deck.is_empty());
}
#[test]
fn test_deck_clone() {
let mut deck1 = Deck::new();
deck1.deal(5);
let deck2 = deck1.clone();
assert_eq!(deck1.remaining(), deck2.remaining());
assert_eq!(deck1.cards(), deck2.cards());
}
#[test]
fn test_deck_serialization_partial() {
let mut deck = Deck::new();
deck.deal(10); // Remove 10 cards
// Serialize partial deck
let json = serde_json::to_string(&deck).unwrap();
let deserialized: Deck = serde_json::from_str(&json).unwrap();
assert_eq!(deck.remaining(), deserialized.remaining());
assert_eq!(deck.cards(), deserialized.cards());
// Should be able to continue dealing from deserialized deck
let mut deserialized = deserialized;
let more_cards = deserialized.deal(5);
assert_eq!(more_cards.len(), 5);
}
#[test]
fn test_deck_deal_uniqueness() {
let mut deck = Deck::new();
// Deal all cards and ensure uniqueness
let mut all_cards = Vec::new();
while let Some(card) = deck.deal_one() {
all_cards.push(card);
}
assert_eq!(all_cards.len(), 52);
let mut card_set = HashSet::new();
for card in all_cards {
assert!(card_set.insert(card), "Duplicate card found: {}", card);
}
assert_eq!(card_set.len(), 52);
}
#[test]
fn test_deck_performance() {
use std::time::Instant;
// Test creation performance
let start = Instant::now();
for _ in 0..1000 {
let _deck = Deck::new();
}
let creation_time = start.elapsed();
// Test dealing performance
let mut deck = Deck::new();
let start = Instant::now();
for _ in 0..52 {
deck.deal_one();
}
let dealing_time = start.elapsed();
// Should be very fast
assert!(
creation_time.as_millis() < 100,
"Deck creation too slow: {:?}",
creation_time
);
assert!(
dealing_time.as_millis() < 10,
"Dealing too slow: {:?}",
dealing_time
);
}
}