diff --git a/Cargo.lock b/Cargo.lock index 2bc78f4..cd3f268 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -789,6 +789,7 @@ dependencies = [ "profont", "rand", "shared", + "strum 0.26.3", "weact-studio-epd", ] diff --git a/Cargo.toml b/Cargo.toml index d86a330..c7a3f0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ weact-studio-epd = { version = "0.1.2", features = ["blocking"] } shared = { path = "./shared", features = ['trng'] } maze = { path = "./maze" } +strum = { version = "0.26.3", default-features = false, features = ["derive"] } [profile.dev] # Rust debug is too slow. diff --git a/maze/src/lib.rs b/maze/src/lib.rs index fe02044..19e93cc 100644 --- a/maze/src/lib.rs +++ b/maze/src/lib.rs @@ -315,7 +315,6 @@ fn choose_orientation(width: usize, height: usize, trng: &mut impl GenU32) -> Or #[cfg(test)] mod tests { use super::*; - use shared::GenU32; #[test] fn create_maze() { @@ -485,19 +484,19 @@ impl BinaryMap { Self([MazePart::Noop; SIZE]) } - pub fn at(&self, x: usize, y: usize) -> MazePart { - self.0[y * X + x] + pub fn at(&self, x: u16, y: u16) -> MazePart { + self.0[y as usize * X + x as usize] } - pub fn can_move(&self, player: (usize, usize), dir: Direction) -> bool { + pub fn can_move(&self, player: (u16, u16), dir: Direction) -> bool { match dir { Direction::West if player.0 == 0 => false, Direction::West => self.at(player.0 - 1, player.1) != MazePart::Wall, - Direction::East if player.0 == X - 1 => false, + Direction::East if player.0 as usize == X - 1 => false, Direction::East => self.at(player.0 + 1, player.1) != MazePart::Wall, Direction::North if player.1 == 0 => false, Direction::North => self.at(player.0, player.1 - 1) != MazePart::Wall, - Direction::South if player.1 == Y - 1 => false, + Direction::South if player.1 as usize == Y - 1 => false, Direction::South => self.at(player.0, player.1 + 1) != MazePart::Wall, } } @@ -624,8 +623,6 @@ pub enum MazePart { #[cfg(test)] mod print_tests { use super::*; - use std::io::Cursor; - use std::io::Write; #[test] fn print() { diff --git a/src/apps/guess_game.rs b/src/apps/guess_game.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/apps/maze_game.rs b/src/apps/maze_game.rs new file mode 100644 index 0000000..81a4e13 --- /dev/null +++ b/src/apps/maze_game.rs @@ -0,0 +1,89 @@ +use maze::{BinaryMapVisitor, Direction}; + +use crate::Button; + +use super::*; + +pub struct MazeGame { + map: maze::BinaryMap<122, 122, 14884>, + player: (u16, u16), +} + +impl MazeGame { + pub fn new() -> Self { + Self { + map: maze::BinaryMap::new(), + player: (0, 1), + } + } +} + +impl App for MazeGame { + fn start(&mut self, trng: &mut Trng) { + let mut grid = maze::Grid::<60, 60, 3600>::new(); + maze::RecursiveDivision.generate(&mut grid, trng); + BinaryMapVisitor.format(&mut grid, &mut self.map.0); + } + + fn draw(&self, display: &mut Display290TriColor) { + let wall_style = PrimitiveStyleBuilder::new() + .stroke_color(TriColor::Black) + .stroke_width(3) + .fill_color(TriColor::Black) + .build(); + let player_style = PrimitiveStyleBuilder::new() + .stroke_color(TriColor::Red) + .stroke_width(3) + .fill_color(TriColor::Red) + .build(); + + const X_OFFSET: i32 = 2; + const Y_OFFSET: i32 = 2; + + for x in 0..122 { + for y in 0..122 { + match self.map.at(x, y) { + maze::MazePart::Wall => { + let p = Rectangle::new( + Point::new(x as i32 + X_OFFSET, y as i32 + Y_OFFSET), + Size::new(1, 1), + ); + p.draw_styled(&wall_style, display).unwrap(); + } + _ => {} + } + } + let p = Rectangle::new( + Point::new( + self.player.0 as i32 + X_OFFSET, + self.player.1 as i32 + Y_OFFSET, + ), + Size::new(1, 1), + ); + p.draw_styled(&player_style, display).unwrap(); + } + } + + fn update(&mut self, ctx: &Context) -> Action { + let Some(button) = ctx.button_pressed else { + return Action::Noop; + }; + match button { + Button::Up if self.map.can_move(self.player, Direction::North) => { + self.player.1 -= 1; + } + Button::Down if self.map.can_move(self.player, Direction::South) => { + self.player.1 += 1; + } + Button::Left if self.map.can_move(self.player, Direction::West) => { + self.player.0 -= 1; + } + Button::Right if self.map.can_move(self.player, Direction::East) => { + self.player.0 += 1; + } + Button::Back => return Action::GoToMenu, + _ => {} + }; + Action::Noop + } +} diff --git a/src/apps/menu.rs b/src/apps/menu.rs new file mode 100644 index 0000000..ae8ed1e --- /dev/null +++ b/src/apps/menu.rs @@ -0,0 +1,90 @@ +use crate::Button; + +use super::*; +use profont::PROFONT_24_POINT; +use strum::IntoEnumIterator; + +#[derive(Debug, Clone, Copy, Default, PartialEq, strum::EnumIter)] +enum MenuEntry { + #[default] + Labirynth, + Pairs, +} + +impl MenuEntry { + fn as_str(&self) -> &str { + match self { + MenuEntry::Labirynth => "Labirynt", + MenuEntry::Pairs => "Pary", + } + } + + fn position(&self) -> Point { + match self { + MenuEntry::Labirynth => Point::new(8, 68), + MenuEntry::Pairs => Point::new(38, 68), + } + } +} + +pub struct Menu { + selected: MenuEntry, +} + +impl Menu { + pub fn new() -> Menu { + Menu { + selected: MenuEntry::default(), + } + } +} + +impl App for Menu { + fn start(&mut self, _trng: &mut Trng) {} + + fn update(&mut self, ctx: &Context) -> Action { + let Some(button) = ctx.button_pressed else { + return Action::Noop; + }; + match button { + Button::Up => match self.selected { + MenuEntry::Labirynth => return Action::Noop, + MenuEntry::Pairs => { + self.selected = MenuEntry::Labirynth; + } + }, + Button::Down => match self.selected { + MenuEntry::Labirynth => { + self.selected = MenuEntry::Pairs; + } + MenuEntry::Pairs => return Action::Noop, + }, + Button::Circle => match self.selected { + MenuEntry::Labirynth => return Action::StartMaze, + MenuEntry::Pairs => return Action::StartPairs, + }, + _ => return Action::Noop, + }; + Action::Noop + } + + fn draw(&self, display: &mut Display290TriColor) { + let style = MonoTextStyle::new(&PROFONT_24_POINT, TriColor::Black); + let selected_style = MonoTextStyle::new(&PROFONT_24_POINT, TriColor::Red); + + MenuEntry::iter().for_each(|entry| { + let _ = Text::with_text_style( + entry.as_str(), + entry.position(), + if entry == self.selected { + selected_style + } else { + style + }, + TextStyle::default(), + ) + .draw(display) + .unwrap(); + }); + } +} diff --git a/src/apps/mod.rs b/src/apps/mod.rs new file mode 100644 index 0000000..f80866d --- /dev/null +++ b/src/apps/mod.rs @@ -0,0 +1,67 @@ +use embedded_graphics::{ + geometry::{Point, Size}, + mono_font::MonoTextStyle, + primitives::{PrimitiveStyleBuilder, Rectangle, StyledDrawable}, + text::{Text, TextStyle}, + Drawable, +}; +use esp_hal::rng::Trng; +use maze_game::MazeGame; +use menu::Menu; +use weact_studio_epd::{graphics::Display290TriColor, TriColor}; + +use crate::Context; + +pub mod guess_game; +pub mod maze_game; +pub mod menu; + +pub enum Action { + Noop, + GoToMenu, + StartMaze, + StartPairs, +} + +pub enum Application { + Menu(Menu), + Maze(maze_game::MazeGame), +} + +impl Application { + pub fn update(&mut self, ctx: &Context, trng: &mut Trng) { + let action = match self { + Self::Menu(menu) => menu.update(ctx), + Self::Maze(maze) => maze.update(ctx), + }; + match action { + Action::StartMaze => { + let mut maze = MazeGame::new(); + maze.start(trng); + *self = Application::Maze(maze); + } + Action::GoToMenu => { + let mut menu = Menu::new(); + menu.start(trng); + *self = Application::Menu(menu); + } + Action::Noop => {} + Action::StartPairs => {} + } + } + + pub fn draw(&self, display: &mut Display290TriColor) { + match self { + Self::Menu(menu) => menu.draw(display), + Self::Maze(maze) => maze.draw(display), + } + } +} + +pub trait App { + fn start(&mut self, trng: &mut Trng); + + fn draw(&self, display: &mut Display290TriColor); + + fn update(&mut self, ctx: &Context) -> Action; +} diff --git a/src/main.rs b/src/main.rs index 550e7ff..18e5a79 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,11 @@ #![no_std] #![no_main] -use core::fmt::Write; +use apps::{menu::Menu, Application}; use display_interface_spi::SPIInterface; use embedded_graphics::{ geometry::Point, mono_font::MonoTextStyle, - pixelcolor::Rgb565, - primitives::{Line, PrimitiveStyle}, text::{Text, TextStyle}, Drawable, }; @@ -16,19 +14,22 @@ use esp_backtrace as _; use esp_hal::{ clock::ClockControl, delay::Delay, - gpio::{Input, Io, Level, Output, NO_PIN}, + gpio::{ + Gpio0, Gpio1, Gpio2, Gpio3, Gpio4, Gpio5, Gpio6, Gpio7, GpioPin, Input, Io, Level, Output, + NO_PIN, + }, peripherals::Peripherals, prelude::*, rng::Trng, spi::{master::Spi, SpiMode}, system::SystemControl, }; -use heapless::String; -use maze::{BinaryMap, BinaryMapVisitor}; use profont::PROFONT_24_POINT; use weact_studio_epd::{graphics::Display290TriColor, TriColor}; use weact_studio_epd::{graphics::DisplayRotation, WeActStudio290TriColorDriver}; +mod apps; + #[entry] fn main() -> ! { let peripherals = Peripherals::take(); @@ -44,9 +45,9 @@ fn main() -> ! { log::info!("Intializing SPI Bus..."); - // Pins for Seeedstudio XIAO ESP32-C6 - let sclk = io.pins.gpio19; // D8 / GPIO19 + // Pins for WeAct let mosi = io.pins.gpio18; // D10 / GPIO18 + let sclk = io.pins.gpio19; // D8 / GPIO19 let cs = io.pins.gpio20; // D9 / GPIO20 let dc = io.pins.gpio21; // D3 / GPIO21 let rst = io.pins.gpio22; // D4 / GPIO22 @@ -84,7 +85,7 @@ fn main() -> ! { let style = MonoTextStyle::new(&PROFONT_24_POINT, TriColor::Black); let _ = Text::with_text_style( - "Hello World!", + "Welcome in Reddy", Point::new(8, 68), style, TextStyle::default(), @@ -93,51 +94,95 @@ fn main() -> ! { driver.full_update(&display).unwrap(); - log::info!("Sleeping for 5s..."); + log::info!("Sleeping for 0.5s..."); driver.sleep().unwrap(); - delay.delay(5_000.millis()); + delay.delay(500.millis()); + + let kbd = Keyboard { + gpio0: io.pins.gpio0, + gpio1: io.pins.gpio1, + gpio2: io.pins.gpio2, + gpio3: io.pins.gpio3, + gpio4: io.pins.gpio4, + gpio5: io.pins.gpio5, + gpio6: io.pins.gpio6, + gpio7: io.pins.gpio7, + }; + + let mut ctx = Context { + button_pressed: None, + }; + let mut app = Application::Menu(Menu::new()); - let mut n: u8 = 0; loop { log::info!("Wake up!"); driver.wake_up().unwrap(); display.clear(TriColor::White); - let mut string_buf = String::<30>::new(); - write!(string_buf, "Hello World {}!", n).unwrap(); - let _ = Text::with_text_style(&string_buf, Point::new(8, 68), style, TextStyle::default()) - .draw(&mut display) - .unwrap(); - string_buf.clear(); + ctx.button_pressed = kbd.pressed(); + app.update(&ctx, &mut trng); + app.draw(&mut display); // TODO: try fast update? driver.full_update(&display).unwrap(); - n = n.wrapping_add(1); // Wrap from 0..255 - log::info!("Sleeping for 5s..."); driver.sleep().unwrap(); delay.delay(5_000.millis()); } } -struct MazeGame { - map: maze::BinaryMap<122, 122, 14884>, +/// Buttons +/// +/// +-------------------------------+ +/// | A | Up | B | X | +/// | Left | Circle | Right | Y | +/// | C | Down | D | Z | +/// | M1 | M2 | M3 | Back | +/// +-------------------------------+ +#[derive(Debug, Copy, Clone)] +pub enum Button { + // row 1 + A, + Up, + B, + X, + // row 2 + Left, + Circle, + Right, + Y, + // row 3 + C, + Down, + D, + Z, + // row 4 + M1, + M2, + M3, + Back, } -impl MazeGame { - pub fn new() -> Self { - Self { - map: maze::BinaryMap::new(), - } - } - - pub fn start(&mut self, trng: &mut Trng) { - let mut grid = maze::Grid::<60, 60, 3600>::new(); - maze::RecursiveDivision.generate(&mut grid, trng); - BinaryMapVisitor.format(&mut grid, &mut self.map.0); - } - - pub fn draw(&self, dispay: Display290TriColor) {} +pub struct Context { + button_pressed: Option