use crate::tile::Operator; use enum_map::EnumMap; use rand::Rng; type DeckSize = u16; type DigitDeck = [DeckSize; 19]; /// When a deck is empty, new tiles cannot be retrieved. pub type EmptyDeckError = (); /// A entire deck of tiles. #[derive(Debug, Clone, Default, PartialEq)] pub struct Deck { /// The digits and their current count. digits: DigitDeck, /// The operators and their current count. operators: EnumMap, } impl Deck { pub fn new_complete() -> Self { let mut deck = Self::default(); deck.add_operator_times(Operator::Add, 10); deck.add_operator_times(Operator::Subtract, 10); deck.add_operator_times(Operator::Multiply, 6); deck.add_operator_times(Operator::Divide, 4); for digit in -9..0 { deck.add_digit_times(digit, 2); } for digit in 0..=9 { deck.add_digit_times(digit, 8); } deck } /// Adds a single digit to the deck. pub fn add_digit(&mut self, digit: i8) { self.add_digit_times(digit, 1) } /// Adds a digit multiple times to the deck. pub fn add_digit_times(&mut self, digit: i8, times: DeckSize) { self.digits[Deck::digit_index(digit)] += times; } /// Adds a single operator to the deck. pub fn add_operator(&mut self, operator: Operator) { self.add_operator_times(operator, 1) } /// Adds an operator multiple times to the deck. pub fn add_operator_times(&mut self, operator: Operator, times: DeckSize) { self.operators[operator] += times; } /// Gets the index of a digit in the digit deck. fn digit_index(digit: i8) -> usize { (digit + 9) as usize } } /// A deck of tiles that can be chosen at random. #[derive(Debug, Clone, Default)] pub struct RngDeck { deck: Deck, rng: rand::rngs::ThreadRng, } impl RngDeck { pub fn new_complete() -> Self { Self { deck: Deck::new_complete(), rng: rand::thread_rng(), } } /// Gets a random tile from the deck and remove it from the deck. pub fn rand_digit(&mut self) -> Option { let sum = self.deck.digits.iter().sum(); Self::select_rng(&mut self.rng, sum, self.deck.digits.iter_mut().enumerate()) .map(|n| n as i8 - 9) } /// Gets a random operator from the deck and remove it from the deck. pub fn rand_operator(&mut self) -> Option { let sum = self.deck.operators.values().sum(); Self::select_rng(&mut self.rng, sum, self.deck.operators.iter_mut()) } /// Selects a random item from an iterator of (item, count) pairs. /// The count is decremented by one if the item is selected. fn select_rng<'a, T>( rng: &mut rand::rngs::ThreadRng, sum: DeckSize, it: impl Iterator, ) -> Option { if sum == 0 { return None; } let mut threshold = rng.gen_range(1..=sum); for (item, count) in it { threshold = threshold.saturating_sub(*count); if threshold == 0 { *count -= 1; return Some(item); } } unreachable!() } } impl PartialEq for RngDeck { fn eq(&self, other: &Self) -> bool { self.deck == other.deck } } #[cfg(test)] mod tests { use super::*; #[test] fn default_is_empty() { let mut deck = RngDeck::default(); assert_eq!(deck.rand_digit(), None); assert_eq!(deck.rand_operator(), None); } #[test] fn one_digit() { let mut deck = RngDeck::default(); deck.deck.add_digit(1); assert_eq!(deck.rand_digit(), Some(1)); assert_eq!(deck.rand_operator(), None); assert_eq!(deck.rand_digit(), None); } #[test] fn one_operator() { let mut deck = RngDeck::default(); deck.deck.add_operator(Operator::Multiply); assert_eq!(deck.rand_digit(), None); assert_eq!(deck.rand_operator(), Some(Operator::Multiply)); assert_eq!(deck.rand_operator(), None); } #[test] fn respect_proportion() { let mut deck = RngDeck::default(); deck.deck.add_digit_times(-4, 2); deck.deck.add_digit_times(3, 7); deck.deck.add_digit_times(7, 3); let mut actual = DigitDeck::default(); while let Some(digit) = deck.rand_digit() { actual[Deck::digit_index(digit)] += 1; } let mut excepted = DigitDeck::default(); excepted[Deck::digit_index(-4)] = 2; excepted[Deck::digit_index(3)] = 7; excepted[Deck::digit_index(7)] = 3; assert_eq!(actual, excepted); } }