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