#[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(); }