diff options
| author | Mica White <botahamec@outlook.com> | 2026-10-26 11:04:14 -0400 |
|---|---|---|
| committer | Mica White <botahamec@outlook.com> | 2026-10-26 11:04:14 -0400 |
| commit | 2987e310e8e6eaeaa60546bb92a61d9c3713263a (patch) | |
| tree | 530ff5abe1b584bc1a6da5babd328d71d1e1173c /src | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/exit_on_escape.rs | 10 | ||||
| -rw-r--r-- | src/main.rs | 51 | ||||
| -rw-r--r-- | src/move_snake.rs | 50 | ||||
| -rw-r--r-- | src/render.rs | 131 | ||||
| -rw-r--r-- | src/snake_controller.rs | 38 |
5 files changed, 280 insertions, 0 deletions
diff --git a/src/exit_on_escape.rs b/src/exit_on_escape.rs new file mode 100644 index 0000000..a7885ca --- /dev/null +++ b/src/exit_on_escape.rs @@ -0,0 +1,10 @@ +use tortuise_script::{input::KeyCode, ScriptContext, TortuiseScript}; + +pub const EXIT_ON_ESCAPE: TortuiseScript = TortuiseScript { + update: |mut context: ScriptContext| { + if context.is_key_pressed_this_frame(KeyCode::Esc) { + context.exit(); + } + }, + ..TortuiseScript::empty() +}; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..3b6605a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,51 @@ +#![cfg_attr(target_arch = "wasm32", no_std)] +#![cfg_attr(target_arch = "wasm32", no_main)] + +extern crate alloc; + +mod exit_on_escape; +mod move_snake; +mod render; +mod snake_controller; + +use alloc::vec::Vec; +use core::time::Duration; +use tortuise_script::{entity::Entity, time::Instant, TortuiseScene}; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +struct Position { + pub x: i16, + pub y: i16, +} + +const GRID_SIZE: u16 = 16; + +static APPLE_LOCATION: Entity<Position> = + Entity::new("apple_location", |_| Position { x: 2, y: 2 }); +static SNAKE_CELLS: Entity<Vec<Position>> = Entity::new("snake_cells", |_| { + alloc::vec![Position { x: 10, y: 5 }, Position { x: 9, y: 5 }] +}); +static DIRECTION: Entity<Position> = Entity::new("direction", |_| Position { x: 1, y: 0 }); +static NEXT_MOVE_TIME: Entity<Instant> = Entity::new("next_move_time", |cx| { + Instant::now(cx) + Duration::from_millis(200) +}); + +tortuise_script::game! { + name: "snake", + author: "botahamec", + tld: "dev", + first_scene: "main", + scenes: { + "main" => TortuiseScene { + scripts: &["snake_controller", "exit_on_escape", "move_snake", "render"], + } + }, + scripts: { + "snake_controller" => snake_controller::SNAKE_CONTROLLER, + "exit_on_escape" => exit_on_escape::EXIT_ON_ESCAPE, + "move_snake" => move_snake::MOVE_SNAKE, + "render" => render::RENDER + } +} diff --git a/src/move_snake.rs b/src/move_snake.rs new file mode 100644 index 0000000..453e795 --- /dev/null +++ b/src/move_snake.rs @@ -0,0 +1,50 @@ +use core::time::Duration; + +use alloc::vec::Vec; +use rand::RngExt; +use tortuise_script::{random::TortuiseRng, time::Instant, ScriptContext, TortuiseScript}; + +use crate::{Position, APPLE_LOCATION, DIRECTION, GRID_SIZE, NEXT_MOVE_TIME, SNAKE_CELLS}; + +pub const MOVE_SNAKE: TortuiseScript = TortuiseScript { + update: |context: ScriptContext| { + let mut apple_location = APPLE_LOCATION.get_mut(&context); + let mut snake_cells = SNAKE_CELLS.get_mut(&context); + let mut next_move = NEXT_MOVE_TIME.get_mut(&context); + let direction = DIRECTION.get(&context); + + let mut available_squares = + Vec::with_capacity((GRID_SIZE * GRID_SIZE) as usize - snake_cells.len()); + for i in 0..GRID_SIZE { + for j in 0..GRID_SIZE { + let position = Position { + x: i as i16, + y: j as i16, + }; + if !snake_cells.contains(&position) { + available_squares.push(position); + } + } + } + + let mut rng = TortuiseRng::new(); + let time = Instant::now(&context); + if time > *next_move { + if snake_cells[0] == *apple_location { + *apple_location = available_squares[rng.random_range(0..available_squares.len())]; + let new_head = Position { + x: snake_cells[0].x + direction.x, + y: snake_cells[0].y + direction.y, + }; + snake_cells.insert(0, new_head); + } + *next_move += Duration::from_millis(200); + for i in (1..snake_cells.len()).rev() { + snake_cells[i] = snake_cells[i - 1]; + } + snake_cells[0].x += direction.x; + snake_cells[0].y += direction.y; + } + }, + ..TortuiseScript::empty() +}; diff --git a/src/render.rs b/src/render.rs new file mode 100644 index 0000000..80b06f7 --- /dev/null +++ b/src/render.rs @@ -0,0 +1,131 @@ +use alloc::string::ToString; +use tortuise_script::{ + buffer::{Cell, Color}, + ScriptContext, TortuiseScript, +}; + +use crate::{APPLE_LOCATION, GRID_SIZE, SNAKE_CELLS}; + +fn draw_box(cx: &mut ScriptContext, char: char, x: u16, y: u16) { + for i in 1..5 { + cx.add_cell(Cell { + character: '-', + x: x + i, + y, + ..Default::default() + }); + cx.add_cell(Cell { + character: '-', + x: x + i, + y: y + 2, + ..Default::default() + }); + } + cx.add_cell(Cell { + character: '|', + x, + y: y + 1, + ..Default::default() + }); + cx.add_cell(Cell { + character: '|', + x: x + 5, + y: y + 1, + ..Default::default() + }); + cx.add_cell(Cell { + character: char, + x: x + 3, + y: y + 1, + ..Default::default() + }); +} + +pub const RENDER: TortuiseScript = TortuiseScript { + update: |mut context: ScriptContext| { + let apple_location = APPLE_LOCATION.get(&context); + let snake_cells = SNAKE_CELLS.get(&context); + let window_size = context.window_size(); + let offset_x = window_size.columns.saturating_sub(GRID_SIZE) / 2; + let offset_y = window_size.rows.saturating_sub(GRID_SIZE) / 2; + + for i in 0..GRID_SIZE { + context.add_cell(Cell { + character: '|', + x: offset_x.saturating_sub(1), + y: offset_y + i, + z: 0, + ..Default::default() + }); + context.add_cell(Cell { + character: '|', + x: offset_x + GRID_SIZE, + y: offset_y + i, + z: 0, + ..Default::default() + }); + context.add_cell(Cell { + character: '-', + x: offset_x + i, + y: offset_y.saturating_sub(1), + z: 0, + ..Default::default() + }); + context.add_cell(Cell { + character: '-', + x: offset_x + i, + y: offset_y + GRID_SIZE, + z: 0, + ..Default::default() + }); + } + + for (i, char) in "Score:".chars().enumerate() { + context.add_cell(Cell { + character: char, + x: offset_x + GRID_SIZE - 10 + i as u16, + y: offset_y.saturating_sub(2), + ..Default::default() + }); + } + + draw_box(&mut context, '<', 4, 4); + draw_box(&mut context, '>', 16, 4); + draw_box(&mut context, '^', 10, 1); + draw_box(&mut context, 'v', 10, 7); + + for (i, char) in (snake_cells.len() - 2) + .to_string() + .chars() + .rev() + .enumerate() + { + context.add_cell(Cell { + character: char, + x: offset_x + GRID_SIZE - i as u16, + y: offset_y.saturating_sub(2), + ..Default::default() + }); + } + + context.add_cell(Cell { + character: '●', + foreground_color: Color::Red, + x: apple_location.x.unsigned_abs() + offset_x, + y: apple_location.y.unsigned_abs() + offset_y, + z: 0, + ..Default::default() + }); + for cell in &*snake_cells { + context.add_cell(Cell { + character: '█', + foreground_color: Color::Green, + x: cell.x.unsigned_abs() + offset_x, + y: cell.y.unsigned_abs() + offset_y, + z: 1, + ..Default::default() + }); + } + }, + ..TortuiseScript::empty() +}; diff --git a/src/snake_controller.rs b/src/snake_controller.rs new file mode 100644 index 0000000..0c48175 --- /dev/null +++ b/src/snake_controller.rs @@ -0,0 +1,38 @@ +use tortuise_script::{ + input::{KeyCode, Position as MousePosition}, + ScriptContext, TortuiseScript, +}; + +use crate::{Position, DIRECTION}; + +fn is_inside_box(mouse: Option<MousePosition>, button: MousePosition, size: u16) -> bool { + mouse.is_some_and(|mouse| { + (mouse.x >= button.x && mouse.x < button.x + size * 2) + && (mouse.y >= button.y && mouse.y < button.y + size) + }) +} + +pub const SNAKE_CONTROLLER: TortuiseScript = TortuiseScript { + update: |context: ScriptContext| { + let mut direction = DIRECTION.get_mut(&context); + let mouse_location = context.mouse_location(); + if is_inside_box(mouse_location, MousePosition { x: 4, y: 4 }, 3) { + *direction = Position { x: -1, y: 0 }; + } else if is_inside_box(mouse_location, MousePosition { x: 16, y: 4 }, 3) { + *direction = Position { x: 1, y: 0 }; + } else if is_inside_box(mouse_location, MousePosition { x: 10, y: 1 }, 3) { + *direction = Position { x: 0, y: -1 }; + } else if is_inside_box(mouse_location, MousePosition { x: 10, y: 7 }, 3) { + *direction = Position { x: 0, y: 1 }; + } else if context.is_key_pressed_this_frame(KeyCode::Up) { + *direction = Position { x: 0, y: -1 }; + } else if context.is_key_pressed_this_frame(KeyCode::Right) { + *direction = Position { x: 1, y: 0 }; + } else if context.is_key_pressed_this_frame(KeyCode::Left) { + *direction = Position { x: -1, y: 0 }; + } else if context.is_key_pressed_this_frame(KeyCode::Down) { + *direction = Position { x: 0, y: 1 }; + } + }, + ..TortuiseScript::empty() +}; |
