diff options
| author | Mica White <botahamec@outlook.com> | 2026-07-03 11:28:15 -0400 |
|---|---|---|
| committer | Mica White <botahamec@outlook.com> | 2026-07-03 11:28:15 -0400 |
| commit | bb7525206da1782fd9af49621aa52d87bd227838 (patch) | |
| tree | e2eeb352b964e35e9a902f31c67771a0f1bb9e02 | |
| parent | 3eb02cbe5b106cd332f25d658430f7e0ae320745 (diff) | |
Snake example
| -rw-r--r-- | Cargo.lock | 21 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | examples/snake.rs | 156 | ||||
| -rw-r--r-- | src/file.rs | 2 | ||||
| -rw-r--r-- | src/main.rs | 11 | ||||
| -rw-r--r-- | src/scene.rs | 8 | ||||
| -rw-r--r-- | src/script.rs | 13 |
7 files changed, 201 insertions, 11 deletions
@@ -280,6 +280,26 @@ dependencies = [ ] [[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1939,6 +1959,7 @@ checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" name = "tortuise" version = "0.1.0" dependencies = [ + "bytemuck", "crossterm", "getrandom", "mimalloc", @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +bytemuck = { version = "1", features = ["derive"] } crossterm = "0.29" getrandom = "0.4" mimalloc = "0.1" 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(); +} diff --git a/src/file.rs b/src/file.rs index 8e96fa9..42fecfa 100644 --- a/src/file.rs +++ b/src/file.rs @@ -14,7 +14,7 @@ pub struct FileManager { cache: FxHashMap<Box<Path>, FileData>, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct GameManifest { pub name: Box<str>, pub first_scene: usize, diff --git a/src/main.rs b/src/main.rs index 0cbb7c8..81fffcf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -78,8 +78,15 @@ fn main() { let manifest = files .load_file(Box::from("manifest.toml".as_ref())) .unwrap() - .unwrap_manifest(); - switch_scenes(&manifest.scenes, &mut scripts, &mut context, 0); + .unwrap_manifest() + .clone(); + switch_scenes( + &manifest.scenes, + &mut scripts, + &mut context, + &mut files, + manifest.first_scene, + ); buffer .render_loop(|buffer, event| match event { diff --git a/src/scene.rs b/src/scene.rs index 143eb0b..8f70350 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -1,7 +1,10 @@ use rustc_hash::FxHashSet; use serde::{Deserialize, Serialize}; -use crate::script::{ScriptContext, ScriptManager}; +use crate::{ + file::FileManager, + script::{ScriptContext, ScriptManager}, +}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Scene { @@ -29,13 +32,14 @@ pub fn switch_scenes( scenes: &[Scene], scripts: &mut ScriptManager, context: &mut ScriptContext<'_>, + files: &mut FileManager, new: usize, ) { let active_scripts = FxHashSet::from_iter(scripts.active_scripts.iter().cloned()); let scripts_to_load = &scenes[new].scripts.required; for script in scripts_to_load { if !active_scripts.contains(script) { - scripts.activate_script(*script, context); + scripts.activate_script(*script, context, files); } else { scripts.reset_script(*script, context); } diff --git a/src/script.rs b/src/script.rs index dd65106..eeb943f 100644 --- a/src/script.rs +++ b/src/script.rs @@ -79,12 +79,13 @@ impl<'a> ScriptManager<'a> { }); } ScriptDeclaration::Wasm { path } => { - let module = files.load_file(path.clone()).unwrap().unwrap_wasm_module(); - let mut store = wasmi::Store::new(&files.wasm_compiler, context); - let instance = self - .linker - .instantiate_and_start(&mut store, &module) - .unwrap(); + todo!("wasm scripts") + // let module = files.load_file(path.clone()).unwrap().unwrap_wasm_module(); + // let mut store = wasmi::Store::new(&files.wasm_compiler, context); + // let instance = self + // .linker + // .instantiate_and_start(&mut store, &module) + // .unwrap(); } } self.active_scripts.push(script_id); |
