Build make

This commit is contained in:
eraden 2024-09-16 12:14:45 +02:00
commit 353e59d66a
15 changed files with 2294 additions and 0 deletions

18
.cargo/config.toml Normal file
View File

@ -0,0 +1,18 @@
[target.riscv32imac-unknown-none-elf]
runner = "espflash flash --monitor"
[env]
ESP_LOGLEVEL="INFO"
[build]
rustflags = [
# Required to obtain backtraces (e.g. when using the "esp-backtrace" crate.)
# NOTE: May negatively impact performance of produced code
"-C", "force-frame-pointers",
]
target = "riscv32imac-unknown-none-elf"
[unstable]
build-std = ["core"]

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

1190
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

35
Cargo.toml Normal file
View File

@ -0,0 +1,35 @@
[package]
name = "readdy"
version = "0.1.0"
edition = "2021"
[dependencies]
display-interface-spi = "0.5.0"
embedded-graphics = "0.8.1"
embedded-hal-bus = "0.2.0"
esp-backtrace = { version = "0.13.0", features = ["esp32c6", "exception-handler", "panic-handler", "println"] }
esp-hal = { version = "0.19.0", features = ["esp32c6", "embedded-hal"] }
esp-println = { version = "0.10.0", default-features = false, features = ["esp32c6", "log", "jtag-serial"] }
heapless = "0.8.0"
log = { version = "0.4.21" }
nutype = { version = "0.5.0", default-features = false }
profont = "0.7.0"
rand = { version = "0.8.5", default-features = false, features = ["small_rng"] }
weact-studio-epd = { version = "0.1.2", features = ["blocking"] }
shared = { path = "./shared", features = ['trng'] }
maze = { path = "./maze" }
[profile.dev]
# Rust debug is too slow.
# For debug builds always builds with some optimization
opt-level = "s"
[profile.release]
codegen-units = 1 # LLVM can perform better optimizations using a single thread
debug = 2
debug-assertions = false
incremental = false
lto = 'fat'
opt-level = 's'
overflow-checks = false

3
build.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
println!("cargo:rustc-link-arg-bins=-Tlinkall.x");
}

1
maze/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target

271
maze/Cargo.lock generated Normal file
View File

