entroll_core/
lib.rs

1pub mod ranges;
2pub use crate::ranges::Ranges;
3
4/// `Faces` is a trait defining behavior for objects that have a set number of faces, like dice.
5/// It provides methods to calculate the entropy based on the number of faces.
6#[allow(clippy::len_without_is_empty)]
7pub trait Faces {
8    /// Returns the number of faces.
9    fn len(&self) -> usize;
10
11    /// Calculates the entropy in bits for one face.
12    fn entropy_bits_per_face(&self) -> f64 {
13        (self.len() as f64).log2()
14    }
15
16    fn entropy_bits(&self, n: usize) -> f64 {
17        self.entropy_bits_per_face() * (n as f64)
18    }
19
20    fn needs_faces(&self, entropy_bits: f64) -> usize {
21        (entropy_bits / self.entropy_bits_per_face()).ceil() as usize
22    }
23
24    fn needs_length_as_bytes(&self, entropy_bits: f64) -> usize {
25        (entropy_bits / self.entropy_bits_per_face() / 8.).ceil() as usize
26    }
27}
28
29impl<T> Faces for [T] {
30    fn len(&self) -> usize {
31        self.len()
32    }
33}
34
35impl<T, const N: usize> Faces for [T; N] {
36    fn len(&self) -> usize {
37        N
38    }
39}
40
41impl<T> Faces for Vec<T> {
42    fn len(&self) -> usize {
43        self.len()
44    }
45}
46
47pub struct Rollable<T>
48where
49    T: Faces + core::ops::Index<usize>,
50{
51    inner: T,
52}
53
54impl<T> Default for Rollable<T>
55where
56    T: Default + Faces + core::ops::Index<usize>,
57{
58    fn default() -> Self {
59        Self {
60            inner: T::default(),
61        }
62    }
63}
64
65impl<T> Rollable<T>
66where
67    T: Faces + core::ops::Index<usize>,
68{
69    pub fn new(inner: T) -> Self {
70        Self { inner }
71    }
72}
73
74impl<T> Faces for Rollable<T>
75where
76    T: Faces + core::ops::Index<usize>,
77{
78    fn len(&self) -> usize {
79        self.inner.len()
80    }
81}
82
83impl<T> rand::distributions::Distribution<<T as core::ops::Index<usize>>::Output> for Rollable<T>
84where
85    T: Faces + std::ops::Index<usize>,
86    T::Output: Copy,
87{
88    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> T::Output {
89        let idx = rng.gen_range(0..self.inner.len());
90        self.inner[idx]
91    }
92}
93
94/// `Dice` struct represents an implementation of dice, using a generic `Faces` collection and RNG.
95pub struct Dice<F, Rng>
96where
97    F: Faces + std::ops::Index<usize>,
98    Rng: rand::CryptoRng,
99{
100    faces: Rollable<F>,
101    rng: Rng,
102}
103
104impl<F, Rng> Dice<F, Rng>
105where
106    F: Faces + std::ops::Index<usize>,
107    Rng: rand::CryptoRng + rand::Rng,
108{
109    pub fn new(faces: F, rng: Rng) -> Self {
110        Self {
111            faces: Rollable::new(faces),
112            rng,
113        }
114    }
115}
116
117impl<F, Rng> Dice<F, Rng>
118where
119    F: Faces + std::ops::Index<usize>,
120    F::Output: Copy,
121    Rng: rand::CryptoRng + rand::Rng,
122{
123    pub fn roll(&mut self) -> <F as std::ops::Index<usize>>::Output {
124        use rand::distributions::Distribution;
125        self.faces.sample(&mut self.rng)
126    }
127}
128
129impl<F, Rng> Faces for Dice<F, Rng>
130where
131    F: Faces + std::ops::Index<usize>,
132    Rng: rand::CryptoRng + rand::Rng,
133{
134    fn len(&self) -> usize {
135        self.faces.len()
136    }
137}
138
139pub trait ToDice: Faces
140where
141    Self: std::ops::Index<usize> + Sized,
142    <Self as std::ops::Index<usize>>::Output: Sized,
143{
144    fn to_dice<Rng>(self, rng: Rng) -> Dice<Self, Rng>
145    where
146        Self: Sized,
147        Rng: rand::CryptoRng + rand::Rng,
148    {
149        Dice::new(self, rng)
150    }
151
152    fn dice(self) -> Dice<Self, rand::rngs::ThreadRng> {
153        self.to_dice(rand::thread_rng())
154    }
155}
156
157impl<T> ToDice for T
158where
159    T: Faces + std::ops::Index<usize> + Sized,
160    <T as std::ops::Index<usize>>::Output: Sized,
161{
162}
163
164/// Non ascii charactor error.
165#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
166#[error("Invalid charactor")]
167pub struct InvalidCharactor;
168
169/// AsciiFaces is a type that contains ascii charactors.
170#[derive(Debug, Clone)]
171pub struct AsciiFaces {
172    inner: Vec<u8>,
173}
174
175impl AsciiFaces {
176    fn try_from_iter<I: Iterator<Item = char>>(iter: I) -> Result<Self, InvalidCharactor> {
177        iter.map(|c| {
178            if c.is_ascii() {
179                Ok(c as u8)
180            } else {
181                Err(InvalidCharactor)
182            }
183        })
184        .collect()
185    }
186
187    pub fn contains(&self, c: u8) -> bool {
188        self.inner.contains(&c)
189    }
190}
191
192impl std::iter::FromIterator<u8> for AsciiFaces {
193    fn from_iter<I: IntoIterator<Item = u8>>(iter: I) -> Self {
194        Self {
195            inner: iter.into_iter().collect(),
196        }
197    }
198}
199
200/* This is implemented in CompactWrapper
201impl std::convert::TryFrom<&str> for AsciiFaces;
202*/
203
204impl<T: Compact> std::convert::TryFrom<CompactWrapper<'_, T>> for AsciiFaces {
205    type Error = InvalidCharactor;
206
207    fn try_from(compact: CompactWrapper<T>) -> Result<Self, Self::Error> {
208        use ranges::Ranges;
209        Self::try_from_iter(compact.compact().chars().ranges())
210    }
211}
212
213impl std::ops::Index<usize> for AsciiFaces {
214    type Output = u8;
215    fn index(&self, idx: usize) -> &u8 {
216        &self.inner[idx]
217    }
218}
219
220impl Faces for AsciiFaces {
221    fn len(&self) -> usize {
222        self.inner.len()
223    }
224}
225
226/// Compact form. using ranges.
227/// like a-z, A-Z, 0-9, etc.
228pub trait Compact {
229    fn compact(&self) -> &str;
230
231    fn ranges(&self) -> CompactWrapper<'_, &str> {
232        CompactWrapper {
233            inner: self.compact(),
234            _marker: std::marker::PhantomData,
235        }
236    }
237}
238
239impl<S: AsRef<str>> Compact for S {
240    fn compact(&self) -> &str {
241        self.as_ref()
242    }
243}
244
245pub struct CompactWrapper<'a, T: 'a>
246where
247    T: Compact,
248{
249    inner: T,
250    _marker: std::marker::PhantomData<&'a T>,
251}
252
253impl<T: Compact> Compact for CompactWrapper<'_, T> {
254    fn compact(&self) -> &str {
255        self.inner.compact()
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    #[test]
264    fn test_faces() {
265        let v = vec![1, 2];
266        assert_eq!(Faces::len(&v), 2);
267    }
268
269    #[test]
270    fn test_dice() {
271        let v = vec![1, 2];
272        let mut dice = v.clone().dice();
273        assert!(v.contains(&dice.roll()));
274        assert!(v.contains(&dice.roll()));
275        assert!(v.contains(&dice.roll()));
276
277        let v = vec!["test", "test2"];
278        let mut dice = v.clone().dice();
279        assert!(v.contains(&dice.roll()));
280        assert!(v.contains(&dice.roll()));
281        assert!(v.contains(&dice.roll()));
282    }
283
284    #[test]
285    fn test_str() {
286        let v = "12";
287        let mut dice = v.chars().collect::<Vec<char>>().dice();
288        assert!(v.contains(dice.roll()));
289        assert_eq!(dice.entropy_bits_per_face(), 1.);
290    }
291
292    #[test]
293    fn test_compact() {
294        assert!(AsciiFaces::try_from("a-z".ranges()).is_ok());
295        assert!(AsciiFaces::try_from("a-z".ranges()).unwrap().contains(b'g'));
296        assert!(!AsciiFaces::try_from("a-z".ranges()).unwrap().contains(b'G'));
297    }
298}