use std::{
io::BufWriter,
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, GameManifest},
input::InputManager,
scene::{ResourceRequirements, Scene, switch_scenes},
script::{ScriptContext, ScriptDeclaration, ScriptManager, WasmRunner},
time::TimeContext,
};
#[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 manifest = GameManifest {
name: Box::from("snake"),
first_scene: 0,
scenes: Box::from([Scene {
name: Box::from("main"),
preload_scenes: Box::from([]),
scripts: ResourceRequirements {
required: Box::from([0, 1]),
preload: Box::from([]),
},
}]),
scripts: Box::from([
ScriptDeclaration::Static {
name: Box::from("exit_on_escape"),
start: None,
update: Some(exit_on_escape),
end: None,
},
ScriptDeclaration::Static {
name: Box::from("game_state"),
start: Some(init_scene),
update: Some(render),
end: None,
},
]),
};
let stdout = Box::new(BufWriter::new(std::io::stdout().lock()));
let mut buffer = Buffer::new(stdout).unwrap();
let mut files = FileManager::default();
let mut input = InputManager::new();
let wasm_runner = WasmRunner::new();
let mut scripts = ScriptManager::new(&manifest.scripts);
let mut time = TimeContext::new();
let mut context = ScriptContext {
manifest: &manifest,
buffer: &mut buffer,
input: &input,
time: &time,
operation_queue: Vec::new(),
};
switch_scenes(
&mut scripts,
&mut context,
&mut files,
&wasm_runner,
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 {
manifest: &manifest,
buffer,
input: &input,
time: &time,
operation_queue: Vec::new(),
};
scripts.run_scripts(&mut context, &wasm_runner, &mut files);
input.reset_next_frame();
time.next_frame();
}
})
.unwrap();
}
|