use crate::cards::card::CardData;
use std::sync::Arc;
use super::card::Card;
use super::suit_rank::{Rank, Suit};
use rand::{rngs::StdRng, seq::SliceRandom, SeedableRng};
use strum::IntoEnumIterator;
#[derive(Clone, Default, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DeckConfig {
pub shuffle_seed: Option<u64>,
pub pack_count: usize,
pub high_rank: Option<Rank>,
pub wildcard_rank: Option<Rank>,
}
impl DeckConfig {
pub fn new() -> Self {
DeckConfig {
shuffle_seed: None,
pack_count: 1,
high_rank: None,
wildcard_rank: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Deck {
pub(crate) config: Arc<DeckConfig>,
pub(crate) stock: Vec<Card>,
pub(crate) discard_pile: Vec<Card>,
}
impl Deck {
pub fn new(mut config: DeckConfig) -> Self {
config.pack_count = config.pack_count.max(1);
let config = Arc::new(config);
let mut deck = Deck {
config: config.clone(),
stock: Vec::new(),
discard_pile: Vec::new(),
};
Deck::generate_cards(&mut deck.stock, &config);
Deck::shuffle_cards(&mut deck.stock, &config);
deck
}
pub fn reset(&mut self) {
self.stock.clear();
self.discard_pile.clear();
Deck::generate_cards(&mut self.stock, &self.config);
Deck::shuffle_cards(&mut self.stock, &self.config);
}
pub fn try_draw(&mut self, amount: usize) -> Result<Vec<Card>, String> {
if amount > self.stock.len() {
return Err(format!(
"Draw amount ({amount}) greater than stock size ({})",
self.stock.len()
));
}
let cards = self.stock.split_off(self.stock.len() - amount);
Ok(cards)
}
pub fn draw(&mut self, amount: usize) -> Result<Vec<Card>, String> {
if amount > self.stock.len() {
self.turnover_discarded();
}
if amount > self.stock.len() {
return Err(format!(
"Draw amount ({amount}) greater than stock + discard pile size (technically shouldn't happen)"
));
}
let cards = self.stock.split_off(self.stock.len() - amount);
Ok(cards)
}
pub fn peek_discard_pile(&self) -> Option<CardData> {
self.discard_pile.last().map(|card| card.data())
}
pub fn draw_discard_pile(&mut self, amount: usize) -> Result<Vec<Card>, String> {
let discard_size = self.discard_pile.len();
if discard_size == 0 {
Err("Can't draw from empty discard pile".to_string())
} else {
if amount > discard_size {
return Err(format!(
"Draw amount ({amount}) greater than discard pile size ({discard_size})"
));
}
Ok(self.discard_pile.split_off(discard_size - amount))
}
}
pub fn add_multiple_to_discard_pile(&mut self, cards: &mut Vec<Card>) {
self.discard_pile.append(cards);
}
pub fn add_to_discard_pile(&mut self, card: Card) {
self.discard_pile.push(card);
}
pub fn shuffle_discarded(&mut self) {
self.stock.append(&mut self.discard_pile);
self.stock.shuffle(&mut rand::thread_rng());
}
pub fn turnover_discarded(&mut self) {
self.stock.append(&mut self.discard_pile);
self.stock.reverse();
}
pub fn config(&self) -> &DeckConfig {
&self.config
}
pub fn stock(&self) -> &Vec<Card> {
&self.stock
}
pub fn discard_pile(&self) -> &Vec<Card> {
&self.discard_pile
}
fn generate_cards(stock: &mut Vec<Card>, config: &Arc<DeckConfig>) {
for _ in 0..config.pack_count {
for rank in Rank::iter() {
if rank == Rank::Joker {
continue;
}
for suit in Suit::iter() {
if suit == Suit::Joker {
continue;
}
stock.push(Card {
rank,
suit,
deck_config: config.clone(),
});
}
}
if config.wildcard_rank == Some(Rank::Joker) {
for _ in 0..2 {
stock.push(Card {
rank: Rank::Joker,
suit: Suit::Joker,
deck_config: config.clone(),
});
}
}
}
}
fn shuffle_cards(stock: &mut Vec<Card>, config: &Arc<DeckConfig>) {
match config.shuffle_seed {
Some(seed) => {
if seed != 0 {
stock.shuffle(&mut StdRng::seed_from_u64(seed));
}
}
None => stock.shuffle(&mut rand::thread_rng()),
}
}
}