@ -0,0 +1,271 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "convert_case"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hash32"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
dependencies = [
"byteorder",
]
[[package]]
name = "heapless"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
dependencies = [
"hash32",
"stable_deref_trait",
]
[[package]]
name = "kinded"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce4bdbb2f423660b19f0e9f7115182214732d8dd5f840cd0a3aee3e22562f34c"
dependencies = [
"kinded_macros",
]
[[package]]
name = "kinded_macros"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a13b4ddc5dcb32f45dac3d6f606da2a52fdb9964a18427e63cd5ef6c0d13288d"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "libc"
version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]]
name = "maze"
version = "0.1.0"
dependencies = [
"bitflags",
"heapless",
"nutype",
"pretty_assertions",
"rand",
"shared",
]
[[package]]
name = "nutype"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8789358e2d6cdffb0cb170c7802ee7548beb8067ed643f3122fa36c335f3c64"
dependencies = [
"nutype_macros",
]
[[package]]
name = "nutype_macros"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93a3e222ba1f06a03552910fe89a232a1661dcf8ad4c837531fb199828d0916b"
dependencies = [
"cfg-if",
"kinded",
"proc-macro2",
"quote",
"syn",
"urlencoding",
]
[[package]]
name = "ppv-lite86"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]]
name = "pretty_assertions"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
dependencies = [
"diff",
"yansi",
]
[[package]]
name = "proc-macro2"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "shared"
version = "0.1.0"
dependencies = [
"nutype",
"rand",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "syn"
version = "2.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

17
maze/Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
name = "maze"
version = "0.1.0"
edition = "2021"
[dependencies]
nutype = { version = "0.5.0", default-features = false }
heapless = "0.8.0"
shared = { path = "../shared" }
bitflags = "2.6.0"
rand = { version = "0.8.5", default-features = false, features = ["small_rng"] }
[dev-dependencies]
rand = { version = "0.8.5", features = ["getrandom", "small_rng", "std", "std_rng"] }
shared = { path = "../shared", features = ['full-rand'] }
pretty_assertions = "1"

4
maze/rust-toolchain.toml Normal file
View File

@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
components = ["rust-src"]
# targets = ["riscv32imac-unknown-none-elf"]

582
maze/src/lib.rs Normal file
View File

@ -0,0 +1,582 @@
#![cfg_attr(not(test), no_std)]
use core::fmt::Write;
use bitflags::bitflags;
use nutype::nutype;
use rand::{Rng, SeedableRng};
use shared::GenU32;
pub type Coords = (usize, usize);
bitflags! {
#[derive(Default, Debug, Copy, Clone, PartialEq)]
pub struct Cell: u8 {
const NORTH = 0b0001;
const SOUTH = 0b0010;
const EAST = 0b0100;
const WEST = 0b1000;
}
}
impl Cell {
pub fn south_blocked(&self) -> bool {
self.contains(Cell::SOUTH)
}
pub fn east_blocked(&self) -> bool {
self.contains(Cell::EAST)
}
}
#[derive(Default, Copy, Clone, Debug)]
pub struct CellStatus {
visited: bool,
marked: bool,
}
impl CellStatus {
pub fn visited(&self) -> bool {
self.visited
}
pub fn marked(&self) -> bool {
self.marked
}
pub fn visit(&mut self) {
self.visited = true;
}
pub fn mark(&mut self) {
self.marked = true;
}
}
#[nutype(derive(Deref, Debug, Clone, Copy, From, PartialEq, Eq, Hash, PartialOrd, Ord))]
pub struct XPos(usize);
#[nutype(derive(Deref, Debug, Clone, Copy, From, PartialEq, Eq, Hash, PartialOrd, Ord))]
pub struct YPos(usize);
#[derive(Debug, Clone)]
pub struct Grid<const X: usize, const Y: usize, const SIZE: usize> {
pub cells: [Cell; SIZE],
pub cell_statuses: [CellStatus; SIZE],
}
impl<const X: usize, const Y: usize, const SIZE: usize> Grid<X, Y, SIZE> {
pub fn new() -> Self {
Self {
cells: core::array::from_fn(|_x| Cell::default()),
cell_statuses: core::array::from_fn(|_x| CellStatus::default()),
}
}
pub fn x(&self) -> usize {
X
}
pub fn y(&self) -> usize {
Y
}
pub fn mark_cell(&mut self, coords: Coords) {
self.get_cell_status_mut(coords).mark()
}
pub fn is_cell_visited(&self, coords: Coords) -> bool {
self.get_cell_status(coords).visited()
}
pub fn is_cell_marked(&self, coords: Coords) -> bool {
self.get_cell_status(coords).marked()
}
pub fn get_cell_status(&self, coords: Coords) -> CellStatus {
let (x, y) = coords;
self.cell_statuses[y * X + x].clone()
}
pub fn is_carved(&self, coords: Coords, direction: Cell) -> bool {
let (x, y) = coords;
self.cells[y * X + x].contains(direction)
}
pub fn carve_passage(&mut self, coords: Coords, direction: Cell) -> TransitResult<Coords> {
let (x, y) = coords;
let (nx, ny) = self.get_next_cell_coords(coords, direction)?;
match direction {
Cell::NORTH => {
self.cells[y * X + x] |= Cell::NORTH;
self.cells[ny * X + nx] |= Cell::SOUTH;
}
Cell::SOUTH => {
self.cells[y * X + x] |= Cell::SOUTH;
self.cells[ny * X + nx] |= Cell::NORTH;
}
Cell::EAST => {
self.cells[y * X + x] |= Cell::EAST;
self.cells[ny * X + nx] |= Cell::WEST;
}
Cell::WEST => {
self.cells[y * X + x] |= Cell::WEST;
self.cells[ny * X + nx] |= Cell::EAST;
}
_ => (),
}
self.visit_cell(coords);
self.visit_cell((nx, ny));
Ok((nx, ny))
}
pub fn get_next_cell_coords(&self, coords: Coords, direction: Cell) -> TransitResult<Coords> {
self.validate_transit(coords, direction)?;
let (x, y) = coords;
let (nx, ny) = match direction {
Cell::NORTH => (x, y - 1),
Cell::SOUTH => (x, y + 1),
Cell::WEST => (x - 1, y),
Cell::EAST => (x + 1, y),
_ => (x, y),
};
Ok((nx, ny))
}
fn visit_cell(&mut self, coords: Coords) {
self.get_cell_status_mut(coords).visit()
}
fn get_cell_status_mut(&mut self, coords: Coords) -> &mut CellStatus {
let (x, y) = coords;
&mut self.cell_statuses[y * X + x]
}
fn validate_transit(&self, coords: Coords, direction: Cell) -> TransitResult<()> {
let (x, y) = coords;
let reason = match direction {
Cell::NORTH if y < 1 => "First row in the grid cannot go North",
Cell::SOUTH if y + 1 == Y => "Last row in the grid cannot go South",
Cell::WEST if x < 1 => "First cell in a row cannot go West",
Cell::EAST if x + 1 == X => "Last column in the grid cannot go East",
_ => return Ok(()),
};
Err(TransitError {
coords: (x, y),
reason,
})
}
}
type TransitResult<T> = Result<T, TransitError>;
#[derive(Debug, Clone)]
pub struct TransitError {
pub coords: Coords,
pub reason: &'static str,
}
enum Orientation {
Horizontal,
Vertical,
}
/// The "Recursive Division" algorithm for generating mazes
///
/// According to [Wikipedia](https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_division_method)
/// and [James Buck's maze generation blogs](https://weblog.jamisbuck.org/2011/1/12/maze-generation-recursive-division-algorithm)
/// this algorithm should be implemented as a wall adder, as opposed to the rest of the algorithms
/// in this library which are designated as "passage carvers". In a nutshell, the idea is to
/// recursively divide the original grid with no walls — call this a "chamber" — into smaller
/// subchambers with randomly positioned walls and single passages within them. This fractal nature
/// makes this algorithm novel. You could theoretically continue the process indefinitely at
/// progressively finer and finer levels of detail on demand. This may be useful when developing a
/// game where a player wanders from one section of the maze to another without a need to store the
/// entire maze in the memory.
///
/// My implementation of this algorithm is a bit different from the above. The design of this
/// library supports only the passage-carving technique (mixing both techniques would lead to an
/// uglier API and less efficient generation mechanism, which I tested using benchmarks). Given that
/// I've slightly modified the algorithm to be rather a "passage carver". The method and its fractal
/// nature remain though. Of course, the generation process would be less fancy this way, but this
/// library has nothing to do with visualizing the generation progress step-by-step. If you're
/// interested in this side of the maze generation world, feel free to check my other program for
/// the terminal called [Daedalus](https://github.com/unrenamed/daedalus).
///
/// It's also worth mentioning that the algorithm's fractal nature leads to some visual artifacts
/// and bottlenecks like a single passage between two sections that effectively divide the entire
/// maze into two distinct regions, thus making it easy to spot the passage and work backward to a
/// solution.
pub struct RecursiveDivision<const X: usize, const Y: usize, const SIZE: usize>;
impl<const X: usize, const Y: usize, const SIZE: usize> RecursiveDivision<X, Y, SIZE> {
fn divide(
grid: &mut Grid<X, Y, SIZE>,
x: usize,
y: usize,
ax: usize,
ay: usize,
trng: &mut impl GenU32,
) {
// Calculate subfield width
let w = ax - x + 1;
// Calculate subfield height
let h = ay - y + 1;
if w < 2 || h < 2 {
if w > 1 {
// Carve passages till the horizontal end of the subfield
for cx in x..ax {
grid.carve_passage((cx, y), Cell::EAST).unwrap();
}
} else if h > 1 {
// Carve passages till the vertical end of the subfield
for cy in y..ay {
grid.carve_passage((x, cy), Cell::SOUTH).unwrap();
}
}
return;
}
let mut rng = rand::rngs::SmallRng::seed_from_u64(trng.random() as u64);
// Which way a subfield with the given dimensions ought to be bisected
let orientation = choose_orientation(w, h, trng);
// Get X and Y coordinates of a cell where a passage will be carved
let px = rng.gen_range(x..ax);
let py = rng.gen_range(y..ay);
// Define what direction is corresponding to the wall orientation
let dir = match orientation {
Orientation::Horizontal => Cell::SOUTH,
Orientation::Vertical => Cell::EAST,
};
// Carve passage
let (nx, ny) = grid.carve_passage((px, py), dir).unwrap();
// Determine the bounds of the subfields and get them split
match orientation {
Orientation::Horizontal => {
// Top subfield
Self::divide(grid, x, y, ax, py, trng);
// Bottom subfield
Self::divide(grid, x, ny, ax, ay, trng);
}
Orientation::Vertical => {
// Left subfield
Self::divide(grid, x, y, px, ay, trng);
// Right subfield
Self::divide(grid, nx, y, ax, ay, trng);
}
}
}
/// An implementation of the "Recursive Division" algorithm for generating mazes
///
/// It works like this:
///
/// 1. Begins with an original grid as a working field
///
/// 2. Bisects the field either horizontally or vertically by carving a passage
/// through the wall from a random cell
///
/// 3. Repeats step #2 with the areas on either side of the wall where the passage
/// was carved.
///
/// 4. Continues, recursively, until the maze reaches the desired resolution
pub fn generate(&mut self, grid: &mut Grid<X, Y, SIZE>, trng: &mut impl GenU32) {
RecursiveDivision::divide(grid, 0, 0, X - 1, Y - 1, trng);
}
}
fn choose_orientation(width: usize, height: usize, trng: &mut impl GenU32) -> Orientation {
if width < height {
return Orientation::Horizontal;
}
if height < width {
return Orientation::Vertical;
}
if trng.random() % 1 == 0 {
Orientation::Horizontal
} else {
Orientation::Vertical
}
}
#[cfg(test)]
mod tests {
use super::*;
use shared::GenU32;
#[test]
fn create_maze() {
let maze = Grid::<4, 4, 16>::new();
}
#[test]
fn generate_4x4() {
let mut trng = rand::thread_rng();
let mut grid = Grid::<4, 4, 16>::new();
RecursiveDivision.generate(&mut grid, &mut trng);
}
}
/// A formatter to emit the maze as ASCII with narrow passages
///
/// # Example:
///
/// ```no_test
/// _______
/// | ___| |
/// |_ | _|
/// | | |_ |
/// |_______|
/// ```
pub struct AsciiNarrow<const X: usize, const Y: usize, const SIZE: usize>;
/// An implementation of an broad ASCII formatter
impl<const X: usize, const Y: usize, const SIZE: usize> AsciiNarrow<X, Y, SIZE> {
/// Generate ASCII image which:
///
/// * takes 2 characters per 1 cell
/// * each row ends with newline character
/// * has 1 additional row for top border
/// * each row starts with border
fn format(&self, grid: &Grid<X, Y, SIZE>, result: &mut impl Write) {
write!(result, " ").unwrap();
(0..(X * 2 - 1)).into_iter().for_each(|_| {
write!(result, "_").unwrap();
});
writeln!(result, " ").unwrap();
(0..Y).into_iter().for_each(|y| {
write!(result, "|").unwrap();
(0..X).into_iter().for_each(|x| {
if grid.is_carved((x, y), Cell::SOUTH) {
write!(result, " ").unwrap();
} else {
write!(result, "_").unwrap();
}
if grid.is_carved((x, y), Cell::EAST) {
if grid.is_carved((x, y), Cell::SOUTH)
|| grid.is_carved((x + 1, y), Cell::SOUTH)
{
write!(result, " ").unwrap();
} else {
write!(result, "_").unwrap();
}
} else {
write!(result, "|").unwrap();
}
});
writeln!(result).unwrap();
});
//
}
}
/// A formatter to emit the maze as ASCII with broad passages and "+" to contact walls
///
/// # Example:
///
/// ```no_test
/// +---+---+---+---+
/// | |
/// +---+---+ + +
/// | | |
/// + +---+ + +
/// | | | |
/// + +---+---+ +
/// | | |
/// +---+---+---+---+
/// ```
pub struct AsciiBroad<const X: usize, const Y: usize, const SIZE: usize>;
/// An implementation of a narrow ASCII formatter
impl<const X: usize, const Y: usize, const SIZE: usize> AsciiBroad<X, Y, SIZE> {
// const CELL_SIZE: usize = 3;
// const LINE_SIZE: usize = X * Self::CELL_SIZE + 1;
// const BUFFER_SIZE: usize = Y + Self::CELL_SIZE * Self::LINE_SIZE;
fn format(&self, grid: &Grid<X, Y, SIZE>, output: &mut impl Write) {
write!(output, "#").unwrap();
(0..X).into_iter().for_each(|_| {
write!(output, "##").unwrap();
});
writeln!(output, "").unwrap();
let mut top_line = heapless::String::<SIZE>::new();
let mut bottom_line = heapless::String::<SIZE>::new();
for y in 0..Y {
top_line.clear();
write!(top_line, "#").unwrap();
bottom_line.clear();
write!(bottom_line, "#").unwrap();
for x in 0..X {
write!(top_line, " ").unwrap();
let east_boundary = if grid.is_carved((x, y), Cell::EAST) {
" "
} else {
"#"
};
write!(top_line, "{east_boundary}").unwrap();
let south_boundary = if grid.is_carved((x, y), Cell::SOUTH) {
" "
} else {
"#"
};
write!(bottom_line, "{south_boundary}#").unwrap();
if x == X - 1 {
writeln!(output, "{top_line}").unwrap();
writeln!(output, "{bottom_line}").unwrap();
}
}
}
}
}
struct BufWriter<'s>(&'s mut [MazePart]);
impl<'s> BufWriter<'s> {
pub fn push(&mut self, part: MazePart) {
self.0[0] = part;
self.0 = &mut self.0[1..];
}
}
pub struct BinaryMap<const X: usize, const Y: usize, const SIZE: usize>;
impl<const X: usize, const Y: usize, const SIZE: usize> BinaryMap<X, Y, SIZE> {
fn format(&self, grid: &Grid<X, Y, SIZE>, buffer: &mut [MazePart]) {
let push = |p: MazePart, buffer: &mut [MazePart]| {
buffer[0] = p;
&mut buffer[1..]
};
let copy = |src: &[MazePart], dst: &mut [MazePart]| {
src.iter()
.take_while(|p| **p != MazePart::Noop)
.for_each(|p| {
dst = push(*p, dst);
});
};
buffer = push(MazePart::Wall, buffer);
(0..X).into_iter().for_each(|_| {
push(MazePart::Wall, buffer);
push(MazePart::Wall, buffer);
});
let mut top_line = [MazePart::Noop; SIZE];
let mut bottom_line = [MazePart::Noop; SIZE];
for y in 0..Y {
top_line.fill(MazePart::Noop);
bottom_line.fill(MazePart::Noop);
let tl = &mut top_line;
let bl = &mut bottom_line;
push(MazePart::Wall, tl);
push(MazePart::Wall, bl);
for x in 0..X {
push(MazePart::Passage, tl);
push(
if grid.is_carved((x, y), Cell::EAST) {
MazePart::Passage
} else {
MazePart::Wall
},
tl,
);
push(
if grid.is_carved((x, y), Cell::SOUTH) {
MazePart::Passage
} else {
MazePart::Wall
},
bl,
);
}
copy(&top_line, buffer);
copy(&bottom_line, buffer);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MazePart {
Noop,
Passage,
Wall,
Player,
}
#[cfg(test)]
mod print_tests {
use super::*;
use std::io::Cursor;
use std::io::Write;
#[test]
fn print() {
let mut trng = rand::thread_rng();
let mut grid = Grid::<4, 4, 16>::new();
RecursiveDivision.generate(&mut grid, &mut trng);
let mut result = String::with_capacity(40);
AsciiNarrow.format(&mut grid, &mut result);
println!("{result}");
}
#[test]
fn heapless_narrow() {
let mut trng = rand::thread_rng();
let mut grid = Grid::<4, 4, 16>::new();
RecursiveDivision.generate(&mut grid, &mut trng);
// 4 rows * 2 + 1 newline + 1 border = 10
// 4 rows + 1 header row = 5
let mut result = heapless::String::<50>::new();
AsciiNarrow.format(&mut grid, &mut result);
println!("{result}");
}
#[test]
fn heapless_broad() {
let mut trng = rand::thread_rng();
let mut grid = Grid::<4, 4, 16>::new();
RecursiveDivision.generate(&mut grid, &mut trng);
// 4 rows * 3 = 12
// 4 rows + 3 = 12
let mut result = heapless::String::<144>::new();
AsciiBroad.format(&mut grid, &mut result);
println!("{result}");
}
#[test]
fn large() {
let mut trng = rand::thread_rng();
let mut grid = Grid::<20, 20, 400>::new();
RecursiveDivision.generate(&mut grid, &mut trng);
let mut result = String::with_capacity(40);
AsciiBroad.format(&mut grid, &mut result);
println!("{result}");
}
}

1
maze/test.sh Executable file
View File

@ -0,0 +1 @@
cargo test --target x86_64-unknown-linux-gnu $*

4
rust-toolchain.toml Normal file
View File

@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
components = ["rust-src"]
targets = ["riscv32imac-unknown-none-elf"]

14
shared/Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "shared"
version = "0.1.0"
edition = "2021"
[features]
default-features = []
trng = ["esp-hal"]
full-rand = [ 'rand' ]
[dependencies]
nutype = { version = "0.5.0", default-features = false }
esp-hal = { version = "0.19.0", features = ["esp32c6", "embedded-hal"], optional = true }
rand = { version = "0.8.5", features = ["getrandom", "small_rng", "std", "std_rng"], optional = true }

19
shared/src/lib.rs Normal file
View File

@ -0,0 +1,19 @@
#![cfg_attr(not(test), no_std)]
pub trait GenU32 {
fn random(&mut self) -> u32;
}
#[cfg(feature = "trng")]
impl<'l> GenU32 for esp_hal::rng::Trng<'l> {
fn random(&mut self) -> u32 {
self.random()
}
}
#[cfg(feature = "full-rand")]
impl GenU32 for rand::rngs::ThreadRng {
fn random(&mut self) -> u32 {
use rand::RngCore;
self.next_u32()
}
}

134
src/main.rs Normal file
View File

@ -0,0 +1,134 @@
#![no_std]
#![no_main]
use core::fmt::Write;
use display_interface_spi::SPIInterface;
use embedded_graphics::{
geometry::Point,
mono_font::MonoTextStyle,
text::{Text, TextStyle},
Drawable,
};
use embedded_hal_bus::spi::ExclusiveDevice;
use esp_backtrace as _;
use esp_hal::{
clock::ClockControl,
delay::Delay,
gpio::{Input, Io, Level, Output, NO_PIN},
peripherals::Peripherals,
prelude::*,
rng::Trng,
spi::{master::Spi, SpiMode},
system::SystemControl,
};
use heapless::String;
use profont::PROFONT_24_POINT;
use shared::GenU32;
use weact_studio_epd::{graphics::Display290BlackWhite, Color};
use weact_studio_epd::{graphics::DisplayRotation, WeActStudio290BlackWhiteDriver};
#[entry]
fn main() -> ! {
let peripherals = Peripherals::take();
let system = SystemControl::new(peripherals.SYSTEM);
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
let clocks = ClockControl::max(system.clock_control).freeze();
let delay = Delay::new(&clocks);
let mut trng = Trng::new(peripherals.RNG, peripherals.ADC1);
let _ = trng.random();
esp_println::logger::init_logger_from_env();
log::info!("Intializing SPI Bus...");
// Pins for Seeedstudio XIAO ESP32-C6
let sclk = io.pins.gpio19; // D8 / GPIO19
let mosi = io.pins.gpio18; // D10 / GPIO18
let cs = io.pins.gpio20; // D9 / GPIO20
let dc = io.pins.gpio21; // D3 / GPIO21
let rst = io.pins.gpio22; // D4 / GPIO22
let busy = io.pins.gpio23; // D5 / GPIO23
let spi_bus = Spi::new(peripherals.SPI2, 100.kHz(), SpiMode::Mode0, &clocks).with_pins(
Some(sclk),
Some(mosi),
NO_PIN,
NO_PIN, // cs is handled by the exclusive device
);
// Convert pins into InputPins and OutputPins
/*
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
*/
let cs = Output::new(cs, Level::High);
let busy = Input::new(busy, esp_hal::gpio::Pull::Up);
let dc = Output::new(dc, Level::Low);
let rst = Output::new(rst, Level::High);
log::info!("Intializing SPI Device...");
let spi_device = ExclusiveDevice::new(spi_bus, cs, delay).expect("SPI device initialize error");
let spi_interface = SPIInterface::new(spi_device, dc);
// Setup EPD
log::info!("Intializing EPD...");
let mut driver = WeActStudio290BlackWhiteDriver::new(spi_interface, busy, rst, delay);
let mut display = Display290BlackWhite::new();
display.set_rotation(DisplayRotation::Rotate90);
driver.init().unwrap();
let style = MonoTextStyle::new(&PROFONT_24_POINT, Color::Black);
let _ = Text::with_text_style(
"Hello World!",
Point::new(8, 68),
style,
TextStyle::default(),
)
.draw(&mut display);
driver.full_update(&display).unwrap();
log::info!("Sleeping for 5s...");
driver.sleep().unwrap();
delay.delay(5_000.millis());
let mut n: u8 = 0;
loop {
log::info!("Wake up!");
driver.wake_up().unwrap();
display.clear(Color::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();
// 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());
}
}
#[derive(Debug, Default)]
struct MazeGame {
maze: Option<maze::Grid<100, 100, 10_000>>,
}
impl MazeGame {
pub fn start(&mut self, trng: &mut Trng) {
let mut maze = maze::Grid::new();
maze::RecursiveDivision.generate(&mut maze, trng);
self.maze = Some(maze);
}
}