diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..2fe067f --- /dev/null +++ b/src/app.rs @@ -0,0 +1,95 @@ +use crate::channel::{ButtonReceiver, FrameSender}; + + + +#[derive(Debug, Clone, Copy)] +pub enum AppError +{ + Unknown +} + +impl core::fmt::Display for AppError +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result + { + match self + { + AppError::Unknown => + { + write!(f, "Application caused an unknown error.") + } + } + } +} + +//#[cfg(not(feature = "std"))] +impl core::error::Error for AppError {} + + + +#[repr(u8)] +pub enum AppId +{ + Menu = 0, + Snake = 1, + Badge = 2, + Nfc = 3 +} + +impl From for u8 +{ + fn from(value: AppId) -> u8 + { + value as u8 + } +} + +impl From for usize +{ + fn from(value: AppId) -> usize + { + value as usize + } +} + +impl core::convert::TryFrom for AppId +{ + type Error = (); + + fn try_from(value: u8) -> Result + { + match value + { + 0 => Ok(AppId::Menu), + 1 => Ok(AppId::Snake), + 2 => Ok(AppId::Badge), + 3 => Ok(AppId::Nfc), + _ => Err(()) + } + } +} + +impl core::convert::TryFrom for AppId +{ + type Error = (); + + fn try_from(value: usize) -> Result + { + match value + { + 0 => Ok(AppId::Menu), + 1 => Ok(AppId::Snake), + 2 => Ok(AppId::Badge), + 3 => Ok(AppId::Nfc), + _ => Err(()) + } + } +} + + +pub trait App +{ + fn new(frame_output: FrameSender, button_input: ButtonReceiver) -> Self; + + async fn run(&mut self) -> Result; +} diff --git a/src/badge.rs b/src/badge.rs new file mode 100644 index 0000000..c05d0f7 --- /dev/null +++ b/src/badge.rs @@ -0,0 +1,4 @@ +mod badge; +mod renderer; + +pub use crate::badge::badge::Badge; diff --git a/src/badge/badge.rs b/src/badge/badge.rs new file mode 100644 index 0000000..0099130 --- /dev/null +++ b/src/badge/badge.rs @@ -0,0 +1,275 @@ +use embassy_time::Timer; + +use crate::app::{App, AppError, AppId}; +use crate::badge::renderer::{BadgeRenderer, Icon}; +use crate::bounded::{BoundedU8, WrappedU8}; +use crate::channel::{ButtonReceiver, FrameSender}; +use crate::image::{Grayscale, Image, MicrobitImage}; +use crate::microbit::Button; +use crate::renderer::Renderer; +use crate::string::String; + + + +const MAX_NAME: usize = 128; +const SPACE_SIZE: usize = 2; + + +type AnimationImage = + Grayscale<{ MicrobitImage::WIDTH * 2 + SPACE_SIZE }, 5, 11, 4>; + + + +pub struct Badge +{ + renderer: BadgeRenderer, + button_input: ButtonReceiver, + text_images: [MicrobitImage; 6], + animation_image: AnimationImage, + text: String, + current_letter: WrappedU8<0, 5>, + current_col: WrappedU8<0, 7> +} + + + +impl Badge +{ + fn load_text_images(&mut self) + { + self.text_images = [MicrobitImage::new(), + [[BoundedU8::new(9), BoundedU8::new(9), BoundedU8::new(9), BoundedU8::new(9), BoundedU8::new(9)], + [BoundedU8::new(0), BoundedU8::new(0), BoundedU8::new(9), BoundedU8::new(0), BoundedU8::new(0)], + [BoundedU8::new(0), BoundedU8::new(0), BoundedU8::new(9), BoundedU8::new(0), BoundedU8::new(0)], + [BoundedU8::new(9), BoundedU8::new(0), BoundedU8::new(9), BoundedU8::new(0), BoundedU8::new(0)], + [BoundedU8::new(0), BoundedU8::new(9), BoundedU8::new(0), BoundedU8::new(0), BoundedU8::new(0)]].into(), + + [[BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(0)], + [BoundedU8::new(0), + BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(9), + BoundedU8::new(0)], + [BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(9)], + [BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(9)], + [BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(9)]].into(), + [[BoundedU8::new(0), + BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(9)], + [BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(0)], + [BoundedU8::new(0), + BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(0)], + [BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(9)], + [BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(0)]].into(), + [[BoundedU8::new(0), + BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(0)], + [BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(9)], + [BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(9)], + [BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(9)], + [BoundedU8::new(0), + BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(0)]].into(), + [[BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(9)], + [BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(9)], + [BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(9)], + [BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(9), + BoundedU8::new(9)], + [BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(9)]].into()]; + } + + fn animate_name(&mut self) -> bool + { + let mut pause: bool = false; + + // Clear the image. + self.renderer.clear(); + + if self.current_col.get() == 0 + { + pause = true; + } + + // Now copy the sub image of the larger animation image. + for row in 0..MicrobitImage::HEIGHT + { + for col in 0..MicrobitImage::WIDTH + { + let adjusted_col = col + (self.current_col.get() as usize); + let level = self.animation_image + .get_pixel(adjusted_col.into(), row.into()); + + self.renderer.set_level(col.into(), row.into(), *level); + } + } + + // If we have finished + if (self.current_col.get() as usize) == + (AnimationImage::WIDTH - MicrobitImage::WIDTH) + { + self.current_letter += 1; + self.load_animation_image(self.current_letter.get() as usize, + (self.current_letter + 1).get() as usize); + } + + self.current_col += 1; + + pause + } + + fn load_animation_image(&mut self, curr_char: usize, next_char: usize) + { + self.animation_image.clear(); + + let left: &MicrobitImage = &self.text_images[curr_char]; + let right: &MicrobitImage = &self.text_images[next_char]; + + // Now write the animation image. + for row in 0..MicrobitImage::WIDTH + { + for col in 0..MicrobitImage::HEIGHT + { + let left_level = left.get_pixel(col.into(), row.into()); + let right_level = right.get_pixel(col.into(), row.into()); + + self.animation_image + .set_pixel(col.into(), row.into(), *left_level); + self.animation_image.set_pixel((col + + MicrobitImage::WIDTH + + SPACE_SIZE) + .into(), + row.into(), + *right_level); + } + } + } +} + +impl App for Badge +{ + fn new(frame_output: FrameSender, button_input: ButtonReceiver) -> Self + { + // We create an image large enough to hold two + // letters and a space. Then we just cycle through + // each step on what we draw. The run timer will + // ultimately decide the animations speed. + // + // 0 1 1 1 1 0 0 0 1 1 1 0 + // 1 0 0 0 0 0 0 1 0 0 0 1 + // 0 1 1 1 0 -> 0 0 -> 1 0 0 0 1 + // 0 0 0 0 1 0 0 1 0 0 0 1 + // 1 1 1 1 0 0 0 0 1 1 1 0 + + let mut badge = Self { renderer: BadgeRenderer::new(frame_output), + button_input, + text_images: [MicrobitImage::new(); 6], + animation_image: AnimationImage::new(), + text: String::from_str("Jason"), + current_letter: 0.into(), + current_col: 0.into() }; + badge.load_text_images(); + badge.load_animation_image(0, 1); + + badge + } + + async fn run(&mut self) -> Result + { + loop + { + // Get the button input. + if let Ok(button) = self.button_input.try_receive() + { + match button + { + Button::A | + Button::B | + Button::Start => + { + return Ok(AppId::Menu); + } + } + } + + // Draw the animation of a scrolling name. + if self.animate_name() + { + Timer::after_micros(150000).await; + } + + // Swap the frame buffer so it can be drawn. + self.renderer.swap_buffers().await; + + // Sleep enough to acheive 80Hz. + Timer::after_micros(150000).await; + } + } +} diff --git a/src/badge/renderer.rs b/src/badge/renderer.rs new file mode 100644 index 0000000..30c97b2 --- /dev/null +++ b/src/badge/renderer.rs @@ -0,0 +1,83 @@ +use crate::bounded::BoundedUsize; +use crate::channel::FrameSender; +use crate::image::{Level, MicrobitImage}; +use crate::renderer::{Col, Renderer, Row}; + + + +pub type Icon = MicrobitImage; + + + +/// +pub struct BadgeRenderer +{ + sender: FrameSender, + frame: MicrobitImage +} + + +impl BadgeRenderer +{ + pub fn clear(&mut self) + { + self.frame.clear(); + } + + pub fn turn_on(&mut self, x: Col, y: Row) + { + self.frame.set_pixel(x, y, Level::MAX.into()); + } + + pub fn set_level(&mut self, x: Col, y: Row, level: Level) + { + self.frame.set_pixel(x, y, level); + } + + pub fn turn_off(&mut self, x: Col, y: Row) + { + self.frame.set_pixel(x, y, Level::MIN.into()); + } + + pub fn draw_icon(&mut self, icon: Icon) + { + self.frame = icon; + } +} + +impl Renderer for BadgeRenderer +{ + fn new(sender: FrameSender) -> Self + { + Self { sender, + frame: MicrobitImage::new() } + } + + async fn swap_buffers(&mut self) + { + self.sender.send(self.frame.clone()).await; + } +} + +// impl crate::snake::Renderer for Renderer +// { +// fn clear(&mut self) +// { +// self.clear(); +// } +// +// fn draw_food(&mut self, position: (usize, usize)) +// { +// self.turn_on(position.0.into(), position.1.into()); +// } +// +// fn draw_snake(&mut self, position: &[(usize, usize)]) +// { +// self.set_level(position[0].0.into(), position[0].1.into(), 5u8.into()); +// +// for segment in position.iter().skip(1) +// { +// self.set_level(segment.0.into(), segment.1.into(), 2u8.into()); +// } +// } +// } diff --git a/src/bounded.rs b/src/bounded.rs new file mode 100644 index 0000000..f758980 --- /dev/null +++ b/src/bounded.rs @@ -0,0 +1,2461 @@ +use core::fmt::Display; +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Sub, SubAssign}; + + + +/// +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BoundedError +{ + OutOfBounds + { + val: T, min: T, max: T + } +} + +impl core::fmt::Display for BoundedError +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result + { + match self + { + BoundedError::OutOfBounds { val, min, max } => + { + write!(f, "value {} is out of bounds [{}, {}]", val, min, max) + } + } + } +} + +//#[cfg(not(feature = "std"))] +impl core::error::Error + for BoundedError +{ +} + +//#[cfg(feature = "std")] +// impl std::error::Error for +// BoundedError {} + + + +/// Defines an additive identity element for `Self`. +pub trait Zero: Sized + Add +{ + /// Returns the additive identity element of `Self`, `0`. + /// + /// # Laws + /// + /// ```text + /// a + 0 = a ∀ a ∈ Self + /// 0 + a = a ∀ a ∈ Self + /// ``` + /// + /// # Purity + /// + /// This function should return the same result at all times regardless of + /// external mutable state, for example values stored in TLS or in + /// `static mut`s. + fn zero() -> Self; +} + + + +macro_rules! zero_impl { + ($varType:ty, $val:expr) => { + impl Zero for $varType + { + fn zero() -> $varType + { + $val + } + } + }; +} + + + +zero_impl!(u8, 0u8); +zero_impl!(u16, 0u16); +zero_impl!(u32, 0u32); +zero_impl!(u64, 0u64); +zero_impl!(usize, 0usize); + +zero_impl!(i8, 0i8); +zero_impl!(i16, 0i16); +zero_impl!(i32, 0i32); +zero_impl!(i64, 0i64); +zero_impl!(isize, 0isize); + +zero_impl!(f32, 0.0f32); +zero_impl!(f64, 0.0f64); + + + +/// Primitive types that have upper and lower bounds. +pub trait Bound +{ + /// The minimum value for this type. + const MIN: Self; + + /// The maximum value for this type. + const MAX: Self; +} + + + +/// A macro for making implementation of +/// the Bounded trait easier. +macro_rules! bound_trait_impl { + ($T:ty, $minVal:expr, $maxVal:expr) => { + impl Bound for $T + { + const MAX: $T = $maxVal; + const MIN: $T = $minVal; + } + }; +} + + +// Implement the Bounded for all the primitive types. +bound_trait_impl!(u8, 0u8, !0u8); +bound_trait_impl!(u16, 0u16, !0u16); +bound_trait_impl!(u32, 0u32, !0u32); +bound_trait_impl!(u64, 0u64, !0u64); +bound_trait_impl!(usize, 0usize, !0usize); + +bound_trait_impl!(i8, + (!0i8 ^ (!0u8 >> 1u8) as i8), + !(!0i8 ^ (!0u8 >> 1u8) as i8)); +bound_trait_impl!(i16, + (!0i16 ^ (!0u16 >> 1u16) as i16), + !(!0i16 ^ (!0u16 >> 1u16) as i16)); +bound_trait_impl!(i32, + (!0i32 ^ (!0u32 >> 1u32) as i32), + !(!0i32 ^ (!0u32 >> 1u32) as i32)); +bound_trait_impl!(i64, + (!0i64 ^ (!0u64 >> 1u64) as i64), + !(!0i64 ^ (!0u64 >> 1u64) as i64)); +bound_trait_impl!(isize, + (!0isize ^ (!0usize >> 1usize) as isize), + !(!0isize ^ (!0usize >> 1usize) as isize)); + + +/// Bounded will panic if its value goes out of bounds. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Bounded +{ + value: T, + lower: T, + upper: T +} + +impl Bounded where T: Bound + PartialOrd + Copy +{ + pub fn new(value: T, lower: T, upper: T) -> Self + { + if !(value >= lower && value <= upper) + { + panic!("Bounded value out of bounds."); + } + else + { + Self { value, + lower, + upper } + } + } + + pub fn get(&self) -> T + { + self.value + } + + pub const fn min(&self) -> T + { + self.lower + } + + pub const fn max(&self) -> T + { + self.upper + } +} + +impl Default for Bounded where T: Bound + Default +{ + fn default() -> Self + { + Self { value: T::default(), + lower: T::MIN, + upper: T::MAX } + } +} + +impl From for Bounded where T: Bound + Display + PartialOrd +{ + fn from(value: T) -> Self + { + assert!(value >= T::MIN && value <= T::MAX, + "value {} is out of bounds [{}, {}]", + value, + T::MIN, + T::MAX); + Self { value, + lower: T::MIN, + upper: T::MAX } + } +} + +// Arithmetic implementations panic on out-of-bounds result +impl Add for Bounded where T: Bound + PartialOrd + Copy + Add +{ + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output + { + Self::new(self.value + rhs.value, self.lower, self.upper) + } +} + +impl Add for Bounded + where T: Bound + PartialOrd + Copy + Add +{ + type Output = Self; + + fn add(self, rhs: T) -> Self::Output + { + Self::new(self.value + rhs, self.lower, self.upper) + } +} + +impl Sub for Bounded where T: Bound + PartialOrd + Copy + Sub +{ + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output + { + Self::new(self.value - rhs.value, self.lower, self.upper) + } +} + +impl Sub for Bounded + where T: Bound + PartialOrd + Copy + Sub +{ + type Output = Self; + + fn sub(self, rhs: T) -> Self::Output + { + Self::new(self.value - rhs, self.lower, self.upper) + } +} + +impl Mul for Bounded where T: Bound + PartialOrd + Copy + Mul +{ + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output + { + Self::new(self.value * rhs.value, self.lower, self.upper) + } +} + +impl Mul for Bounded + where T: Bound + PartialOrd + Copy + Mul +{ + type Output = Self; + + fn mul(self, rhs: T) -> Self::Output + { + Self::new(self.value * rhs, self.lower, self.upper) + } +} + +impl Div for Bounded + where T: Bound + PartialOrd + Copy + Div + Zero +{ + type Output = Self; + + fn div(self, rhs: Self) -> Self::Output + { + assert!(rhs.value != T::zero(), "Division by zero."); + Self::new(self.value / rhs.value, self.lower, self.upper) + } +} + +impl Div for Bounded + where T: Bound + PartialOrd + Copy + Div + Zero +{ + type Output = Self; + + fn div(self, rhs: T) -> Self::Output + { + assert!(rhs != T::zero(), "Division by zero."); + Self::new(self.value / rhs, self.lower, self.upper) + } +} + +impl Rem for Bounded + where T: Bound + PartialOrd + Copy + Rem + Zero +{ + type Output = Self; + + fn rem(self, rhs: Self) -> Self::Output + { + assert!(rhs.value != T::zero(), "Division by zero."); + Self::new(self.value % rhs.value, self.lower, self.upper) + } +} + +impl Rem for Bounded + where T: Bound + PartialOrd + Copy + Rem + Zero +{ + type Output = Self; + + fn rem(self, rhs: T) -> Self::Output + { + assert!(rhs != T::zero(), "Division by zero."); + Self::new(self.value % rhs, self.lower, self.upper) + } +} + +impl AddAssign for Bounded + where T: Bound + PartialOrd + Copy + Display + Add +{ + fn add_assign(&mut self, rhs: Self) + { + let result = self.value + rhs.value; + + assert!(result >= self.lower && result <= self.upper, + "AddAssign: Result {} out of bounds [{}, {}].", + result, + self.lower, + self.upper); + + self.value = result; + } +} + +impl AddAssign for Bounded + where T: Bound + PartialOrd + Copy + Display + Add +{ + fn add_assign(&mut self, rhs: T) + { + let result = self.value + rhs; + + assert!(result >= self.lower && result <= self.upper, + "AddAssign: Result {} out of bounds [{}, {}].", + result, + self.lower, + self.upper); + + self.value = result; + } +} + +impl SubAssign for Bounded + where T: Bound + PartialOrd + Copy + Display + Sub +{ + fn sub_assign(&mut self, rhs: Self) + { + let result = self.value - rhs.value; + + assert!(result >= self.lower && result <= self.upper, + "SubAssign: Result {} out of bounds [{}, {}].", + result, + self.lower, + self.upper); + + self.value = result; + } +} + +impl SubAssign for Bounded + where T: Bound + PartialOrd + Copy + Display + Sub +{ + fn sub_assign(&mut self, rhs: T) + { + let result = self.value - rhs; + + assert!(result >= self.lower && result <= self.upper, + "SubAssign: Result {} out of bounds [{}, {}].", + result, + self.lower, + self.upper); + + self.value = result; + } +} + +impl MulAssign for Bounded + where T: Bound + PartialOrd + Copy + Display + Mul +{ + fn mul_assign(&mut self, rhs: Self) + { + let result = self.value * rhs.value; + + assert!(result >= self.lower && result <= self.upper, + "MulAssign: Result {} out of bounds [{}, {}].", + result, + self.lower, + self.upper); + + self.value = result; + } +} + +impl MulAssign for Bounded + where T: Bound + PartialOrd + Copy + Display + Mul +{ + fn mul_assign(&mut self, rhs: T) + { + let result = self.value * rhs; + + assert!(result >= self.lower && result <= self.upper, + "MulAssign: Result {} out of bounds [{}, {}].", + result, + self.lower, + self.upper); + + self.value = result; + } +} + +impl DivAssign for Bounded + where T: Bound + PartialOrd + Copy + Display + Div + Zero +{ + fn div_assign(&mut self, rhs: Self) + { + assert!(rhs.value != T::zero(), "Division by zero."); + + let result = self.value / rhs.value; + + assert!(result >= self.lower && result <= self.upper, + "DivAssign: Result {} out of bounds [{}, {}].", + result, + self.lower, + self.upper); + + self.value = result; + } +} + +impl DivAssign for Bounded + where T: Bound + PartialOrd + Copy + Display + Div + Zero +{ + fn div_assign(&mut self, rhs: T) + { + assert!(rhs != T::zero(), "Division by zero."); + + let result = self.value / rhs; + + assert!(result >= self.lower && result <= self.upper, + "DivAssign: Result {} out of bounds [{}, {}].", + result, + self.lower, + self.upper); + + self.value = result; + } +} + +impl RemAssign for Bounded + where T: Bound + PartialOrd + Copy + Display + Rem + Zero +{ + fn rem_assign(&mut self, rhs: Self) + { + assert!(rhs.value != T::zero(), "Division by zero."); + + let result = self.value % rhs.value; + + assert!(result >= self.lower && result <= self.upper, + "RemAssign: Result {} out of bounds [{}, {}].", + result, + self.lower, + self.upper); + + self.value = result; + } +} + +impl RemAssign for Bounded + where T: Bound + PartialOrd + Copy + Display + Rem + Zero +{ + fn rem_assign(&mut self, rhs: T) + { + assert!(rhs != T::zero(), "Division by zero."); + + let result = self.value % rhs; + + assert!(result >= self.lower && result <= self.upper, + "RemAssign: Result {} out of bounds [{}, {}].", + result, + self.lower, + self.upper); + + self.value = result; + } +} + + + +macro_rules! define_bounded { + ($structName:ident, $T:ty) => { + /// $structName will panic if its value goes out of bounds. + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + #[repr(transparent)] + pub struct $structName + { + value: $T + } + + impl $structName + { + pub const MAX: $T = U; + pub const MIN: $T = L; + + pub const fn new(value: $T) -> Self + { + if !(value >= L && value <= U) + { + panic!("$structName value out of bounds."); + } + + Self { value } + } + + pub fn get(&self) -> $T + { + self.value + } + + pub const fn min() -> $T + { + L + } + + pub const fn max() -> $T + { + U + } + } + + impl Default for $structName + { + fn default() -> Self + { + Self { value: L } + } + } + + impl From<$T> for $structName + { + fn from(value: $T) -> Self + { + assert!(value >= L && value <= U, + "value {} is out of bounds [{}, {}]", + value, + L, + U); + Self { value } + } + } + + // Arithmetic implementations panic on out-of-bounds result + impl Add for $structName + { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output + { + Self::new(self.value + rhs.value) + } + } + + impl Add<$T> for $structName + { + type Output = Self; + + fn add(self, rhs: $T) -> Self::Output + { + Self::new(self.value + rhs) + } + } + + impl Sub for $structName + { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output + { + Self::new(self.value - rhs.value) + } + } + + impl Sub<$T> for $structName + { + type Output = Self; + + fn sub(self, rhs: $T) -> Self::Output + { + Self::new(self.value - rhs) + } + } + + impl Mul for $structName + { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output + { + Self::new(self.value * rhs.value) + } + } + + impl Mul<$T> for $structName + { + type Output = Self; + + fn mul(self, rhs: $T) -> Self::Output + { + Self::new(self.value * rhs) + } + } + + impl Div for $structName + { + type Output = Self; + + fn div(self, rhs: Self) -> Self::Output + { + assert!(rhs.value != 0, "Division by zero."); + Self::new(self.value / rhs.value) + } + } + + impl Div<$T> for $structName + { + type Output = Self; + + fn div(self, rhs: $T) -> Self::Output + { + assert!(rhs != 0, "Division by zero."); + Self::new(self.value / rhs) + } + } + + impl Rem for $structName + { + type Output = Self; + + fn rem(self, rhs: Self) -> Self::Output + { + assert!(rhs.value != 0, "Division by zero."); + Self::new(self.value % rhs.value) + } + } + + impl Rem<$T> for $structName + { + type Output = Self; + + fn rem(self, rhs: $T) -> Self::Output + { + assert!(rhs != 0, "Division by zero."); + Self::new(self.value % rhs) + } + } + + impl AddAssign for $structName + { + fn add_assign(&mut self, rhs: Self) + { + let result = self.value + rhs.value; + + assert!(result >= L && result <= U, + "AddAssign: Result {} out of bounds [{}, {}].", + result, + L, + U); + + self.value = result; + } + } + + impl AddAssign<$T> for $structName + { + fn add_assign(&mut self, rhs: $T) + { + let result = self.value + rhs; + + assert!(result >= L && result <= U, + "AddAssign: Result {} out of bounds [{}, {}].", + result, + L, + U); + + self.value = result; + } + } + + impl SubAssign for $structName + { + fn sub_assign(&mut self, rhs: Self) + { + let result = self.value - rhs.value; + + assert!(result >= L && result <= U, + "SubAssign: Result {} out of bounds [{}, {}].", + result, + L, + U); + + self.value = result; + } + } + + impl SubAssign<$T> for $structName + { + fn sub_assign(&mut self, rhs: $T) + { + let result = self.value - rhs; + + assert!(result >= L && result <= U, + "SubAssign: Result {} out of bounds [{}, {}].", + result, + L, + U); + + self.value = result; + } + } + + impl MulAssign for $structName + { + fn mul_assign(&mut self, rhs: Self) + { + let result = self.value * rhs.value; + + assert!(result >= L && result <= U, + "MulAssign: Result {} out of bounds [{}, {}].", + result, + L, + U); + + self.value = result; + } + } + + impl MulAssign<$T> for $structName + { + fn mul_assign(&mut self, rhs: $T) + { + let result = self.value * rhs; + + assert!(result >= L && result <= U, + "MulAssign: Result {} out of bounds [{}, {}].", + result, + L, + U); + + self.value = result; + } + } + + impl DivAssign for $structName + { + fn div_assign(&mut self, rhs: Self) + { + assert!(rhs.value != 0, "Division by zero."); + + let result = self.value / rhs.value; + + assert!(result >= L && result <= U, + "DivAssign: Result {} out of bounds [{}, {}].", + result, + L, + U); + + self.value = result; + } + } + + impl DivAssign<$T> for $structName + { + fn div_assign(&mut self, rhs: $T) + { + assert!(rhs != 0, "Division by zero."); + + let result = self.value / rhs; + + assert!(result >= L && result <= U, + "DivAssign: Result {} out of bounds [{}, {}].", + result, + L, + U); + + self.value = result; + } + } + + impl RemAssign for $structName + { + fn rem_assign(&mut self, rhs: Self) + { + assert!(rhs.value != 0, "Division by zero."); + + let result = self.value % rhs.value; + + assert!(result >= L && result <= U, + "RemAssign: Result {} out of bounds [{}, {}].", + result, + L, + U); + + self.value = result; + } + } + + impl RemAssign<$T> for $structName + { + fn rem_assign(&mut self, rhs: $T) + { + assert!(rhs != 0, "Division by zero."); + + let result = self.value % rhs; + + assert!(result >= L && result <= U, + "RemAssign: Result {} out of bounds [{}, {}].", + result, + L, + U); + + self.value = result; + } + } + + impl core::fmt::Display for $structName + { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result + { + write!(f, "{} - [{}, {}]", self.value, L, U) + } + } + + impl defmt::Format for $structName + { + fn format(&self, f: defmt::Formatter) + { + // format the bitfields of the register as struct fields + defmt::write!(f, "{} - [{}, {}]", self.value, L, U) + } + } + }; +} + +define_bounded!(BoundedI8, i8); +define_bounded!(BoundedI16, i16); +define_bounded!(BoundedI32, i32); +define_bounded!(BoundedI64, i64); +define_bounded!(BoundedIsize, isize); + +define_bounded!(BoundedU8, u8); +define_bounded!(BoundedU16, u16); +define_bounded!(BoundedU32, u32); +define_bounded!(BoundedU64, u64); +define_bounded!(BoundedUsize, usize); + + + +macro_rules! define_clamped { + ($structName:ident, $T:ty) => { + /// ClampedU8 - clamps arithmetic results into bounds + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + #[repr(transparent)] + pub struct $structName + { + value: $T + } + + impl $structName + { + pub const MAX: $T = U; + pub const MIN: $T = L; + + pub fn new(value: $T) -> Self + { + let clamped = value.clamp(L, U); + Self { value: clamped } + } + + pub fn get(&self) -> $T + { + self.value + } + + pub const fn min() -> $T + { + L + } + + pub const fn max() -> $T + { + U + } + } + + impl Default for $structName + { + fn default() -> Self + { + Self::new(L) + } + } + + impl From<$T> for $structName + { + fn from(value: $T) -> Self + { + Self::new(value) + } + } + + impl Add for $structName + { + type Output = Self; + + fn add(self, rhs: Self) -> Self + { + Self::new(self.value.saturating_add(rhs.value)) + } + } + + impl Add<$T> for $structName + { + type Output = Self; + + fn add(self, rhs: $T) -> Self + { + Self::new(self.value.saturating_add(rhs)) + } + } + + impl Sub for $structName + { + type Output = Self; + + fn sub(self, rhs: Self) -> Self + { + Self::new(self.value.saturating_sub(rhs.value)) + } + } + + impl Sub<$T> for $structName + { + type Output = Self; + + fn sub(self, rhs: $T) -> Self + { + Self::new(self.value.saturating_sub(rhs)) + } + } + impl Mul for $structName + { + type Output = Self; + + fn mul(self, rhs: Self) -> Self + { + Self::new(self.value.saturating_mul(rhs.value)) + } + } + + impl Mul<$T> for $structName + { + type Output = Self; + + fn mul(self, rhs: $T) -> Self + { + Self::new(self.value.saturating_mul(rhs)) + } + } + + impl Div for $structName + { + type Output = Self; + + fn div(self, rhs: Self) -> Self + { + if rhs.value == 0 + { + Self::new(U) + } + else + { + Self::new(self.value / rhs.value) + } + } + } + + impl Div<$T> for $structName + { + type Output = Self; + + fn div(self, rhs: $T) -> Self + { + if rhs == 0 + { + Self::new(U) + } + else + { + Self::new(self.value / rhs) + } + } + } + + impl Rem for $structName + { + type Output = Self; + + fn rem(self, rhs: Self) -> Self::Output + { + if rhs.value == 0 + { + Self::new(U) + } + else + { + Self::new(self.value % rhs.value) + } + } + } + + impl Rem<$T> for $structName + { + type Output = Self; + + fn rem(self, rhs: $T) -> Self::Output + { + if rhs == 0 + { + Self::new(U) + } + else + { + Self::new(self.value % rhs) + } + } + } + + impl AddAssign for $structName + { + fn add_assign(&mut self, rhs: Self) + { + let result = (self.value + rhs.value).clamp(L, U); + + self.value = result; + } + } + + impl AddAssign<$T> for $structName + { + fn add_assign(&mut self, rhs: $T) + { + let result = (self.value + rhs).clamp(L, U); + + self.value = result; + } + } + + impl SubAssign for $structName + { + fn sub_assign(&mut self, rhs: Self) + { + let result = (self.value - rhs.value).clamp(L, U); + + self.value = result; + } + } + + impl SubAssign<$T> for $structName + { + fn sub_assign(&mut self, rhs: $T) + { + let result = (self.value - rhs).clamp(L, U); + + self.value = result; + } + } + + impl MulAssign for $structName + { + fn mul_assign(&mut self, rhs: Self) + { + let result = (self.value * rhs.value).clamp(L, U); + + self.value = result; + } + } + + impl MulAssign<$T> for $structName + { + fn mul_assign(&mut self, rhs: $T) + { + let result = (self.value * rhs).clamp(L, U); + + self.value = result; + } + } + + impl DivAssign for $structName + { + fn div_assign(&mut self, rhs: Self) + { + assert!(rhs.value != 0, "Division by zero."); + + let result = (self.value / rhs.value).clamp(L, U); + + self.value = result; + } + } + + impl DivAssign<$T> for $structName + { + fn div_assign(&mut self, rhs: $T) + { + assert!(rhs != 0, "Division by zero."); + + let result = (self.value / rhs).clamp(L, U); + + self.value = result; + } + } + + impl RemAssign for $structName + { + fn rem_assign(&mut self, rhs: Self) + { + assert!(rhs.value != 0, "Division by zero."); + + let result = (self.value % rhs.value).clamp(L, U); + + self.value = result; + } + } + + impl RemAssign<$T> for $structName + { + fn rem_assign(&mut self, rhs: $T) + { + assert!(rhs != 0, "Division by zero."); + + let result = (self.value % rhs).clamp(L, U); + + self.value = result; + } + } + }; +} + +define_clamped!(ClampedI8, i8); +define_clamped!(ClampedI16, i16); +define_clamped!(ClampedI32, i32); +define_clamped!(ClampedI64, i64); +define_clamped!(ClampedIsize, isize); + +define_clamped!(ClampedU8, u8); +define_clamped!(ClampedU16, u16); +define_clamped!(ClampedU32, u32); +define_clamped!(ClampedU64, u64); +define_clamped!(ClampedUsize, usize); + + + +macro_rules! define_wrapped { + ($structName:ident, $T:ty, $LT:ty) => { + /// $structName - wraps arithmetic results around [L, U] + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + #[repr(transparent)] + pub struct $structName + { + value: $T + } + + impl $structName + { + pub const MAX: $T = U; + pub const MIN: $T = L; + const RANGE: $T = U - L + 1; + + /// # Panics + /// + /// Panics if the lower bound 'L' is greater than the upper + /// bound 'U', or if the range size is 0. + pub fn new(value: $T) -> Self + { + if L > U + { + panic!("Lower bound cannot be greater than Upper bound."); + } + + if Self::RANGE == 0 + { + panic!("The calculated range size must be greater than 0."); + } + + Self { value: Self::wrap_check(value, 0) } + } + + pub fn get(&self) -> $T + { + self.value + } + + pub const fn min() -> $T + { + L + } + + pub const fn max() -> $T + { + U + } + + fn wrap_check(current_val: $T, delta: $LT) -> $T + { + let current_val_relative_to_zero = + (current_val as $LT - L as $LT).rem_euclid(Self::RANGE as $LT); + let range_size = Self::RANGE as $LT; + + let shifted_result_unnormalized = + current_val_relative_to_zero + delta; + let shifted_result_normalized = + shifted_result_unnormalized.rem_euclid(range_size); + + (shifted_result_normalized + L as $LT) as $T + } + } + + impl Default for $structName + { + fn default() -> Self + { + Self::new(L) + } + } + + impl From<$T> for $structName + { + fn from(value: $T) -> Self + { + Self::new(value) + } + } + + impl Add for $structName + { + type Output = Self; + + fn add(self, rhs: Self) -> Self + { + Self::new(Self::wrap_check(self.value, rhs.value as $LT)) + } + } + + impl Add<$T> for $structName + { + type Output = Self; + + fn add(self, rhs: $T) -> Self + { + Self::new(Self::wrap_check(self.value, rhs as $LT)) + } + } + + impl Sub for $structName + { + type Output = Self; + + fn sub(self, rhs: Self) -> Self + { + Self::new(Self::wrap_check(self.value, -(rhs.value as $LT))) + } + } + + impl Sub<$T> for $structName + { + type Output = Self; + + fn sub(self, rhs: $T) -> Self + { + Self::new(Self::wrap_check(self.value, -(rhs as $LT))) + } + } + + impl AddAssign for $structName + { + fn add_assign(&mut self, rhs: Self) + { + self.value = Self::wrap_check(self.value, rhs.value as $LT); + } + } + + impl AddAssign<$T> for $structName + { + fn add_assign(&mut self, rhs: $T) + { + self.value = Self::wrap_check(self.value, rhs as $LT); + } + } + + impl SubAssign for $structName + { + fn sub_assign(&mut self, rhs: Self) + { + self.value = Self::wrap_check(self.value, -(rhs.value as $LT)); + } + } + + impl SubAssign<$T> for $structName + { + fn sub_assign(&mut self, rhs: $T) + { + self.value = Self::wrap_check(self.value, -(rhs as $LT)); + } + } + }; +} + + + +define_wrapped!(WrappedI8, i8, i16); +define_wrapped!(WrappedI16, i16, i32); +define_wrapped!(WrappedI32, i32, i64); +define_wrapped!(WrappedI64, i64, i128); +define_wrapped!(WrappedIsize, isize, i128); + +define_wrapped!(WrappedU8, u8, i16); +define_wrapped!(WrappedU16, u16, i32); +define_wrapped!(WrappedU32, u32, i64); +define_wrapped!(WrappedU64, u64, i128); +define_wrapped!(WrappedUsize, usize, i128); + + + +#[cfg(test)] +mod tests +{ + use super::*; // Import everything from the parent module + + // Test for BoundedError + #[test] + fn test_bounded_error_display() + { + let error = BoundedError::OutOfBounds { val: 15, + min: 0, + max: 10 }; + assert_eq!(format!("{}", error), "value 15 is out of bounds [0, 10]"); + } + + // Test for Zero trait + #[test] + fn test_zero_trait_u8() + { + assert_eq!(u8::zero(), 0u8); + let a = 5u8; + assert_eq!(a + u8::zero(), a); + assert_eq!(u8::zero() + a, a); + } + + #[test] + fn test_zero_trait_i32() + { + assert_eq!(i32::zero(), 0i32); + let a = -10i32; + assert_eq!(a + i32::zero(), a); + assert_eq!(i32::zero() + a, a); + } + + #[test] + fn test_zero_trait_f64() + { + assert_eq!(f64::zero(), 0.0f64); + let a = 3.14f64; + assert_eq!(a + f64::zero(), a); + assert_eq!(f64::zero() + a, a); + } + + // Test for Bound trait + #[test] + fn test_bound_trait_u8() + { + assert_eq!(u8::MIN, 0u8); + assert_eq!(u8::MAX, 255u8); + } + + #[test] + fn test_bound_trait_i8() + { + assert_eq!(i8::MIN, -128i8); + assert_eq!(i8::MAX, 127i8); + } + + // Tests for Bounded struct + #[test] + fn test_bounded_new_valid() + { + let b = Bounded::new(5u8, 0u8, 10u8); + assert_eq!(b.get(), 5); + assert_eq!(b.min(), 0); + assert_eq!(b.max(), 10); + } + + #[test] + #[should_panic(expected = "Bounded value out of bounds.")] + fn test_bounded_new_panic_lower() + { + Bounded::new(-1i8, 0i8, 10i8); + } + + #[test] + #[should_panic(expected = "Bounded value out of bounds.")] + fn test_bounded_new_panic_upper() + { + Bounded::new(11u8, 0u8, 10u8); + } + + #[test] + fn test_bounded_default() + { + let b: Bounded = Default::default(); + assert_eq!(b.get(), 0); + assert_eq!(b.min(), u8::MIN); + assert_eq!(b.max(), u8::MAX); + } + + #[test] + fn test_bounded_from_valid() + { + let b = Bounded::::from(5u8); + assert_eq!(b.get(), 5); + assert_eq!(b.min(), u8::MIN); + assert_eq!(b.max(), u8::MAX); + } + + #[test] + #[should_panic(expected = "value 256 is out of bounds [0, 255]")] + fn test_bounded_from_panic_upper() + { + let _ = Bounded::::from(256u8); // This will panic + } + + #[test] + #[should_panic(expected = "value -129 is out of bounds [-128, 127]")] + fn test_bounded_from_panic_lower() + { + let _ = Bounded::::from(-129i8); // This will panic + } + + // Bounded arithmetic operations + #[test] + fn test_bounded_add() + { + let b1 = Bounded::new(5i8, -10i8, 10i8); + let b2 = Bounded::new(3i8, -10i8, 10i8); + let result = b1 + b2; + assert_eq!(result.get(), 8); + } + + #[test] + #[should_panic(expected = "Bounded value out of bounds.")] + fn test_bounded_add_panic() + { + let b1 = Bounded::new(7i8, -10i8, 10i8); + let b2 = Bounded::new(4i8, -10i8, 10i8); + let _ = b1 + b2; // Should panic (7 + 4 = 11, out of bounds) + } + + #[test] + fn test_bounded_add_primitive() + { + let b = Bounded::new(5i8, -10i8, 10i8); + let result = b + 3i8; + assert_eq!(result.get(), 8); + } + + #[test] + #[should_panic(expected = "Bounded value out of bounds.")] + fn test_bounded_add_primitive_panic() + { + let b = Bounded::new(7i8, -10i8, 10i8); + let _ = b + 4i8; // Should panic + } + + #[test] + fn test_bounded_sub() + { + let b1 = Bounded::new(8i8, -10i8, 10i8); + let b2 = Bounded::new(3i8, -10i8, 10i8); + let result = b1 - b2; + assert_eq!(result.get(), 5); + } + + #[test] + #[should_panic(expected = "Bounded value out of bounds.")] + fn test_bounded_sub_panic() + { + let b1 = Bounded::new(-7i8, -10i8, 10i8); + let b2 = Bounded::new(4i8, -10i8, 10i8); + let _ = b1 - b2; // Should panic (-7 - 4 = -11, out of bounds) + } + + #[test] + fn test_bounded_sub_primitive() + { + let b = Bounded::new(8i8, -10i8, 10i8); + let result = b - 3i8; + assert_eq!(result.get(), 5); + } + + #[test] + #[should_panic(expected = "Bounded value out of bounds.")] + fn test_bounded_sub_primitive_panic() + { + let b = Bounded::new(-7i8, -10i8, 10i8); + let _ = b - 4i8; // Should panic + } + + #[test] + fn test_bounded_mul() + { + let b1 = Bounded::new(2i8, -10i8, 10i8); + let b2 = Bounded::new(3i8, -10i8, 10i8); + let result = b1 * b2; + assert_eq!(result.get(), 6); + } + + #[test] + #[should_panic(expected = "Bounded value out of bounds.")] + fn test_bounded_mul_panic() + { + let b1 = Bounded::new(3i8, -10i8, 10i8); + let b2 = Bounded::new(4i8, -10i8, 10i8); + let _ = b1 * b2; // Should panic (3 * 4 = 12, out of bounds) + } + + #[test] + fn test_bounded_mul_primitive() + { + let b = Bounded::new(2i8, -10i8, 10i8); + let result = b * 3i8; + assert_eq!(result.get(), 6); + } + + #[test] + #[should_panic(expected = "Bounded value out of bounds.")] + fn test_bounded_mul_primitive_panic() + { + let b = Bounded::new(3i8, -10i8, 10i8); + let _ = b * 4i8; // Should panic + } + + #[test] + fn test_bounded_div() + { + let b1 = Bounded::new(10i8, -10i8, 10i8); + let b2 = Bounded::new(2i8, -10i8, 10i8); + let result = b1 / b2; + assert_eq!(result.get(), 5); + } + + #[test] + #[should_panic(expected = "Division by zero.")] + fn test_bounded_div_by_zero() + { + let b1 = Bounded::new(10i8, -10i8, 10i8); + let b2 = Bounded::new(0i8, -10i8, 10i8); + let _ = b1 / b2; // Should panic + } + + #[test] + fn test_bounded_div_primitive() + { + let b = Bounded::new(10i8, -10i8, 10i8); + let result = b / 2i8; + assert_eq!(result.get(), 5); + } + + #[test] + #[should_panic(expected = "Division by zero.")] + fn test_bounded_div_primitive_by_zero() + { + let b = Bounded::new(10i8, -10i8, 10i8); + let _ = b / 0i8; // Should panic + } + + #[test] + fn test_bounded_rem() + { + let b1 = Bounded::new(10i8, -10i8, 10i8); + let b2 = Bounded::new(3i8, -10i8, 10i8); + let result = b1 % b2; + assert_eq!(result.get(), 1); + } + + #[test] + #[should_panic(expected = "Division by zero.")] + fn test_bounded_rem_by_zero() + { + let b1 = Bounded::new(10i8, -10i8, 10i8); + let b2 = Bounded::new(0i8, -10i8, 10i8); + let _ = b1 % b2; // Should panic + } + + #[test] + fn test_bounded_rem_primitive() + { + let b = Bounded::new(10i8, -10i8, 10i8); + let result = b % 3i8; + assert_eq!(result.get(), 1); + } + + #[test] + #[should_panic(expected = "Division by zero.")] + fn test_bounded_rem_primitive_by_zero() + { + let b = Bounded::new(10i8, -10i8, 10i8); + let _ = b % 0i8; // Should panic + } + + // Bounded assignment operations + #[test] + fn test_bounded_add_assign() + { + let mut b1 = Bounded::new(5i8, -10i8, 10i8); + let b2 = Bounded::new(3i8, -10i8, 10i8); + b1 += b2; + assert_eq!(b1.get(), 8); + } + + #[test] + #[should_panic(expected = "AddAssign: Result 11 out of bounds [-10, 10].")] + fn test_bounded_add_assign_panic() + { + let mut b1 = Bounded::new(7i8, -10i8, 10i8); + let b2 = Bounded::new(4i8, -10i8, 10i8); + b1 += b2; // Should panic + } + + #[test] + fn test_bounded_add_assign_primitive() + { + let mut b = Bounded::new(5i8, -10i8, 10i8); + b += 3i8; + assert_eq!(b.get(), 8); + } + + #[test] + #[should_panic(expected = "AddAssign: Result 11 out of bounds [-10, 10].")] + fn test_bounded_add_assign_primitive_panic() + { + let mut b = Bounded::new(7i8, -10i8, 10i8); + b += 4i8; // Should panic + } + + #[test] + fn test_bounded_sub_assign() + { + let mut b1 = Bounded::new(8i8, -10i8, 10i8); + let b2 = Bounded::new(3i8, -10i8, 10i8); + b1 -= b2; + assert_eq!(b1.get(), 5); + } + + #[test] + #[should_panic(expected = "SubAssign: Result -11 out of bounds [-10, 10].")] + fn test_bounded_sub_assign_panic() + { + let mut b1 = Bounded::new(-7i8, -10i8, 10i8); + let b2 = Bounded::new(4i8, -10i8, 10i8); + b1 -= b2; // Should panic + } + + #[test] + fn test_bounded_sub_assign_primitive() + { + let mut b = Bounded::new(8i8, -10i8, 10i8); + b -= 3i8; + assert_eq!(b.get(), 5); + } + + #[test] + #[should_panic(expected = "SubAssign: Result -11 out of bounds [-10, 10].")] + fn test_bounded_sub_assign_primitive_panic() + { + let mut b = Bounded::new(-7i8, -10i8, 10i8); + b -= 4i8; // Should panic + } + + #[test] + fn test_bounded_mul_assign() + { + let mut b1 = Bounded::new(2i8, -10i8, 10i8); + let b2 = Bounded::new(3i8, -10i8, 10i8); + b1 *= b2; + assert_eq!(b1.get(), 6); + } + + #[test] + #[should_panic(expected = "MulAssign: Result 12 out of bounds [-10, 10].")] + fn test_bounded_mul_assign_panic() + { + let mut b1 = Bounded::new(3i8, -10i8, 10i8); + let b2 = Bounded::new(4i8, -10i8, 10i8); + b1 *= b2; // Should panic + } + + #[test] + fn test_bounded_mul_assign_primitive() + { + let mut b = Bounded::new(2i8, -10i8, 10i8); + b *= 3i8; + assert_eq!(b.get(), 6); + } + + #[test] + #[should_panic(expected = "MulAssign: Result 12 out of bounds [-10, 10].")] + fn test_bounded_mul_assign_primitive_panic() + { + let mut b = Bounded::new(3i8, -10i8, 10i8); + b *= 4i8; // Should panic + } + + #[test] + fn test_bounded_div_assign() + { + let mut b1 = Bounded::new(10i8, -10i8, 10i8); + let b2 = Bounded::new(2i8, -10i8, 10i8); + b1 /= b2; + assert_eq!(b1.get(), 5); + } + + #[test] + #[should_panic(expected = "Division by zero.")] + fn test_bounded_div_assign_by_zero() + { + let mut b1 = Bounded::new(10i8, -10i8, 10i8); + let b2 = Bounded::new(0i8, -10i8, 10i8); + b1 /= b2; // Should panic + } + + #[test] + fn test_bounded_div_assign_primitive() + { + let mut b = Bounded::new(10i8, -10i8, 10i8); + b /= 2i8; + assert_eq!(b.get(), 5); + } + + #[test] + #[should_panic(expected = "Division by zero.")] + fn test_bounded_div_assign_primitive_by_zero() + { + let mut b = Bounded::new(10i8, -10i8, 10i8); + b /= 0i8; // Should panic + } + + #[test] + fn test_bounded_rem_assign() + { + let mut b1 = Bounded::new(10i8, -10i8, 10i8); + let b2 = Bounded::new(3i8, -10i8, 10i8); + b1 %= b2; + assert_eq!(b1.get(), 1); + } + + #[test] + #[should_panic(expected = "Division by zero.")] + fn test_bounded_rem_assign_by_zero() + { + let mut b1 = Bounded::new(10i8, -10i8, 10i8); + let b2 = Bounded::new(0i8, -10i8, 10i8); + b1 %= b2; // Should panic + } + + #[test] + fn test_bounded_rem_assign_primitive() + { + let mut b = Bounded::new(10i8, -10i8, 10i8); + b %= 3i8; + assert_eq!(b.get(), 1); + } + + #[test] + #[should_panic(expected = "Division by zero.")] + fn test_bounded_rem_assign_primitive_by_zero() + { + let mut b = Bounded::new(10i8, -10i8, 10i8); + b %= 0i8; // Should panic + } + + + // Tests for define_bounded macro (using BoundedU8 and BoundedI8 as examples) + #[test] + fn test_bounded_u8_new_valid() + { + let b = BoundedU8::<0, 10>::new(5); + assert_eq!(b.get(), 5); + assert_eq!(BoundedU8::<0, 10>::min(), 0); + assert_eq!(BoundedU8::<0, 10>::max(), 10); + } + + #[test] + #[should_panic(expected = "BoundedU8 value out of bounds.")] + fn test_bounded_u8_new_panic() + { + let _ = BoundedU8::<0, 10>::new(11); + } + + #[test] + fn test_bounded_u8_default() + { + let b: BoundedU8<0, 10> = Default::default(); + assert_eq!(b.get(), 0); + } + + #[test] + fn test_bounded_u8_from_valid() + { + let b = BoundedU8::<0, 10>::from(5); + assert_eq!(b.get(), 5); + } + + #[test] + #[should_panic(expected = "value 11 is out of bounds [0, 10]")] + fn test_bounded_u8_from_panic() + { + let _ = BoundedU8::<0, 10>::from(11); + } + + #[test] + fn test_bounded_u8_add() + { + let b1 = BoundedU8::<0, 10>::new(5); + let b2 = BoundedU8::<0, 10>::new(3); + let result = b1 + b2; + assert_eq!(result.get(), 8); + } + + #[test] + #[should_panic(expected = "BoundedU8 value out of bounds.")] + fn test_bounded_u8_add_panic() + { + let b1 = BoundedU8::<0, 10>::new(7); + let b2 = BoundedU8::<0, 10>::new(4); + let _ = b1 + b2; // 11, out of bounds + } + + #[test] + fn test_bounded_u8_add_primitive() + { + let b = BoundedU8::<0, 10>::new(5); + let result = b + 3; + assert_eq!(result.get(), 8); + } + + #[test] + #[should_panic(expected = "BoundedU8 value out of bounds.")] + fn test_bounded_u8_add_primitive_panic() + { + let b = BoundedU8::<0, 10>::new(7); + let _ = b + 4; // 11, out of bounds + } + + #[test] + fn test_bounded_u8_add_assign() + { + let mut b = BoundedU8::<0, 10>::new(5); + let rhs = BoundedU8::<0, 10>::new(3); + b += rhs; + assert_eq!(b.get(), 8); + } + + #[test] + #[should_panic(expected = "AddAssign: Result 11 out of bounds [0, 10].")] + fn test_bounded_u8_add_assign_panic() + { + let mut b = BoundedU8::<0, 10>::new(7); + let rhs = BoundedU8::<0, 10>::new(4); + b += rhs; // 11, out of bounds + } + + #[test] + fn test_bounded_u8_add_assign_primitive() + { + let mut b = BoundedU8::<0, 10>::new(5); + b += 3; + assert_eq!(b.get(), 8); + } + + #[test] + #[should_panic(expected = "AddAssign: Result 11 out of bounds [0, 10].")] + fn test_bounded_u8_add_assign_primitive_panic() + { + let mut b = BoundedU8::<0, 10>::new(7); + b += 4; // 11, out of bounds + } + + #[test] + fn test_bounded_u8_display() + { + let b = BoundedU8::<0, 10>::new(5); + assert_eq!(format!("{}", b), "5 - [0, 10]"); + } + + // Tests for define_clamped macro (using ClampedU8 and ClampedI8 as examples) + #[test] + fn test_clamped_u8_new() + { + let b = ClampedU8::<0, 10>::new(5); + assert_eq!(b.get(), 5); + let b_clamped_upper = ClampedU8::<0, 10>::new(15); + assert_eq!(b_clamped_upper.get(), 10); + let b_clamped_lower = ClampedU8::<5, 10>::new(2); + assert_eq!(b_clamped_lower.get(), 5); + } + + #[test] + fn test_clamped_u8_default() + { + let b: ClampedU8<0, 10> = Default::default(); + assert_eq!(b.get(), 0); + } + + #[test] + fn test_clamped_u8_from() + { + let b = ClampedU8::<0, 10>::from(15); + assert_eq!(b.get(), 10); + let b_from_lower = ClampedU8::<5, 10>::from(2); + assert_eq!(b_from_lower.get(), 5); + } + + #[test] + fn test_clamped_u8_add() + { + let b1 = ClampedU8::<0, 10>::new(5); + let b2 = ClampedU8::<0, 10>::new(3); + let result = b1 + b2; + assert_eq!(result.get(), 8); + + let b3 = ClampedU8::<0, 10>::new(7); + let b4 = ClampedU8::<0, 10>::new(4); + let result_clamped = b3 + b4; // 7 + 4 = 11, clamped to 10 + assert_eq!(result_clamped.get(), 10); + } + + #[test] + fn test_clamped_u8_add_primitive() + { + let b = ClampedU8::<0, 10>::new(5); + let result = b + 3; + assert_eq!(result.get(), 8); + + let b_clamped = ClampedU8::<0, 10>::new(7); + let result_clamped = b_clamped + 4; // 7 + 4 = 11, clamped to 10 + assert_eq!(result_clamped.get(), 10); + } + + #[test] + fn test_clamped_u8_sub() + { + let b1 = ClampedU8::<0, 10>::new(8); + let b2 = ClampedU8::<0, 10>::new(3); + let result = b1 - b2; + assert_eq!(result.get(), 5); + + let b3 = ClampedU8::<5, 10>::new(6); + let b4 = ClampedU8::<5, 10>::new(4); + let result_clamped = b3 - b4; // 6 - 4 = 2, clamped to 5 + assert_eq!(result_clamped.get(), 5); + } + + #[test] + fn test_clamped_u8_sub_primitive() + { + let b = ClampedU8::<0, 10>::new(8); + let result = b - 3; + assert_eq!(result.get(), 5); + + let b_clamped = ClampedU8::<5, 10>::new(6); + let result_clamped = b_clamped - 4; // 6 - 4 = 2, clamped to 5 + assert_eq!(result_clamped.get(), 5); + } + + #[test] + fn test_clamped_u8_mul() + { + let b1 = ClampedU8::<0, 10>::new(2); + let b2 = ClampedU8::<0, 10>::new(3); + let result = b1 * b2; + assert_eq!(result.get(), 6); + + let b3 = ClampedU8::<0, 10>::new(3); + let b4 = ClampedU8::<0, 10>::new(4); + let result_clamped = b3 * b4; // 3 * 4 = 12, clamped to 10 + assert_eq!(result_clamped.get(), 10); + } + + #[test] + fn test_clamped_u8_mul_primitive() + { + let b = ClampedU8::<0, 10>::new(2); + let result = b * 3; + assert_eq!(result.get(), 6); + + let b_clamped = ClampedU8::<0, 10>::new(3); + let result_clamped = b_clamped * 4; // 3 * 4 = 12, clamped to 10 + assert_eq!(result_clamped.get(), 10); + } + + #[test] + fn test_clamped_u8_div() + { + let b1 = ClampedU8::<0, 10>::new(10); + let b2 = ClampedU8::<0, 10>::new(2); + let result = b1 / b2; + assert_eq!(result.get(), 5); + + let b3 = ClampedU8::<0, 10>::new(10); + let b4 = ClampedU8::<0, 10>::new(0); // Division by zero should clamp to max + let result_div_by_zero = b3 / b4; + assert_eq!(result_div_by_zero.get(), 10); + } + + #[test] + fn test_clamped_u8_div_primitive() + { + let b = ClampedU8::<0, 10>::new(10); + let result = b / 2; + assert_eq!(result.get(), 5); + + let b_div_by_zero = ClampedU8::<0, 10>::new(10); + let result_div_by_zero = b_div_by_zero / 0; // Division by zero should clamp to max + assert_eq!(result_div_by_zero.get(), 10); + } + + #[test] + fn test_clamped_u8_rem() + { + let b1 = ClampedU8::<0, 10>::new(10); + let b2 = ClampedU8::<0, 10>::new(3); + let result = b1 % b2; + assert_eq!(result.get(), 1); + + let b3 = ClampedU8::<0, 10>::new(10); + let b4 = ClampedU8::<0, 10>::new(0); // Remainder by zero should clamp to max + let result_rem_by_zero = b3 % b4; + assert_eq!(result_rem_by_zero.get(), 10); + } + + #[test] + fn test_clamped_u8_rem_primitive() + { + let b = ClampedU8::<0, 10>::new(10); + let result = b % 3; + assert_eq!(result.get(), 1); + + let b_rem_by_zero = ClampedU8::<0, 10>::new(10); + let result_rem_by_zero = b_rem_by_zero % 0; // Remainder by zero should clamp to max + assert_eq!(result_rem_by_zero.get(), 10); + } + + #[test] + fn test_clamped_u8_add_assign() + { + let mut b = ClampedU8::<0, 10>::new(5); + let rhs = ClampedU8::<0, 10>::new(3); + b += rhs; + assert_eq!(b.get(), 8); + + let mut b_clamped = ClampedU8::<0, 10>::new(7); + let rhs_clamped = ClampedU8::<0, 10>::new(4); + b_clamped += rhs_clamped; // 7 + 4 = 11, clamped to 10 + assert_eq!(b_clamped.get(), 10); + } + + #[test] + fn test_clamped_u8_add_assign_primitive() + { + let mut b = ClampedU8::<0, 10>::new(5); + b += 3; + assert_eq!(b.get(), 8); + + let mut b_clamped = ClampedU8::<0, 10>::new(7); + b_clamped += 4; // 7 + 4 = 11, clamped to 10 + assert_eq!(b_clamped.get(), 10); + } + + #[test] + fn test_clamped_u8_sub_assign() + { + let mut b = ClampedU8::<0, 10>::new(8); + let rhs = ClampedU8::<0, 10>::new(3); + b -= rhs; + assert_eq!(b.get(), 5); + + let mut b_clamped = ClampedU8::<5, 10>::new(6); + let rhs_clamped = ClampedU8::<5, 10>::new(4); + b_clamped -= rhs_clamped; // 6 - 4 = 2, clamped to 5 + assert_eq!(b_clamped.get(), 5); + } + + #[test] + fn test_clamped_u8_sub_assign_primitive() + { + let mut b = ClampedU8::<0, 10>::new(8); + b -= 3; + assert_eq!(b.get(), 5); + + let mut b_clamped = ClampedU8::<5, 10>::new(6); + b_clamped -= 4; // 6 - 4 = 2, clamped to 5 + assert_eq!(b_clamped.get(), 5); + } + + #[test] + fn test_clamped_u8_mul_assign() + { + let mut b = ClampedU8::<0, 10>::new(2); + let rhs = ClampedU8::<0, 10>::new(3); + b *= rhs; + assert_eq!(b.get(), 6); + + let mut b_clamped = ClampedU8::<0, 10>::new(3); + let rhs_clamped = ClampedU8::<0, 10>::new(4); + b_clamped *= rhs_clamped; // 3 * 4 = 12, clamped to 10 + assert_eq!(b_clamped.get(), 10); + } + + #[test] + fn test_clamped_u8_mul_assign_primitive() + { + let mut b = ClampedU8::<0, 10>::new(2); + b *= 3; + assert_eq!(b.get(), 6); + + let mut b_clamped = ClampedU8::<0, 10>::new(3); + b_clamped *= 4; // 3 * 4 = 12, clamped to 10 + assert_eq!(b_clamped.get(), 10); + } + + #[test] + fn test_clamped_u8_div_assign() + { + let mut b = ClampedU8::<0, 10>::new(10); + let rhs = ClampedU8::<0, 10>::new(2); + b /= rhs; + assert_eq!(b.get(), 5); + + let mut b_div_by_zero = ClampedU8::<0, 10>::new(10); + let rhs_div_by_zero = ClampedU8::<0, 10>::new(0); + b_div_by_zero /= rhs_div_by_zero; + assert_eq!(b_div_by_zero.get(), 10); + } + + #[test] + fn test_clamped_u8_div_assign_primitive() + { + let mut b = ClampedU8::<0, 10>::new(10); + b /= 2; + assert_eq!(b.get(), 5); + + let mut b_div_by_zero = ClampedU8::<0, 10>::new(10); + b_div_by_zero /= 0; + assert_eq!(b_div_by_zero.get(), 10); + } + + #[test] + fn test_clamped_u8_rem_assign() + { + let mut b = ClampedU8::<0, 10>::new(10); + let rhs = ClampedU8::<0, 10>::new(3); + b %= rhs; + assert_eq!(b.get(), 1); + + let mut b_rem_by_zero = ClampedU8::<0, 10>::new(10); + let rhs_rem_by_zero = ClampedU8::<0, 10>::new(0); + b_rem_by_zero %= rhs_rem_by_zero; + assert_eq!(b_rem_by_zero.get(), 10); + } + + #[test] + fn test_clamped_u8_rem_assign_primitive() + { + let mut b = ClampedU8::<0, 10>::new(10); + b %= 3; + assert_eq!(b.get(), 1); + + let mut b_rem_by_zero = ClampedU8::<0, 10>::new(10); + b_rem_by_zero %= 0; + assert_eq!(b_rem_by_zero.get(), 10); + } + + + // Tests for define_wrapped macro (using WrappedI8 as example) + #[test] + fn test_wrapped_i8_new_valid() + { + let w = WrappedI8::<-5, 5>::new(0); + assert_eq!(w.get(), 0); + assert_eq!(WrappedI8::<-5, 5>::min(), -5); + assert_eq!(WrappedI8::<-5, 5>::max(), 5); + } + + #[test] + fn test_wrapped_i8_new_wraps() + { + let w1 = WrappedI8::<-5, 5>::new(6); // 6 - (-5) = 11, 11 % 11 = 0, 0 + (-5) = -5 + assert_eq!(w1.get(), -5); + let w2 = WrappedI8::<-5, 5>::new(-6); // -6 - (-5) = -1, -1 % 11 = 10, 10 + (-5) = 5 + assert_eq!(w2.get(), 5); + } + + #[test] + #[should_panic(expected = "Lower bound cannot be greater than Upper bound.")] + fn test_wrapped_i8_new_panic_invalid_bounds() + { + let _ = WrappedI8::<5, 0>::new(1); + } + + #[test] + #[should_panic(expected = "The calculated range size must be greater than \ + 0.")] + fn test_wrapped_i8_new_panic_zero_range() + { + let _ = WrappedI8::<5, 4>::new(1); // U - L + 1 = 4 - 5 + 1 = 0 + } + + #[test] + fn test_wrapped_i8_default() + { + let w: WrappedI8<-5, 5> = Default::default(); + assert_eq!(w.get(), -5); + } + + #[test] + fn test_wrapped_i8_from() + { + let w = WrappedI8::<-5, 5>::from(6); + assert_eq!(w.get(), -5); + let w2 = WrappedI8::<-5, 5>::from(-6); + assert_eq!(w2.get(), 5); + } + + #[test] + fn test_wrapped_i8_add() + { + let w1 = WrappedI8::<-5, 5>::new(3); + let w2 = WrappedI8::<-5, 5>::new(3); + let result = w1 + w2; // 3 + 3 = 6, wraps to -5 + assert_eq!(result.get(), -5); + + let w3 = WrappedI8::<-5, 5>::new(5); + let w4 = WrappedI8::<-5, 5>::new(1); + let result2 = w3 + w4; // 5 + 1 = 6, wraps to -5 + assert_eq!(result2.get(), -5); + } + + #[test] + fn test_wrapped_i8_add_primitive() + { + let w = WrappedI8::<-5, 5>::new(3); + let result = w + 3; // 3 + 3 = 6, wraps to -5 + assert_eq!(result.get(), -5); + } + + #[test] + fn test_wrapped_i8_sub() + { + let w1 = WrappedI8::<-5, 5>::new(-3); + let w2 = WrappedI8::<-5, 5>::new(3); + let result = w1 - w2; // -3 - 3 = -6, wraps to 4 + assert_eq!(result.get(), 4); + + let w3 = WrappedI8::<-5, 5>::new(-5); + let w4 = WrappedI8::<-5, 5>::new(1); + let result2 = w3 - w4; // -5 - 1 = -6, wraps to 4 + assert_eq!(result2.get(), 4); + } + + #[test] + fn test_wrapped_i8_sub_primitive() + { + let w = WrappedI8::<-5, 5>::new(-3); + let result = w - 3; // -3 - 3 = -6, wraps to 4 + assert_eq!(result.get(), 4); + } + + #[test] + fn test_wrapped_i8_add_assign() + { + let mut w = WrappedI8::<-5, 5>::new(3); + let rhs = WrappedI8::<-5, 5>::new(3); + w += rhs; // 3 + 3 = 6, wraps to -5 + assert_eq!(w.get(), -5); + } + + #[test] + fn test_wrapped_i8_add_assign_primitive() + { + let mut w = WrappedI8::<-5, 5>::new(3); + w += 3; // 3 + 3 = 6, wraps to -5 + assert_eq!(w.get(), -5); + } + + #[test] + fn test_wrapped_i8_sub_assign() + { + let mut w = WrappedI8::<-5, 5>::new(-3); + let rhs = WrappedI8::<-5, 5>::new(3); + w -= rhs; // -3 - 3 = -6, wraps to 4 + assert_eq!(w.get(), 4); + } + + #[test] + fn test_wrapped_i8_sub_assign_primitive() + { + let mut w = WrappedI8::<-5, 5>::new(-3); + w -= 3; // -3 - 3 = -6, wraps to 4 + assert_eq!(w.get(), 4); + } + + // Tests for define_wrapped macro (using WrappedU8 as example) + #[test] + fn test_wrapped_u8_new_valid() + { + let w = WrappedU8::<0, 10>::new(5); + assert_eq!(w.get(), 5); + assert_eq!(WrappedU8::<0, 10>::min(), 0); + assert_eq!(WrappedU8::<0, 10>::max(), 10); + } + + #[test] + fn test_wrapped_u8_new_wraps() + { + let w1 = WrappedU8::<0, 10>::new(11); // 11 -> 0 + assert_eq!(w1.get(), 0); + let w2 = WrappedU8::<0, 10>::new(12); // 12 -> 1 + assert_eq!(w2.get(), 1); + let w3 = WrappedU8::<5, 15>::new(16); // 16 - 5 = 11. 11 % 11 = 0. 0 + 5 = 5 + assert_eq!(w3.get(), 5); + } + + #[test] + #[should_panic(expected = "Lower bound cannot be greater than Upper bound.")] + fn test_wrapped_u8_new_panic_invalid_bounds() + { + let _ = WrappedU8::<10, 0>::new(5); + } + + #[test] + #[should_panic(expected = "The calculated range size must be greater than \ + 0.")] + fn test_wrapped_u8_new_panic_zero_range() + { + let _ = WrappedU8::<5, 4>::new(1); // U - L + 1 = 4 - 5 + 1 = 0 + } + + #[test] + fn test_wrapped_u8_default() + { + let w: WrappedU8<0, 10> = Default::default(); + assert_eq!(w.get(), 0); + } + + #[test] + fn test_wrapped_u8_from() + { + let w = WrappedU8::<0, 10>::from(11); + assert_eq!(w.get(), 0); + let w2 = WrappedU8::<0, 10>::from(12); + assert_eq!(w2.get(), 1); + } + + #[test] + fn test_wrapped_u8_add() + { + let w1 = WrappedU8::<0, 10>::new(8); + let w2 = WrappedU8::<0, 10>::new(5); + let result = w1 + w2; // 8 + 5 = 13, wraps to 2 + assert_eq!(result.get(), 2); + + let w3 = WrappedU8::<5, 15>::new(12); + let w4 = WrappedU8::<5, 15>::new(5); + let result2 = w3 + w4; // 12 + 5 = 17. (17 - 5) % 11 = 12 % 11 = 1. 1 + 5 = 6 + assert_eq!(result2.get(), 6); + } + + #[test] + fn test_wrapped_u8_add_primitive() + { + let w = WrappedU8::<0, 10>::new(8); + let result = w + 5; // 8 + 5 = 13, wraps to 2 + assert_eq!(result.get(), 2); + } + + #[test] + fn test_wrapped_u8_sub() + { + let w1 = WrappedU8::<0, 10>::new(2); + let w2 = WrappedU8::<0, 10>::new(5); + let result = w1 - w2; // 2 - 5 = -3. (-3 % 11) + 0 = 8 + assert_eq!(result.get(), 8); + + let w3 = WrappedU8::<5, 15>::new(7); + let w4 = WrappedU8::<5, 15>::new(4); + let result2 = w3 - w4; // 7 - 4 = 3. (3 - 5) % 11 = -2 % 11 = 9. 9 + 5 = 14 + assert_eq!(result2.get(), 14); + } + + #[test] + fn test_wrapped_u8_sub_primitive() + { + let w = WrappedU8::<0, 10>::new(2); + let result = w - 5; // 2 - 5 = -3. (-3 % 11) + 0 = 8 + assert_eq!(result.get(), 8); + } + + #[test] + fn test_wrapped_u8_add_assign() + { + let mut w = WrappedU8::<0, 10>::new(8); + let rhs = WrappedU8::<0, 10>::new(5); + w += rhs; // 8 + 5 = 13, wraps to 2 + assert_eq!(w.get(), 2); + } + + #[test] + fn test_wrapped_u8_add_assign_primitive() + { + let mut w = WrappedU8::<0, 10>::new(8); + w += 5; // 8 + 5 = 13, wraps to 2 + assert_eq!(w.get(), 2); + } + + #[test] + fn test_wrapped_u8_sub_assign() + { + let mut w = WrappedU8::<0, 10>::new(2); + let rhs = WrappedU8::<0, 10>::new(5); + w -= rhs; // 2 - 5 = -3. (-3 % 11) + 0 = 8 + assert_eq!(w.get(), 8); + } + + #[test] + fn test_wrapped_u8_sub_assign_primitive() + { + let mut w = WrappedU8::<0, 10>::new(2); + w -= 5; // 2 - 5 = -3. (-3 % 11) + 0 = 8 + assert_eq!(w.get(), 8); + } +} diff --git a/src/channel.rs b/src/channel.rs new file mode 100644 index 0000000..c79bb66 --- /dev/null +++ b/src/channel.rs @@ -0,0 +1,17 @@ +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::{Channel, Receiver, Sender}; + +use crate::image::MicrobitImage; +use crate::microbit::Button; + + + +pub type FrameChannel = Channel; +pub type FrameReceiver = + Receiver<'static, CriticalSectionRawMutex, MicrobitImage, 1>; +pub type FrameSender = + Sender<'static, CriticalSectionRawMutex, MicrobitImage, 1>; + +pub type ButtonChannel = Channel; +pub type ButtonReceiver = Receiver<'static, CriticalSectionRawMutex, Button, 4>; +pub type ButtonSender = Sender<'static, CriticalSectionRawMutex, Button, 4>; diff --git a/src/display.rs b/src/display.rs new file mode 100644 index 0000000..6dacb7a --- /dev/null +++ b/src/display.rs @@ -0,0 +1,144 @@ +use embassy_nrf::gpio::{Output, OutputDrive}; +use embassy_nrf::peripherals::{P0_11, P0_15, P0_19, P0_21, P0_22, P0_24, P0_28, P0_30, P0_31, P1_05}; +use embassy_time::Timer; + +use crate::channel::FrameReceiver; +use crate::image::{Level, MicrobitImage}; +use crate::microbit::{LED_MATRIX_HEIGHT, LED_MATRIX_WIDTH}; + + +const WIDTH: usize = LED_MATRIX_WIDTH; +const HEIGHT: usize = LED_MATRIX_HEIGHT; +const WIDTH_BOUND: usize = WIDTH - 1; +const HEIGHT_BOUND: usize = HEIGHT - 1; +const STEP_DELAY: u64 = 250; + + + +/// This will own and control the pins for the 5x5 LED matrix. +/// +/// row_1: P0_21 +/// row_2: P0_22 +/// row_3: P0_15 +/// row_4: P0_24 +/// row_5: P0_19 +/// +/// col_1: P0_28 +/// col_2: P0_11 +/// col_3: P0_31 +/// col_4: P1_05 +/// col_5: P0_30 +pub struct Display<'a> +{ + rows: [Output<'a>; 5], + cols: [Output<'a>; 5], + receiver: FrameReceiver +} + + +impl<'a> Display<'a> +{ + pub fn new(row_1: P0_21, row_2: P0_22, row_3: P0_15, row_4: P0_24, + row_5: P0_19, col_1: P0_28, col_2: P0_11, col_3: P0_31, + col_4: P1_05, col_5: P0_30, receiver: FrameReceiver) + -> Self + { + Self { rows: [Output::new(row_1, + embassy_nrf::gpio::Level::Low, + OutputDrive::Standard), + Output::new(row_2, + embassy_nrf::gpio::Level::Low, + OutputDrive::Standard), + Output::new(row_3, + embassy_nrf::gpio::Level::Low, + OutputDrive::Standard), + Output::new(row_4, + embassy_nrf::gpio::Level::Low, + OutputDrive::Standard), + Output::new(row_5, + embassy_nrf::gpio::Level::Low, + OutputDrive::Standard)], + cols: [Output::new(col_1, + embassy_nrf::gpio::Level::High, + OutputDrive::Standard), + Output::new(col_2, + embassy_nrf::gpio::Level::High, + OutputDrive::Standard), + Output::new(col_3, + embassy_nrf::gpio::Level::High, + OutputDrive::Standard), + Output::new(col_4, + embassy_nrf::gpio::Level::High, + OutputDrive::Standard), + Output::new(col_5, + embassy_nrf::gpio::Level::High, + OutputDrive::Standard)], + receiver } + } + + /// + pub async fn present(&mut self) + { + // Get the frame from the renderer and present it. + let mut frame = MicrobitImage::new(); + + loop + { + if let Ok(new_frame) = self.receiver.try_receive() + { + frame = new_frame; + } + + self.present_frame(frame).await; + } + } + + async fn present_frame(&mut self, frame: MicrobitImage) + { + // Draw it row by row. + for pwm_counter in 0..Level::MAX + 1 + { + for (y, row) in frame.row_iter().enumerate() + { + self.present_row(y, row, pwm_counter).await; + } + } + } + + async fn present_row(&mut self, index: usize, row: &[Level], pwm_counter: u8) + { + // Clear the row we are drawing to. + for col in &mut self.cols + { + col.set_high(); + } + + self.rows[index].set_high(); + + // We need to flash draw the row Brightness step times. + self.pwm_cycle(pwm_counter, row).await; + + // Unset the row. + self.rows[index].set_low(); + } + + async fn pwm_cycle(&mut self, pwm_counter: u8, row: &[Level]) + { + // + for (x, &brightness) in row.iter().enumerate() + { + if brightness.get() > pwm_counter + { + // Turn on. + self.cols[x].set_low(); + } + else + { + // Turn off. + self.cols[x].set_high(); + } + } + + Timer::after_micros(STEP_DELAY).await; + } +} diff --git a/src/image.rs b/src/image.rs new file mode 100644 index 0000000..30ae631 --- /dev/null +++ b/src/image.rs @@ -0,0 +1,97 @@ +// TODO: Change the BoundedUsize to be {W-1} and {H-1} afer +// [generic_const_exprs](https://github.com/rust-lang/rust/issues/76560) +// lands.. If it ever lands. +// +// Then this can just be Grayscale. + +use crate::bounded::{BoundedU8, BoundedUsize}; +use crate::microbit::{LED_MATRIX_HEIGHT, LED_MATRIX_WIDTH}; + + + +pub type Level = BoundedU8<0, 9>; +pub type MicrobitImage = Grayscale; + + + +pub trait Image +{ + fn width() -> usize; + fn height() -> usize; +} + + +#[derive(Clone, Copy)] +pub struct Grayscale +{ + pixels: [[Level; W]; H] +} + +impl + Grayscale +{ + pub const HEIGHT: usize = H; + pub const WIDTH: usize = W; + + pub fn new() -> Self + { + Self { pixels: [[Level::MIN.into(); W]; H] } + } + + pub fn clear(&mut self) + { + self.pixels + .iter_mut() + .flatten() + .for_each(|level| *level = Level::MIN.into()); + } + + /// + pub fn set_pixel(&mut self, + x: BoundedUsize<0, WB>, // TODO:Change to {W-1} + y: BoundedUsize<0, HB>, // TODO:Change to {H-1} + level: Level) + { + self.pixels[y.get()][x.get()] = level; + } + + pub fn get_pixel(&self, x: BoundedUsize<0, WB>, y: BoundedUsize<0, HB>) + -> &Level + { + &self.pixels[y.get()][x.get()] + } + + pub fn row_iter(&self) -> impl Iterator + { + self.pixels.iter() + } +} + +impl + From<[[Level; W]; H]> for Grayscale +{ + fn from(pixels: [[Level; W]; H]) -> Self + { + Self { pixels } + } +} + +impl Image + for Grayscale +{ + fn width() -> usize + { + W + } + + fn height() -> usize + { + H + } +} diff --git a/src/info.rs b/src/info.rs index 8b4ec0d..c74b9f3 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,14 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// Sealed with Magistamp. + //! This is where the cargo build information can be retrieved from. /// The environment variable defined by Cargo for the name. +#[allow(dead_code)] const NAME: Option<&str> = option_env!("CARGO_PKG_NAME"); /// The environment variable defined by Cargo for the version. +#[allow(dead_code)] const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION"); /// The string to display if a value is not defined during compile time. +#[allow(dead_code)] const NOT_DEFINED: &'static str = "UNDEFINED"; @@ -17,6 +23,7 @@ const NOT_DEFINED: &'static str = "UNDEFINED"; /// set at compile time and comes from the Cargo.toml file. /// /// If a value is not found, then it will return the not defined value. +#[allow(dead_code)] pub fn get_name() -> &'static str { NAME.unwrap_or(NOT_DEFINED) @@ -27,6 +34,7 @@ pub fn get_name() -> &'static str /// This is set at compile time and comes from the Cargo.toml file. /// /// If a value is not found, then it will return the not defined value. +#[allow(dead_code)] pub fn get_version() -> &'static str { VERSION.unwrap_or(NOT_DEFINED) diff --git a/src/main.rs b/src/main.rs index 6428804..252d582 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,8 @@ -//! MicroBadge is a software application suite for the BBC micro:bit v2, designed as a digital conference badge. +// SPDX-License-Identifier: Apache-2.0 +// Sealed with Magistamp. + +//! Examples of my code in an embedded environment. This is targeting the +//! Microbit v2 platform. #![no_std] #![no_main] @@ -7,13 +11,42 @@ mod info; +mod app; +mod bounded; +mod channel; +mod display; +mod image; +mod microbit; +mod renderer; +mod string; +mod switcher; + +mod badge; +mod menu; +mod snake; -use defmt::info; + +use defmt::{error, info, warn}; use embassy_executor::Spawner; -use {embassy_nrf as _}; +use embassy_nrf::gpio::{AnyPin, Input, Pull}; +use embassy_sync::channel::Channel; +use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; +use crate::channel::{ButtonChannel, ButtonSender, FrameChannel}; +use crate::display::Display; +use crate::microbit::Button; +use crate::switcher::Switcher; + + + +// Static channel for frame passing. +pub static FRAME_CHANNEL: FrameChannel = Channel::new(); + +// Create a channel for button events. +pub static BUTTON_CHANNEL: ButtonChannel = Channel::new(); + /// Print the version. @@ -23,10 +56,101 @@ fn print_version() } +#[embassy_executor::task(pool_size = 3)] +async fn button_listener(pin: AnyPin, button_id: Button, sender: ButtonSender) +{ + let mut button: Input<'_> = Input::new(pin, Pull::None); + + loop + { + // Wait for the button to be pressed. + button.wait_for_low().await; + if let Err(_error) = sender.try_send(button_id) + { + warn!("Button event dropped."); + } + + // Debounce the button. + // + // I saw that others use 54ms for this, but I was still getting a + // few phantom signals, so I upped it to 100ms. Plenty fast enough. + Timer::after_millis(100).await; + + // Then wait for the button to be released. + button.wait_for_high().await; + } +} + +#[embassy_executor::task] +async fn display_task(mut display: Display<'static>) +{ + display.present().await; +} + +#[embassy_executor::task] +async fn app_task(mut switcher: Switcher) +{ + switcher.run().await; +} + #[embassy_executor::main] -async fn main(_spawner: Spawner) +async fn main(spawner: Spawner) { // Print the version of our project. print_version(); + + let p = embassy_nrf::init(Default::default()); + + let switcher = + Switcher::new(FRAME_CHANNEL.sender(), BUTTON_CHANNEL.receiver()); + + let display = Display::new(p.P0_21, + p.P0_22, + p.P0_15, + p.P0_24, + p.P0_19, + p.P0_28, + p.P0_11, + p.P0_31, + p.P1_05, + p.P0_30, + FRAME_CHANNEL.receiver()); + + + // Create the Button listeners for the input we are interested in. + // + // Start (the logo) is actually a capacitive touch sensor, but we'll use it + // like a button. + if let Err(error) = spawner.spawn(button_listener(p.P0_14.into(), + Button::A, + BUTTON_CHANNEL.sender())) + { + error!("Unable to spawn Button Listener for A: {}", error); + } + if let Err(error) = spawner.spawn(button_listener(p.P0_23.into(), + Button::B, + BUTTON_CHANNEL.sender())) + { + error!("Unable to spawn Button Listener for B: {}", error); + } + if let Err(error) = spawner.spawn(button_listener(p.P1_04.into(), + Button::Start, + BUTTON_CHANNEL.sender())) + { + error!("Unable to spawn Button Listener for Start: {}", error); + } + + // Create the display task. It will constantly render to the screen. + if let Err(error) = spawner.spawn(display_task(display)) + { + error!("Unable to spawn Display Task: {}", error); + } + + // Create the app task. It will handle running and switching the current + // application. + if let Err(error) = spawner.spawn(app_task(switcher)) + { + error!("Unable to spawn App Task: {}", error); + } } diff --git a/src/menu.rs b/src/menu.rs new file mode 100644 index 0000000..1facbe5 --- /dev/null +++ b/src/menu.rs @@ -0,0 +1,4 @@ +mod menu; +mod renderer; + +pub use crate::menu::menu::Menu; diff --git a/src/menu/menu.rs b/src/menu/menu.rs new file mode 100644 index 0000000..c87b716 --- /dev/null +++ b/src/menu/menu.rs @@ -0,0 +1,125 @@ +use crate::app::{App, AppError, AppId}; +use crate::bounded::WrappedUsize; +use crate::channel::{ButtonReceiver, FrameSender}; +use crate::menu::renderer::{Icon, MenuRenderer}; +use crate::microbit::Button; +use crate::renderer::Renderer; + + + +pub type ItemId = usize; + + + +#[derive(Clone, Copy)] +pub struct Item +{ + id: ItemId, + icon: Icon +} + +pub struct Menu +{ + items: [Option; C], + count: usize, + renderer: MenuRenderer, + button_input: ButtonReceiver +} + + +impl Menu +{ + pub fn add(&mut self, id: ItemId, icon: Icon) -> Result<(), ()> + { + for (index, option_item) in self.items.iter_mut().enumerate() + { + if option_item.is_none() + { + *option_item = Some(Item { id: id, icon: icon }); + self.count += 1; + return Ok(()); + } + } + + // No space in the array! + Err(()) + } + + pub fn remove(&mut self, id: ItemId, icon: Icon) -> Result<(), ()> + { + for (index, option_item) in self.items.iter_mut().enumerate() + { + if let Some(item) = option_item + { + if item.id == id + { + *option_item = None; + self.count -= 1; + return Ok(()); + } + } + } + + // Item wasn't in array! + Err(()) + } +} + +impl App for Menu +{ + fn new(frame_output: FrameSender, button_input: ButtonReceiver) -> Self + { + Self { items: [None; C], + count: 0, + renderer: MenuRenderer::new(frame_output), + button_input } + } + + async fn run(&mut self) -> Result + { + // This cheats for now and assumes that C is the amount of items in the + // menu list. Really it is self.count, but I can't use that as a constant. + let mut selected: WrappedUsize<0, CB> = 0.into(); + + loop + { + // Get the button input. + if let Ok(button) = self.button_input.try_receive() + { + match button + { + Button::A => + { + selected -= 1; + } + Button::B => + { + selected += 1; + } + Button::Start => + { + if let Some(item) = self.items[selected.get()] + { + let result: Result = (item.id as u8).try_into(); + if let Ok(id) = result + { + return Ok(id); + } + } + + return Ok(AppId::Menu); + } + } + } + + // Draw the item currently selected. + if let Some(item) = self.items[selected.get()] + { + self.renderer.draw_icon(item.icon); + } + + // Swap the frame buffer so it can be drawn. + self.renderer.swap_buffers().await; + } + } +} diff --git a/src/menu/renderer.rs b/src/menu/renderer.rs new file mode 100644 index 0000000..926c821 --- /dev/null +++ b/src/menu/renderer.rs @@ -0,0 +1,83 @@ +use crate::bounded::BoundedUsize; +use crate::channel::FrameSender; +use crate::image::{Level, MicrobitImage}; +use crate::renderer::{Col, Renderer, Row}; + + + +pub type Icon = MicrobitImage; + + + +/// +pub struct MenuRenderer +{ + sender: FrameSender, + frame: MicrobitImage +} + + +impl MenuRenderer +{ + pub fn clear(&mut self) + { + self.frame.clear(); + } + + pub fn turn_on(&mut self, x: Col, y: Row) + { + self.frame.set_pixel(x, y, Level::MAX.into()); + } + + pub fn set_level(&mut self, x: Col, y: Row, level: Level) + { + self.frame.set_pixel(x, y, level); + } + + pub fn turn_off(&mut self, x: Col, y: Row) + { + self.frame.set_pixel(x, y, Level::MIN.into()); + } + + pub fn draw_icon(&mut self, icon: Icon) + { + self.frame = icon; + } +} + +impl Renderer for MenuRenderer +{ + fn new(sender: FrameSender) -> Self + { + Self { sender, + frame: MicrobitImage::new() } + } + + async fn swap_buffers(&mut self) + { + self.sender.send(self.frame.clone()).await; + } +} + +// impl crate::snake::Renderer for Renderer +// { +// fn clear(&mut self) +// { +// self.clear(); +// } +// +// fn draw_food(&mut self, position: (usize, usize)) +// { +// self.turn_on(position.0.into(), position.1.into()); +// } +// +// fn draw_snake(&mut self, position: &[(usize, usize)]) +// { +// self.set_level(position[0].0.into(), position[0].1.into(), 5u8.into()); +// +// for segment in position.iter().skip(1) +// { +// self.set_level(segment.0.into(), segment.1.into(), 2u8.into()); +// } +// } +// } diff --git a/src/microbit.rs b/src/microbit.rs new file mode 100644 index 0000000..6c3500a --- /dev/null +++ b/src/microbit.rs @@ -0,0 +1,21 @@ +/// +pub const LED_MATRIX_WIDTH: usize = 5; + +/// +pub const LED_MATRIX_HEIGHT: usize = 5; + + + +/// +#[derive(Clone, Copy)] +pub enum Button +{ + /// + A, + + /// + B, + + /// + Start +} diff --git a/src/renderer.rs b/src/renderer.rs new file mode 100644 index 0000000..281b162 --- /dev/null +++ b/src/renderer.rs @@ -0,0 +1,90 @@ +use crate::bounded::BoundedUsize; +use crate::channel::FrameSender; +use crate::image::{Level, MicrobitImage}; +use crate::microbit::{LED_MATRIX_HEIGHT, LED_MATRIX_WIDTH}; + + + +pub const WIDTH: usize = LED_MATRIX_WIDTH; +pub const HEIGHT: usize = LED_MATRIX_HEIGHT; + + +pub type Row = BoundedUsize<0, { HEIGHT - 1 }>; +pub type Col = BoundedUsize<0, { WIDTH - 1 }>; + + +pub trait Renderer +{ + fn new(sender: FrameSender) -> Self; + async fn swap_buffers(&mut self); +} + + + +/// +pub struct MicrobitRenderer +{ + sender: FrameSender, + frame: MicrobitImage +} + + +impl MicrobitRenderer +{ + pub fn clear(&mut self) + { + self.frame.clear(); + } + + pub fn turn_on(&mut self, x: Col, y: Row) + { + self.frame.set_pixel(x, y, Level::MAX.into()); + } + + pub fn set_level(&mut self, x: Col, y: Row, level: Level) + { + self.frame.set_pixel(x, y, level); + } + + pub fn turn_off(&mut self, x: Col, y: Row) + { + self.frame.set_pixel(x, y, Level::MIN.into()); + } +} + +impl Renderer for MicrobitRenderer +{ + fn new(sender: FrameSender) -> Self + { + Self { sender, + frame: MicrobitImage::new() } + } + + async fn swap_buffers(&mut self) + { + self.sender.send(self.frame.clone()).await; + } +} + +// impl crate::snake::Renderer for Renderer +// { +// fn clear(&mut self) +// { +// self.clear(); +// } +// +// fn draw_food(&mut self, position: (usize, usize)) +// { +// self.turn_on(position.0.into(), position.1.into()); +// } +// +// fn draw_snake(&mut self, position: &[(usize, usize)]) +// { +// self.set_level(position[0].0.into(), position[0].1.into(), 5u8.into()); +// +// for segment in position.iter().skip(1) +// { +// self.set_level(segment.0.into(), segment.1.into(), 2u8.into()); +// } +// } +// } diff --git a/src/snake.rs b/src/snake.rs new file mode 100644 index 0000000..ed47df6 --- /dev/null +++ b/src/snake.rs @@ -0,0 +1,9 @@ +mod game; +mod position; +mod prng; +mod renderer; +mod snake; + + + +pub use self::game::Game; diff --git a/src/snake/game.rs b/src/snake/game.rs new file mode 100644 index 0000000..96c5fec --- /dev/null +++ b/src/snake/game.rs @@ -0,0 +1,283 @@ +use embassy_time::{Instant, Timer}; + +use crate::app::{App, AppError, AppId}; +use crate::bounded::ClampedU8; +use crate::channel::{ButtonReceiver, FrameSender}; +use crate::microbit::Button; +use crate::renderer::Renderer; +use crate::snake::position::*; +use crate::snake::prng::Prng; +use crate::snake::renderer::SnakeRenderer; +use crate::snake::snake::Snake; + + + +/// The different states that the snake game can be in. +#[derive(Clone, Copy, PartialEq)] +pub enum GameState +{ + Difficulty, + Playing, + GameOver, + PulseScore +} + + + +pub struct Game +{ + renderer: SnakeRenderer, + button_input: ButtonReceiver, + game_state: GameState, + rng: Prng, + snake: Snake, + food: Position, + difficulty: ClampedU8<1, 24>, + speed: u8, + score: ClampedU8<0, 24>, + score_shown: ClampedU8<0, 24> +} + + + +impl Game +{ + pub fn reset(&mut self) + { + self.snake = Snake::new(); + self.food = get_random_pos(&mut self.rng, self.snake.get_body()).unwrap_or(Position::new(0, 0)); + self.difficulty = 3.into(); + self.speed = 1; + self.score = 0.into(); + self.score_shown = 0.into(); + } + + fn difficulty_input(&mut self) + { + // Get the button input. + if let Ok(button) = self.button_input.try_receive() + { + match button + { + Button::A => + { + defmt::info!("Button A"); + self.difficulty -= 1; + } + + Button::B => + { + defmt::info!("Button B"); + self.difficulty += 1; + } + + Button::Start => + { + defmt::info!("Button Start"); + self.game_state = GameState::Playing + } + } + } + } + + fn difficulty_render(&mut self) + { + self.renderer.clear(); + + // Light up the chosen difficulty. Turn the linear value into an X,Y on + // the board. + let x: usize = (self.difficulty.get() as usize) % 5; + let y: usize = ((self.difficulty.get() as usize) - x) / 5; + defmt::info!("Draw {} -- ({}, {})", self.difficulty.get(), x, y); + + for row in 0..y + { + for col in 0..5 + { + self.renderer.turn_on(col.into(), row.into()); + } + } + for col in 0..x + { + self.renderer.turn_on(col.into(), y.into()); + } + } + + fn playing_input(&mut self) + { + // Get the button input. + if let Ok(button) = self.button_input.try_receive() + { + match button + { + Button::A => + { + defmt::info!("Button A"); + self.snake.turn(Turn::Left); + } + + Button::B => + { + defmt::info!("Button B"); + self.snake.turn(Turn::Right); + } + + Button::Start => + { + defmt::info!("Button Start"); + self.game_state = GameState::Playing + } + } + } + } + + fn playing_render(&mut self) + { + defmt::info!("Snake size: {}", self.snake.get_size()); + let mut grow: bool = false; + + // Check the next move of the snake for a collision. + let next_pos: Position = self.snake.get_next_position(); + let grow = next_pos == self.food; + let collision = self.snake.check_collision(); + + // Generate the next food pellet. + if grow + { + self.food = get_random_pos(&mut self.rng, self.snake.get_body()) + .unwrap_or_else(|| { + defmt::warn!("Board full, placing food at default."); + Position::new(0, 0) + }); + + // Increase the score if the snake grew. + self.score += 1; + } + + // If the snake collided with itself, then it's gameover time. + if collision + { + self.game_state = GameState::GameOver; + } + else + { + // Move the snake. + self.snake.move_forward(grow); + } + + self.renderer.clear(); + self.renderer.draw_food(&self.food); + self.renderer.draw_snake(self.snake.get_body()); + } + + fn gameover_step(&mut self) + { + // Light up the number of LEDs for the score. + // One at a time. + self.score_shown += 1; + } + + fn pulsescore_step(&mut self) + { + // Pulse the score LEDs. + } +} + +impl App for Game +{ + fn new(frame_output: FrameSender, button_input: ButtonReceiver) -> Self + { + let now = Instant::now().as_ticks() as u32; + + let mut game = Game { + renderer: SnakeRenderer::new(frame_output), + button_input, + game_state: GameState::Difficulty, + rng: Prng::gen_state(200), + snake: Snake::new(), + food: Position::default(), + difficulty: 4.into(), + speed: 1, + score: 0.into(), + score_shown: 0.into() }; + + game.food = get_random_pos(&mut game.rng, game.snake.get_body()).unwrap_or(Position::new(0, 0)); + game + } + + async fn run(&mut self) -> Result + { + loop + { + match self.game_state + { + GameState::Difficulty => + { + self.difficulty_input(); + self.difficulty_render(); + } + GameState::Playing => + { + self.playing_input(); + self.playing_render(); + } + GameState::GameOver => + { + self.gameover_step(); + } + GameState::PulseScore => + { + self.pulsescore_step(); + } + } + + self.renderer.swap_buffers().await; + // Sleep enough to acheive 80Hz. + Timer::after_micros(250000).await; + } + } +} + + + +fn get_random_pos(prng: &mut Prng, exclusion: &[Position]) -> Option +{ + // This doesn't really work if there are no spaces left on the board. + if exclusion.len() == 25 + { + defmt::info!("Default food: {}, {}", 0, 0); + return None; + } + + // Calculate how many slots there are free on the board. + let slots: usize = 25 - exclusion.len(); + + // Fill in the board to find the slots. The board is a flat array here for + // easy enumeration. + // + // Free slots are true; Filled slots are false. + let mut temp_board: [bool; 25] = [true; 25]; + for pos in exclusion.iter() + { + let index: usize = ((5u8 * pos.y.get()) + pos.x.get()) as usize; + temp_board[index] = false; + } + + // Out of all the available slots on the board, pick one randomly. + // Then get it's index. + defmt::info!("Random stuff: {}, {}, {}, {}, {}", (prng.get() as usize), (prng.get() as usize), (prng.get() as usize), (prng.get() as usize), (prng.get() as usize)); + let slot: usize = (prng.get() as usize) % slots; + let pos: usize = temp_board.iter() + .enumerate() + .filter(|&(_, &val)| val) + .nth(slot) + .map(|(i, _)| i) + .unwrap_or(0); + + // Now turn our flat array of a board into a x and y coordinate on + // our grid. + let x: u8 = (pos as u8) % 5; + let y: u8 = ((pos as u8) - x) / 5; + defmt::info!("Next food: {}, {}", x, y); + Some(Position::new(x, y)) +} diff --git a/src/snake/position.rs b/src/snake/position.rs new file mode 100644 index 0000000..f8baae5 --- /dev/null +++ b/src/snake/position.rs @@ -0,0 +1,127 @@ +use crate::bounded::WrappedU8; + + + +/// Global game Direction. +#[derive(Clone, Copy, PartialEq)] +pub enum Direction +{ + /// Towards the top of the board. + Up, + + /// Towarfs the bottom of the board. + Down, + + /// Towards the Left of the board. + Left, + + /// Towards the Right of the board. + Right +} + + +/// The way to Turn the Snake relative to it's global Direction. +/// +/// For example if the Snake is heading Right and it is told to go Left then +/// it's next Direction would be Up. +#[derive(Clone, Copy, PartialEq)] +pub enum Turn +{ + /// Turn left! + Left, + + /// Turn right! + Right, + + /// Go straight. + Straight +} + + +/// Defines a position on the Game board. +/// +/// The game board is a grid with the upper left being (0, 0) +/// with positive X to the right and positive y going down. +/// +/// Below is a 5x5 representation. +/// +/// [ (0,0) (1,0), (2,0), (3,0), (4,0) ] +/// [ (0,1) (1,1), (2,1), (3,1), (4,1) ] +/// [ (0,2) (1,2), (2,2), (3,2), (4,2) ] +/// [ (0,3) (1,3), (2,3), (3,3), (4,3) ] +/// [ (0,4) (1,4), (2,4), (3,4), (4,4) ] +#[derive(Clone, Copy, Default, PartialEq)] +pub struct Position +{ + /// The column we are in. + pub x: WrappedU8<0, 4>, + + /// The row we are in. + pub y: WrappedU8<0, 4> +} + +impl Position +{ + pub fn new(x: u8, y: u8) -> Self + { + Position { x: x.into(), + y: y.into() } + } + + pub fn set(&mut self, new_x: u8, new_y: u8) + { + self.x = new_x.into(); + self.y = new_y.into(); + } +} + + +impl From<&Position> for (usize, usize) +{ + fn from(pos: &Position) -> Self + { + (pos.x.get() as usize, pos.y.get() as usize) + } +} + + + +#[cfg(test)] +mod tests +{ + // use super::bounded::WrappedU8; + use super::Position; + + #[test] + fn position_new_sets_coordinates_correctly() + { + let pos = Position::new(2, 3); + assert_eq!(pos.x.get(), 2); + assert_eq!(pos.y.get(), 3); + } + + #[test] + fn position_set_updates_coordinates() + { + let mut pos = Position::new(0, 0); + pos.set(4, 1); + assert_eq!(pos.x.get(), 4); + assert_eq!(pos.y.get(), 1); + } + + #[test] + fn position_new_wraps_coordinates() + { + let pos = Position::new(5, 6); + assert_eq!(pos.x.get(), 1); + assert_eq!(pos.y.get(), 2); + } + + #[test] + fn position_default_is_zero_zero() + { + let pos = Position::default(); + assert_eq!(pos.x.get(), 0); + assert_eq!(pos.y.get(), 0); + } +} diff --git a/src/snake/prng.rs b/src/snake/prng.rs new file mode 100644 index 0000000..d970843 --- /dev/null +++ b/src/snake/prng.rs @@ -0,0 +1,111 @@ +/// Defines the size of the state array needed for the Well algorithm. +const RNG_STATE_MAX: usize = 16; + +/// A magic number for the Well algorithm. +const WELL_MAGIC_NUM: u32 = 3660128548; // 0xDA442D24 + + + +/// Generate psuedo random numbers. +pub struct Prng +{ + /// This is the state of the Well algorithm. + state: [u32; RNG_STATE_MAX], + + /// The index into the state machine array. + index: usize +} + + + +impl Prng +{ + /// Create a new pseudo random number generator from a given seed of states. + pub fn new(seed: [u32; RNG_STATE_MAX]) -> Self + { + Prng { state: seed, + index: 0 } + } + + /// Create a new pseudo random number generator from a single given seed. + /// + /// A basic XOR PRNG is used to fill in the Well state array. + pub fn gen_state(seed: u32) -> Self + { + let mut input: u32 = seed; + let mut seeded: [u32; RNG_STATE_MAX] = [0; RNG_STATE_MAX]; + + for i in 0..RNG_STATE_MAX { + seeded[i] = Prng::xor_rng(&mut input); +} + + Prng { state: seeded, + index: 0 } + } + + /// Get the next pseudo random number. + /// + /// This is using Well 512 state algorithm from this paper: + /// [Lomont PRNG 2008](https://lomont.org/papers/2008/Lomont_PRNG_2008.pdf) + pub fn get(&mut self) -> u32 + { + let mut a: u32 = self.state[self.index]; + let mut c: u32 = self.state[(self.index + 13) & 15]; + let b: u32 = a ^ c ^ (a << 16) ^ (c << 15); + + c = self.state[(self.index + 9) & 15]; + c = c ^ (c >> 11); + self.state[self.index] = b ^ c; + a = self.state[self.index]; + + let d: u32 = a ^ ((a << 5) & WELL_MAGIC_NUM); + self.index = (self.index + 15) & 15; + a = self.state[self.index]; + self.state[self.index] = a ^ b ^ d ^ (a << 2) ^ (b << 18) ^ (c << 28); + + self.state[self.index] + } + + /// An XOR PRNG algorithm. + fn xor_rng(input: &mut u32) -> u32 + { + *input ^= *input << 13; + *input ^= *input >> 17; + *input ^= *input << 5; + *input + } +} + + + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_gen_state_produces_different_values() { + let mut prng = Prng::gen_state(12345); + let a = prng.get(); + let b = prng.get(); + assert_ne!(a, b); // Should not produce the same number twice + } + + #[test] + fn test_deterministic_output() { + let mut prng1 = Prng::gen_state(42); + let mut prng2 = Prng::gen_state(42); + + for _ in 0..100 { + assert_eq!(prng1.get(), prng2.get()); + } + } + + #[test] + fn test_state_based_initialization() { + let seed = [1u32; 16]; + let mut prng = Prng::new(seed); + let _ = prng.get(); // Just ensure it runs + } +} + diff --git a/src/snake/renderer.rs b/src/snake/renderer.rs new file mode 100644 index 0000000..cef5637 --- /dev/null +++ b/src/snake/renderer.rs @@ -0,0 +1,88 @@ +use crate::bounded::BoundedU8; +use crate::channel::FrameSender; +use crate::bounded::BoundedUsize; +use crate::image::{Level, MicrobitImage}; +use crate::renderer::{Col, Renderer, Row}; +use crate::snake::position::Position; + + +const FOOD_LEVEL: Level = BoundedU8::<0,9>::new(5); +const HEAD_LEVEL: Level = BoundedU8::<0,9>::new(9); +const TAIL_LEVEL: Level = BoundedU8::<0,9>::new(1); + + +/// Defines the functionality needed to render the Snake game. +pub struct SnakeRenderer +{ + sender: FrameSender, + frame: MicrobitImage +} + + +impl SnakeRenderer +{ + pub fn clear(&mut self) + { + self.frame.clear(); + } + + pub fn turn_on(&mut self, x: Col, y: Row) + { + self.frame.set_pixel(x, y, Level::MAX.into()); + } + + pub fn set_level(&mut self, x: Col, y: Row, level: Level) + { + self.frame.set_pixel(x, y, level); + } + + pub fn turn_off(&mut self, x: Col, y: Row) + { + self.frame.set_pixel(x, y, Level::MIN.into()); + } + + /// Draw the food the snake is after. + pub fn draw_food(&mut self, position: &Position) + { + self.set_level((position.x.get() as usize).into(), (position.y.get() as usize).into(), FOOD_LEVEL); + } + + /// Draw the snake. + pub fn draw_snake(&mut self, positions: &[Position]) + { + if positions.is_empty() { + return; + } + + // Draw the whole snake at body brightness + for segment in positions { + self.set_level( + (segment.x.get() as usize).into(), + (segment.y.get() as usize).into(), + TAIL_LEVEL + ); + } + + // Draw the head brighter (it's the last element) + let head = positions[positions.len() - 1]; + self.set_level( + (head.x.get() as usize).into(), + (head.y.get() as usize).into(), + HEAD_LEVEL + ); + } +} + +impl Renderer for SnakeRenderer +{ + fn new(sender: FrameSender) -> Self + { + Self { sender, + frame: MicrobitImage::new() } + } + + async fn swap_buffers(&mut self) + { + self.sender.send(self.frame.clone()).await; + } +} diff --git a/src/snake/snake.rs b/src/snake/snake.rs new file mode 100644 index 0000000..7d4dd66 --- /dev/null +++ b/src/snake/snake.rs @@ -0,0 +1,164 @@ +use crate::bounded::ClampedUsize; +use crate::snake::position::{Direction, Position, Turn}; + + + +/// +const BODY_MAX: usize = 25; + + + +/// The main star of the game. The Snake will start off with a head and a +/// single body piece so that everyone knows that the snake isn't foot. It +/// also makes it easy to tell after a single move what direction the snake is +/// headed in. +pub struct Snake +{ + /// The locations of the body of the snake. + /// The grid is a 5x5 so a full board would + /// be 25 body pieces. + body: [Position; BODY_MAX], + + /// The location in the body array of the head. + head: ClampedUsize<1, {BODY_MAX - 1}>, + + /// The Direction the Snake is headed in. + direction: Direction +} + + + +impl Snake +{ + pub fn new() -> Self + { + let mut snake_body: [Position; BODY_MAX] = + [Position::default(); BODY_MAX]; + + snake_body[1].set(2, 2); // Head + snake_body[0].set(2, 3); // Tail + + Snake { body: snake_body, + head: 1.into(), + direction: Direction::Up } + } + + pub fn check_collision(&self) -> bool + { + // Check if the head is in the same spot as any of the tail pieces. + for segment in 0..self.head.get() + { + if self.body[segment] == self.body[self.head.get()] + { + return true; + } + } + + false + } + + pub fn get_size(&self) -> usize + { + self.head.get() + 1 + } + + pub fn get_body(&self) -> &[Position] + { + defmt::info!("body({})", self.body[0..=self.head.get()].len()); + &self.body[0..=self.head.get()] + } + + pub fn get_next_position(&self) -> Position + { + let mut next_pos: Position = self.body[self.head.get()]; + + match self.direction + { + Direction::Up => + { + next_pos.y -= 1; + } + Direction::Down => + { + next_pos.y += 1; + } + Direction::Left => + { + next_pos.x -= 1; + } + Direction::Right => + { + next_pos.x += 1; + } + } + + next_pos + } + + pub fn move_forward(&mut self, grow: bool) + { + // Get the next position of the Snake. + let next_pos: Position = self.get_next_position(); + + // If the snake is supposed to grow, then we will add the head value to + // a new index in the array. + if grow + { + self.head += 1; + self.body[self.head.get()] = next_pos; + } + else + { + // Ripple the movement forward through the body by copying the + // positions backwards through the array. + let mut prev_val: Position = self.body[self.head.get()]; + self.body[self.head.get()] = next_pos; + + for index in (0..self.head.get()).rev() + { + let temp = self.body[index]; + self.body[index] = prev_val; + prev_val = temp; + } + } + } + + pub fn turn(&mut self, direction: Turn) + { + match direction + { + Turn::Left => + { + self.turn_left(); + } + Turn::Right => + { + self.turn_right(); + } + _ => + {} + } + } + + fn turn_left(&mut self) + { + self.direction = match self.direction + { + Direction::Up => Direction::Left, + Direction::Down => Direction::Right, + Direction::Left => Direction::Down, + Direction::Right => Direction::Up + } + } + + fn turn_right(&mut self) + { + self.direction = match self.direction + { + Direction::Up => Direction::Right, + Direction::Down => Direction::Left, + Direction::Left => Direction::Up, + Direction::Right => Direction::Down + } + } +} diff --git a/src/string.rs b/src/string.rs new file mode 100644 index 0000000..15bb0b0 --- /dev/null +++ b/src/string.rs @@ -0,0 +1,264 @@ +#![no_std] + + +use core::fmt; +use core::ops::{Deref, Index, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}; + + + +/// A UTF-8-safe, fixed-capacity string backed by a `[u8; N]` buffer. +pub struct String +{ + buffer: [u8; N], + len: usize +} + +impl String +{ + /// Creates a new, empty string. + pub const fn new() -> Self + { + Self { buffer: [0; N], + len: 0 } + } + + /// Creates a new string from a `&str`, truncating safely if needed. + pub fn from_str(s: &str) -> Self + { + let mut string = Self::new(); + string.push_str(s); + string + } + + /// Returns the current length of the string in UTF-8 bytes. + pub fn len(&self) -> usize + { + self.len + } + + /// Returns whether the string is empty. + pub fn is_empty(&self) -> bool + { + self.len == 0 + } + + /// Returns the string as a `&str`. + pub fn as_str(&self) -> &str + { + // Safety: Only valid UTF-8 bytes are inserted. + unsafe { core::str::from_utf8_unchecked(&self.buffer[..self.len]) } + } + + /// Returns an iterator over the characters in the string. + pub fn chars(&self) -> core::str::Chars<'_> + { + self.as_str().chars() + } + + /// Clears the string to empty. + pub fn clear(&mut self) + { + self.len = 0; + } + + /// Appends a string slice, truncating safely if it doesn’t fit. + pub fn push_str(&mut self, s: &str) + { + let available = N - self.len; + let bytes = s.as_bytes(); + + if bytes.len() <= available + { + self.buffer[self.len..self.len + bytes.len()].copy_from_slice(bytes); + self.len += bytes.len(); + } + else + { + // Truncate on UTF-8 boundary. + let mut end = available; + while end > 0 && core::str::from_utf8(&bytes[..end]).is_err() + { + end -= 1; + } + self.buffer[self.len..self.len + end].copy_from_slice(&bytes[..end]); + self.len += end; + } + } + + /// Appends a single character, if space permits. + pub fn push(&mut self, c: char) -> bool + { + let size = c.len_utf8(); + if self.len + size > N + { + return false; + } + + let mut buf = [0u8; 4]; + c.encode_utf8(&mut buf); + self.buffer[self.len..self.len + size].copy_from_slice(&buf[..size]); + self.len += size; + true + } + + /// Removes and returns the last character, if any. + pub fn pop(&mut self) -> Option + { + if self.len == 0 + { + return None; + } + + // Walk backward to find the start of the last UTF-8 character. + let mut idx = self.len - 1; + while idx > 0 && (self.buffer[idx] & 0b1100_0000) == 0b1000_0000 + { + idx -= 1; + } + + let ch = core::str::from_utf8(&self.buffer[idx..self.len]).ok() + .and_then(|s| { + s.chars() + .next() + }); + + if let Some(_) = ch + { + self.len = idx; + } + + ch + } + + /// Truncates the string to the first `n` characters, if it contains more. + pub fn truncate_to_char_len(&mut self, max_chars: usize) + { + let mut count = 0; + let mut byte_pos = 0; + + for (i, c) in self.as_str().char_indices() + { + if count == max_chars + { + break; + } + byte_pos = i + c.len_utf8(); + count += 1; + } + + self.len = byte_pos; + } +} + +// Allow dereferencing to &str. +impl Deref for String +{ + type Target = str; + + fn deref(&self) -> &Self::Target + { + self.as_str() + } +} + +// Allow equality comparison with another String of same capacity. +impl PartialEq for String +{ + fn eq(&self, other: &Self) -> bool + { + self.as_str() == other.as_str() + } +} + +// Allow equality comparison with &str. +impl PartialEq<&str> for String +{ + fn eq(&self, other: &&str) -> bool + { + self.as_str() == *other + } +} + +// Implement Index for various slicing types, returning &str. + +impl Index> for String +{ + type Output = str; + + fn index(&self, index: Range) -> &Self::Output + { + &self.as_str()[index] + } +} + +impl Index> for String +{ + type Output = str; + + fn index(&self, index: RangeFrom) -> &Self::Output + { + &self.as_str()[index] + } +} + +impl Index> for String +{ + type Output = str; + + fn index(&self, index: RangeTo) -> &Self::Output + { + &self.as_str()[index] + } +} + +impl Index for String +{ + type Output = str; + + fn index(&self, _: RangeFull) -> &Self::Output + { + self.as_str() + } +} + +impl Index> for String +{ + type Output = str; + + fn index(&self, index: RangeInclusive) -> &Self::Output + { + &self.as_str()[index] + } +} + +impl Index> for String +{ + type Output = str; + + fn index(&self, index: RangeToInclusive) -> &Self::Output + { + &self.as_str()[index] + } +} + +// Implement core::fmt::Write so it can be used with `write!` macros. +impl fmt::Write for String +{ + fn write_str(&mut self, s: &str) -> fmt::Result + { + self.push_str(s); + Ok(()) + } + + fn write_char(&mut self, c: char) -> fmt::Result + { + if self.push(c) + { + Ok(()) + } + else + { + Err(fmt::Error) + } + } +} diff --git a/src/switcher.rs b/src/switcher.rs new file mode 100644 index 0000000..f212fee --- /dev/null +++ b/src/switcher.rs @@ -0,0 +1,166 @@ +use crate::app::{App, AppId}; +use crate::badge::Badge; +use crate::bounded::BoundedU8; +use crate::channel::{ButtonReceiver, FrameSender}; +use crate::image::MicrobitImage; +use crate::menu::Menu; +use crate::renderer::Renderer; +use crate::snake::Game; + + + +pub struct Switcher +{ + menu: Menu<3, 2>, + snake: Game, + badge: Badge +} + +impl Switcher +{ + pub fn new(frame_output: FrameSender, button_input: ButtonReceiver) -> Self + { + let mut menu_app = Menu::new(frame_output, button_input); + menu_app.add(AppId::Snake.into(), + [[BoundedU8::new(0), + BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(9)], + [BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(0)], + [BoundedU8::new(0), + BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(0)], + [BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(9)], + [BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(0)]].into()); + menu_app.add(AppId::Badge.into(), + [[BoundedU8::new(0), + BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(9), + BoundedU8::new(0)], + [BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(9)], + [BoundedU8::new(0), + BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(9), + BoundedU8::new(0)], + [BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(0)], + [BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(0)]].into()); + menu_app.add(AppId::Nfc.into(), + [[BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(0)], + [BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(9), + BoundedU8::new(0)], + [BoundedU8::new(0), + BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(9)], + [BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(9), + BoundedU8::new(0)], + [BoundedU8::new(0), + BoundedU8::new(0), + BoundedU8::new(9), + BoundedU8::new(0), + BoundedU8::new(0)]].into()); + + let snake_app = Game::new(frame_output, button_input); + let badge_app = Badge::new(frame_output, button_input); + + Self { menu: menu_app, + snake: snake_app, + badge: badge_app } + } + + pub async fn run(&mut self) + { + // Default to the menu. + let mut curr_app: AppId = AppId::Menu; + + loop + { + match curr_app + { + AppId::Menu => + { + match self.menu.run().await + { + Ok(next_app) => + { + curr_app = next_app; + } + Err(_error) => + { /* TODO: Log the error. */ } + } + } + + AppId::Snake => + { + match self.snake.run().await + { + Ok(next_app) => + { + curr_app = next_app; + } + Err(error) => + { /* TODO: Log the error. */ } + } + } + + AppId::Badge => + { + match self.badge.run().await + { + Ok(next_app) => + { + curr_app = next_app; + } + Err(error) => + { /* TODO: Log the error. */ } + } + } + + AppId::Nfc => + { + curr_app = AppId::Menu; + } + } + } + } +}