pub mod ranges;
pub use crate::ranges::Ranges;
#[allow(clippy::len_without_is_empty)]
pub trait Faces {
fn len(&self) -> usize;
fn entropy_bits_per_face(&self) -> f64 {
(self.len() as f64).log2()
}
fn entropy_bits(&self, n: usize) -> f64 {
self.entropy_bits_per_face() * (n as f64)
}
fn needs_faces(&self, entropy_bits: f64) -> usize {
(entropy_bits / self.entropy_bits_per_face()).ceil() as usize
}
fn needs_length_as_bytes(&self, entropy_bits: f64) -> usize {
(entropy_bits / self.entropy_bits_per_face() / 8.).ceil() as usize
}
}
impl<T> Faces for [T] {
fn len(&self) -> usize {
self.len()
}
}
impl<T, const N: usize> Faces for [T; N] {
fn len(&self) -> usize {
N
}
}
impl<T> Faces for Vec<T> {
fn len(&self) -> usize {
self.len()
}
}
pub struct Rollable<T>
where
T: Faces + core::ops::Index<usize>,
{
inner: T,
}
impl<T> Default for Rollable<T>
where
T: Default + Faces + core::ops::Index<usize>,
{
fn default() -> Self {
Self {
inner: T::default(),
}
}
}
impl<T> Rollable<T>
where
T: Faces + core::ops::Index<usize>,
{
pub fn new(inner: T) -> Self {
Self { inner }
}
}
impl<T> Faces for Rollable<T>
where
T: Faces + core::ops::Index<usize>,
{
fn len(&self) -> usize {
self.inner.len()
}
}
impl<T> rand::distributions::Distribution<<T as core::ops::Index<usize>>::Output> for Rollable<T>
where
T: Faces + std::ops::Index<usize>,
T::Output: Copy,
{
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> T::Output {
let idx = rng.gen_range(0..self.inner.len());
self.inner[idx]
}
}
pub struct Dice<F, Rng>
where
F: Faces + std::ops::Index<usize>,
Rng: rand::CryptoRng,
{
faces: Rollable<F>,
rng: Rng,
}
impl<F, Rng> Dice<F, Rng>
where
F: Faces + std::ops::Index<usize>,
Rng: rand::CryptoRng + rand::Rng,
{
pub fn new(faces: F, rng: Rng) -> Self {
Self {
faces: Rollable::new(faces),
rng,
}
}
}
impl<F, Rng> Dice<F, Rng>
where
F: Faces + std::ops::Index<usize>,
F::Output: Copy,
Rng: rand::CryptoRng + rand::Rng,
{
pub fn roll(&mut self) -> <F as std::ops::Index<usize>>::Output {
use rand::distributions::Distribution;
self.faces.sample(&mut self.rng)
}
}
impl<F, Rng> Faces for Dice<F, Rng>
where
F: Faces + std::ops::Index<usize>,
Rng: rand::CryptoRng + rand::Rng,
{
fn len(&self) -> usize {
self.faces.len()
}
}
pub trait ToDice: Faces
where
Self: std::ops::Index<usize> + Sized,
<Self as std::ops::Index<usize>>::Output: Sized,
{
fn to_dice<Rng>(self, rng: Rng) -> Dice<Self, Rng>
where
Self: Sized,
Rng: rand::CryptoRng + rand::Rng,
{
Dice::new(self, rng)
}
fn dice(self) -> Dice<Self, rand::rngs::ThreadRng> {
self.to_dice(rand::thread_rng())
}
}
impl<T> ToDice for T
where
T: Faces + std::ops::Index<usize> + Sized,
<T as std::ops::Index<usize>>::Output: Sized,
{
}
#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
#[error("Invalid charactor")]
pub struct InvalidCharactor;
#[derive(Debug, Clone)]
pub struct AsciiFaces {
inner: Vec<u8>,
}
impl AsciiFaces {
fn try_from_iter<I: Iterator<Item = char>>(iter: I) -> Result<Self, InvalidCharactor> {
iter.map(|c| {
if c.is_ascii() {
Ok(c as u8)
} else {
Err(InvalidCharactor)
}
})
.collect()
}
pub fn contains(&self, c: u8) -> bool {
self.inner.contains(&c)
}
}
impl std::iter::FromIterator<u8> for AsciiFaces {
fn from_iter<I: IntoIterator<Item = u8>>(iter: I) -> Self {
Self {
inner: iter.into_iter().collect(),
}
}
}
impl<T: Compact> std::convert::TryFrom<CompactWrapper<'_, T>> for AsciiFaces {
type Error = InvalidCharactor;
fn try_from(compact: CompactWrapper<T>) -> Result<Self, Self::Error> {
use ranges::Ranges;
Self::try_from_iter(compact.compact().chars().ranges())
}
}
impl std::ops::Index<usize> for AsciiFaces {
type Output = u8;
fn index(&self, idx: usize) -> &u8 {
&self.inner[idx]
}
}
impl Faces for AsciiFaces {
fn len(&self) -> usize {
self.inner.len()
}
}
pub trait Compact {
fn compact(&self) -> &str;
fn ranges(&self) -> CompactWrapper<'_, &str> {
CompactWrapper {
inner: self.compact(),
_marker: std::marker::PhantomData,
}
}
}
impl<S: AsRef<str>> Compact for S {
fn compact(&self) -> &str {
self.as_ref()
}
}
pub struct CompactWrapper<'a, T: 'a>
where
T: Compact,
{
inner: T,
_marker: std::marker::PhantomData<&'a T>,
}
impl<T: Compact> Compact for CompactWrapper<'_, T> {
fn compact(&self) -> &str {
self.inner.compact()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_faces() {
let v = vec![1, 2];
assert_eq!(Faces::len(&v), 2);
}
#[test]
fn test_dice() {
let v = vec![1, 2];
let mut dice = v.clone().dice();
assert!(v.contains(&dice.roll()));
assert!(v.contains(&dice.roll()));
assert!(v.contains(&dice.roll()));
let v = vec!["test", "test2"];
let mut dice = v.clone().dice();
assert!(v.contains(&dice.roll()));
assert!(v.contains(&dice.roll()));
assert!(v.contains(&dice.roll()));
}
#[test]
fn test_str() {
let v = "12";
let mut dice = v.chars().collect::<Vec<char>>().dice();
assert!(v.contains(dice.roll()));
assert_eq!(dice.entropy_bits_per_face(), 1.);
}
#[test]
fn test_compact() {
assert!(AsciiFaces::try_from("a-z".ranges()).is_ok());
assert!(AsciiFaces::try_from("a-z".ranges()).unwrap().contains(b'g'));
assert!(!AsciiFaces::try_from("a-z".ranges()).unwrap().contains(b'G'));
}
}