use crate::traits::{Polyhedral, Rotate, RotateMut, Step, StepMut};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SliceDie<'a, T, const LENGTH: usize> {
position: usize,
elements: &'a [T; LENGTH],
}
impl<'a, T, const LENGTH: usize> SliceDie<'a, T, LENGTH> {
pub const fn new(elements: &'a [T; LENGTH]) -> Self {
assert!(LENGTH > 0);
Self {
elements,
position: 0,
}
}
pub unsafe fn from_unchecked(position: usize, elements: &'a [T; LENGTH]) -> Self {
Self { elements, position }
}
pub fn with_position(elements: &'a [T; LENGTH], position: usize) -> Self {
assert!(position < LENGTH);
Self { elements, position }
}
pub const fn position(&self) -> usize {
self.position
}
pub const fn sides(&self) -> &'a [T; LENGTH] {
self.elements
}
pub const fn value(&self) -> &T {
&self.elements[self.position]
}
}
impl<'a, T, const LENGTH: usize> From<&'a [T; LENGTH]> for SliceDie<'a, T, LENGTH> {
fn from(elements: &'a [T; LENGTH]) -> Self {
Self::new(elements)
}
}
impl<T, const MAXIMUM: usize> Polyhedral for SliceDie<'_, T, MAXIMUM> {
fn sides() -> usize {
MAXIMUM
}
}
impl<'a, T, const MAXIMUM: usize> Step for SliceDie<'a, T, MAXIMUM> {
fn next(&self) -> Self {
let mut next = self.position + 1;
if next == MAXIMUM {
next = 0;
}
unsafe { Self::from_unchecked(next, self.elements) }
}
fn back(&self) -> Self {
let mut next = self.position;
if next == 0 {
next = MAXIMUM - 1;
} else {
next -= 1;
}
unsafe { Self::from_unchecked(next, self.elements) }
}
}
impl<'a, T, const MAXIMUM: usize> StepMut for SliceDie<'a, T, MAXIMUM> {
fn next_mut(&mut self) {
let mut next = self.position + 1;
if next == MAXIMUM {
next = 0;
}
self.position = next;
}
fn back_mut(&mut self) {
let mut next = self.position;
if next == 0 {
next = MAXIMUM - 1;
} else {
next -= 1;
}
self.position = next;
}
}
fn rotate_forward_usize<const MAXIMUM: usize>(position: usize, amount: usize) -> usize {
debug_assert!(amount > 0);
(position + amount) % MAXIMUM
}
fn rotate_backward_usize<const MAXIMUM: usize>(position: usize, amount: i8) -> usize {
let current = position as i8;
let rotated = current + amount;
if rotated >= 0 {
return rotated.unsigned_abs() as usize;
}
let size = MAXIMUM as i8;
let rotated = rotated % size + size;
debug_assert!(rotated >= 0);
rotated as usize
}
impl<'a, T, const MAXIMUM: usize> Rotate for SliceDie<'a, T, MAXIMUM>
where
T: Clone,
{
#[allow(clippy::comparison_chain)]
fn rotate(&self, amount: i8) -> Self {
if amount == 0 {
return self.clone();
}
let position = if amount > 0 {
rotate_forward_usize::<MAXIMUM>(self.position, amount.unsigned_abs() as usize)
} else {
rotate_backward_usize::<MAXIMUM>(self.position, amount)
};
unsafe { Self::from_unchecked(position, self.elements) }
}
}
impl<'a, T, const MAXIMUM: usize> RotateMut for SliceDie<'a, T, MAXIMUM> {
fn rotate_mut(&mut self, amount: i8) {
if amount == 0 {
return;
}
let position = if amount > 0 {
rotate_forward_usize::<MAXIMUM>(self.position, amount.unsigned_abs() as usize)
} else {
rotate_backward_usize::<MAXIMUM>(self.position, amount)
};
self.position = position;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::Polyhedral;
type GradeDie<'a> = SliceDie<'a, char, 5>;
const GRADES: [char; 5] = ['A', 'B', 'C', 'D', 'F'];
#[test]
fn slice_new_ok() {
let die = GradeDie::new(&GRADES);
assert_eq!(die.sides(), &GRADES);
assert_eq!(die.position(), 0);
assert_eq!(die.value(), &'A');
}
#[test]
#[should_panic]
fn slice_new_empty() {
type InvalidDie<'a> = SliceDie<'a, char, 0>;
InvalidDie::new(&[]);
}
#[test]
fn slice_from() {
let a = GradeDie::from(&GRADES);
let b = GradeDie::from(&GRADES);
assert_eq!(a, b);
}
#[test]
fn slice_sides() {
let a = GradeDie::from(&GRADES);
assert_eq!(a.sides(), &GRADES);
}
#[test]
fn slice_next() {
let d = GradeDie::new(&GRADES).next();
assert_eq!(d.position(), 1);
assert_eq!(d.value(), &'B');
}
#[test]
fn slice_next_wrap() {
let d = GradeDie::with_position(&GRADES, 4).next();
assert_eq!(d.position(), 0);
assert_eq!(d.value(), &'A');
}
#[test]
fn slice_next_mut() {
let mut d = GradeDie::new(&GRADES);
d.next_mut();
assert_eq!(d.position(), 1);
assert_eq!(d.value(), &'B');
}
#[test]
fn slice_next_mut_wrap() {
let mut d = GradeDie::with_position(&GRADES, 4);
d.next_mut();
assert_eq!(d.position(), 0);
assert_eq!(d.value(), &'A');
}
#[test]
fn slice_back() {
let d = GradeDie::with_position(&GRADES, 4).back();
assert_eq!(d.position(), 3);
assert_eq!(d.value(), &'D');
}
#[test]
fn slice_back_wrap() {
let d = GradeDie::new(&GRADES).back();
assert_eq!(d.position(), 4);
assert_eq!(d.value(), &'F');
}
#[test]
fn slice_back_mut() {
let mut d = GradeDie::with_position(&GRADES, 4);
d.back_mut();
assert_eq!(d.position(), 3);
assert_eq!(d.value(), &'D');
}
#[test]
fn slice_back_mut_wrap() {
let mut d = GradeDie::new(&GRADES);
d.back_mut();
assert_eq!(d.position(), 4);
assert_eq!(d.value(), &'F');
}
#[test]
fn slice_polyhedral_sides() {
let d = GradeDie::new(&GRADES);
fn get_sides<P: Polyhedral>(_: P) -> usize {
P::sides()
}
assert_eq!(get_sides(d), GRADES.len());
}
#[test]
fn slice_rotate_none() {
let d = GradeDie::new(&GRADES);
let r = d.rotate(0);
assert_eq!(r.value(), &'A');
}
#[test]
fn slice_rotate_mut_none() {
let mut d = GradeDie::new(&GRADES);
d.rotate_mut(0);
assert_eq!(d.value(), &'A');
}
#[test]
fn slice_rotate_next() {
let d = GradeDie::new(&GRADES);
let r = d.rotate(1);
assert_eq!(r.value(), &'B');
}
#[test]
fn numeric_die_rotate_next_wrap() {
let d = GradeDie::with_position(&GRADES, 4);
let r = d.rotate(1);
assert_eq!(r.value(), &'A');
}
#[test]
fn numeric_die_rotate_back() {
let d = GradeDie::with_position(&GRADES, 1);
let r = d.rotate(-1);
assert_eq!(r.value(), &'A');
}
#[test]
fn numeric_die_rotate_back_wrap() {
let d = GradeDie::new(&GRADES);
let r = d.rotate(-1);
assert_eq!(r.value(), &'F');
}
#[test]
fn numeric_die_rotate_next_mut() {
let mut d = GradeDie::new(&GRADES);
d.rotate_mut(1);
assert_eq!(d.value(), &'B');
}
#[test]
fn numeric_die_rotate_next_mut_wrap() {
let mut d = GradeDie::with_position(&GRADES, 4);
d.rotate_mut(1);
assert_eq!(d.value(), &'A');
}
#[test]
fn numeric_die_rotate_back_mut() {
let mut d = GradeDie::with_position(&GRADES, 1);
d.rotate_mut(-1);
assert_eq!(d.value(), &'A');
}
#[test]
fn numeric_die_rotate_back_mut_wrap() {
let mut d = GradeDie::new(&GRADES);
d.rotate_mut(-1);
assert_eq!(d.value(), &'F');
}
}