akesi/src/lib.rs

218 lines
5.1 KiB
Rust

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