X-Git-Url: http://git.scottworley.com/pluta-lesnura/blobdiff_plain/754e97300990e3e7d3e4bfd305f3e90b677751c2..b255b7c6916b4cd069e6e807d952aae314e713d8:/src/lib.rs diff --git a/src/lib.rs b/src/lib.rs index 7d12d9a..e2604dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,139 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right +use rand::Rng; + +pub const NUM_RANKS: usize = 13; +pub const NUM_SUITS: usize = 4; +pub const NUM_JOKERS: usize = 2; +pub const NUM_CARDS: usize = NUM_RANKS * NUM_SUITS + NUM_JOKERS; + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Rank(u8); +impl Rank { + #[must_use] + pub fn value(&self) -> u8 { + self.0 + 1 + } +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Suit(u8); + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Card(u8); +impl Card { + #[must_use] + pub fn is_joker(&self) -> bool { + usize::from(self.0) >= NUM_RANKS * NUM_SUITS + } + #[must_use] + pub fn rank(&self) -> Option { + (!self.is_joker()).then_some(Rank(self.0 >> 2)) + } + #[must_use] + pub fn suit(&self) -> Option { + (!self.is_joker()).then_some(Suit(self.0 & 3)) + } +} + +#[derive(Clone, Copy)] +pub enum WithOrWithoutJokers { + WithJokers, + WithoutJokers, +} + +#[must_use] +pub fn deck(j: WithOrWithoutJokers) -> Vec { + let limit = u8::try_from(match j { + WithOrWithoutJokers::WithJokers => NUM_CARDS, + WithOrWithoutJokers::WithoutJokers => NUM_SUITS * NUM_RANKS, + }) + .expect("Too many cards?"); + (0..limit).map(Card).collect() +} + +#[derive(Clone, Copy)] +pub struct PathLength(Rank); + +#[derive(Clone, Copy, Default)] +pub struct PathLengthInfo(u16); +impl PathLengthInfo { + #[must_use] + pub fn is_showing(&self, i: Rank) -> bool { + (self.0 >> i.0) & 1 == 1 + } + fn reveal(&mut self, i: Rank) { + self.0 |= 1 << i.0; + } + pub fn reveal_random(&mut self, true_length: PathLength) -> Option { + let showing = usize::try_from(self.0.count_ones()).expect("There aren't that many bits"); + let not_showing = NUM_RANKS - showing; + if not_showing <= 1 { + return None; + } + + let mut show = rand::thread_rng().gen_range(0..not_showing - 1); + for i in 0..NUM_RANKS { + let r = Rank(u8::try_from(i).expect("Too many cards?")); + if !self.is_showing(r) && r != true_length.0 { + if show == 0 { + self.reveal(r); + return Some(r); + } + show -= 1; + } + } + unreachable!() + } +} + +#[derive(Default)] +pub struct Discard { + cards: Vec, +} +impl Discard { + pub fn discard(&mut self, card: Card) { + self.cards.push(card); + } +} + +pub struct Library { + cards: Vec, +} +impl Library { + #[must_use] + pub fn new(cards: Vec) -> Self { + Self { cards } + } + pub fn draw(&mut self, discard: &mut Discard) -> Option { + if self.cards.is_empty() { + if let Some(top_discard) = discard.cards.pop() { + std::mem::swap(&mut self.cards, &mut discard.cards); + discard.discard(top_discard); + // TODO: Shuffle + } + } + self.cards.pop() + } +} + +#[cfg(test)] +#[derive(Default)] +struct Hand { + cards: Vec, +} +#[cfg(test)] +impl Hand { + fn add(&mut self, card: Card) { + self.cards.push(card); + } + fn remove(&mut self, card: Card) -> Result<(), &'static str> { + let i = self + .cards + .iter() + .position(|&e| e == card) + .ok_or("That card is not in your hand")?; + self.cards.swap_remove(i); + Ok(()) + } } #[cfg(test)] @@ -7,8 +141,55 @@ mod tests { use super::*; #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + fn path_length_info_random_reveal() { + let length = PathLength(Rank(7)); + let mut pli = PathLengthInfo::default(); + for _ in 0..12 { + let old_pli = PathLengthInfo::clone(&pli); + match pli.reveal_random(length) { + None => panic!("Nothing revealed?"), + Some(r) => { + assert!(!old_pli.is_showing(r)); + assert!(pli.is_showing(r)); + } + } + assert_eq!(pli.0.count_ones(), 1 + old_pli.0.count_ones()); + } + assert!(pli.reveal_random(length).is_none()); + } + + #[test] + fn test_deck() { + use WithOrWithoutJokers::*; + let d = deck(WithoutJokers); + let rank_sum: u32 = d + .iter() + .map(Card::rank) + .flatten() + .map(|r| u32::from(r.value())) + .sum(); + assert_eq!(rank_sum, 364); + let _dj = deck(WithJokers); + } + + #[test] + fn test_library() { + let mut lib = Library::new(vec![Card(7)]); + let mut dis = Discard::default(); + dis.discard(Card(8)); + dis.discard(Card(9)); + assert_eq!(lib.draw(&mut dis), Some(Card(7))); + assert_eq!(lib.draw(&mut dis), Some(Card(8))); + assert_eq!(lib.draw(&mut dis), None); + } + + #[test] + fn test_hand() { + let mut h = Hand::default(); + assert!(h.remove(Card(4)).is_err()); + h.add(Card(4)); + assert!(h.remove(Card(3)).is_err()); + assert!(h.remove(Card(4)).is_ok()); + assert!(h.remove(Card(4)).is_err()); } }