]> git.scottworley.com Git - pluta-lesnura/blame_incremental - src/lib.rs
1-based rank value
[pluta-lesnura] / src / lib.rs
... / ...
CommitLineData
1use rand::Rng;
2
3pub const NUM_RANKS: u8 = 13;
4pub const NUM_SUITS: u8 = 4;
5pub const NUM_JOKERS: u8 = 2;
6pub const NUM_CARDS: u8 = NUM_RANKS * NUM_SUITS + NUM_JOKERS;
7
8#[derive(Clone, Copy, Eq, PartialEq)]
9pub struct Rank(u8);
10impl Rank {
11 #[must_use]
12 pub fn value(&self) -> u8 {
13 self.0 + 1
14 }
15}
16
17#[derive(Clone, Copy, Eq, PartialEq)]
18pub struct Suit(u8);
19
20#[derive(Clone, Copy, Eq, PartialEq)]
21pub struct Card(u8);
22impl Card {
23 #[must_use]
24 pub fn is_joker(&self) -> bool {
25 self.0 >= NUM_RANKS * NUM_SUITS
26 }
27 #[must_use]
28 pub fn rank(&self) -> Option<Rank> {
29 (!self.is_joker()).then_some(Rank(self.0 >> 2))
30 }
31 #[must_use]
32 pub fn suit(&self) -> Option<Suit> {
33 (!self.is_joker()).then_some(Suit(self.0 & 3))
34 }
35}
36
37#[derive(Clone, Copy)]
38pub enum WithOrWithoutJokers {
39 WithJokers,
40 WithoutJokers,
41}
42
43#[must_use]
44pub fn deck(j: WithOrWithoutJokers) -> Vec<Card> {
45 let limit = match j {
46 WithOrWithoutJokers::WithJokers => NUM_CARDS,
47 WithOrWithoutJokers::WithoutJokers => NUM_SUITS * NUM_RANKS,
48 };
49 (0..limit).map(Card).collect()
50}
51
52#[derive(Clone, Copy)]
53pub struct PathLength(Rank);
54
55#[derive(Clone, Copy, Default)]
56pub struct PathLengthInfo(u16);
57impl PathLengthInfo {
58 #[must_use]
59 pub fn is_showing(&self, i: Rank) -> bool {
60 (self.0 >> i.0) & 1 == 1
61 }
62 fn reveal(&mut self, i: Rank) {
63 self.0 |= 1 << i.0;
64 }
65 pub fn reveal_random(&mut self, true_length: PathLength) -> Option<Rank> {
66 let showing = u8::try_from(self.0.count_ones()).expect("There aren't that many bits");
67 let not_showing = NUM_RANKS - showing;
68 if not_showing <= 1 {
69 return None;
70 }
71
72 let mut show = rand::thread_rng().gen_range(0..not_showing - 1);
73 for i in 0..NUM_RANKS {
74 let r = Rank(i);
75 if !self.is_showing(r) && r != true_length.0 {
76 if show == 0 {
77 self.reveal(r);
78 return Some(r);
79 }
80 show -= 1;
81 }
82 }
83 unreachable!()
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn path_length_info_random_reveal() {
93 let length = PathLength(Rank(7));
94 let mut pli = PathLengthInfo::default();
95 for _ in 0..12 {
96 let old_pli = PathLengthInfo::clone(&pli);
97 match pli.reveal_random(length) {
98 None => panic!("Nothing revealed?"),
99 Some(r) => {
100 assert!(!old_pli.is_showing(r));
101 assert!(pli.is_showing(r));
102 }
103 }
104 assert_eq!(pli.0.count_ones(), 1 + old_pli.0.count_ones());
105 }
106 assert!(pli.reveal_random(length).is_none());
107 }
108
109 #[test]
110 fn test_deck() {
111 use WithOrWithoutJokers::*;
112 let d = deck(WithoutJokers);
113 let rank_sum: u32 = d
114 .iter()
115 .map(Card::rank)
116 .flatten()
117 .map(|r| u32::from(r.value()))
118 .sum();
119 assert_eq!(rank_sum, 364);
120 let _dj = deck(WithJokers);
121 }
122}