diff --git a/src/main.rs b/src/main.rs index 16cfc0b..2df705b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,9 +7,10 @@ mod world; // namespacing use ggez::{ conf::{WindowMode, WindowSetup}, - event::{self, EventHandler}, + event::{self, EventHandler, KeyCode, KeyMods}, graphics, Context, ContextBuilder, GameResult, }; +use std::collections::VecDeque; use world::World; /// lazy result type @@ -31,25 +32,78 @@ pub enum Error { } // primary game state -struct MainState { +pub struct MainState { world: World, + action_buffer: Vec>, + action_history: VecDeque>, + action_history_max_length: usize, } impl MainState { // create a new gamestate - pub fn new(ctx: &mut Context) -> MainState { - MainState { - world: World::new(ctx, "/data/tile_conf.toml".into(), 20, 20, 1) - .expect("failed init world"), - } + pub fn new(ctx: &mut Context) -> Result { + let world = World::new(ctx, "/data/tile_conf.toml".into(), 20, 20, 1)?; + Ok(MainState { + world, + action_history_max_length: 100, + action_history: VecDeque::new(), + action_buffer: Vec::new(), + }) } } impl EventHandler for MainState { - fn update(&mut self, _ctx: &mut Context) -> GameResult<()> { + fn update(&mut self, ctx: &mut Context) -> GameResult<()> { + // check through the input buffer + while let Some(action) = self.action_buffer.pop() { + action.execute(ctx, self).expect("failed to execute action"); + + if self.action_history.len() < self.action_history_max_length { + self.action_history.push_back(action); + } else { + self.action_history.pop_front(); + self.action_history.push_back(action); + } + } + Ok(()) } + fn key_down_event( + &mut self, + _ctx: &mut Context, + key_code: KeyCode, + _keymods: KeyMods, + repeat: bool, + ) { + use world::{OffsetAction, ZoomAction}; + + if !repeat { + let action_maybe: Option> = match key_code { + // movement key codes + KeyCode::Up => Some(Box::new(OffsetAction::new([0.0, 1.0].into()))), + KeyCode::Down => Some(Box::new(OffsetAction::new([0.0, -1.0].into()))), + KeyCode::Left => Some(Box::new(OffsetAction::new([1.0, 0.0].into()))), + KeyCode::Right => Some(Box::new(OffsetAction::new([-1.0, 0.0].into()))), + + // zoom key codes + KeyCode::Equals => Some(Box::new(ZoomAction::new([0.1, 0.1].into()))), + KeyCode::Minus => Some(Box::new(ZoomAction::new([-0.1, -0.1].into()))), + + // if escape, quit program (for now) + // !! isabelle please remove this eventually + KeyCode::Escape => Some(Box::new(QuitAction::new())), + + // ignore all other key codes + _ => None, + }; + + if let Some(action) = action_maybe { + self.action_buffer.push(action); + } + } + } + fn draw(&mut self, ctx: &mut Context) -> GameResult<()> { // clear the screen graphics::clear(ctx, graphics::BLACK); @@ -61,24 +115,56 @@ impl EventHandler for MainState { } } -fn main() { +trait Action: std::fmt::Debug { + fn execute(&self, ctx: &mut Context, state: &mut MainState) -> Result<()>; + fn undo(&self, ctx: &mut Context, state: &mut MainState) -> Result<()>; +} + +/// struct to wrap quitting the game +#[derive(Debug)] +struct QuitAction; + +impl QuitAction { + fn new() -> QuitAction { + QuitAction + } +} + +impl Action for QuitAction { + fn execute(&self, ctx: &mut Context, _state: &mut MainState) -> Result<()> { + event::quit(ctx); + Ok(()) + } + fn undo(&self, _ctx: &mut Context, _state: &mut MainState) -> Result<()> { + unreachable!() + } +} + +fn main_wrapper() -> Result<()> { let win_mode = WindowMode { width: 1024.0, height: 768.0, ..Default::default() }; + let win_setup = WindowSetup { title: "unnamed game thinger".to_owned(), ..Default::default() }; + let cb = ContextBuilder::new("unnamed", "Isabelle L.") .add_resource_path("./") .window_mode(win_mode) .window_setup(win_setup); - let (mut ctx, mut event_loop) = cb.build().expect("failed to start context"); - let mut state = MainState::new(&mut ctx); + let (mut ctx, mut event_loop) = cb.build()?; + let mut state = MainState::new(&mut ctx)?; - if let Err(err) = event::run(&mut ctx, &mut event_loop, &mut state) { - eprintln!("an error occured: {:?}", err); + event::run(&mut ctx, &mut event_loop, &mut state)?; + Ok(()) +} + +fn main() { + if let Err(err) = main_wrapper() { + panic!("err occured: {:?}", err); } } diff --git a/src/world.rs b/src/world.rs index f2c1f55..6ec70ba 100644 --- a/src/world.rs +++ b/src/world.rs @@ -2,12 +2,12 @@ mod tile; // namespacing -use crate::{util::*, Result}; +use crate::{util::*, MainState, Result}; use ggez::{ graphics::{self, DrawParam}, Context, }; -use mint::{Point2, Vector3}; +use mint::{Point2, Vector2, Vector3}; use std::{collections::HashMap, path::PathBuf}; use tile::*; @@ -19,6 +19,7 @@ pub struct World { data: HashMap, Tile>, builder: TileBuilder, offset: Point2, + zoom: Vector2, } impl World { @@ -33,6 +34,7 @@ impl World { let builder = TileBuilder::new(ctx, tile_config)?; let mut data: HashMap, Tile> = HashMap::new(); let offset: Point2 = [350.0f32, 100.0f32].into(); + let zoom: Vector2 = [1.0f32, 1.0f32].into(); for x in 0..width { for y in 0..depth { @@ -49,6 +51,7 @@ impl World { data, builder: builder, offset, + zoom, }) } @@ -59,11 +62,17 @@ impl World { for z in 0..self.height { let tile = self.data.get(&[x, y, z].into()).unwrap(); let iso_coord: IsometricVector2 = Point2::from([x as f32, y as f32]).into(); + let scale = [self.zoom.x, self.zoom.y]; let dest = [ - iso_coord.x() * self.builder.tile_width() / 2.0 + self.offset.x, - iso_coord.y() * self.builder.tile_height() / 2.0 + self.offset.y, + iso_coord.x() * (self.builder.tile_width() / 2.0) * scale[0] + + self.offset.x, + iso_coord.y() * (self.builder.tile_height() / 2.0) * scale[1] + + self.offset.y, ]; - let param = DrawParam::default().dest(mint::Point2::from_slice(&dest)); + + let param = DrawParam::default() + .dest(Point2::from_slice(&dest)) + .scale(Point2::from_slice(&scale)); graphics::draw(ctx, tile.texture(), param)?; } @@ -73,3 +82,55 @@ impl World { Ok(()) } } + +/// action struct for wrapping offsetting input +#[derive(Debug)] +pub struct OffsetAction(Vector2); + +impl OffsetAction { + /// construct a new offset action + pub fn new(offset: Vector2) -> OffsetAction { + OffsetAction(offset) + } +} + +impl crate::Action for OffsetAction { + fn execute(&self, _ctx: &mut Context, state: &mut MainState) -> Result<()> { + state.world.offset.x = + self.0.x * (state.world.builder.tile_width() / 2.0) + state.world.offset.x; + state.world.offset.y = + self.0.y * (state.world.builder.tile_height() / 2.0) + state.world.offset.y; + Ok(()) + } + + fn undo(&self, _ctx: &mut Context, state: &mut MainState) -> Result<()> { + state.world.offset.x = state.world.offset.x - self.0.x; + state.world.offset.y = state.world.offset.y - self.0.y; + Ok(()) + } +} + +/// action struct for wrapping zoom input +#[derive(Debug)] +pub struct ZoomAction(Vector2); + +impl ZoomAction { + /// construct a new scale action + pub fn new(scale: Vector2) -> ZoomAction { + ZoomAction(scale) + } +} + +impl crate::Action for ZoomAction { + fn execute(&self, _ctx: &mut Context, state: &mut MainState) -> Result<()> { + state.world.zoom.x = self.0.x + state.world.zoom.x; + state.world.zoom.y = self.0.y + state.world.zoom.y; + Ok(()) + } + + fn undo(&self, _ctx: &mut Context, state: &mut MainState) -> Result<()> { + state.world.zoom.x = state.world.zoom.x - self.0.x; + state.world.zoom.y = state.world.zoom.y - self.0.y; + Ok(()) + } +}