summaryrefslogtreecommitdiff
path: root/examples/snake.rs
blob: d14618e90e68e5f4c7262a5f5918208167f7105d (plain)
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();
}