From bb7525206da1782fd9af49621aa52d87bd227838 Mon Sep 17 00:00:00 2001 From: Mica White Date: Fri, 3 Jul 2026 11:28:15 -0400 Subject: Snake example --- examples/snake.rs | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 examples/snake.rs (limited to 'examples') diff --git a/examples/snake.rs b/examples/snake.rs new file mode 100644 index 0000000..2163acf --- /dev/null +++ b/examples/snake.rs @@ -0,0 +1,156 @@ +use std::time::{Duration, SystemTime}; + +use bytemuck::{Pod, Zeroable, bytes_of, from_bytes_mut}; +use crossterm::{event::KeyCode, style::ContentStyle}; +use tortuise::{ + buffer::{Buffer, Cell, Event}, + file::FileManager, + input::InputManager, + scene::switch_scenes, + script::{ScriptContext, ScriptDeclaration, ScriptManager}, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Pod, Zeroable)] +#[repr(C)] +struct Position { + x: i16, + y: i16, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Pod, Zeroable)] +#[repr(C)] +struct GameState { + apple_location: Position, + snake_cells: [Position; 2], + direction: Position, + next_move: u128, +} + +fn init_scene(_: &mut ScriptContext, state: &mut Box<[u8]>) { + *state = Box::from(bytes_of(&GameState { + apple_location: Position { x: 2, y: 2 }, + snake_cells: [Position { x: 10, y: 5 }, Position { x: 9, y: 5 }], + direction: Position { x: 1, y: 0 }, + next_move: SystemTime::now() + .checked_add(Duration::from_millis(200)) + .unwrap() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis(), + })) +} + +fn render(ctx: &mut ScriptContext, byte_state: &mut Box<[u8]>) { + let state: &mut GameState = from_bytes_mut(byte_state); + if ctx.input.is_key_pressed_this_frame(KeyCode::Up) { + state.direction = Position { x: 0, y: -1 }; + } else if ctx.input.is_key_pressed_this_frame(KeyCode::Right) { + state.direction = Position { x: 1, y: 0 }; + } else if ctx.input.is_key_pressed_this_frame(KeyCode::Left) { + state.direction = Position { x: -1, y: 0 }; + } else if ctx.input.is_key_pressed_this_frame(KeyCode::Down) { + state.direction = Position { x: 0, y: 1 }; + } + + let time = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis(); + if time > state.next_move { + state.next_move += 200; + state.snake_cells[1] = state.snake_cells[0]; + state.snake_cells[0].x += state.direction.x; + state.snake_cells[0].y += state.direction.y; + } + + if state.snake_cells[0] == state.apple_location { + let new_x = (getrandom::u32().unwrap() % ctx.buffer.window_size.columns as u32) as i16; + let new_y = (getrandom::u32().unwrap() % ctx.buffer.window_size.rows as u32) as i16; + state.apple_location = Position { x: new_x, y: new_y } + } + + ctx.buffer.cells.clear(); + ctx.buffer.cells.push(Cell { + character: '●', + style: ContentStyle { + foreground_color: Some(crossterm::style::Color::Red), + ..Default::default() + }, + x: state.apple_location.x.unsigned_abs(), + y: state.apple_location.y.unsigned_abs(), + z: 0, + }); + for cell in state.snake_cells { + ctx.buffer.cells.push(Cell { + character: '█', + style: ContentStyle { + foreground_color: Some(crossterm::style::Color::Green), + ..Default::default() + }, + x: cell.x.unsigned_abs(), + y: cell.y.unsigned_abs(), + z: 1, + }); + } +} + +fn exit_on_escape(ctx: &mut ScriptContext, _: &mut Box<[u8]>) { + if ctx.input.is_key_pressed_this_frame(KeyCode::Esc) { + ctx.exit(); + } +} + +fn main() { + let stdout = Box::new(std::io::stdout().lock()); + let mut buffer = Buffer::new(stdout).unwrap(); + let mut input = InputManager::new(); + let mut context = ScriptContext { + buffer: &mut buffer, + input: &input, + }; + let scripts = [ + ScriptDeclaration::Static { + start: None, + update: Some(exit_on_escape), + end: None, + }, + ScriptDeclaration::Static { + start: Some(init_scene), + update: Some(render), + end: None, + }, + ]; + let mut scripts = ScriptManager::new(&scripts); + + let mut files = FileManager::default(); + let manifest = files + .load_file(Box::from("manifest.toml".as_ref())) + .unwrap() + .unwrap_manifest() + .clone(); + switch_scenes( + &manifest.scenes, + &mut scripts, + &mut context, + &mut files, + manifest.first_scene, + ); + + buffer + .render_loop(|buffer, event| match event { + Event::Key(event) => { + if event.is_press() { + input.press_key(event.code); + } + } + Event::Render => { + let mut context = ScriptContext { + buffer, + input: &input, + }; + scripts.run_scripts(&mut context); + input.reset_next_frame(); + } + }) + .unwrap(); +} -- cgit v1.2.3