1pub mod ranges;
2pub use crate::ranges::Ranges;
3
4#[allow(clippy::len_without_is_empty)]
7pub trait Faces {
8 fn len(&self) -> usize;
10
11 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
94pub 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#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
166#[error("Invalid charactor")]
167pub struct InvalidCharactor;
168
169#[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
200impl<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
226pub 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}