commit 884f0125bc9a0f74f399fc1e80c827699b25e9ee Author: Jessie Williams Date: Sat Mar 5 16:44:55 2022 -0500 initial commit Signed-off-by: Jessie Williams diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..2c694b5 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,15 @@ +[build] +target = "wasm32-unknown-unknown" + +[target.wasm32-unknown-unknown] +rustflags = [ + # Import memory from WASM-4 + "-C", "link-arg=--import-memory", + "-C", "link-arg=--initial-memory=65536", + "-C", "link-arg=--max-memory=65536", + + # Temporary workaround for #255 issue. + # Reserve 8192 bytes of Rust stack space, offset from 6560. + # Bump this value, 16-byte aligned, if the framebuffer gets corrupted. + "-C", "link-arg=-zstack-size=14752", +] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b1b34f5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target +dist +.DS_Store diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..085eeed --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,48 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "buddy-alloc" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ff9f338986406db85e2b5deb40a9255b796ca03a194c7457403d215173f3fd5" + +[[package]] +name = "cart" +version = "0.1.0" +dependencies = [ + "buddy-alloc", + "fastrand", + "lazy_static", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..023d9e6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "cart" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib"] + +[dependencies] +buddy-alloc = { version = "0.4.1", optional = true } +fastrand = "1.6.0" +lazy_static = "1.4.0" + +[profile.release] +opt-level = "z" +lto = true + +[features] +# use `--no-default-features` or comment out next line to disable allocator +default = ["buddy-alloc"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..22023c1 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# akesi + +A game written in Rust for the [WASM-4](https://wasm4.org) fantasy console. + +## Building + +Build the cart by running: + +```shell +cargo build --release +``` + +Then run it with: + +```shell +w4 run target/wasm32-unknown-unknown/release/cart.wasm +``` + +For more info about setting up WASM-4, see the [quickstart guide](https://wasm4.org/docs/getting-started/setup?code-lang=rust#quickstart). + +## Links + +- [Documentation](https://wasm4.org/docs): Learn more about WASM-4. +- [Snake Tutorial](https://wasm4.org/docs/tutorials/snake/goal): Learn how to build a complete game + with a step-by-step tutorial. +- [GitHub](https://github.com/aduros/wasm4): Submit an issue or PR. Contributions are welcome! diff --git a/sprites/akesi.png b/sprites/akesi.png new file mode 100644 index 0000000..17ce3d0 Binary files /dev/null and b/sprites/akesi.png differ diff --git a/src/alloc.rs b/src/alloc.rs new file mode 100644 index 0000000..92d488e --- /dev/null +++ b/src/alloc.rs @@ -0,0 +1,16 @@ +use buddy_alloc::{BuddyAllocParam, FastAllocParam, NonThreadsafeAlloc}; + +// These values can be tuned +const FAST_HEAP_SIZE: usize = 4 * 1024; // 4 KB +const HEAP_SIZE: usize = 16 * 1024; // 16 KB +const LEAF_SIZE: usize = 16; + +static mut FAST_HEAP: [u8; FAST_HEAP_SIZE] = [0u8; FAST_HEAP_SIZE]; +static mut HEAP: [u8; HEAP_SIZE] = [0u8; HEAP_SIZE]; + +#[global_allocator] +static ALLOC: NonThreadsafeAlloc = unsafe { + let fast_param = FastAllocParam::new(FAST_HEAP.as_ptr(), FAST_HEAP_SIZE); + let buddy_param = BuddyAllocParam::new(HEAP.as_ptr(), HEAP_SIZE, LEAF_SIZE); + NonThreadsafeAlloc::new(fast_param, buddy_param) +}; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9d7c99a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,217 @@ +#[cfg(feature = "buddy-alloc")] +mod alloc; +mod palette; +mod snake; +mod sprites; +mod wasm4; + +use crate::snake::{Point, Snake}; +use fastrand::Rng; +use lazy_static::lazy_static; +use std::sync::Mutex; + +lazy_static! { + static ref SNAKE_GAME: Mutex = Mutex::new(Game::new()); +} + +enum State { + Title, + Playing, + Dead, +} + +pub struct Game { + state: State, + rng: Rng, + snake: Snake, + frame_count: u32, + prev_gamepad: u8, + fruit: Point, +} + +impl Game { + pub fn new() -> Self { + let rng = Rng::with_seed(235); + Self { + state: State::Title, + snake: Snake::new(), + frame_count: 0, + prev_gamepad: 0, + fruit: Point { + x: rng.i32(0..20), + y: rng.i32(0..20), + }, + rng, + } + } + + pub fn reset(&mut self) { + self.snake = Snake::new(); + self.state = State::Playing; + } + + pub fn game_input(&mut self) { + let gamepad = unsafe { *wasm4::GAMEPAD1 }; + let just_pressed = gamepad & (gamepad ^ self.prev_gamepad); + + if just_pressed & wasm4::BUTTON_UP != 0 { + self.snake.up(); + } + + if just_pressed & wasm4::BUTTON_DOWN != 0 { + self.snake.down(); + } + + if just_pressed & wasm4::BUTTON_LEFT != 0 { + self.snake.left(); + } + + if just_pressed & wasm4::BUTTON_RIGHT != 0 { + self.snake.right(); + } + + self.prev_gamepad = gamepad; + } + + pub fn update(&mut self) { + self.frame_count += 1; + + match self.state { + State::Title => self.show_title(), + State::Playing => self.step_game(), + State::Dead => self.show_score(), + } + } + + pub fn show_title(&mut self) { + wasm4::text("akesi", 48, 8); + wasm4::text("Press x", 48, 128); + wasm4::text("From Within", 32, 144); + + palette::set_draw_color(0x30); + + wasm4::blit( + &sprites::AKESI, + 48, + 24, + sprites::AKESI_WIDTH, + sprites::AKESI_HEIGHT, + wasm4::BLIT_1BPP, + ); + + let gamepad = unsafe { *wasm4::GAMEPAD1 }; + let just_pressed = gamepad & (gamepad ^ self.prev_gamepad); + + if just_pressed & wasm4::BUTTON_1 != 0 { + wasm4::tone(0 | (1000 << 16), 4 | (20 << 8), 80, wasm4::TONE_TRIANGLE); + self.reset(); + } + + if just_pressed & wasm4::BUTTON_UP != 0 { + palette::en4(); + } + + if just_pressed & wasm4::BUTTON_DOWN != 0 { + palette::moonlight(); + } + + if just_pressed & wasm4::BUTTON_LEFT != 0 { + palette::cafe_nouveau(); + } + + if just_pressed & wasm4::BUTTON_RIGHT != 0 { + palette::amanita(); + } + + self.prev_gamepad = gamepad; + } + + pub fn show_score(&mut self) { + wasm4::text("Score", 48, 48); + wasm4::text("Press x to\nplay again", 36, 128); + + let mut x = 16; + let mut y = 64; + for _ in 0..self.snake.body.len() - 3 { + palette::set_draw_color(0x4320); + wasm4::blit(&sprites::FRUIT, x, y, 8, 8, wasm4::BLIT_2BPP); + x += 8; + if x > 140 { + x = 16; + y += 8; + } + } + + let gamepad = unsafe { *wasm4::GAMEPAD1 }; + let just_pressed = gamepad & (gamepad ^ self.prev_gamepad); + + if just_pressed & wasm4::BUTTON_1 != 0 { + wasm4::tone(0 | (1000 << 16), 4 | (20 << 8), 80, wasm4::TONE_TRIANGLE); + self.reset(); + } + self.prev_gamepad = gamepad; + } + + pub fn step_game(&mut self) { + self.game_input(); + + if self.snake.is_dead() { + wasm4::tone( + 460 | (140 << 16), + 94 | (48 << 8) | (62 << 16), + 80, + wasm4::TONE_TRIANGLE, + ); + self.state = State::Dead; + } + + let speed = match self.snake.body.len() { + 3..=8 => 18, + 9..=15 => 14, + 16..=24 => 12, + 25..=32 => 10, + _ => 5, + }; + + if self.frame_count % speed == 0 { + let dropped_pos = self.snake.update(); + + if self.snake.body[0] == self.fruit { + if let Some(last_pos) = dropped_pos { + self.snake.body.push(last_pos); + } + self.fruit.x = self.rng.i32(0..20); + self.fruit.y = self.rng.i32(0..20); + + while self.snake.inside(&self.fruit) { + self.fruit.x = self.rng.i32(0..20); + self.fruit.y = self.rng.i32(0..20); + } + + wasm4::tone(140 | (250 << 16), 12 | (6 << 8), 80, wasm4::TONE_TRIANGLE); + } + } + self.snake.draw(); + + palette::set_draw_color(0x4320); + wasm4::blit( + &sprites::FRUIT, + self.fruit.x * 8, + self.fruit.y * 8, + 8, + 8, + wasm4::BLIT_2BPP, + ); + } +} + +#[no_mangle] +fn start() { + palette::moonlight(); +} + +#[no_mangle] +fn update() { + palette::set_draw_color(0x2); + SNAKE_GAME.lock().expect("game_state").update(); +} diff --git a/src/palette.rs b/src/palette.rs new file mode 100644 index 0000000..21d0ae6 --- /dev/null +++ b/src/palette.rs @@ -0,0 +1,31 @@ +use crate::wasm4; + +pub fn set_draw_color(idx: u16) { + unsafe { *wasm4::DRAW_COLORS = idx.into() } +} + +pub fn set_palette(palette: [u32; 4]) { + unsafe { + *wasm4::PALETTE = palette; + } +} + +pub fn moonlight() { + set_palette([0xf3eaab, 0x86a0b7, 0x3d476a, 0x19152a]); +} + +pub fn en4() { + set_palette([0xfbf7f3, 0xe5b083, 0x426e5d, 0x20283d]); +} + +pub fn amanita() { + set_palette([0xf1eee3, 0xccc2b8, 0xb34750, 0x4d0f40]); +} + +pub fn cafe_nouveau() { + set_palette([0xf8e6d0, 0xc08e70, 0x683a34, 0x200816]); +} + +/*pub fn coldfire() { + set_palette([0x46425e, 0x5b768d, 0xd17c7c, 0xf6c6a8]); +}*/ diff --git a/src/snake.rs b/src/snake.rs new file mode 100644 index 0000000..a569871 --- /dev/null +++ b/src/snake.rs @@ -0,0 +1,89 @@ +use crate::{palette::set_draw_color, wasm4}; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct Point { + pub x: i32, + pub y: i32, +} + +pub struct Snake { + pub body: Vec, + pub direction: Point, +} + +impl Snake { + pub fn new() -> Self { + Self { + body: vec![ + Point { x: 2, y: 0 }, + Point { x: 1, y: 0 }, + Point { x: 0, y: 0 }, + ], + direction: Point { x: 1, y: 0 }, + } + } + + pub fn is_dead(&self) -> bool { + self.inside(&self.body[0]) + } + + pub fn inside(&self, pt: &Point) -> bool { + self.body + .iter() + .skip(1) + .any(|body_section| body_section == pt) + } + + pub fn down(&mut self) { + if self.direction.y == 0 { + self.direction = Point { x: 0, y: 1 }; + } + } + + pub fn up(&mut self) { + if self.direction.y == 0 { + self.direction = Point { x: 0, y: -1 }; + } + } + + pub fn left(&mut self) { + if self.direction.x == 0 { + self.direction = Point { x: -1, y: 0 }; + } + } + + pub fn right(&mut self) { + if self.direction.x == 0 { + self.direction = Point { x: 1, y: 0 }; + } + } + + pub fn update(&mut self) -> Option { + self.body.insert( + 0, + Point { + x: (self.body[0].x + self.direction.x) % 20, + y: (self.body[0].y + self.direction.y) % 20, + }, + ); + + if self.body[0].x < 0 { + self.body[0].x = 19; + } + + if self.body[0].y < 0 { + self.body[0].y = 19; + } + self.body.pop() + } + + pub fn draw(&self) { + set_draw_color(0x43); + for &Point { x, y } in self.body.iter() { + wasm4::rect(x * 8, y * 8, 8, 8); + } + + set_draw_color(0x4); + wasm4::rect(self.body[0].x * 8, self.body[0].y * 8, 8, 8); + } +} diff --git a/src/sprites.rs b/src/sprites.rs new file mode 100644 index 0000000..7cfef65 --- /dev/null +++ b/src/sprites.rs @@ -0,0 +1,57 @@ +pub const FRUIT: [u8; 16] = [ + 0x00, 0xa0, 0x02, 0x00, 0x0e, 0xf0, 0x36, 0x5c, 0xd6, 0x57, 0xd5, 0x57, 0x35, 0x5c, 0x0f, 0xf0, +]; + +// akesi +pub const AKESI_WIDTH: u32 = 64; +pub const AKESI_HEIGHT: u32 = 96; +pub const AKESI: [u8; 768] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xc0, 0x00, 0x00, 0x07, 0x80, 0x00, + 0x00, 0x1f, 0xe0, 0x00, 0x00, 0x0f, 0xc0, 0x00, 0x00, 0x3f, 0xf0, 0x00, 0x00, 0x1f, 0xe0, 0x00, + 0x00, 0x3f, 0xf0, 0x00, 0x00, 0x3f, 0xf0, 0x00, 0x00, 0x3f, 0xf0, 0x00, 0x00, 0x3f, 0xf0, 0x00, + 0x00, 0x3f, 0xf0, 0x00, 0x00, 0x3f, 0xf0, 0x00, 0x00, 0x1f, 0xe0, 0x00, 0x00, 0x3f, 0xf0, 0x00, + 0x00, 0x0f, 0xc0, 0x00, 0x00, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x0f, 0xc0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xc0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xfe, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, 0x00, 0x00, + 0x00, 0x00, 0x3f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x7f, 0xe3, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc1, 0xff, 0xf8, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xc0, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xff, 0xff, 0x80, 0x1f, 0xfc, 0x1f, 0x80, + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, + 0x00, 0x07, 0xf8, 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x07, 0xf8, 0x00, 0x01, 0xfe, 0x00, 0x00, + 0x00, 0x07, 0xf8, 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x07, 0xf8, 0x00, 0x01, 0xfe, 0x00, 0x00, + 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x00, 0x07, 0xf8, 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x07, 0xf8, 0x00, 0x01, 0xfe, 0x00, 0x00, + 0x00, 0x07, 0xf8, 0x00, 0x03, 0xfe, 0x00, 0x00, 0x00, 0x07, 0xf8, 0x00, 0x03, 0xfe, 0x00, 0x00, + 0x00, 0x07, 0xf8, 0x00, 0x03, 0xfe, 0x00, 0x00, 0x00, 0x07, 0xf8, 0x00, 0x03, 0xfc, 0x00, 0x00, + 0x00, 0x07, 0xf8, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x00, 0x07, 0xf8, 0x00, 0x03, 0xfc, 0x00, 0x00, + 0x00, 0x07, 0xf8, 0x00, 0x07, 0xfc, 0x03, 0xc0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, + 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x01, 0xff, 0x01, 0xff, 0xe0, 0x00, 0x00, + 0x00, 0x01, 0xff, 0x87, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0x8f, 0xff, 0x80, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xfe, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7f, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xf8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3f, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xc0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0f, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; diff --git a/src/wasm4.rs b/src/wasm4.rs new file mode 100644 index 0000000..c3dde0d --- /dev/null +++ b/src/wasm4.rs @@ -0,0 +1,222 @@ +// +// WASM-4: https://wasm4.org/docs + +#![allow(unused)] + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Platform Constants │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +pub const SCREEN_SIZE: u32 = 160; + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Memory Addresses │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +pub const PALETTE: *mut [u32; 4] = 0x04 as *mut [u32; 4]; +pub const DRAW_COLORS: *mut u16 = 0x14 as *mut u16; +pub const GAMEPAD1: *const u8 = 0x16 as *const u8; +pub const GAMEPAD2: *const u8 = 0x17 as *const u8; +pub const GAMEPAD3: *const u8 = 0x18 as *const u8; +pub const GAMEPAD4: *const u8 = 0x19 as *const u8; +pub const MOUSE_X: *const i16 = 0x1a as *const i16; +pub const MOUSE_Y: *const i16 = 0x1c as *const i16; +pub const MOUSE_BUTTONS: *const u8 = 0x1e as *const u8; +pub const SYSTEM_FLAGS: *mut u8 = 0x1f as *mut u8; +pub const FRAMEBUFFER: *mut [u8; 6400] = 0xa0 as *mut [u8; 6400]; + +pub const BUTTON_1: u8 = 1; +pub const BUTTON_2: u8 = 2; +pub const BUTTON_LEFT: u8 = 16; +pub const BUTTON_RIGHT: u8 = 32; +pub const BUTTON_UP: u8 = 64; +pub const BUTTON_DOWN: u8 = 128; + +pub const MOUSE_LEFT: u8 = 1; +pub const MOUSE_RIGHT: u8 = 2; +pub const MOUSE_MIDDLE: u8 = 4; + +pub const SYSTEM_PRESERVE_FRAMEBUFFER: u8 = 1; +pub const SYSTEM_HIDE_GAMEPAD_OVERLAY: u8 = 2; + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Drawing Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +/// Copies pixels to the framebuffer. +pub fn blit(sprite: &[u8], x: i32, y: i32, width: u32, height: u32, flags: u32) { + unsafe { extern_blit(sprite.as_ptr(), x, y, width, height, flags) } +} +extern "C" { + #[link_name = "blit"] + fn extern_blit(sprite: *const u8, x: i32, y: i32, width: u32, height: u32, flags: u32); +} + +/// Copies a subregion within a larger sprite atlas to the framebuffer. +#[allow(clippy::too_many_arguments)] +pub fn blit_sub( + sprite: &[u8], + x: i32, + y: i32, + width: u32, + height: u32, + src_x: u32, + src_y: u32, + stride: u32, + flags: u32, +) { + unsafe { + extern_blit_sub( + sprite.as_ptr(), + x, + y, + width, + height, + src_x, + src_y, + stride, + flags, + ) + } +} +extern "C" { + #[link_name = "blitSub"] + fn extern_blit_sub( + sprite: *const u8, + x: i32, + y: i32, + width: u32, + height: u32, + src_x: u32, + src_y: u32, + stride: u32, + flags: u32, + ); +} + +pub const BLIT_2BPP: u32 = 1; +pub const BLIT_1BPP: u32 = 0; +pub const BLIT_FLIP_X: u32 = 2; +pub const BLIT_FLIP_Y: u32 = 4; +pub const BLIT_ROTATE: u32 = 8; + +/// Draws a line between two points. +pub fn line(x1: i32, y1: i32, x2: i32, y2: i32) { + unsafe { extern_line(x1, y1, x2, y2) } +} +extern "C" { + #[link_name = "line"] + fn extern_line(x1: i32, y1: i32, x2: i32, y2: i32); +} + +/// Draws an oval (or circle). +pub fn oval(x: i32, y: i32, width: u32, height: u32) { + unsafe { extern_oval(x, y, width, height) } +} +extern "C" { + #[link_name = "oval"] + fn extern_oval(x: i32, y: i32, width: u32, height: u32); +} + +/// Draws a rectangle. +pub fn rect(x: i32, y: i32, width: u32, height: u32) { + unsafe { extern_rect(x, y, width, height) } +} +extern "C" { + #[link_name = "rect"] + fn extern_rect(x: i32, y: i32, width: u32, height: u32); +} + +/// Draws text using the built-in system font. +pub fn text>(text: T, x: i32, y: i32) { + let text_ref = text.as_ref(); + unsafe { extern_text(text_ref.as_ptr(), text_ref.len(), x, y) } +} +extern "C" { + #[link_name = "textUtf8"] + fn extern_text(text: *const u8, length: usize, x: i32, y: i32); +} + +/// Draws a vertical line +pub fn vline(x: i32, y: i32, len: u32) { + unsafe { + extern_vline(x, y, len); + } +} + +extern "C" { + #[link_name = "vline"] + fn extern_vline(x: i32, y: i32, len: u32); +} + +/// Draws a horizontal line +pub fn hline(x: i32, y: i32, len: u32) { + unsafe { + extern_hline(x, y, len); + } +} + +extern "C" { + #[link_name = "hline"] + fn extern_hline(x: i32, y: i32, len: u32); +} + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Sound Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +/// Plays a sound tone. +pub fn tone(frequency: u32, duration: u32, volume: u32, flags: u32) { + unsafe { extern_tone(frequency, duration, volume, flags) } +} +extern "C" { + #[link_name = "tone"] + fn extern_tone(frequency: u32, duration: u32, volume: u32, flags: u32); +} + +pub const TONE_PULSE1: u32 = 0; +pub const TONE_PULSE2: u32 = 1; +pub const TONE_TRIANGLE: u32 = 2; +pub const TONE_NOISE: u32 = 3; +pub const TONE_MODE1: u32 = 0; +pub const TONE_MODE2: u32 = 4; +pub const TONE_MODE3: u32 = 8; +pub const TONE_MODE4: u32 = 12; + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Storage Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +extern "C" { + /// Reads up to `size` bytes from persistent storage into the pointer `dest`. + pub fn diskr(dest: *mut u8, size: u32) -> u32; + + /// Writes up to `size` bytes from the pointer `src` into persistent storage. + pub fn diskw(src: *const u8, size: u32) -> u32; +} + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Other Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +/// Prints a message to the debug console. +pub fn trace>(text: T) { + let text_ref = text.as_ref(); + unsafe { extern_trace(text_ref.as_ptr(), text_ref.len()) } +} +extern "C" { + #[link_name = "traceUtf8"] + fn extern_trace(trace: *const u8, length: usize); +}