From 1181759ace9b7c544c49eb2f0fa53bf1d318b041 Mon Sep 17 00:00:00 2001 From: Myrddin Dundragon Date: Fri, 18 Jul 2025 10:47:10 -0400 Subject: [PATCH] One major weekend coding session later... I hate doing this as one giant commit, but I was under the gun and had to just code as fast as I could, so some common software development things had to be skipped. Mostly git commits. Here we have several mini applications that reside on the microbit. * Menu - The main app switching presentation. * Badge - A name scroller. * Snake - A snake game. The switcher task is the main running task of the program. All the logic goes through here. It will pass the running off to different apps. When an app returns from its main function it will return the next app to switch to. The display task is a PWM LED matrix updater. It will turn on the LEDs for what ever frame is passed to it. It keeps a backing frame for draw calls that occur between new frames arriving. There are several button listeners for input handling. Everything is sent using channels, button input and presentation frames. --- src/app.rs | 95 ++ src/badge.rs | 4 + src/badge/badge.rs | 275 +++++ src/badge/renderer.rs | 83 ++ src/bounded.rs | 2461 +++++++++++++++++++++++++++++++++++++++++ src/channel.rs | 17 + src/display.rs | 144 +++ src/image.rs | 97 ++ src/info.rs | 8 + src/main.rs | 132 ++- src/menu.rs | 4 + src/menu/menu.rs | 125 +++ src/menu/renderer.rs | 83 ++ src/microbit.rs | 21 + src/renderer.rs | 90 ++ src/snake.rs | 9 + src/snake/game.rs | 283 +++++ src/snake/position.rs | 127 +++ src/snake/prng.rs | 111 ++ src/snake/renderer.rs | 88 ++ src/snake/snake.rs | 164 +++ src/string.rs | 264 +++++ src/switcher.rs | 166 +++ 23 files changed, 4847 insertions(+), 4 deletions(-) create mode 100644 src/app.rs create mode 100644 src/badge.rs create mode 100644 src/badge/badge.rs create mode 100644 src/badge/renderer.rs create mode 100644 src/bounded.rs create mode 100644 src/channel.rs create mode 100644 src/display.rs create mode 100644 src/image.rs create mode 100644 src/menu.rs create mode 100644 src/menu/menu.rs create mode 100644 src/menu/renderer.rs create mode 100644 src/microbit.rs create mode 100644 src/renderer.rs create mode 100644 src/snake.rs create mode 100644 src/snake/game.rs create mode 100644 src/snake/position.rs create mode 100644 src/snake/prng.rs create mode 100644 src/snake/renderer.rs create mode 100644 src/snake/snake.rs create mode 100644 src/string.rs create mode 100644 src/switcher.rs 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; + } + } + } + } +}