use std::{
fmt::Debug,
ops::{Add, Sub},
};
use crate::traits::{Numeric, Polyhedral, Rotate, RotateMut, Step, StepMut};
#[derive(Clone, PartialEq, Eq)]
pub struct NumericDie<T, const MAXIMUM: usize>(T)
where
T: Numeric;
pub type D4 = NumericDie<u8, 4>;
pub type D6 = NumericDie<u8, 6>;
pub type D8 = NumericDie<u8, 8>;
pub type D10 = NumericDie<u8, 10>;
pub type D12 = NumericDie<u8, 12>;
pub type D20 = NumericDie<u8, 20>;
impl<T, const MAXIMUM: usize> NumericDie<T, MAXIMUM>
where
T: Numeric,
{
pub fn new() -> Self {
Self(T::MINIMUM)
}
unsafe fn from_unchecked(value: T) -> Self {
Self(value)
}
pub const fn sides() -> usize {
MAXIMUM
}
pub const fn value(&self) -> T {
self.0
}
}
impl<T, const MAXIMUM: usize> Debug for NumericDie<T, MAXIMUM>
where
T: Debug + Numeric,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "D{}:{:?}", MAXIMUM, self.0)?;
Ok(())
}
}
impl<T, const MAXIMUM: usize> Default for NumericDie<T, MAXIMUM>
where
T: Numeric,
{
fn default() -> Self {
Self::new()
}
}
impl<T, const MAXIMUM: usize> From<T> for NumericDie<T, MAXIMUM>
where
T: Numeric,
{
fn from(number: T) -> Self {
assert!(number >= T::MINIMUM);
assert!(number.as_usize() <= MAXIMUM);
unsafe { Self::from_unchecked(number) }
}
}
impl<T, const MAXIMUM: usize> Polyhedral for NumericDie<T, MAXIMUM>
where
T: Numeric,
{
fn sides() -> usize {
Self::sides()
}
}
impl<T, const MAXIMUM: usize> Step for NumericDie<T, MAXIMUM>
where
T: Numeric + Add<Output = T> + Sub<Output = T>,
{
fn next(&self) -> Self {
let mut next = self.0 + T::STEPONE;
if next >= T::from_usize(MAXIMUM) {
next = T::MINIMUM;
}
unsafe { Self::from_unchecked(next) }
}
fn back(&self) -> Self {
let mut back = self.0 - T::STEPONE;
if back < T::MINIMUM {
back = T::from_usize(MAXIMUM);
}
unsafe { Self::from_unchecked(back) }
}
}
impl<T, const MAXIMUM: usize> StepMut for NumericDie<T, MAXIMUM>
where
T: Numeric + Add<Output = T> + Sub<Output = T>,
{
fn next_mut(&mut self) {
let mut next = self.0 + T::STEPONE;
if next >= T::from_usize(MAXIMUM) {
next = T::MINIMUM;
}
self.0 = next;
}
fn back_mut(&mut self) {
let mut back = self.0 - T::STEPONE;
if back < T::MINIMUM {
back = T::from_usize(MAXIMUM);
}
self.0 = back;
}
}
fn rotate_forward_usize<T, const MAXIMUM: usize>(amount: usize, mut next: usize) -> T
where
T: Numeric + Add<Output = T> + Sub<Output = T>,
{
debug_assert!(amount > 0);
next += amount;
if next > MAXIMUM {
next %= MAXIMUM;
}
T::from_usize(next)
}
fn rotate_backward_usize<T, const MAXIMUM: usize>(amount: usize, mut next: usize) -> T
where
T: Numeric + Add<Output = T> + Sub<Output = T>,
{
debug_assert!(amount > 0);
println!("next:{next} - amount:{amount}");
let rotated = next as i64 - (amount as i64);
if rotated < 1 {
let rotated = rotated % MAXIMUM as i64 + MAXIMUM as i64;
next = rotated as usize;
} else {
next -= amount;
}
T::from_usize(next)
}
impl<T, const MAXIMUM: usize> Rotate for NumericDie<T, MAXIMUM>
where
T: Numeric + Add<Output = T> + Sub<Output = T>,
{
#[allow(clippy::comparison_chain)]
#[must_use]
fn rotate(&self, amount: i8) -> Self {
if amount == 0 {
return self.clone();
}
let result = if amount > 0 {
rotate_forward_usize::<T, MAXIMUM>(amount.unsigned_abs() as usize, self.0.as_usize())
} else {
rotate_backward_usize::<T, MAXIMUM>(amount.unsigned_abs() as usize, self.0.as_usize())
};
unsafe { Self::from_unchecked(result) }
}
}
impl<T, const MAXIMUM: usize> RotateMut for NumericDie<T, MAXIMUM>
where
T: Numeric + Debug + Add<Output = T> + Sub<Output = T>,
{
fn rotate_mut(&mut self, amount: i8) {
if amount == 0 {
return;
}
let result = if amount > 0 {
rotate_forward_usize::<T, MAXIMUM>(amount.unsigned_abs() as usize, self.0.as_usize())
} else {
rotate_backward_usize::<T, MAXIMUM>(amount.unsigned_abs() as usize, self.0.as_usize())
};
self.0 = result;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn numeric_die_is_clone() {
#[allow(clippy::redundant_clone)]
fn clone<T>(die: T) -> T
where
T: Clone,
{
die.clone()
}
let d4_2 = clone(D4::from(2));
assert_eq!(d4_2.value(), 2);
}
#[test]
fn numeric_die_is_debug() {
let d4_2 = D4::from(2);
assert_eq!(format!("{:?}", d4_2), "D4:2");
}
#[test]
fn numeric_die_is_default() {
let d4_1: D4 = Default::default();
assert_eq!(d4_1.value(), 1);
}
#[test]
fn numeric_die_is_eq() {
let a = D4::from(2);
let b = D4::from(2);
assert_eq!(a, b);
}
#[test]
#[should_panic]
#[allow(unused_must_use)]
fn numeric_die_from_out_of_bounds_minimum() {
D4::from(0);
}
#[test]
#[should_panic]
#[allow(unused_must_use)]
fn numeric_die_from_out_of_bounds_maximum() {
D4::from(5);
}
#[test]
fn numeric_die_step_next() {
let d4_2 = D4::from(2);
let d4_3 = d4_2.next();
assert_eq!(d4_3.value(), 3);
}
#[test]
fn numeric_die_step_next_wrap() {
let d4_4 = D4::from(4);
let d4_1 = d4_4.next();
assert_eq!(d4_1.value(), 1);
}
#[test]
fn numeric_die_step_back() {
let d4_2 = D4::from(2);
let d4_3 = d4_2.back();
assert_eq!(d4_3.value(), 1);
}
#[test]
fn numeric_die_step_back_wrap() {
let d4_1 = D4::from(1);
let d4_4 = d4_1.back();
assert_eq!(d4_4.value(), 4);
}
#[test]
fn numeric_die_step_next_mut() {
let mut d4 = D4::from(2);
d4.next_mut();
assert_eq!(d4.value(), 3);
}
#[test]
fn numeric_die_step_next_mut_wrap() {
let mut d4 = D4::from(4);
d4.next_mut();
assert_eq!(d4.value(), 1);
}
#[test]
fn numeric_die_step_back_mut() {
let mut d4 = D4::from(2);
d4.back_mut();
assert_eq!(d4.value(), 1);
}
#[test]
fn numeric_die_step_back_mut_wrap() {
let mut d4 = D4::from(1);
d4.back_mut();
assert_eq!(d4.value(), 4);
}
#[test]
fn numeric_die_rotate_none() {
let d4_2 = D4::from(2);
let d4_2 = d4_2.rotate(0);
assert_eq!(d4_2.value(), 2);
}
#[test]
fn numeric_die_rotate_mut_none() {
let mut d4_2 = D4::from(2);
d4_2.rotate_mut(0);
assert_eq!(d4_2.value(), 2);
}
#[test]
fn numeric_die_rotate_next() {
let d4_2 = D4::from(2);
let d4_3 = d4_2.rotate(1);
assert_eq!(d4_3.value(), 3);
}
#[test]
fn numeric_die_rotate_next_wrap() {
let d4_2 = D4::from(2);
let d4_1 = d4_2.rotate(3);
assert_eq!(d4_1.value(), 1);
}
#[test]
fn numeric_die_rotate_back() {
let d4_2 = D4::from(2);
let d4_1 = d4_2.rotate(-1);
assert_eq!(d4_1.value(), 1);
}
#[test]
fn numeric_die_rotate_back_wrap() {
let d4_2 = D4::from(2);
let d4_3 = d4_2.rotate(-3);
assert_eq!(d4_3.value(), 3);
}
#[test]
fn numeric_die_rotate_next_mut() {
let mut d4 = D4::from(2);
d4.rotate_mut(3);
assert_eq!(d4.value(), 1);
}
#[test]
fn numeric_die_rotate_next_mut_wrap() {
let mut d4 = D4::from(2);
d4.rotate_mut(-1);
assert_eq!(d4.value(), 1);
}
#[test]
fn numeric_die_rotate_back_mut() {
let mut d4 = D4::from(2);
d4.rotate_mut(-1);
assert_eq!(d4.value(), 1);
}
#[test]
fn numeric_die_rotate_back_mut_wrap() {
let mut d4 = D4::from(2);
d4.rotate_mut(-3);
assert_eq!(d4.value(), 3);
}
}