use std::{array, fmt};
use std::fmt::Formatter;
use std::num::NonZeroU64;
use std::ops::Neg;
use crate::Rgb;
#[cfg(feature = "fluent")]
use crate::{FluentBundle, message};
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[repr(u8)]
pub enum Snack {
Apple = 0,
Pear = 1,
Berries = 2,
Plum = 3,
Fruit = 4,
Pineapple = 5
}
impl Snack {
pub const VALUES: [Snack; 6] = [
Snack::Apple,
Snack::Pear,
Snack::Berries,
Snack::Plum,
Snack::Fruit,
Snack::Pineapple
];
#[must_use]
#[inline]
pub const fn short_name(self) -> &'static str {
match self {
Snack::Apple => "apple",
Snack::Pear => "pear",
Snack::Berries => "berries",
Snack::Plum => "plum",
Snack::Fruit => "fruit",
Snack::Pineapple => "pineapple"
}
}
#[cfg(feature = "fluent")]
#[cfg_attr(docsrs, doc(cfg(feature = "fluent")))]
pub fn quantified_name(self, bundle: &FluentBundle, quantity: u32) -> String {
message!(bundle, self.short_name(), { "quantity" = quantity })
}
#[must_use]
#[inline]
pub const fn effect(self) -> (i8, i8, i8) {
match self {
Snack::Apple => ( 5, -5, -5),
Snack::Pear => (-5, 5, -5),
Snack::Berries => (-5, -5, 5),
Snack::Plum => (-5, 5, 5),
Snack::Fruit => ( 5, -5, 5),
Snack::Pineapple => ( 5, 5, -5)
}
}
#[must_use]
#[inline]
pub const fn alter(self, color: Rgb) -> Option<Rgb> {
let (r, g, b) = self.effect();
color.checked_add_signed(r, g, b)
}
}
impl Neg for Snack {
type Output = Snack;
#[inline]
fn neg(self) -> Self::Output {
match self {
Snack::Apple => Snack::Plum,
Snack::Pear => Snack::Fruit,
Snack::Berries => Snack::Pineapple,
Snack::Plum => Snack::Apple,
Snack::Fruit => Snack::Pear,
Snack::Pineapple => Snack::Berries
}
}
}
#[test]
fn neg() {
for snack in Snack::VALUES {
let a = snack.effect();
let b = snack.neg().effect();
assert_eq!(a.0 + b.0, 0);
assert_eq!(a.1 + b.1, 0);
assert_eq!(a.2 + b.2, 0);
}
}
#[repr(transparent)]
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct SnackList(NonZeroU64);
impl SnackList {
#[must_use]
#[inline]
pub const fn new() -> SnackList {
SnackList(unsafe { NonZeroU64::new_unchecked(1 << 63) })
}
#[must_use]
#[inline]
pub const fn get(&self, snack: Snack) -> u8 {
((self.0.get() >> (8 * snack as usize)) & 0xFF) as u8
}
#[inline]
pub fn set(&mut self, snack: Snack, value: u8) {
self.0 = unsafe { NonZeroU64::new_unchecked(
(self.0.get() & !(0xFF_u64 << (8 * snack as usize))) | ((value as u64) << (8 * snack as usize))
) };
}
#[inline]
pub fn add(&mut self, snack: Snack, n: u8) {
self.set(snack, self.get(snack) + n);
}
#[must_use]
#[inline]
pub const fn is_empty(&self) -> bool {
self.0.get() == SnackList::new().0.get()
}
#[must_use]
pub fn sum(&self) -> u64 {
let mut me = self.0.get();
let mut acc = 0;
for _ in 0..7 {
acc += me & 0xFF;
me >>= 8;
}
acc
}
#[must_use]
pub fn kinds(&self) -> u8 {
let mut me = self.0.get();
let mut count = 0;
for _ in 0..7 {
if (me & 0xFF) != 0 {
count += 1;
}
me >>= 8;
}
count
}
}
impl From<&[Snack]> for SnackList {
fn from(snacks: &[Snack]) -> SnackList {
let mut sl = SnackList::new().0.get();
for snack in snacks {
sl += 1 << (8 * *snack as usize);
}
SnackList(NonZeroU64::new(sl).expect("integer overflow"))
}
}
impl From<SnackList> for [(Snack, u8); 6] {
fn from(value: SnackList) -> [(Snack, u8); 6] {
[
(Snack::Apple, ((value.0.get() ) & 0xFF) as u8),
(Snack::Pear, ((value.0.get() >> 8) & 0xFF) as u8),
(Snack::Berries, ((value.0.get() >> 16) & 0xFF) as u8),
(Snack::Plum, ((value.0.get() >> 24) & 0xFF) as u8),
(Snack::Fruit, ((value.0.get() >> 32) & 0xFF) as u8),
(Snack::Pineapple, ((value.0.get() >> 40) & 0xFF) as u8)
]
}
}
impl IntoIterator for SnackList {
type Item = (Snack, u8);
type IntoIter = array::IntoIter<Self::Item, 6>;
fn into_iter(self) -> Self::IntoIter {
<SnackList as Into<[Self::Item; 6]>>::into(self).into_iter()
}
}
impl Default for SnackList {
#[inline]
fn default() -> SnackList {
SnackList::new()
}
}
impl fmt::Debug for SnackList {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut dm = f.debug_map();
for snack in Snack::VALUES {
dm.entry(&snack, &self.get(snack));
}
dm.finish()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn snacklist_get_set() {
let mut list = SnackList::new();
assert_eq!(list.0.get(), 1 << 63);
assert!(list.is_empty());
assert_eq!(list.sum(), 0);
assert_eq!(list.kinds(), 0);
list.set(Snack::Pear, 210);
list.add(Snack::Pear, 12);
assert_ne!(list.0.get(), 1 << 63);
assert_eq!(list.get(Snack::Pear), 222);
assert!(!list.is_empty());
assert_eq!(list.sum(), 222);
assert_eq!(list.kinds(), 1);
list.set(Snack::Pear, 0);
assert_eq!(list.0.get(), 1 << 63);
assert!(list.is_empty());
assert_eq!(list.sum(), 0);
assert_eq!(list.kinds(), 0);
}
#[test]
fn snacklist_into_array() {
let mut list = SnackList::new();
list.set(Snack::Pear, 1);
list.set(Snack::Pineapple, 2);
list.set(Snack::Fruit, 3);
list.set(Snack::Plum, 4);
list.set(Snack::Berries, 5);
list.set(Snack::Apple, 6);
assert_eq!(<SnackList as Into<[(Snack, u8); 6]>>::into(list), [
(Snack::Apple, 6),
(Snack::Pear, 1),
(Snack::Berries, 5),
(Snack::Plum, 4),
(Snack::Fruit, 3),
(Snack::Pineapple, 2)
]);
assert!(!list.is_empty());
assert_eq!(list.sum(), 21);
assert_eq!(list.kinds(), 6);
for snack in Snack::VALUES {
assert!((snack as u8) < 8);
}
}
}