diff options
author | Toby Vincent <tobyv13@gmail.com> | 2022-08-09 15:32:27 -0500 |
---|---|---|
committer | Toby Vincent <tobyv13@gmail.com> | 2022-08-10 12:08:40 -0500 |
commit | 37c88bfc0b6dcb4a32e33b3dc35d8eba456388c4 (patch) | |
tree | 9169e04bd87aa4059861b3cc265ed2c9ef8274ae /src | |
parent | 26eb05dbf181b22897a43c88e1d3fa75502780ac (diff) |
Diffstat (limited to 'src')
-rw-r--r-- | src/entity.rs | 11 | ||||
-rw-r--r-- | src/entity/player.rs | 82 | ||||
-rw-r--r-- | src/error.rs | 10 | ||||
-rw-r--r-- | src/game.rs | 98 | ||||
-rw-r--r-- | src/lib.rs | 57 | ||||
-rw-r--r-- | src/main.rs | 22 | ||||
-rw-r--r-- | src/physics.rs | 64 | ||||
-rw-r--r-- | src/structure.rs | 24 | ||||
-rw-r--r-- | src/structure/building.rs | 119 |
9 files changed, 486 insertions, 1 deletions
diff --git a/src/entity.rs b/src/entity.rs new file mode 100644 index 0000000..25f30b2 --- /dev/null +++ b/src/entity.rs @@ -0,0 +1,11 @@ +pub use self::player::Player; + +mod player; + +pub(crate) trait Entity { + fn render_map(&self) -> std::collections::HashMap<crate::physics::Point, char>; +} + +pub(crate) trait Controllable { + fn handle_input(&mut self, key: termion::event::Key) -> Option<crate::game::Command>; +} diff --git a/src/entity/player.rs b/src/entity/player.rs new file mode 100644 index 0000000..45f3e13 --- /dev/null +++ b/src/entity/player.rs @@ -0,0 +1,82 @@ +use std::{ + collections::HashMap, + fmt::{Display, Formatter}, +}; + +use termion::event::Key; + +use crate::{ + game::Command, + physics::{Point, Vector}, + Position, Render, Velocity, +}; + +use crate::entity::Controllable; + +#[derive(Debug, Default)] +pub struct Player { + pub position: Point, + pub velocity: Vector, + render_map: HashMap<Point, char>, +} + +impl Player { + pub fn new(position: Point, velocity: Vector) -> Self { + Self { + position, + velocity, + render_map: HashMap::from([(position, '@')]), + } + } +} + +impl Display for Player { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + return write!( + f, + "{}@", + termion::cursor::Goto(self.position.x, self.position.y) + ); + } +} + +impl Render for Player { + fn render_map(&self) -> &HashMap<Point, char> { + &self.render_map + } +} + +impl Controllable for Player { + fn handle_input(&mut self, key: Key) -> Option<Command> { + match key { + Key::Up | Key::Char('w') | Key::Char('k') => self.velocity.y -= 1, + Key::Down | Key::Char('s') | Key::Char('j') => self.velocity.y += 1, + Key::Left | Key::Char('a') | Key::Char('h') => self.velocity.x -= 1, + Key::Right | Key::Char('d') | Key::Char('l') => self.velocity.x += 1, + _ => return None, + }; + Some(Command::UpdatePlayer) + } +} + +impl Position for Player { + fn get_position(&self) -> Point { + self.position + } + + fn set_position<P: Into<crate::physics::Point>>(&mut self, position: P) -> crate::Result<()> { + self.position = position.into(); + Ok(()) + } +} + +impl Velocity for Player { + fn get_velocity(&self) -> Vector { + self.velocity + } + + fn set_velocity<V: Into<crate::physics::Vector>>(&mut self, velocity: V) -> crate::Result<()> { + self.velocity = velocity.into(); + Ok(()) + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..81dd4fa --- /dev/null +++ b/src/error.rs @@ -0,0 +1,10 @@ +pub type Result<T> = std::result::Result<T, Error>; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Failed to parse key input: {0:?}")] + IO(#[from] std::io::Error), + + #[error("Failed to write to screen: {0:?}")] + Fmt(#[from] std::fmt::Error), +} diff --git a/src/game.rs b/src/game.rs new file mode 100644 index 0000000..0358638 --- /dev/null +++ b/src/game.rs @@ -0,0 +1,98 @@ +use std::io::{Read, Write}; + +use termion::{ + cursor::HideCursor, event::Key, input::TermRead, raw::IntoRawMode, screen::AlternateScreen, +}; + +use crate::{ + entity::{Controllable, Player}, + error::Result, + physics::Vector, + structure::Building, + Render, Velocity, +}; + +#[derive(Debug)] +pub(crate) enum Command { + Quit, + UpdatePlayer, +} + +#[derive(Debug, Default)] +pub struct Game { + pub player: Player, + pub structures: Vec<Building>, +} + +impl Game { + pub fn run_loop<R: Read, W: IntoRawMode>(&mut self, reader: R, writer: W) -> Result<()> { + let mut screen = AlternateScreen::from(HideCursor::from(writer).into_raw_mode().unwrap()); + self.write_screen(&mut screen)?; + + for key in reader.keys() { + match self.handle_input(key?) { + Some(Command::Quit) => break, + Some(Command::UpdatePlayer) => &mut self.update()?, + None => continue, + }; + self.write_screen(&mut screen)?; + } + + write!(screen, "{}", termion::cursor::Show).map_err(From::from) + } + + fn write_screen(&self, writer: &mut impl Write) -> Result<()> { + write!(writer, "{}", termion::clear::All)?; + + self.player.render(writer)?; + + for entity in &self.structures { + entity.render(writer)?; + } + + writer.flush().map_err(From::from) + } + + fn handle_input(&mut self, key: Key) -> Option<Command> { + match key { + Key::Char('q') => Some(Command::Quit), + _ => self.player.handle_input(key), + } + } + + fn update(&mut self) -> Result<()> { + self.player.tick_time()?; + self.player.set_velocity(Vector::default()) + } +} + +#[cfg(test)] +mod tests { + use std::io::stdout; + + use crate::{structure::Building, Game, Position}; + + #[test] + fn run_game() { + let stdin = "q".as_bytes(); + let stdout = stdout(); + + let mut game = Game::default(); + + if let Err(err) = game.run_loop(stdin, stdout) { + eprintln!("{}", err) + } + } + + #[test] + fn move_player() { + let stdin = "sssdddq".as_bytes(); + let stdout = stdout(); + + let mut game = Game::default(); + + if let Err(err) = game.run_loop(stdin, stdout) { + eprintln!("{}", err) + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..aed9bcc --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,57 @@ +pub use crate::error::{Error, Result}; +pub use crate::game::Game; + +pub mod entity; +pub mod structure; + +mod error; +mod game; +mod physics; + +pub trait Render { + fn render_map(&self) -> &std::collections::HashMap<crate::physics::Point, char>; + fn render<W: std::io::Write>(&self, w: &mut W) -> crate::Result<()> { + for (p, c) in self.render_map() { + write!(w, "{}{}", termion::cursor::Goto(p.x, p.y), c)? + } + Ok(()) + } +} + +pub trait Derender { + fn derender_map(&self) -> &std::collections::HashMap<crate::physics::Point, char>; + fn derender<W: std::io::Write>(&self, w: &mut W) -> crate::Result<()> { + for p in self.derender_map().keys() { + write!(w, "{}\x08", termion::cursor::Goto(p.x, p.y))? + } + Ok(()) + } +} + +impl<T: Render> Derender for T { + fn derender_map(&self) -> &std::collections::HashMap<crate::physics::Point, char> { + self.render_map() + } +} + +pub trait Collision { + fn collision_map(&self) -> &Vec<crate::physics::Point>; + fn is_collision<P: Into<crate::physics::Point>>(&self, point: P) -> bool { + self.collision_map().contains(&point.into()) + } +} + +pub trait Position { + fn get_position(&self) -> crate::physics::Point; + fn set_position<P: Into<crate::physics::Point>>(&mut self, position: P) -> crate::Result<()>; +} + +pub trait Velocity: Position { + fn get_velocity(&self) -> crate::physics::Vector; + + fn set_velocity<V: Into<crate::physics::Vector>>(&mut self, velocity: V) -> crate::Result<()>; + + fn tick_time(&mut self) -> Result<()> { + self.set_position(self.get_position() + self.get_velocity()) + } +} diff --git a/src/main.rs b/src/main.rs index e7a11a9..95b6ea3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,23 @@ +use std::io::{stdin, stdout}; + +use rustic_adventure::{structure::Building, Game, Position}; + fn main() { - println!("Hello, world!"); + let stdin = stdin(); + let stdout = stdout(); + + let mut game = Game::default(); + + let building = Building::builder() + .width(50) + .height(50) + .position((10, 10)) + .build(); + + game.structures.push(building); + game.player.set_position((5, 5)).unwrap(); + + if let Err(err) = game.run_loop(stdin, stdout) { + eprintln!("{}", err) + } } diff --git a/src/physics.rs b/src/physics.rs new file mode 100644 index 0000000..de09a2f --- /dev/null +++ b/src/physics.rs @@ -0,0 +1,64 @@ +use std::ops::Add; + +#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)] +pub struct Point { + pub x: u16, + pub y: u16, +} + +impl Point { + pub fn new(x: u16, y: u16) -> Self { + Self { x, y } + } + + pub fn add_velocity(&mut self, velocity: Vector) { + self.x += velocity.x; + self.y += velocity.y; + } +} + +impl From<(u16, u16)> for Point { + fn from((x, y): (u16, u16)) -> Self { + Self { x, y } + } +} + +impl From<Point> for (u16, u16) { + fn from(point: Point) -> Self { + (point.x, point.y) + } +} + +impl Add<Vector> for Point { + type Output = Self; + + fn add(self, rhs: Vector) -> Self::Output { + Self { + x: self.x + rhs.x, + y: self.y + rhs.y, + } + } +} + +#[derive(Debug, Default, Clone, Copy)] +pub struct Vector { + pub x: u16, + pub y: u16, +} + +impl Vector { + pub fn new(x: u16, y: u16) -> Self { + Self { x, y } + } +} + +impl Add<Vector> for Vector { + type Output = Self; + + fn add(self, rhs: Vector) -> Self::Output { + Self { + x: self.x + rhs.x, + y: self.y + rhs.y, + } + } +} diff --git a/src/structure.rs b/src/structure.rs new file mode 100644 index 0000000..2137766 --- /dev/null +++ b/src/structure.rs @@ -0,0 +1,24 @@ +use std::collections::HashMap; + +pub use building::Building; + +use crate::{physics::Point, Collision, Render}; + +mod building; + +pub struct Structure { + render_map: HashMap<Point, char>, + collision_map: Vec<Point>, +} + +impl Render for Structure { + fn render_map(&self) -> &HashMap<Point, char> { + &self.render_map + } +} + +impl Collision for Structure { + fn collision_map(&self) -> &Vec<crate::physics::Point> { + &self.collision_map + } +} diff --git a/src/structure/building.rs b/src/structure/building.rs new file mode 100644 index 0000000..b6c6346 --- /dev/null +++ b/src/structure/building.rs @@ -0,0 +1,119 @@ +use std::collections::HashMap; + +use crate::{physics::Point, Render}; + +#[derive(Debug, PartialEq, Eq)] +pub struct Building { + pub position: Point, + pub width: u16, + pub height: u16, + render_map: HashMap<Point, char>, +} + +impl Building { + pub fn builder() -> BuildingBuilder { + BuildingBuilder::default() + } +} + +#[derive(Debug, Default)] +pub struct BuildingBuilder { + position: Option<Point>, + width: Option<u16>, + height: Option<u16>, +} + +impl BuildingBuilder { + pub fn new() -> Self { + Self::default() + } + + pub fn position<P: Into<Point>>(mut self, position: P) -> BuildingBuilder { + self.position = Some(position.into()); + self + } + + pub fn width(mut self, width: u16) -> BuildingBuilder { + self.width = Some(width); + self + } + + pub fn height(mut self, height: u16) -> BuildingBuilder { + self.height = Some(height); + self + } + + pub fn build(self) -> Building { + let position = self.position.unwrap_or_default(); + let width = self.width.unwrap_or_default(); + let height = self.height.unwrap_or_default(); + let mut render_map = HashMap::new(); + + render_map.insert(position, '┌'); + render_map.insert((position.x, height).into(), '└'); + render_map.insert((width, position.y).into(), '┐'); + render_map.insert((width, height).into(), '┘'); + + for x in position.x + 1..width { + render_map.insert((x, position.y).into(), '─'); + render_map.insert((x, height).into(), '─'); + } + + for y in position.y + 1..height { + render_map.insert((position.x, y).into(), '│'); + render_map.insert((width, y).into(), '│'); + } + + Building { + position, + width, + height, + render_map, + } + } +} + +impl Render for Building { + fn render_map(&self) -> &std::collections::HashMap<crate::physics::Point, char> { + &self.render_map + } +} +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use super::*; + use crate::physics::Point; + + #[test] + fn building_builder() { + let position = Point { x: 1, y: 1 }; + let width = 3u16; + let height = 3u16; + let render_map = HashMap::from([ + (position, '┌'), + ((position.x, height).into(), '└'), + ((width, position.y).into(), '┐'), + ((width, height).into(), '┘'), + ((position.x + 1, position.y).into(), '─'), + ((position.x + 1, height).into(), '─'), + ((position.x, position.y + 1).into(), '│'), + ((width, position.y + 1).into(), '│'), + ]); + + let building = Building { + position, + width, + height, + render_map, + }; + + let building_from_builder = BuildingBuilder::new() + .width(width) + .height(height) + .position(position) + .build(); + + assert_eq!(building, building_from_builder); + } +} |