semi-working snake game

This commit is contained in:
2025-09-19 13:08:16 -06:00
parent 2012e68995
commit d00e644100
14 changed files with 252 additions and 40 deletions

84
Cargo.lock generated
View File

@@ -18,6 +18,7 @@ version = "0.1.0"
dependencies = [
"abi_sys",
"embedded-graphics",
"rand_core 0.9.3",
"shared",
"spin",
"talc",
@@ -956,6 +957,14 @@ dependencies = [
"heapless",
]
[[package]]
name = "embedded-snake"
version = "0.0.3"
dependencies = [
"embedded-graphics",
"rand_core 0.9.3",
]
[[package]]
name = "embedded-storage"
version = "0.3.1"
@@ -1198,9 +1207,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.15.5"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
[[package]]
name = "heapless"
@@ -1227,12 +1236,12 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indexmap"
version = "2.11.1"
version = "2.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921"
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
dependencies = [
"equivalent",
"hashbrown 0.15.5",
"hashbrown 0.16.0",
]
[[package]]
@@ -1266,9 +1275,9 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.78"
version = "0.3.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738"
checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e"
dependencies = [
"once_cell",
"wasm-bindgen",
@@ -1319,6 +1328,7 @@ dependencies = [
"once_cell",
"panic-probe",
"portable-atomic",
"rand",
"shared",
"spin",
"st7365p-lcd",
@@ -1416,9 +1426,9 @@ checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]]
name = "libredox"
version = "0.1.9"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
dependencies = [
"bitflags 2.9.4",
"libc",
@@ -1806,6 +1816,15 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_core 0.9.3",
]
[[package]]
name = "rand_core"
version = "0.6.4"
@@ -1954,18 +1973,27 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.219"
version = "1.0.225"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.225"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
version = "1.0.225"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516"
dependencies = [
"proc-macro2",
"quote",
@@ -2026,6 +2054,16 @@ dependencies = [
"rgb",
]
[[package]]
name = "snake"
version = "0.1.0"
dependencies = [
"abi",
"embedded-graphics",
"embedded-snake",
"rand",
]
[[package]]
name = "spin"
version = "0.10.0"
@@ -2371,9 +2409,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasm-bindgen"
version = "0.2.101"
version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b"
checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819"
dependencies = [
"cfg-if",
"once_cell",
@@ -2384,9 +2422,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.101"
version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb"
checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c"
dependencies = [
"bumpalo",
"log",
@@ -2398,9 +2436,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.101"
version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d"
checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -2408,9 +2446,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.101"
version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa"
checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32"
dependencies = [
"proc-macro2",
"quote",
@@ -2421,9 +2459,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.101"
version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1"
checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf"
dependencies = [
"unicode-ident",
]

View File

@@ -1,6 +1,6 @@
[workspace]
resolver = "3"
members = ["kernel", "abi", "shared", "user-apps/calculator"]
members = ["kernel", "abi", "shared", "user-apps/calculator", "user-apps/snake"]
[profile.release]
debug = true

View File

@@ -9,3 +9,4 @@ shared = { path = "../shared" }
abi_sys = { path = "../abi_sys" }
talc = "4.4.3"
spin = "0.10.0"
rand_core = "0.9.3"

View File

@@ -1,7 +1,8 @@
#![no_std]
use abi_sys::draw_iter;
use abi_sys::{RngRequest, draw_iter, gen_rand};
pub use abi_sys::{get_key, lock_display, print, sleep};
use rand_core::RngCore;
pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers};
use talc::*;
@@ -71,3 +72,32 @@ pub mod display {
}
}
}
pub struct Rng;
impl RngCore for Rng {
fn next_u32(&mut self) -> u32 {
let mut req = RngRequest::U32(0);
gen_rand(&mut req);
if let RngRequest::U32(i) = req {
return i;
};
0
}
fn next_u64(&mut self) -> u64 {
let mut req = RngRequest::U64(0);
gen_rand(&mut req);
if let RngRequest::U64(i) = req {
return i;
};
0
}
fn fill_bytes(&mut self, dst: &mut [u8]) {
let mut req = RngRequest::Bytes {
ptr: dst.as_mut_ptr(),
len: dst.len(),
};
gen_rand(&mut req);
}
}

View File

@@ -24,10 +24,11 @@ pub enum CallAbiTable {
LockDisplay = 2,
DrawIter = 3,
GetKey = 4,
GenRand = 5,
}
impl CallAbiTable {
pub const COUNT: usize = 5;
pub const COUNT: usize = 6;
}
pub type PrintAbi = extern "Rust" fn(msg: &str);
@@ -84,3 +85,21 @@ pub fn get_key() -> Option<KeyEvent> {
f()
}
}
pub type GenRand = extern "Rust" fn(req: &mut RngRequest);
#[repr(C)]
pub enum RngRequest {
U32(u32),
U64(u64),
Bytes { ptr: *mut u8, len: usize },
}
#[allow(unused)]
pub fn gen_rand(req: &mut RngRequest) {
unsafe {
let ptr = CALL_ABI_TABLE[CallAbiTable::GenRand as usize];
let f: GenRand = core::mem::transmute(ptr);
f(req)
}
}

View File

@@ -1,4 +1,6 @@
kernel: calculator
kernel:
cargo run --bin kernel
calculator:
RUSTFLAGS="-C link-arg=--noinhibit-exec" cargo build --bin calculator --profile release-binary
snake:
RUSTFLAGS="-C link-arg=--noinhibit-exec" cargo build --bin snake --profile release-binary

View File

@@ -79,6 +79,7 @@ embedded-text = "0.7.2"
embedded-layout = "0.4.2"
kolibri-embedded-gui = "0.1.0"
rand = { version = "0.9.0", default-features = false }
once_cell = { version = "1.21.3", default-features = false }
static_cell = "2.1.1"
bitflags = "2.9.4"

View File

@@ -1,8 +1,8 @@
use core::sync::atomic::Ordering;
use abi_sys::{DrawIterAbi, GetKeyAbi, LockDisplay, PrintAbi, SleepAbi};
use embassy_rp::clocks::clk_sys_freq;
use abi_sys::{DrawIterAbi, GetKeyAbi, LockDisplay, PrintAbi, RngRequest, SleepAbi};
use core::{ptr::slice_from_raw_parts, sync::atomic::Ordering};
use embassy_rp::clocks::{RoscRng, clk_sys_freq};
use embedded_graphics::{Pixel, draw_target::DrawTarget, pixelcolor::Rgb565};
use rand::Rng;
use shared::keyboard::KeyEvent;
use crate::{
@@ -23,10 +23,10 @@ pub extern "Rust" fn print(msg: &str) {
pub extern "Rust" fn sleep(ms: u64) {
let cycles_per_ms = clk_sys_freq() / 1000;
for _ in 0..ms {
for _ in 0..cycles_per_ms {
cortex_m::asm::nop();
}
let total_cycles = ms * cycles_per_ms as u64;
for _ in 0..total_cycles {
cortex_m::asm::nop();
}
}
@@ -42,3 +42,16 @@ pub extern "Rust" fn draw_iter(pixels: &[Pixel<Rgb565>]) {
pub extern "Rust" fn get_key() -> Option<KeyEvent> {
unsafe { KEY_CACHE.dequeue() }
}
pub extern "Rust" fn gen_rand(req: &mut RngRequest) {
let mut rng = RoscRng;
match req {
RngRequest::U32(i) => *i = rng.next_u32(),
RngRequest::U64(i) => *i = rng.next_u64(),
RngRequest::Bytes { ptr, len } => {
let slice: &mut [u8] = unsafe { core::slice::from_raw_parts_mut(*ptr, *len) };
rng.fill_bytes(slice);
}
}
}

View File

@@ -59,6 +59,6 @@ pub async fn display_handler(mut display: DISPLAY) {
}
}
Timer::after_millis(32).await; // 30 fps
Timer::after_millis(10).await;
}
}

View File

@@ -57,6 +57,7 @@ pub async unsafe fn load_binary(name: &ShortFileName) -> Result<EntryFn, &str> {
(CallAbiTable::LockDisplay, abi::lock_display as usize),
(CallAbiTable::DrawIter, abi::draw_iter as usize),
(CallAbiTable::GetKey, abi::get_key as usize),
(CallAbiTable::GenRand, abi::gen_rand as usize),
];
assert!(entries.len() == CallAbiTable::COUNT);

View File

@@ -15,10 +15,10 @@ use embedded_graphics::{
text::Text,
};
use embedded_layout::{
align::{horizontal, vertical},
View,
align::{Align, horizontal, vertical},
layout::linear::LinearLayout,
object_chain::Chain,
prelude::*,
prelude::Chain,
};
#[panic_handler]
@@ -37,7 +37,7 @@ pub extern "Rust" fn _start() {
}
pub fn main() {
print("Starting Async Calculator app");
print("Starting Calculator app");
let mut display = Display;
let mut input = vec!['e', 'x', 'p', 'r', ':', ' '];

View File

@@ -0,0 +1,10 @@
[package]
name = "snake"
version = "0.1.0"
edition = "2024"
[dependencies]
abi = { path = "../../abi" }
embedded-graphics = "0.8.1"
embedded-snake = { path = "../../../embedded-snake-rs" }
rand = { version = "0.9.0", default-features = false }

28
user-apps/snake/build.rs Normal file
View File

@@ -0,0 +1,28 @@
//! This build script copies the `memory.x` file from the crate root into
//! a directory where the linker can always find it at build time.
//! For many projects this is optional, as the linker always searches the
//! project root directory -- wherever `Cargo.toml` is. However, if you
//! are using a workspace or have a more complicated build setup, this
//! build script becomes required. Additionally, by requesting that
//! Cargo re-run the build script whenever `memory.x` is changed,
//! updating `memory.x` ensures a rebuild of the application with the
//! new memory settings.
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn main() {
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("../memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
println!("cargo:rerun-if-changed=memory.x");
println!("cargo:rustc-link-arg-bins=-Tmemory.x");
}

View File

@@ -0,0 +1,69 @@
#![no_std]
#![no_main]
extern crate alloc;
use abi::{
KeyCode, Rng,
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
get_key, lock_display, print, sleep,
};
use alloc::format;
use core::panic::PanicInfo;
use embedded_graphics::{pixelcolor::Rgb565, prelude::RgbColor};
use embedded_snake::{Direction, SnakeGame};
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
print(&format!(
"user panic: {} @ {:?}",
info.message(),
info.location(),
));
loop {}
}
#[unsafe(no_mangle)]
pub extern "Rust" fn _start() {
main()
}
const CELL_SIZE: usize = 8;
pub fn main() {
print("Starting Snake app");
let mut display = Display;
let mut game = SnakeGame::<100, Rgb565, Rng>::new(
SCREEN_WIDTH as u16,
SCREEN_HEIGHT as u16,
CELL_SIZE as u16,
CELL_SIZE as u16,
Rng,
Rgb565::BLACK,
Rgb565::GREEN,
Rgb565::RED,
50,
);
loop {
if let Some(event) = get_key() {
let direction = match event.key {
KeyCode::Up => Direction::Up,
KeyCode::Down => Direction::Down,
KeyCode::Right => Direction::Right,
KeyCode::Left => Direction::Left,
KeyCode::Esc => return,
_ => Direction::None,
};
game.set_direction(direction);
}
// ensure all draws show up at once
lock_display(true);
game.pre_draw(&mut display);
game.draw(&mut display);
lock_display(false);
sleep(15);
}
}