use rand::{RngExt, seq::SliceRandom};
use strum::IntoEnumIterator;
use crate::core::{
Card, Suit,
french::{FrenchCard, FrenchRank, FrenchWithJoker, Joker},
italian::{ItalianCard, ItalianRank},
};
#[derive(Default, Debug, Clone)]
pub struct Deck<T>
where
T: Card,
{
cards: Vec<T>,
}
const FRENCH_CARDS: usize = 52;
const ITALIAN_CARDS: usize = 40;
impl Deck<ItalianCard> {
pub fn italian() -> Deck<ItalianCard> {
let mut cards = Vec::with_capacity(ITALIAN_CARDS);
for suit in Suit::iter() {
for rank in ItalianRank::iter() {
cards.push(ItalianCard::new(rank, suit));
}
}
Deck { cards }
}
}
impl Deck<FrenchCard> {
pub fn french() -> Deck<FrenchCard> {
let mut cards = Vec::with_capacity(FRENCH_CARDS);
for suit in Suit::iter() {
for rank in FrenchRank::iter() {
cards.push(FrenchCard::new(rank, suit));
}
}
Deck { cards }
}
pub fn french_with_jokers(jokers: u8) -> Deck<FrenchWithJoker> {
let mut cards = Vec::with_capacity(FRENCH_CARDS + jokers as usize);
for suit in Suit::iter() {
for rank in FrenchRank::iter() {
cards.push(FrenchWithJoker::Normal(FrenchCard::new(rank, suit)));
}
}
for _ in 0..jokers {
cards.push(FrenchWithJoker::Joker(Joker {}));
}
Deck { cards }
}
}
impl<T: Card> Deck<T> {
pub fn shuffle(&mut self) {
let mut rng = rand::rng();
self.cards.shuffle(&mut rng);
}
pub fn shuffle_card(&mut self, card: T) {
let len = self.cards.len();
let mut rng = rand::rng();
let pos = match len {
0 => 0,
1 => 1,
2 => 1,
_ => rng.random_range(1..len - 1),
};
self.cards.insert(pos, card);
}
pub fn push(&mut self, card: T) {
self.cards.push(card);
}
pub fn draw(&mut self) -> Option<T> {
self.cards.pop()
}
pub fn draw_n(&mut self, n: usize) -> Option<impl Iterator<Item = T>> {
if self.cards.len() < n {
None
} else {
let mut drawn = Vec::with_capacity(n);
for _ in 0..n {
drawn.push(self.cards.pop()?);
}
Some(drawn.into_iter())
}
}
pub fn new() -> Deck<T> {
Deck { cards: Vec::new() }
}
pub fn with_capacity(capacity: usize) -> Deck<T> {
Deck {
cards: Vec::with_capacity(capacity),
}
}
pub fn iter(&self) -> std::slice::Iter<'_, T> {
self.cards.iter()
}
pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, T> {
self.cards.iter_mut()
}
pub fn as_slice(&self) -> &[T] {
&self.cards
}
pub fn as_mut_slice(&mut self) -> &mut [T] {
&mut self.cards
}
pub fn len(&self) -> usize {
self.cards.len()
}
pub fn is_empty(&self) -> bool {
self.cards.is_empty()
}
}
impl<T: Card> From<Vec<T>> for Deck<T> {
fn from(cards: Vec<T>) -> Self {
Deck { cards }
}
}
impl<T: Card> FromIterator<T> for Deck<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
Deck {
cards: Vec::from_iter(iter),
}
}
}
impl<T: Card> AsRef<[T]> for Deck<T> {
fn as_ref(&self) -> &[T] {
&self.cards
}
}
impl<T: Card> IntoIterator for Deck<T> {
type Item = T;
type IntoIter = std::vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.cards.into_iter()
}
}
impl<'a, T: Card> IntoIterator for &'a Deck<T> {
type Item = &'a T;
type IntoIter = std::slice::Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.cards.iter()
}
}
impl<'a, T: Card> IntoIterator for &'a mut Deck<T> {
type Item = &'a mut T;
type IntoIter = std::slice::IterMut<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.cards.iter_mut()
}
}
#[cfg(test)]
mod tests {
use super::Deck;
use crate::core::deck::test_utils::deck_strategy;
use crate::core::italian::ItalianCard;
use crate::core::italian::test_utils::italian_rank_strategy;
use crate::core::test_utils::suit_strategy;
use proptest::prelude::*;
#[test]
fn shuffle_changes_order() {
let sorted_deck = Deck::italian();
let mut shuffled_deck = Deck::italian();
shuffled_deck.shuffle();
assert_ne!(sorted_deck.as_slice(), shuffled_deck.as_slice());
}
#[test]
fn shuffle_empty_deck() {
let mut deck: Deck<ItalianCard> = Deck::new();
deck.shuffle();
assert!(deck.is_empty());
}
#[test]
fn draw_n_basic() {
let mut deck = Deck::italian();
let drawn: Vec<_> = deck.draw_n(5).unwrap().collect();
assert_eq!(drawn.len(), 5);
assert_eq!(deck.len(), 35);
let mut deck = Deck::italian();
assert!(deck.draw_n(41).is_none());
assert_eq!(deck.len(), 40);
}
proptest! {
#[test]
fn shuffle_preserves_cards(
mut deck in deck_strategy(0..40)
) {
let original: Vec<ItalianCard> = deck.as_slice().to_vec();
deck.shuffle();
let shuffled: Vec<ItalianCard> = deck.as_slice().to_vec();
prop_assert_eq!(original.len(), shuffled.len());
let mut original_sorted = original.clone();
let mut shuffled_sorted = shuffled.clone();
original_sorted.sort_by_key(|c: &ItalianCard| (c.rank() as u8, c.suit() as u8));
shuffled_sorted.sort_by_key(|c: &ItalianCard| (c.rank() as u8, c.suit() as u8));
prop_assert_eq!(original_sorted, shuffled_sorted);
}
#[test]
fn draw_all_cards_yields_unique(
mut deck in deck_strategy(0..40)
) {
let mut seen = std::collections::HashSet::new();
while let Some(card) = deck.draw() {
prop_assert!(seen.insert(card));
}
}
#[test]
fn push_and_draw_returns_same_card(
card in (italian_rank_strategy(), suit_strategy())
) {
let mut deck = Deck::new();
let card = ItalianCard::new(card.0, card.1);
deck.push(card);
prop_assert_eq!(deck.draw(), Some(card));
}
#[test]
fn shuffle_card_inserts_card(
card in (italian_rank_strategy(), suit_strategy()),
mut deck in deck_strategy(0..40)
) {
let card = ItalianCard::new(card.0, card.1);
let old_len: usize = deck.len();
deck.shuffle_card(card);
prop_assert!(deck.as_slice().contains(&card));
prop_assert_eq!(deck.len(), old_len + 1);
}
#[test]
fn shuffle_card_never_inserts_at_top_or_bottom_for_large_deck(
mut deck in deck_strategy(2..40),
card in (italian_rank_strategy(), suit_strategy())
) {
let card = ItalianCard::new(card.0, card.1);
prop_assume!(!deck.as_slice().contains(&card));
deck.shuffle_card(card);
let pos = deck.as_slice().iter().position(|c| *c == card).unwrap();
let new_len = deck.len();
prop_assert!(pos != 0 && pos != new_len - 1);
}
}
}
#[cfg(test)]
pub mod test_utils {
use std::ops::Range;
use super::*;
use crate::core::Suit;
use crate::core::italian::{ItalianCard, ItalianRank};
use proptest::prelude::*;
pub fn deck_strategy(size: Range<usize>) -> impl Strategy<Value = Deck<ItalianCard>> {
let all_cards: Vec<ItalianCard> = [
ItalianRank::Ace,
ItalianRank::Two,
ItalianRank::Three,
ItalianRank::Four,
ItalianRank::Five,
ItalianRank::Six,
ItalianRank::Seven,
ItalianRank::Jack,
ItalianRank::Knight,
ItalianRank::King,
]
.iter()
.flat_map(|&rank| {
[Suit::Hearts, Suit::Diamonds, Suit::Clubs, Suit::Spades]
.iter()
.map(move |&suit| ItalianCard::new(rank, suit))
})
.collect();
proptest::sample::subsequence(all_cards, size).prop_map(Deck::from)
}
}