diff options
Diffstat (limited to 'src')
38 files changed, 0 insertions, 4558 deletions
diff --git a/src/cli.yml b/src/cli.yml deleted file mode 100644 index 4b2e94e57b0e..000000000000 --- a/src/cli.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: xanthous -version: "0.0" -author: Griffin Smith <root@gws.fyi> -about: hey, it's a terminal game -args: - - config: - short: c - long: config - value_name: FILE - help: Sets a custom config file - takes_value: true -subcommands: - - info: - about: Writes debug information to the terminal and exits - - generate-level: - about: Generate a level and print it to the screen - args: - - generator: - long: generator - value_name: GEN - help: Select which generator to use - takes_value: true - - width: - long: width - short: w - value_name: WIDTH - takes_value: true - - height: - long: height - short: h - value_name: HEIGHT - takes_value: true - - start-alive-chance: - long: start-alive-chance - takes_value: true - - birth_limit: - long: birth-limit - takes_value: true - - death_limit: - long: death-limit - takes_value: true - - steps: - long: steps - short: s - value_name: STEPS - takes_value: true diff --git a/src/description.rs b/src/description.rs deleted file mode 100644 index 48c98d76e051..000000000000 --- a/src/description.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::entities::Describe; - -pub fn list_to_sentence(lst: &[String]) -> String { - let mut buf = String::with_capacity( - lst.iter() - .map(|e| e.len() + 2usize /* ", " */) - .sum::<usize>() - + if lst.len() >= 3 { - 3usize /* "and" */ - } else { - 0usize - }, - ); - - match lst.len() { - 0 => {} - 1 => buf.push_str(&lst[0]), - 2 => { - buf.push_str(&lst[0]); - buf.push_str(" and "); - buf.push_str(&lst[1]); - } - _ => { - for desc in &lst[..lst.len() - 1] { - buf.push_str(desc); - buf.push_str(", "); - } - buf.push_str("and "); - buf.push_str(&lst[lst.len() - 1]); - } - } - - buf -} - -pub fn describe_list<A: Describe>(lst: &[A]) -> String { - list_to_sentence( - &lst.iter().map(|e| e.description()).collect::<Vec<String>>(), - ) -} - -#[cfg(test)] -mod tests { - use super::*; - use proptest::prelude::*; - use proptest_derive::Arbitrary; - - #[derive(Debug, Arbitrary)] - struct Description(String); - - impl Describe for Description { - fn description(&self) -> String { - self.0.clone() - } - } - - proptest! { - #[test] - fn test_describe_list_includes_all_descriptions( - descriptions: Vec<Description> - ) { - let res = describe_list(&descriptions); - for Description(desc) in descriptions { - assert!(res.contains(&desc)); - } - } - } - - #[test] - fn test_describe_list() { - assert_eq!( - describe_list(&[Description("one".to_string())]), - "one".to_string() - ); - - assert_eq!( - describe_list(&[ - Description("one".to_string()), - Description("two".to_string()) - ]), - "one and two".to_string() - ); - - assert_eq!( - describe_list(&[ - Description("one".to_string()), - Description("two".to_string()), - Description("three".to_string()) - ]), - "one, two, and three".to_string() - ); - } -} diff --git a/src/display/color.rs b/src/display/color.rs deleted file mode 100644 index afe0039998b8..000000000000 --- a/src/display/color.rs +++ /dev/null @@ -1,163 +0,0 @@ -use serde::de::{self, Unexpected, Visitor}; -use std::fmt; -use std::marker::PhantomData; -use termion::color; - -#[derive(Debug)] -pub struct Color(Box<dyn color::Color>); - -unsafe impl Sync for Color {} -unsafe impl Send for Color {} - -impl Color { - pub fn new<C: color::Color + 'static>(c: C) -> Self { - Color(Box::new(c)) - } -} - -impl PartialEq for Color { - fn eq(&self, other: &Self) -> bool { - format!("{}{}", color::Fg(self), color::Bg(self)) - == format!("{}{}", color::Fg(other), color::Bg(other)) - } -} - -impl Eq for Color {} - -impl color::Color for Color { - fn write_fg(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.write_fg(f) - } - - fn write_bg(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.write_bg(f) - } -} - -impl<'a> color::Color for &'a Color { - fn write_fg(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.write_fg(f) - } - - fn write_bg(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.write_bg(f) - } -} - -impl Default for Color { - fn default() -> Self { - Color::new(color::Reset) - } -} - -pub struct ColorVisitor { - marker: PhantomData<fn() -> Color>, -} - -impl ColorVisitor { - fn new() -> Self { - ColorVisitor { - marker: PhantomData, - } - } -} - -impl<'de> Visitor<'de> for ColorVisitor { - type Value = Color; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("A color") - } - - fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> - where - E: de::Error, - { - match v.to_lowercase().as_ref() { - "black" => Ok(Color(Box::new(color::Black))), - "blue" => Ok(Color(Box::new(color::Blue))), - "cyan" => Ok(Color(Box::new(color::Cyan))), - "green" => Ok(Color(Box::new(color::Green))), - "light black" | "light_black" => { - Ok(Color(Box::new(color::LightBlack))) - } - "light blue" | "light_blue" => { - Ok(Color(Box::new(color::LightBlue))) - } - "light cyan" | "light_cyan" => { - Ok(Color(Box::new(color::LightCyan))) - } - "light green" | "light_green" => { - Ok(Color(Box::new(color::LightGreen))) - } - "light magenta" | "light_magenta" => { - Ok(Color(Box::new(color::LightMagenta))) - } - "light red" | "light_red" => Ok(Color(Box::new(color::LightRed))), - "light white" | "light_white" => { - Ok(Color(Box::new(color::LightWhite))) - } - "light yellow" | "light_yellow" => { - Ok(Color(Box::new(color::LightYellow))) - } - "magenta" => Ok(Color(Box::new(color::Magenta))), - "red" => Ok(Color(Box::new(color::Red))), - "white" => Ok(Color(Box::new(color::White))), - "yellow" => Ok(Color(Box::new(color::Yellow))), - _ => Err(de::Error::invalid_value( - Unexpected::Str(v), - &"a valid color", - )), - } - } - - fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> - where - A: de::MapAccess<'de>, - { - let mut red = None; - let mut green = None; - let mut blue = None; - while let Some((k, v)) = map.next_entry()? { - match k { - "red" => { - red = Some(v); - } - "green" => { - green = Some(v); - } - "blue" => { - blue = Some(v); - } - _ => { - return Err(de::Error::unknown_field( - k, - &["red", "green", "blue"], - )); - } - } - } - - match (red, green, blue) { - (Some(r), Some(g), Some(b)) => { - Ok(Color(Box::new(color::Rgb(r, g, b)))) - } - (None, _, _) => Err(de::Error::missing_field("red")), - (_, None, _) => Err(de::Error::missing_field("green")), - (_, _, None) => Err(de::Error::missing_field("blue")), - } - } - - fn visit_u8<E: de::Error>(self, v: u8) -> Result<Self::Value, E> { - Ok(Color(Box::new(color::AnsiValue(v)))) - } -} - -impl<'de> serde::Deserialize<'de> for Color { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_any(ColorVisitor::new()) - } -} diff --git a/src/display/draw_box.rs b/src/display/draw_box.rs deleted file mode 100644 index e4d34a7acda9..000000000000 --- a/src/display/draw_box.rs +++ /dev/null @@ -1,274 +0,0 @@ -use crate::display::utils::clone_times; -use crate::display::utils::times; -use crate::types::pos; -use crate::types::BoundingBox; -use crate::types::Dimensions; -use crate::types::Neighbors; -use itertools::Itertools; -use proptest::prelude::Arbitrary; -use proptest::strategy; -use proptest_derive::Arbitrary; -use std::io::{self, Write}; - -// Box Drawing -// 0 1 2 3 4 5 6 7 8 9 A B C D E F -// U+250x ─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏ -// U+251x ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟ -// U+252x ┠ ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯ -// U+253x ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿ -// U+254x ╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏ -// U+255x ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟ -// U+256x ╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯ -// U+257x ╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿ - -static BOX: char = '☐'; - -static BOX_CHARS: [[char; 16]; 8] = [ - // 0 - [ - // 0 1 2 3 4 5 6 7 8 9 - '─', '━', '│', '┃', '┄', '┅', '┆', '┇', '┈', '┉', - // 10 - '┊', '┋', '┌', '┍', '┎', '┏', - ], - // 1 - [ - // 0 1 2 3 4 5 6 7 8 9 - '┐', '┑', '┒', '┓', '└', '┕', '┖', '┗', '┘', '┙', - '┚', '┛', '├', '┝', '┞', '┟', - ], - // 2 - [ - // 0 1 2 3 4 5 6 7 8 9 - '┠', '┡', '┢', '┣', '┤', '┥', '┦', '┧', '┨', '┩', - '┪', '┫', '┬', '┭', '┮', '┯', - ], - // 3 - [ - // 0 1 2 3 4 5 6 7 8 9 - '┰', '┱', '┲', '┳', '┴', '┵', '┶', '┷', '┸', '┹', - '┺', '┻', '┼', '┽', '┾', '┿', - ], - // 4 - [ - // 0 1 2 3 4 5 6 7 8 9 - '╀', '╁', '╂', '╃', '╄', '╅', '╆', '╇', '╈', '╉', - '╊', '╋', '╌', '╍', '╎', '╏', - ], - // 5 - [ - // 0 1 2 3 4 5 6 7 8 9 - '═', '║', '╒', '╓', '╔', '╕', '╖', '╗', '╘', '╙', - '╚', '╛', '╜', '╝', '╞', '╟', - ], - // 6 - [ - // 0 1 2 3 4 5 6 7 8 9 - '╠', '╡', '╢', '╣', '╤', '╥', '╦', '╧', '╨', '╩', - '╪', '╫', '╬', '╭', '╮', '╯', - ], - // 7 - [ - // 0 1 2 3 4 5 6 7 8 9 - '╰', '╱', '╲', '╳', '╴', '╵', '╶', '╷', '╸', '╹', - '╺', '╻', '╼', '╽', '╾', '╿', - ], -]; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum BoxStyle { - Thin, - Thick, - Dotted, - ThickDotted, - Dashed, - ThickDashed, - Double, -} - -impl Arbitrary for BoxStyle { - type Parameters = (); - type Strategy = strategy::Just<Self>; - fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { - // TODO - strategy::Just(BoxStyle::Thin) - } -} - -pub trait Stylable { - fn style(&self, style: BoxStyle) -> char; -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)] -enum Corner { - TopRight, - TopLeft, - BottomRight, - BottomLeft, -} - -impl Stylable for Corner { - fn style(&self, style: BoxStyle) -> char { - use BoxStyle::*; - use Corner::*; - - match (self, style) { - (TopRight, Thin) => BOX_CHARS[1][0], - (TopLeft, Thin) => BOX_CHARS[0][12], - (BottomRight, Thin) => BOX_CHARS[1][8], - (BottomLeft, Thin) => BOX_CHARS[1][4], - _ => unimplemented!(), - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)] -enum Line { - H, - V, -} - -impl Stylable for Line { - fn style(&self, style: BoxStyle) -> char { - use BoxStyle::*; - use Line::*; - match (self, style) { - (H, Thin) => BOX_CHARS[0][0], - (V, Thin) => BOX_CHARS[0][2], - _ => unimplemented!(), - } - } -} - -impl Stylable for Neighbors<Option<BoxStyle>> { - fn style(&self, _style: BoxStyle) -> char { - use BoxStyle::*; - match (self.left, self.right, self.top, self.bottom) { - (None, None, None, None) => BOX, - (Some(Thin), None, None, None) => BOX_CHARS[7][4], - (None, Some(Thin), None, None) => BOX_CHARS[7][6], - (None, None, Some(Thin), None) => BOX_CHARS[7][5], - (None, None, None, Some(Thin)) => BOX_CHARS[7][7], - (Some(Thin), Some(Thin), None, None) => Line::H.style(Thin), - (Some(Thin), None, Some(Thin), None) => { - Corner::BottomRight.style(Thin) - } - (Some(Thin), None, None, Some(Thin)) => { - Corner::TopRight.style(Thin) - } - (None, Some(Thin), Some(Thin), None) => { - Corner::BottomLeft.style(Thin) - } - (None, Some(Thin), None, Some(Thin)) => Corner::TopLeft.style(Thin), - (None, None, Some(Thin), Some(Thin)) => Line::V.style(Thin), - (None, Some(Thin), Some(Thin), Some(Thin)) => BOX_CHARS[1][12], - (Some(Thin), None, Some(Thin), Some(Thin)) => BOX_CHARS[2][4], - (Some(Thin), Some(Thin), None, Some(Thin)) => BOX_CHARS[2][12], - (Some(Thin), Some(Thin), Some(Thin), None) => BOX_CHARS[3][4], - (Some(Thin), Some(Thin), Some(Thin), Some(Thin)) => { - BOX_CHARS[3][12] - } - neighs => panic!("unimplemented: {:?}", neighs), - } - } -} - -#[must_use] -pub fn make_box(style: BoxStyle, dims: Dimensions) -> String { - if dims.h == 0 || dims.w == 0 { - "".to_string() - } else if dims.h == 1 && dims.w == 1 { - BOX.to_string() - } else if dims.h == 1 { - times(Line::H.style(style), dims.w) - } else if dims.w == 1 { - (0..dims.h).map(|_| Line::V.style(style)).join("\n\r") - } else { - let h_line: String = times(Line::H.style(style), dims.w - 2); - let v_line = Line::V.style(style); - let v_walls: String = clone_times( - format!( - "{}{}{}\n\r", - v_line, - times::<_, String>(' ', dims.w - 2), - v_line - ), - dims.h - 2, - ); - - format!( - "{}{}{}\n\r{}{}{}{}", - Corner::TopLeft.style(style), - h_line, - Corner::TopRight.style(style), - v_walls, - Corner::BottomLeft.style(style), - h_line, - Corner::BottomRight.style(style), - ) - } -} - -/// Draw the box described by the given BoundingBox's position and dimensions to -/// the given output, with the given style -pub fn draw_box<W: Write>( - out: &mut W, - bbox: BoundingBox, - style: BoxStyle, -) -> io::Result<()> { - let box_str = make_box(style, bbox.dimensions); - if bbox.position.x == 0 { - write!(out, "{}{}", bbox.position.cursor_goto(), box_str)?; - } else { - for (i, line) in box_str.split("\n\r").enumerate() { - debug!("line: {:?}!", line); - write!( - out, - "{}{}", - (bbox.position + pos(0, i as i16)).cursor_goto(), - line - )?; - } - } - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use proptest::prelude::*; - - #[test] - fn make_thin_box() { - let res = make_box(BoxStyle::Thin, Dimensions { w: 10, h: 10 }); - assert_eq!( - res, - "┌────────┐ -\r│ │ -\r│ │ -\r│ │ -\r│ │ -\r│ │ -\r│ │ -\r│ │ -\r│ │ -\r└────────┘" - ); - } - - proptest! { - #[test] - fn box_has_height_lines(dims: Dimensions, style: BoxStyle) { - let res = make_box(style, dims); - prop_assume!((dims.w > 0 && dims.h > 0)); - assert_eq!(res.split("\n\r").count(), dims.h as usize); - } - - #[test] - fn box_lines_have_width_length(dims: Dimensions, style: BoxStyle) { - let res = make_box(style, dims); - prop_assume!(dims.w == 0 && dims.h == 0 || (dims.w > 0 && dims.h > 0)); - assert!(res.split("\n\r").all(|l| l.chars().count() == dims.w as usize)); - } - } -} diff --git a/src/display/mod.rs b/src/display/mod.rs deleted file mode 100644 index 6e37a03d8c55..000000000000 --- a/src/display/mod.rs +++ /dev/null @@ -1,52 +0,0 @@ -pub mod color; -pub mod draw_box; -pub mod utils; -pub mod viewport; -use crate::entities::entity::Entity; -use crate::types::Neighbors; -use crate::types::Positioned; -pub use draw_box::{make_box, BoxStyle}; -use std::io::{self, Write}; -use termion::{clear, cursor, style}; -pub use viewport::Viewport; - -pub fn clear<T: Write>(out: &mut T) -> io::Result<()> { - write!(out, "{}{}{}", clear::All, style::Reset, cursor::Goto(1, 1)) -} - -pub trait Draw: Positioned { - /// Draw this entity, assuming the character is already at the correct - /// position - fn do_draw(&self, out: &mut dyn Write) -> io::Result<()>; -} - -impl<T: Draw> Draw for &T { - fn do_draw(&self, out: &mut dyn Write) -> io::Result<()> { - (**self).do_draw(out) - } -} - -impl<T: Draw> Draw for Box<T> { - fn do_draw(&self, out: &mut dyn Write) -> io::Result<()> { - (**self).do_draw(out) - } -} - -pub trait DrawWithNeighbors: Positioned { - #[allow(clippy::borrowed_box)] - fn do_draw_with_neighbors<'a, 'b>( - &'a self, - out: &'b mut dyn Write, - neighbors: &'a Neighbors<Vec<&'a Box<dyn Entity>>>, - ) -> io::Result<()>; -} - -impl<T: Draw> DrawWithNeighbors for T { - fn do_draw_with_neighbors<'a, 'b>( - &'a self, - out: &'b mut dyn Write, - _neighbors: &'a Neighbors<Vec<&'a Box<dyn Entity>>>, - ) -> io::Result<()> { - self.do_draw(out) - } -} diff --git a/src/display/utils.rs b/src/display/utils.rs deleted file mode 100644 index acd4416cb884..000000000000 --- a/src/display/utils.rs +++ /dev/null @@ -1,9 +0,0 @@ -use std::iter::FromIterator; - -pub fn times<A: Copy, B: FromIterator<A>>(elem: A, n: u16) -> B { - (0..n).map(|_| elem).collect() -} - -pub fn clone_times<A: Clone, B: FromIterator<A>>(elem: A, n: u16) -> B { - (0..n).map(|_| elem.clone()).collect() -} diff --git a/src/display/viewport.rs b/src/display/viewport.rs deleted file mode 100644 index c44316cdaad5..000000000000 --- a/src/display/viewport.rs +++ /dev/null @@ -1,303 +0,0 @@ -use super::BoxStyle; -use super::DrawWithNeighbors; -use crate::display::draw_box::draw_box; -use crate::display::utils::clone_times; -use crate::entities::entity::Entity; -use crate::types::menu::MenuInfo; -use crate::types::Neighbors; -use crate::types::{pos, BoundingBox, Direction, Position, Positioned}; -use std::fmt::{self, Debug}; -use std::io::{self, Write}; - -pub enum CursorState { - Game, - Prompt(Position), -} - -impl Default for CursorState { - fn default() -> Self { - CursorState::Game - } -} - -pub struct Viewport<W> { - /// The box describing the visible part of the viewport. - /// - /// Generally the size of the terminal, and positioned at 0, 0 - pub outer: BoundingBox, - - /// The box describing the game part of the viewport. - pub game: BoundingBox, - - /// The box describing the inner part of the viewport - /// - /// Its position is relative to `outer.inner()`, and its size should - /// generally not be smaller than outer - pub inner: BoundingBox, - - /// The actual screen that the viewport writes to - pub out: W, - - cursor_state: CursorState, - - /// Reset the cursor back to this position after every draw - pub game_cursor_position: Position, -} - -impl<W> Viewport<W> { - pub fn new(outer: BoundingBox, inner: BoundingBox, out: W) -> Self { - Viewport { - outer, - inner, - out, - game: outer.move_tr_corner(Position { x: 0, y: 1 }), - cursor_state: Default::default(), - game_cursor_position: pos(0, 0), - } - } - - /// Returns true if the (inner-relative) position of the given entity is - /// visible within this viewport - pub fn visible<E: Positioned>(&self, ent: &E) -> bool { - self.on_screen(ent.position()).within(self.game.inner()) - } - - /// Convert the given inner-relative position to one on the actual screen - fn on_screen(&self, pos: Position) -> Position { - pos + self.inner.position + self.game.inner().position - } -} - -impl<W> Debug for Viewport<W> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Viewport {{ outer: {:?}, inner: {:?}, out: <OUT> }}", - self.outer, self.inner - ) - } -} - -impl<W: Write> Viewport<W> { - /// Draw the given entity to the viewport at its position, if visible - #[allow(clippy::borrowed_box)] - pub fn draw<T: DrawWithNeighbors>( - &mut self, - entity: &T, - neighbors: &Neighbors<Vec<&Box<dyn Entity>>>, - ) -> io::Result<()> { - if !self.visible(entity) { - return Ok(()); - } - self.cursor_goto(entity.position())?; - entity.do_draw_with_neighbors(self, neighbors)?; - self.reset_cursor() - } - - fn reset_cursor(&mut self) -> io::Result<()> { - self.cursor_goto(self.game_cursor_position) - } - - /// Move the cursor to the given inner-relative position - pub fn cursor_goto(&mut self, pos: Position) -> io::Result<()> { - write!(self, "{}", self.on_screen(pos).cursor_goto()) - } - - /// Clear whatever single character is drawn at the given inner-relative - /// position, if visible - pub fn clear(&mut self, pos: Position) -> io::Result<()> { - write!(self, "{} ", self.on_screen(pos).cursor_goto(),)?; - self.reset_cursor() - } - - /// Initialize this viewport by drawing its outer box to the screen - pub fn init(&mut self) -> io::Result<()> { - draw_box(self, self.game, BoxStyle::Thin) - } - - /// Write a message to the message area on the screen - /// - /// Will overwrite any message already present, and if the given message is - /// longer than the screen will truncate. This means callers should handle - /// message buffering and ellipsisization - pub fn write_message(&mut self, msg: &str) -> io::Result<usize> { - let msg_to_write = if msg.len() <= self.outer.dimensions.w as usize { - msg - } else { - &msg[0..self.outer.dimensions.w as usize] - }; - write!( - self, - "{}{}{}", - self.outer.position.cursor_goto(), - msg_to_write, - clone_times::<_, String>( - " ".to_string(), - self.outer.dimensions.w - msg.len() as u16 - ), - )?; - self.reset_cursor()?; - Ok(msg_to_write.len()) - } - - pub fn clear_message(&mut self) -> io::Result<()> { - write!( - self, - "{}{}", - self.outer.position.cursor_goto(), - clone_times::<_, String>( - " ".to_string(), - self.outer.dimensions.w as u16 - ) - )?; - self.reset_cursor() - } - - /// Write a prompt requesting text input to the message area on the screen. - /// - /// Will overwrite any message already present, and if the given message is - /// longer than the screen will truncate. This means callers should handle - /// message buffering and ellipsisization - pub fn write_prompt<'a, 'b>(&'a mut self, msg: &'b str) -> io::Result<()> { - let len = self.write_message(msg)? + 1; - let pos = self.outer.position + pos(len as i16, 0); - self.cursor_state = CursorState::Prompt(pos); - write!(self, "{}", pos.cursor_goto())?; - self.flush() - } - - pub fn push_prompt_chr(&mut self, chr: char) -> io::Result<()> { - if let CursorState::Prompt(pos) = self.cursor_state { - write!(self, "{}", chr)?; - self.cursor_state = CursorState::Prompt(pos + Direction::Right); - } - Ok(()) - } - - pub fn pop_prompt_chr(&mut self) -> io::Result<()> { - if let CursorState::Prompt(pos) = self.cursor_state { - let new_pos = pos + Direction::Left; - write!( - self, - "{} {}", - new_pos.cursor_goto(), - new_pos.cursor_goto() - )?; - self.cursor_state = CursorState::Prompt(new_pos); - } - Ok(()) - } - - pub fn clear_prompt(&mut self) -> io::Result<()> { - self.clear_message()?; - self.cursor_state = CursorState::Game; - Ok(()) - } - - pub fn write_menu(&mut self, menu: &MenuInfo) -> io::Result<()> { - let menu_dims = menu.dimensions(); - - // TODO: check if the menu is too big - - let menu_position = self.game.position + pos(1, 1); - - let menu_box = BoundingBox { - dimensions: menu_dims, - position: menu_position, - }; - - debug!("writing menu at: {:?}", menu_box); - - draw_box(self, menu_box, BoxStyle::Thin)?; - - write!( - self, - "{}{}", - (menu_position + pos(2, 2)).cursor_goto(), - menu.prompt - )?; - - for (idx, option) in menu.options.iter().enumerate() { - write!( - self, - "{}{}", - (menu_position + pos(2, 4 + idx as i16)).cursor_goto(), - option - )?; - } - - Ok(()) - } -} - -impl<W> Positioned for Viewport<W> { - fn position(&self) -> Position { - self.outer.position - } -} - -impl<W: Write> Write for Viewport<W> { - fn write(&mut self, buf: &[u8]) -> io::Result<usize> { - self.out.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.out.flush() - } - - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - self.out.write_all(buf) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::types::Dimensions; - - #[test] - fn test_visible() { - assert!(Viewport::new( - BoundingBox::at_origin(Dimensions { w: 10, h: 10 }), - BoundingBox { - position: Position { x: -10, y: -10 }, - dimensions: Dimensions { w: 15, h: 15 }, - }, - () - ) - .visible(&Position { x: 13, y: 13 })); - - assert!(!Viewport::new( - BoundingBox::at_origin(Dimensions { w: 10, h: 10 }), - BoundingBox { - position: Position { x: -10, y: -10 }, - dimensions: Dimensions { w: 15, h: 15 }, - }, - (), - ) - .visible(&Position { x: 1, y: 1 })); - } - - #[test] - fn test_write_menu() { - let buf: Vec<u8> = Vec::new(); - - let mut viewport = Viewport::new( - BoundingBox::at_origin(Dimensions::default()), - BoundingBox::at_origin(Dimensions::default()), - buf, - ); - - let menu = MenuInfo::new( - "Test menu".to_string(), - vec!["option 1".to_string(), "option 2".to_string()], - ); - - viewport.write_menu(&menu).unwrap(); - - let res = std::str::from_utf8(&viewport.out).unwrap(); - assert!(res.contains("Test menu")); - assert!(res.contains("option 1")); - assert!(res.contains("option 2")); - } -} diff --git a/src/entities/character.rs b/src/entities/character.rs deleted file mode 100644 index 3e8336b129ff..000000000000 --- a/src/entities/character.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::display; -use crate::entities::item::Item; -use crate::types::{Position, Speed}; -use std::io::{self, Write}; - -const DEFAULT_SPEED: Speed = Speed(100); - -entity! { - pub struct Character { - pub o_name: Option<String>, - pub inventory: Vec<Box<Item>>, - } -} - -static_description!(Character, "yourself"); - -impl Character { - pub fn new() -> Character { - Character { - id: None, - position: Position { x: 0, y: 0 }, - o_name: None, - inventory: Vec::new(), - } - } - - pub fn speed(&self) -> Speed { - Speed(100) - } - - pub fn damage(&self) -> u16 { - // TODO - 1 - } - - pub fn name(&self) -> &str { - self.o_name - .as_ref() - .expect("Character name not initialized") - } - - pub fn set_name(&mut self, name: String) { - self.o_name = Some(name); - } -} - -impl display::Draw for Character { - fn do_draw(&self, out: &mut dyn Write) -> io::Result<()> { - write!(out, "@") - } -} diff --git a/src/entities/creature.rs b/src/entities/creature.rs deleted file mode 100644 index 20071c1d88eb..000000000000 --- a/src/entities/creature.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::display; -use crate::entities::raws::CreatureType; -use crate::entities::raws::EntityRaw; -use crate::entities::{raw, Describe, EntityID}; -use crate::types::Position; -use std::io::{self, Write}; - -#[derive(Debug, Clone)] -pub struct Creature { - pub id: Option<EntityID>, - pub typ: &'static CreatureType<'static>, - pub position: Position, - pub hitpoints: u16, -} - -impl Creature { - pub fn new_from_raw(name: &'static str, position: Position) -> Self { - match raw(name) { - EntityRaw::Creature(typ) => Self::new_with_type(typ, position), - _ => panic!("Invalid raw type for {:?}, expected Creature", name), - } - } - - pub fn new_with_type( - typ: &'static CreatureType<'static>, - position: Position, - ) -> Self { - Creature { - id: None, - typ, - position, - hitpoints: typ.max_hitpoints, - } - } - - /// Damage the given creature by the given amount - pub fn damage(&mut self, amount: u16) { - if self.hitpoints <= amount { - self.hitpoints = 0; - } else { - self.hitpoints -= amount; - } - } - - /// Returns true if this creature has died - pub fn dead(&self) -> bool { - self.hitpoints == 0 - } -} - -entity!(Creature); - -impl Describe for Creature { - fn description(&self) -> String { - self.typ.description.to_string() - } -} - -impl display::Draw for Creature { - fn do_draw(&self, out: &mut dyn Write) -> io::Result<()> { - write!(out, "{}", self.typ.chr) - } -} diff --git a/src/entities/entity.rs b/src/entities/entity.rs deleted file mode 100644 index 01075d298f81..000000000000 --- a/src/entities/entity.rs +++ /dev/null @@ -1,125 +0,0 @@ -use crate::display::DrawWithNeighbors; -use crate::entities::EntityID; -use crate::types::Neighbors; -use crate::types::Position; -use crate::types::{Positioned, PositionedMut}; -use downcast_rs::Downcast; -use std::fmt::Debug; -use std::io::{self, Write}; - -pub trait Identified<ID>: Debug { - fn opt_id(&self) -> Option<ID>; - fn set_id(&mut self, id: ID); - - fn id(&self) -> ID { - self.opt_id() - .unwrap_or_else(|| panic!("Entity ({:?}) is not in the game", self)) - } -} - -impl<'a, A, ID> Identified<ID> for &'a mut A -where - A: Identified<ID>, -{ - fn opt_id(&self) -> Option<ID> { - (**self).opt_id() - } - fn set_id(&mut self, id: ID) { - (**self).set_id(id); - } -} - -impl<ID, A: Identified<ID>> Identified<ID> for Box<A> { - fn opt_id(&self) -> Option<ID> { - (**self).opt_id() - } - fn set_id(&mut self, id: ID) { - (**self).set_id(id); - } -} - -pub trait Describe { - fn description(&self) -> String; -} - -ref_impl! { - impl<T: Describe> Describe for &T { - fn description(&self) -> String { - (**self).description() - } - } -} - -#[macro_export] -macro_rules! static_description { - ($name: ident, $description: expr) => { - impl $crate::entities::entity::Describe for $name { - fn description(&self) -> String { - $description.to_string() - } - } - }; -} - -pub trait Entity: - Positioned - + PositionedMut - + Identified<EntityID> - + DrawWithNeighbors - + Downcast - + Describe -{ -} - -impl Identified<EntityID> for Box<dyn Entity> { - fn opt_id(&self) -> Option<EntityID> { - (**self).opt_id() - } - fn set_id(&mut self, id: EntityID) { - (**self).set_id(id); - } -} - -#[macro_export] -macro_rules! identified { - ($name: ident, $typ: path) => { - identified!($name, $typ, id); - }; - ($name: ident, $typ: path, $attr: ident) => { - impl crate::entities::entity::Identified<$typ> for $name { - fn opt_id(&self) -> Option<$typ> { - self.$attr - } - - fn set_id(&mut self, id: $typ) { - self.$attr = Some(id) - } - } - }; -} - -impl_downcast!(Entity); - -impl DrawWithNeighbors for Box<dyn Entity> { - fn do_draw_with_neighbors<'a, 'b>( - &'a self, - out: &'b mut dyn Write, - neighbors: &'a Neighbors<Vec<&'a Box<dyn Entity>>>, - ) -> io::Result<()> { - (**self).do_draw_with_neighbors(out, neighbors) - } -} - -pub type AnEntity = Box<dyn Entity>; - -impl Positioned for AnEntity { - fn position(&self) -> Position { - (**self).position() - } -} - -impl PositionedMut for AnEntity { - fn set_position(&mut self, pos: Position) { - (**self).set_position(pos) - } -} diff --git a/src/entities/entity_char.rs b/src/entities/entity_char.rs deleted file mode 100644 index 70f26bfffdbd..000000000000 --- a/src/entities/entity_char.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::display::color::Color; -use std::fmt::{self, Display, Formatter}; -use termion::color; - -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct EntityChar { - #[serde(default)] - color: Color, - - #[serde(rename = "char")] - chr: char, -} - -impl Display for EntityChar { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!( - f, - "{}{}{}", - color::Fg(&self.color), - self.chr, - color::Fg(color::Reset) - ) - } -} diff --git a/src/entities/environment.rs b/src/entities/environment.rs deleted file mode 100644 index 8f8a56706287..000000000000 --- a/src/entities/environment.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::display; -use crate::display::draw_box::{BoxStyle, Stylable}; -use crate::entities::Entity; -use crate::types::{Neighbors, Position}; -use std::io::{self, Write}; - -entity! { - pub struct Wall { - pub style: BoxStyle - } -} - -static_description!(Wall, "a wall"); - -impl Wall { - pub fn new(position: Position, style: BoxStyle) -> Self { - new_entity!(Wall { position, style }) - } -} - -impl display::DrawWithNeighbors for Wall { - fn do_draw_with_neighbors<'a, 'b>( - &'a self, - out: &'b mut dyn Write, - neighbors: &'a Neighbors<Vec<&'a Box<dyn Entity>>>, - ) -> io::Result<()> { - let neighbor_styles: Neighbors<Option<BoxStyle>> = - neighbors.map(|es| { - es.iter() - .filter_map(|e| e.downcast_ref::<Wall>()) - .map(|wall| wall.style) - .next() - }); - write!(out, "{}", neighbor_styles.style(self.style)) - } -} diff --git a/src/entities/item.rs b/src/entities/item.rs deleted file mode 100644 index 5f08780d4fb2..000000000000 --- a/src/entities/item.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::display; -use crate::entities::raws::{raw, EntityRaw, ItemType}; -use crate::entities::{Describe, EntityID}; -use crate::types::Position; -use std::io::{self, Write}; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Item { - pub id: Option<EntityID>, - pub typ: &'static ItemType<'static>, - pub position: Position, -} - -impl Item { - pub fn new_from_raw(name: &'static str, position: Position) -> Self { - match raw(name) { - EntityRaw::Item(typ) => Self::new_with_type(typ, position), - _ => panic!("Invalid raw type for {:?}, expected Item", name), - } - } - - pub fn new_with_type( - typ: &'static ItemType<'static>, - position: Position, - ) -> Self { - Item { - id: None, - typ, - position, - } - } - - pub fn is_edible(&self) -> bool { - self.typ.is_edible() - } -} - -entity!(Item); - -impl Describe for Item { - fn description(&self) -> String { - self.typ.description.to_string() - } -} - -impl display::Draw for Item { - fn do_draw(&self, out: &mut dyn Write) -> io::Result<()> { - write!(out, "{}", self.typ.chr) - } -} diff --git a/src/entities/mod.rs b/src/entities/mod.rs deleted file mode 100644 index a8c39ed8aa78..000000000000 --- a/src/entities/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -#[macro_use] -pub mod entity; -#[macro_use] -pub mod util; -pub mod character; -pub mod creature; -pub mod entity_char; -pub mod environment; -pub mod item; -pub mod raw_types; -pub mod raws; - -pub use character::Character; -pub use creature::Creature; -pub use entity::{AnEntity, Describe, Entity, Identified}; -pub use entity_char::EntityChar; -pub use item::Item; -pub use raws::raw; - -pub type EntityID = u32; diff --git a/src/entities/raw_types.rs b/src/entities/raw_types.rs deleted file mode 100644 index 4bc291b69580..000000000000 --- a/src/entities/raw_types.rs +++ /dev/null @@ -1,110 +0,0 @@ -use crate::entities::entity_char::EntityChar; -use crate::messages::Message; -use crate::types::Speed; - -#[derive(Debug, Deserialize)] -pub struct CreatureType<'a> { - /// The name of the creature. Used in raw lookups. - pub name: &'a str, - - /// A description of the entity, used by the "look" command - pub description: &'a str, - - #[serde(rename = "char")] - pub chr: EntityChar, - pub max_hitpoints: u16, - pub speed: Speed, - pub friendly: bool, -} - -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct EdibleItem<'a> { - #[serde(borrow)] - pub eat_message: Option<Message<'a>>, - - /// The number of hitpoints that eating this item heals - pub hitpoints_healed: u16, -} - -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct ItemType<'a> { - pub name: &'a str, - - /// A description of the item, used by the "look" command and when walking - /// over the item on the ground - pub description: &'a str, - - /// A longer description of the item - pub long_description: &'a str, - - pub edible_item: Option<EdibleItem<'a>>, - - #[serde(rename = "char")] - pub chr: EntityChar, -} - -#[cfg(test)] -mod item_type_tests { - use super::*; - - #[test] - fn test_deserialize_item_type() { - let result = serde_json::from_str( - r#"{ - "Item": { - "name": "noodles", - "description": "a big bowl o' noodles", - "long_description": "You know exactly what kind of noodles", - "char": { "char": "n" }, - "edible_item": { - "eat_message": "You slurp up the noodles", - "hitpoints_healed": 2 - } - } - }"#, - ) - .unwrap(); - assert_matches!(result, EntityRaw::Item(_)); - if let EntityRaw::Item(item) = result { - assert_eq!(item.name, "noodles"); - } - - let toml_result = toml::from_str( - r#"[Item] -name = "noodles" -description = "a big bowl o' noodles" -long_description = "You know exactly what kind of noodles" -char = { char = "🍜" } -edible_item = { eat_message = "You slurp up the noodles", hitpoints_healed = 2 } -"#, - ) - .unwrap(); - - assert_matches!(toml_result, EntityRaw::Item(_)); - if let EntityRaw::Item(item) = toml_result { - assert_eq!(item.name, "noodles"); - } - } -} - -impl<'a> ItemType<'a> { - pub fn is_edible(&self) -> bool { - self.edible_item.is_some() - } -} - -#[derive(Debug, Deserialize)] -pub enum EntityRaw<'a> { - Creature(#[serde(borrow)] CreatureType<'a>), - Item(#[serde(borrow)] ItemType<'a>), -} - -impl<'a> EntityRaw<'a> { - pub fn name(&self) -> &'a str { - use EntityRaw::*; - match self { - Creature(typ) => typ.name, - Item(typ) => typ.name, - } - } -} diff --git a/src/entities/raws.rs b/src/entities/raws.rs deleted file mode 100644 index 061e29a84037..000000000000 --- a/src/entities/raws.rs +++ /dev/null @@ -1,38 +0,0 @@ -pub use crate::entities::raw_types::{CreatureType, EntityRaw, ItemType}; -use std::collections::HashMap; - -static_cfg! { - static ref RAWS: Vec<EntityRaw<'static>> = cfg_dir("src/entities/raws"); -} - -lazy_static! { - static ref RAWS_BY_NAME: HashMap<&'static str, &'static EntityRaw<'static>> = { - let mut hm = HashMap::new(); - for er in RAWS.iter() { - if hm.contains_key(er.name()) { - panic!("Duplicate entity: {}", er.name()) - } - - hm.insert(er.name(), er); - } - hm - }; -} - -pub fn raw(name: &'static str) -> &'static EntityRaw<'static> { - RAWS_BY_NAME - .get(name) - .copied() - .unwrap_or_else(|| panic!("Raw not found: {}", name)) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_raws() { - RAWS_BY_NAME.keys(); - assert_eq!(raw("noodles").name(), "noodles"); - } -} diff --git a/src/entities/raws/gormlak.toml b/src/entities/raws/gormlak.toml deleted file mode 100644 index be30362d25bd..000000000000 --- a/src/entities/raws/gormlak.toml +++ /dev/null @@ -1,10 +0,0 @@ -[Creature] -name = "gormlak" -description = """ -A chittering imp-like creature with bright yellow horns. It adores shiny objects -and gathers in swarms. -""" -char = { char = "g", color = "red" } -max_hitpoints = 5 -speed = 120 -friendly = false diff --git a/src/entities/raws/noodles.json b/src/entities/raws/noodles.json deleted file mode 100644 index dfa2609f5ecb..000000000000 --- a/src/entities/raws/noodles.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Item": { - "name": "noodles", - "char": { - "char": "n", - "color": "yellow" - }, - "description": "a big bowl o' noodles", - "long_description": "You know exactly what kind of noodles", - "edible_item": { - "eat_message": "You slurp up the noodles", - "hitpoints_healed": 2 - } - } -} diff --git a/src/entities/util.rs b/src/entities/util.rs deleted file mode 100644 index 6c11ffadf994..000000000000 --- a/src/entities/util.rs +++ /dev/null @@ -1,72 +0,0 @@ -#[macro_export] -macro_rules! new_entity { - ($name: ident) => { - new_entity!($name, {}) - }; - - ($name: ident { position: $position:expr $(, $fields:tt)* }) => { - $name { - id: None, - position: $position, - $($fields)* - } - }; - - ($name: ident { $position:expr $(, $fields:tt)* }) => { - $name { - id: None, - position: $position, - $($fields)* - } - }; -} - -#[macro_export] -macro_rules! boring_entity { - ($name:ident) => { - entity! { - pub struct $name {} - } - - impl $name { - #[allow(dead_code)] - pub fn new(position: $crate::types::Position) -> Self { - $name { id: None, position } - } - } - }; - - ($name:ident, char: $char: expr) => { - boring_entity!($name); - - impl $crate::display::Draw for $name { - fn do_draw(&self, out: &mut Write) -> io::Result<()> { - write!(out, "{}", $char) - } - } - }; -} - -#[macro_export] -macro_rules! entity { - ($name: ident) => { - positioned!($name); - positioned_mut!($name); - identified!($name, $crate::entities::EntityID); - impl $crate::entities::entity::Entity for $name {} - }; - - (pub struct $name:ident { $($struct_contents:tt)* } $($rest:tt)*) => { - #[derive(Debug, PartialEq, Eq, Clone)] - pub struct $name { - pub id: Option<$crate::entities::EntityID>, - pub position: $crate::types::Position, - $($struct_contents)* - } - - entity!($name); - entity!($($rest)*); - }; - - () => {}; -} diff --git a/src/game.rs b/src/game.rs deleted file mode 100644 index c478e0d2f55b..000000000000 --- a/src/game.rs +++ /dev/null @@ -1,617 +0,0 @@ -use crate::description::list_to_sentence; -use crate::display::{self, Viewport}; -use crate::entities::entity::Describe; -use crate::entities::entity::Entity; -use crate::entities::{ - AnEntity, Character, Creature, EntityID, Identified, Item, -}; -use crate::messages::message; -use crate::settings::Settings; -use crate::types::command::Command; -use crate::types::entity_map::EntityMap; -use crate::types::{ - pos, BoundingBox, Collision, Dimensions, Position, Positioned, Ticks, -}; -use crate::util::promise::Cancelled; -use crate::util::promise::{promise, Complete, Promise, Promises}; -use crate::util::template::TemplateParams; -use rand::rngs::SmallRng; -use rand::SeedableRng; -use std::io::{self, StdinLock, StdoutLock, Write}; -use termion::input::Keys; -use termion::input::TermRead; -use termion::raw::RawTerminal; - -type Stdout<'a> = RawTerminal<StdoutLock<'a>>; - -type Rng = SmallRng; - -enum PromptResolution { - Uncancellable(Complete<String>), - Cancellable(Complete<Result<String, Cancelled>>), -} - -/// The mode to use when describing entities on a tile to the user -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum EntityDescriptionMode { - /// Describe the entities that the user is walking over. - /// - /// This means: - /// - Skip the character themselves - /// - Describe nothing if there are no items other than the character - Walk, - - /// Describe entities that the user is actively asking about. - /// - /// This means: - /// - Describe the character themselves if they've asked to look at the tile - /// they're standing on - /// - Explicitly say there's nothing there if there's nothing there. - Look, -} - -impl PromptResolution { - fn is_cancellable(&self) -> bool { - use PromptResolution::*; - match self { - Uncancellable(_) => false, - Cancellable(_) => true, - } - } - - fn fulfill(&mut self, val: String) { - use PromptResolution::*; - match self { - Cancellable(complete) => complete.ok(val), - Uncancellable(complete) => complete.fulfill(val), - } - } - - fn cancel(&mut self) { - use PromptResolution::*; - match self { - Cancellable(complete) => complete.cancel(), - Uncancellable(_complete) => {} - } - } -} - -/// The kind of input the game is waiting to receive -enum InputState { - /// The initial input state of the game - we're currently waiting for direct - /// commands. - Initial, - - /// A free text prompt has been shown to the user, and every character - /// besides "escape" is interpreted as a response to that prompt - Prompt { - complete: PromptResolution, - buffer: String, - }, -} - -impl InputState { - fn uncancellable_prompt(complete: Complete<String>) -> Self { - InputState::Prompt { - complete: PromptResolution::Uncancellable(complete), - buffer: String::new(), - } - } - - fn cancellable_prompt( - complete: Complete<Result<String, Cancelled>>, - ) -> Self { - InputState::Prompt { - complete: PromptResolution::Cancellable(complete), - buffer: String::new(), - } - } -} - -impl Default for InputState { - fn default() -> Self { - InputState::Initial - } -} - -/// The full state of a running Game -pub struct Game<'a> { - settings: Settings, - - viewport: Viewport<Stdout<'a>>, - - /// An iterator on keypresses from the user - keys: Keys<StdinLock<'a>>, - - /// The kind of input the game is waiting to receive - input_state: InputState, - - /// The map of all the entities in the game - entities: EntityMap<AnEntity>, - - /// The entity ID of the player character - character_entity_id: EntityID, - - /// The messages that have been said to the user, in forward time order - messages: Vec<String>, - - /// The index of the currently-displayed message. Used to track the index of - /// the currently displayed message when handling PreviousMessage commands - message_idx: usize, - - /// A global random number generator for the game - rng: Rng, - - /// A list of promises that are waiting on the game and a result - promises: Promises<'a, Self>, -} - -impl<'a> Game<'a> { - pub fn new( - settings: Settings, - stdout: RawTerminal<StdoutLock<'a>>, - stdin: StdinLock<'a>, - w: u16, - h: u16, - ) -> Game<'a> { - let rng = match settings.seed { - Some(seed) => SmallRng::seed_from_u64(seed), - None => SmallRng::from_entropy(), - }; - let mut entities: EntityMap<AnEntity> = EntityMap::new(); - - // TODO make this dynamic - { - entities.insert(Box::new(Creature::new_from_raw( - "gormlak", - pos(10, 0), - ))); - - entities - .insert(Box::new(Item::new_from_raw("noodles", pos(0, 10)))); - } - - Game { - settings, - rng, - message_idx: 0, - viewport: Viewport::new( - BoundingBox::at_origin(Dimensions { w, h }), - BoundingBox::at_origin(Dimensions { w: w - 2, h: h - 2 }), - stdout, - ), - keys: stdin.keys(), - input_state: Default::default(), - character_entity_id: entities.insert(Box::new(Character::new())), - messages: Vec::new(), - entities, - promises: Promises::new(), - } - } - - fn downcast_entities_at<A: Entity>(&self, pos: Position) -> Vec<&A> { - self.entities - .at(pos) - .iter() - .filter_map(|e| e.downcast_ref()) - .collect() - } - - /// Returns a list of all creature entities at the given position - fn creatures_at(&self, pos: Position) -> Vec<&Creature> { - self.downcast_entities_at(pos) - } - - /// Returns a list of all item entities at the given position - fn items_at(&self, pos: Position) -> Vec<&Item> { - self.downcast_entities_at(pos) - } - - /// Returns a collision, if any, at the given Position in the game - fn collision_at(&self, pos: Position) -> Option<Collision> { - if !pos.within(self.viewport.inner) { - Some(Collision::Stop) - } else if self.creatures_at(pos).is_empty() { - None - } else { - Some(Collision::Combat) - } - } - - fn character(&self) -> &Character { - (*self.entities.get(self.character_entity_id).unwrap()) - .downcast_ref() - .unwrap() - } - - fn mut_character(&mut self) -> &mut Character { - (*self.entities.get_mut(self.character_entity_id).unwrap()) - .downcast_mut() - .unwrap() - } - - /// Draw all the game entities to the screen - fn draw_entities(&mut self) -> io::Result<()> { - for entity in self.entities.entities() { - self.viewport.draw( - entity, - &self.entities.neighbor_entities(entity.position()), - )?; - } - Ok(()) - } - - /// Draw all the game entities to the screen - fn draw_entities_at(&mut self, pos: Position) -> io::Result<()> { - for entity in self.entities.at(pos) { - self.viewport.draw( - entity, - &self.entities.neighbor_entities(entity.position()), - )?; - } - Ok(()) - } - - /// Draw the game entity with the given ID, if any, to the screen - fn draw_entity(&mut self, entity_id: EntityID) -> io::Result<bool> { - if let Some(entity) = self.entities.get(entity_id) { - self.viewport.draw( - entity, - &self.entities.neighbor_entities(entity.position()), - )?; - Ok(true) - } else { - Ok(false) - } - } - - /// Describe all the entities at a given position to the user. - /// - /// If `force` is not set to `true`, will not do anything if there are no - /// entities - fn describe_entities_at( - &mut self, - pos: Position, - mode: EntityDescriptionMode, - ) -> io::Result<()> { - use EntityDescriptionMode::*; - let mut entities = self.entities.at(pos); - if mode == Walk { - entities.retain(|e| e.id() != self.character_entity_id); - } - - if entities.is_empty() { - match mode { - Walk => return Ok(()), - Look => { - return self.say( - "global.describe_no_entities", - &template_params!(), - ) - } - } - } - - let descriptions = list_to_sentence( - &entities - .iter() - .map(|e| e.description()) - .collect::<Vec<String>>(), - ); - - self.say( - "global.describe_entities", - &template_params!({ "descriptions" => &descriptions, }), - ) - } - - /// Remove the given entity from the game, drawing over it if it's visible - fn remove_entity(&mut self, entity_id: EntityID) -> io::Result<()> { - if let Some(entity) = self.entities.remove(entity_id) { - self.viewport.clear(entity.position())?; - } - Ok(()) - } - - /// Step the game forward the given number of ticks - fn tick(&mut self, _ticks: Ticks) {} - - /// Get a message from the global map based on the rng in this game - fn message<'params>( - &mut self, - name: &'static str, - params: &TemplateParams<'params>, - ) -> String { - message(name, &mut self.rng, params) - } - - /// Say a message to the user - fn say<'params>( - &mut self, - message_name: &'static str, - params: &TemplateParams<'params>, - ) -> io::Result<()> { - let message = self.message(message_name, params); - self.messages.push(message.to_string()); - self.message_idx = self.messages.len() - 1; - self.viewport.write_message(&message)?; - Ok(()) - } - - /// Prompt the user for input, returning a Future for the result of the - /// prompt - fn prompt( - &mut self, - name: &'static str, - params: &TemplateParams<'_>, - ) -> io::Result<Promise<Self, String>> { - let (complete, promise) = promise(); - self.input_state = InputState::uncancellable_prompt(complete); - let message = self.message(name, params); - self.viewport.write_prompt(&message)?; - self.promises.push(Box::new(promise.clone())); - Ok(promise) - } - - fn prompt_cancellable( - &mut self, - name: &'static str, - params: &TemplateParams<'_>, - ) -> io::Result<Promise<Self, Result<String, Cancelled>>> { - let (complete, promise) = promise(); - self.input_state = InputState::cancellable_prompt(complete); - let message = self.message(name, params); - self.viewport.write_prompt(&message)?; - self.promises.push(Box::new(promise.clone())); - Ok(promise) - } - - fn previous_message(&mut self) -> io::Result<()> { - if self.message_idx == 0 { - return Ok(()); - } - self.message_idx -= 1; - let message = &self.messages[self.message_idx]; - self.viewport.write_message(message)?; - Ok(()) - } - - fn clear_message(&mut self) -> io::Result<()> { - debug!("{:?} {:?}", self.message_idx, self.messages); - if self.message_idx == self.messages.len() { - return Ok(()); - } - self.viewport.clear_message()?; - self.message_idx += 1; - Ok(()) - } - - fn creature(&self, creature_id: EntityID) -> Option<&Creature> { - self.entities - .get(creature_id) - .and_then(|e| e.downcast_ref::<Creature>()) - } - - fn expect_creature(&self, creature_id: EntityID) -> &Creature { - self.creature(creature_id).unwrap_or_else(|| { - panic!("Creature ID went away: {:?}", creature_id) - }) - } - - fn mut_creature(&mut self, creature_id: EntityID) -> Option<&mut Creature> { - self.entities - .get_mut(creature_id) - .and_then(|e| e.downcast_mut::<Creature>()) - } - - fn expect_mut_creature(&mut self, creature_id: EntityID) -> &mut Creature { - self.mut_creature(creature_id).unwrap_or_else(|| { - panic!("Creature ID went away: {:?}", creature_id) - }) - } - - fn attack(&mut self, creature_id: EntityID) -> io::Result<()> { - info!("Attacking creature {:?}", creature_id); - let damage = self.character().damage(); - let creature_name = self.expect_creature(creature_id).typ.name; - let tps = template_params!({ - "creature" => { - "name" => creature_name, - }, - }); - self.say("combat.attack", &tps)?; - - let creature = self.expect_mut_creature(creature_id); - creature.damage(damage); - if creature.dead() { - self.say("combat.killed", &tps)?; - info!("Killed creature {:?}", creature_id); - self.remove_entity(creature_id)?; - } - Ok(()) - } - - fn attack_at(&mut self, pos: Position) -> io::Result<()> { - let creatures = self.creatures_at(pos); - match creatures.len() { - 0 => Ok(()), - 1 => { - let creature = creatures.get(0).unwrap(); - let creature_id = creature.id(); - self.attack(creature_id) - } - _ => { - // TODO prompt with a menu of creatures to combat - unimplemented!() - } - } - } - - fn pick_up(&mut self) -> io::Result<()> { - let pos = self.character().position; - let items = self.items_at(pos); - match items.len() { - 0 => Ok(()), - 1 => { - let item_id = items.get(0).unwrap().id(); - let item: Box<Item> = - self.entities.remove(item_id).unwrap().downcast().unwrap(); - let desc = item.description(); - self.mut_character().inventory.push(item); - self.say( - "global.pick_up", - &template_params!({ - "item" => { "name" => &desc, }, - }), - ) - } - _ => { - // TODO prompt with a menu of items to pick up - unimplemented!() - } - } - } - - fn flush_promises(&mut self) { - unsafe { - let game = self as *mut Self; - (*game).promises.give_all(&mut *game); - } - } - - /// Run the game - pub fn run(mut self) -> io::Result<()> { - info!("Running game"); - self.viewport.init()?; - self.draw_entities()?; - self.flush().unwrap(); - - self.prompt("character.name_prompt", &template_params!())? - .on_fulfill(|game, char_name| { - game.say( - "global.welcome", - &template_params!({ - "character" => { - "name" => char_name, - }, - }), - ) - .unwrap(); - game.flush().unwrap(); - game.mut_character().set_name(char_name.to_string()); - }); - - loop { - let mut old_position = None; - let next_key = self.keys.next().unwrap().unwrap(); - match &mut self.input_state { - InputState::Initial => { - use Command::*; - match Command::from_key(next_key) { - Some(Quit) => { - info!("Quitting game due to user request"); - break; - } - - Some(Move(direction)) => { - use Collision::*; - let new_pos = self.character().position + direction; - match self.collision_at(new_pos) { - None => { - old_position = - Some(self.character().position); - self.entities.update_position( - self.character_entity_id, - new_pos, - ); - } - Some(Combat) => { - self.attack_at(new_pos)?; - } - Some(Stop) => (), - } - } - - Some(PreviousMessage) => self.previous_message()?, - - Some(PickUp) => self.pick_up()?, - - None => (), - } - - if let Some(old_pos) = old_position { - let character = self.character(); - let char_pos = character.position; - self.viewport.game_cursor_position = char_pos; - self.viewport.clear(old_pos)?; - self.draw_entities_at(old_pos)?; - self.draw_entity(self.character_entity_id)?; - self.clear_message()?; - self.describe_entities_at( - char_pos, - EntityDescriptionMode::Walk, - )?; - self.tick( - self.character().speed().tiles_to_ticks( - (old_pos - char_pos).as_tiles(), - ), - ); - } - } - - InputState::Prompt { complete, buffer } => { - use termion::event::Key::*; - match next_key { - Char('\n') => { - info!("Prompt complete: \"{}\"", buffer); - self.viewport.clear_prompt()?; - complete.fulfill(buffer.clone()); - self.input_state = InputState::Initial; - } - Char(chr) => { - buffer.push(chr); - self.viewport.push_prompt_chr(chr)?; - } - Esc => complete.cancel(), - Backspace => { - buffer.pop(); - self.viewport.pop_prompt_chr()?; - } - _ => {} - } - } - } - - self.flush()?; - self.flush_promises(); - debug!("{:?}", self.character()); - } - Ok(()) - } -} - -impl<'a> Drop for Game<'a> { - fn drop(&mut self) { - display::clear(self).unwrap_or(()); - } -} - -impl<'a> Write for Game<'a> { - fn write(&mut self, buf: &[u8]) -> io::Result<usize> { - self.viewport.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.viewport.flush() - } - - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - self.viewport.write_all(buf) - } -} - -impl<'a> Positioned for Game<'a> { - fn position(&self) -> Position { - Position { x: 0, y: 0 } - } -} diff --git a/src/level_gen/cave_automata.rs b/src/level_gen/cave_automata.rs deleted file mode 100644 index e5e2807ab251..000000000000 --- a/src/level_gen/cave_automata.rs +++ /dev/null @@ -1,120 +0,0 @@ -use crate::level_gen::util::fill_outer_edges; -use crate::level_gen::util::rand_initialize; -use crate::types::Dimensions; -use rand::Rng; - -pub struct Params { - chance_to_start_alive: f64, - birth_limit: i32, - death_limit: i32, - steps: usize, -} - -macro_rules! parse_optional { - ($out: ident . $attr: ident, $matches: expr, $arg: expr) => { - if let Some(val_s) = $matches.value_of($arg) { - $out.$attr = val_s.parse().unwrap(); - } - }; -} - -macro_rules! parse_optional_matches { - ($matches: expr) => {}; - ($matches: expr , { $ret: ident . $attr: ident = $arg: expr }) => { - parse_optional!($ret.$attr, $matches, $arg); - }; - ($matches: expr, { $($ret: ident . $attr: ident = $arg: expr ,)* }) => { - $(parse_optional!($ret.$attr, $matches, $arg);)* - }; -} - -impl Params { - pub fn from_matches<'a>(matches: &clap::ArgMatches<'a>) -> Self { - let mut ret: Self = Default::default(); - parse_optional_matches!(matches, { - ret.chance_to_start_alive = "start-alive-chance", - ret.birth_limit = "birth-limit", - ret.death_limit = "death-limit", - ret.steps = "steps", - }); - ret - } -} - -impl Default for Params { - fn default() -> Self { - Params { - chance_to_start_alive: 0.45, - birth_limit: 4, - death_limit: 3, - steps: 2, - } - } -} - -pub fn generate<R: Rng + ?Sized>( - dimensions: Dimensions, - params: &Params, - rand: &mut R, -) -> Vec<Vec<bool>> { - let mut cells = - rand_initialize(dimensions, rand, params.chance_to_start_alive); - for _ in 0..params.steps { - step_automata(&mut cells, dimensions, params); - } - - fill_outer_edges(&mut cells); - - cells -} - -fn step_automata( - cells: &mut Vec<Vec<bool>>, - dimensions: Dimensions, - params: &Params, -) { - let orig_cells = (*cells).clone(); - for x in 0..(dimensions.h as usize) { - for y in 0..(dimensions.w as usize) { - let nbs = num_alive_neighbors(&orig_cells, x as i32, y as i32); - if orig_cells[x][y] { - if nbs < params.death_limit { - cells[x][y] = false; - } else { - cells[x][y] = true; - } - } else if nbs > params.birth_limit { - cells[x][y] = true; - } else { - cells[x][y] = false; - } - } - } -} - -const COUNT_EDGES_AS_NEIGHBORS: bool = true; - -fn num_alive_neighbors(cells: &[Vec<bool>], x: i32, y: i32) -> i32 { - let mut count = 0; - for i in -1..2 { - for j in -1..2 { - if i == 0 && j == 0 { - continue; - } - - let neighbor_x = x + i; - let neighbor_y = y + j; - - if (COUNT_EDGES_AS_NEIGHBORS - && (neighbor_x < 0 - || neighbor_y < 0 - || neighbor_x >= (cells.len() as i32) - || neighbor_y >= (cells[0].len()) as i32)) - || cells[neighbor_x as usize][neighbor_y as usize] - { - count += 1; - } - } - } - count -} diff --git a/src/level_gen/mod.rs b/src/level_gen/mod.rs deleted file mode 100644 index d796a103b11e..000000000000 --- a/src/level_gen/mod.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::display::draw_box::BoxStyle; -use crate::display::utils::clone_times; -use crate::display::DrawWithNeighbors; -use crate::entities::entity::Entity; -use crate::entities::environment::Wall; -use crate::types::entity_map::EntityMap; -use crate::types::pos; -use itertools::Itertools; -use std::io; - -pub mod cave_automata; -pub mod util; - -pub fn level_to_entities(level: Vec<Vec<bool>>) -> EntityMap<Box<dyn Entity>> { - let mut res: EntityMap<Box<dyn Entity>> = EntityMap::new(); - - let xmax = level.len() as i16; - let ymax = if xmax == 0 { - 0i16 - } else { - level[0].len() as i16 - }; - - let get = |mut x: i16, mut y: i16| { - if x < 0 { - x = 0; - } - if y < 0 { - y = 0; - } - if x >= xmax - 1 { - x = xmax - 1; - } - if y >= ymax - 1 { - y = ymax - 1; - } - level[x as usize][y as usize] - }; - - for x in 0..xmax { - for y in 0..ymax { - if get(x, y) { - // don't output walls that are surrounded on all 8 sides by - // walls - if (x == 0 || get(x - 1, y)) - && (y == 0 || get(x, y - 1)) - && (x == xmax - 1 || get(x + 1, y)) - && (y == ymax - 1 || get(x, y + 1)) - && ((x == 0 && y == 0) || get(x - 1, y - 1)) - && ((x == 0 && y == ymax - 1) || get(x - 1, y + 1)) - && ((x == xmax - 1 && y == 0) || get(x + 1, y - 1)) - && ((x == xmax - 1 && y == ymax - 1) || get(x + 1, y + 1)) - { - continue; - } - res.insert(Box::new(Wall::new( - pos(y as i16, x as i16), - BoxStyle::Thin, - ))); - } - } - } - - res -} - -pub fn draw_level<W: io::Write>( - level: Vec<Vec<bool>>, - out: &mut W, -) -> io::Result<()> { - if level.is_empty() { - return Ok(()); - } - - let mut lines = clone_times::<Vec<char>, Vec<Vec<char>>>( - clone_times(' ', level[0].len() as u16), - level.len() as u16, - ); - - let em = level_to_entities(level); - - for entity in em.entities() { - let mut buf = Vec::new(); - entity.do_draw_with_neighbors( - &mut buf, - &em.neighbor_entities(entity.position()), - )?; - let buf_s = std::str::from_utf8(&buf).unwrap(); - if let Some(chr) = buf_s.chars().next() { - lines[entity.position().y as usize][entity.position().x as usize] = - chr; - } - } - - let res = lines - .iter() - .map(|line| line.iter().collect::<String>()) - .join("\n"); - - write!(out, "{}", res) -} diff --git a/src/level_gen/util.rs b/src/level_gen/util.rs deleted file mode 100644 index 4f56fe6c9557..000000000000 --- a/src/level_gen/util.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::types::Dimensions; -use rand::{distributions, Rng}; - -pub fn falses(dims: Dimensions) -> Vec<Vec<bool>> { - let mut ret = Vec::with_capacity(dims.h as usize); - for _ in 0..dims.h { - let mut row = Vec::with_capacity(dims.w as usize); - for _ in 0..dims.w { - row.push(false); - } - ret.push(row); - } - ret -} - -/// Randomly initialize a 2-dimensional boolean vector of the given -/// `Dimensions`, using the given random number generator and alive chance -pub fn rand_initialize<R: Rng + ?Sized>( - dims: Dimensions, - rng: &mut R, - alive_chance: f64, -) -> Vec<Vec<bool>> { - let distrib = distributions::Bernoulli::new(alive_chance).unwrap(); - let mut ret = Vec::with_capacity(dims.h as usize); - for _ in 0..dims.h { - let mut row = Vec::with_capacity(dims.w as usize); - for _ in 0..dims.w { - row.push(rng.sample(distrib)); - } - ret.push(row); - } - ret -} - -/// Fill the outer edges of a generated level with walls -pub fn fill_outer_edges(level: &mut Vec<Vec<bool>>) { - let xmax = level.len(); - if xmax == 0 { - return; - } - let ymax = level[0].len(); - - for row in level.iter_mut() { - row[0] = true; - row[ymax - 1] = true; - } - - for y in 0..level[0].len() { - level[0][y] = true; - level[xmax - 1][y] = true; - } -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 8004a5739ef5..000000000000 --- a/src/main.rs +++ /dev/null @@ -1,130 +0,0 @@ -#[macro_use] -extern crate log; -#[macro_use] -extern crate serde_derive; -#[macro_use] -extern crate clap; -#[macro_use] -extern crate prettytable; -#[macro_use] -extern crate lazy_static; -#[cfg(test)] -#[macro_use] -extern crate maplit; -#[macro_use] -extern crate downcast_rs; -#[macro_use] -extern crate include_dir; -#[macro_use] -extern crate nom; -#[cfg(test)] -#[macro_use] -extern crate matches; - -#[macro_use] -mod util; -#[macro_use] -mod types; -#[macro_use] -mod entities; -mod description; -mod display; -mod game; -mod level_gen; -mod messages; -mod settings; - -use crate::types::Dimensions; -use clap::App; -use game::Game; -use prettytable::format::consts::FORMAT_BOX_CHARS; -use rand::rngs::SmallRng; -use rand::SeedableRng; -use settings::Settings; - -use backtrace::Backtrace; -use std::io::{self, StdinLock, StdoutLock}; -use std::panic; - -use termion; -use termion::raw::IntoRawMode; -use termion::raw::RawTerminal; - -fn init( - settings: Settings, - stdout: RawTerminal<StdoutLock<'_>>, - stdin: StdinLock<'_>, - w: u16, - h: u16, -) -> io::Result<()> { - panic::set_hook(if settings.logging.print_backtrace { - Box::new(|info| (error!("{}\n{:#?}", info, Backtrace::new()))) - } else { - Box::new(|info| (error!("{}\n{:#?}", info, Backtrace::new()))) - }); - - let game = Game::new(settings, stdout, stdin, w, h); - game.run() -} - -fn generate_level<'a, W: io::Write>( - stdout: &mut W, - params: &clap::ArgMatches<'a>, -) -> io::Result<()> { - let mut rand = SmallRng::from_entropy(); - - let mut dimensions: Dimensions = Default::default(); - if let Some(h_s) = params.value_of("height") { - dimensions.h = h_s.parse().unwrap(); - } - if let Some(w_s) = params.value_of("width") { - dimensions.w = w_s.parse().unwrap(); - } - - let level = match params.value_of("generator") { - None => panic!("Must supply a generator with --generator"), - Some("cave_automata") => level_gen::cave_automata::generate( - dimensions, - &level_gen::cave_automata::Params::from_matches(params), - &mut rand, - ), - Some(gen) => panic!("Unrecognized generator: {}", gen), - }; - level_gen::draw_level(level, stdout) -} - -fn main() -> io::Result<()> { - let yaml = load_yaml!("cli.yml"); - let matches = App::from_yaml(yaml).get_matches(); - let settings = Settings::load().unwrap(); - settings.logging.init_log(); - let stdout = io::stdout(); - let mut stdout = stdout.lock(); - - let stdin = io::stdin(); - let stdin = stdin.lock(); - - let termsize = termion::terminal_size().ok(); - let (termwidth, termheight) = termsize.unwrap_or((70, 40)); - - match matches.subcommand() { - ("info", _) => { - let mut table = table!( - [br->"termwidth", termwidth], - [br->"termheight", termheight], - [br->"logfile", settings.logging.file], - [br->"loglevel", settings.logging.level] - ); - table.set_format(*FORMAT_BOX_CHARS); - table.printstd(); - Ok(()) - } - ("generate-level", params) => { - generate_level(&mut stdout, params.unwrap()) - } - _ => { - let stdout = stdout.into_raw_mode().unwrap(); - init(settings, stdout, stdin, termwidth, termheight) - } - } -} diff --git a/src/messages.rs b/src/messages.rs deleted file mode 100644 index b081389efc9d..000000000000 --- a/src/messages.rs +++ /dev/null @@ -1,166 +0,0 @@ -use crate::util::template::Template; -use crate::util::template::TemplateParams; -use rand::seq::SliceRandom; -use rand::Rng; -use std::collections::HashMap; - -#[derive(Deserialize, Debug, PartialEq, Eq)] -#[serde(untagged)] -pub enum Message<'a> { - #[serde(borrow)] - Single(Template<'a>), - Choice(Vec<Template<'a>>), -} - -impl<'a> Message<'a> { - fn resolve<R: Rng + ?Sized>(&self, rng: &mut R) -> Option<&Template<'a>> { - use Message::*; - match self { - Single(msg) => Some(msg), - Choice(msgs) => msgs.choose(rng), - } - } -} - -#[derive(Deserialize, Debug, PartialEq, Eq)] -#[serde(untagged)] -enum NestedMap<'a> { - #[serde(borrow)] - Direct(Message<'a>), - #[serde(borrow)] - Nested(HashMap<&'a str, NestedMap<'a>>), -} - -impl<'a> NestedMap<'a> { - fn lookup(&'a self, path: &str) -> Option<&'a Message<'a>> { - use NestedMap::*; - let leaf = - path.split('.') - .fold(Some(self), |current, key| match current { - Some(Nested(m)) => m.get(key), - _ => None, - }); - match leaf { - Some(Direct(msg)) => Some(msg), - _ => None, - } - } -} - -#[cfg(test)] -mod nested_map_tests { - use super::*; - - #[test] - fn test_deserialize_nested_map() { - let src = r#" -[global] -hello = "Hello World!" - -[foo.bar] -single = "Single" -choice = ["Say this", "Or this"] -"#; - let result = toml::from_str(src); - assert_eq!( - result, - Ok(NestedMap::Nested(hashmap! { - "global" => NestedMap::Nested(hashmap!{ - "hello" => NestedMap::Direct(Message::Single(Template::parse("Hello World!").unwrap())), - }), - "foo" => NestedMap::Nested(hashmap!{ - "bar" => NestedMap::Nested(hashmap!{ - "single" => NestedMap::Direct(Message::Single( - Template::parse("Single").unwrap() - )), - "choice" => NestedMap::Direct(Message::Choice( - vec![ - Template::parse("Say this").unwrap(), - Template::parse("Or this").unwrap() - ] - )) - }) - }) - })) - ) - } - - #[test] - fn test_lookup() { - let map: NestedMap<'static> = toml::from_str( - r#" -[global] -hello = "Hello World!" - -[foo.bar] -single = "Single" -choice = ["Say this", "Or this"] -"#, - ) - .unwrap(); - - assert_eq!( - map.lookup("global.hello"), - Some(&Message::Single(Template::parse("Hello World!").unwrap())) - ); - assert_eq!( - map.lookup("foo.bar.single"), - Some(&Message::Single(Template::parse("Single").unwrap())) - ); - assert_eq!( - map.lookup("foo.bar.choice"), - Some(&Message::Choice(vec![ - Template::parse("Say this").unwrap(), - Template::parse("Or this").unwrap() - ])) - ); - } -} - -// static MESSAGES_RAW: &'static str = include_str!("messages.toml"); - -static_cfg! { - static ref MESSAGES: NestedMap<'static> = toml_file("messages.toml"); -} - -pub fn get<R: Rng + ?Sized>( - name: &'static str, - rng: &mut R, -) -> Option<&'static Template<'static>> { - MESSAGES.lookup(name).and_then(|msg| msg.resolve(rng)) -} - -/// Look up and format a game message based on the given (dot-separated) name, -/// with the given random generator used to select from choice-based messages -pub fn message<'a, R: Rng + ?Sized>( - name: &'static str, - rng: &mut R, - params: &TemplateParams<'a>, -) -> String { - match get(name, rng) { - Some(msg) => msg.format(params).unwrap_or_else(|e| { - error!("Error formatting template: {}", e); - "Template Error".to_string() - }), - None => { - error!("Message not found: {}", name); - "Template Not Found".to_string() - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use rand::rngs::SmallRng; - use rand::SeedableRng; - - #[test] - fn test_static_messages() { - message( - "global.welcome", - &mut SmallRng::from_entropy(), - &template_params!(), - ); - } -} diff --git a/src/messages.toml b/src/messages.toml deleted file mode 100644 index a9a6b2e009a6..000000000000 --- a/src/messages.toml +++ /dev/null @@ -1,27 +0,0 @@ -[global] -welcome = "Welcome to Xanthous, {{character.name}}! It's dangerous out there, why not stay inside?" -describe_entities = "You see here {{descriptions}}" -describe_no_entities = "You see nothing here." -pick_up = "You pick up the {{item.name}}." - -[combat] -attack = "You attack the {{creature.name}}." -killed = [ - "You've killed the {{creature.name}}.", - "The {{creature.name}} dies.", - "The {{creature.name}} kicks it.", - "The {{creature.name}} beefs it." - ] - -[character] -name_prompt = [ - "Hey there friend. What's your name?", - "Hey there friend. What should we call you?", - "Howdy. What's your name?", - "Name please!", - "What's your name?", - "Hey, what's your name?", -] - -[defaults.item] -eat = "You eat the {{item.name}}. {{action.result}}" diff --git a/src/settings.rs b/src/settings.rs deleted file mode 100644 index 1f205814d1dd..000000000000 --- a/src/settings.rs +++ /dev/null @@ -1,70 +0,0 @@ -use config::{Config, ConfigError}; -use log::LevelFilter; -use log4rs::append::file::FileAppender; -use log4rs::config::{Appender, Root}; -use log4rs::encode::pattern::PatternEncoder; - -#[derive(Debug, Deserialize, Clone)] -pub struct Logging { - #[serde(default = "Logging::default_level")] - pub level: LevelFilter, - - #[serde(default = "Logging::default_file")] - pub file: String, - - #[serde(default = "Logging::default_print_backtrace")] - pub print_backtrace: bool, -} - -impl Default for Logging { - fn default() -> Self { - Logging { - level: LevelFilter::Off, - file: "debug.log".to_string(), - print_backtrace: true, - } - } -} - -impl Logging { - pub fn init_log(&self) { - let logfile = FileAppender::builder() - .encoder(Box::new(PatternEncoder::new("{d} {l} - {m}\n"))) - .build(self.file.clone()) - .unwrap(); - - let config = log4rs::config::Config::builder() - .appender(Appender::builder().build("logfile", Box::new(logfile))) - .build(Root::builder().appender("logfile").build(self.level)) - .unwrap(); - - log4rs::init_config(config).unwrap(); - } - - fn default_level() -> LevelFilter { - Logging::default().level - } - - fn default_file() -> String { - Logging::default().file - } - - fn default_print_backtrace() -> bool { - Logging::default().print_backtrace - } -} - -#[derive(Debug, Deserialize, Clone)] -pub struct Settings { - pub seed: Option<u64>, - pub logging: Logging, -} - -impl Settings { - pub fn load() -> Result<Self, ConfigError> { - let mut s = Config::new(); - s.merge(config::File::with_name("Config").required(false))?; - s.merge(config::Environment::with_prefix("XAN"))?; - s.try_into() - } -} diff --git a/src/types/collision.rs b/src/types/collision.rs deleted file mode 100644 index 59c60e69ee50..000000000000 --- a/src/types/collision.rs +++ /dev/null @@ -1,9 +0,0 @@ -/// Describes a kind of game collision -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Collision { - /// Stop moving - you can't move there! - Stop, - - /// Moving into an entity at the given position indicates combat - Combat, -} diff --git a/src/types/command.rs b/src/types/command.rs deleted file mode 100644 index 17ca4d280fd8..000000000000 --- a/src/types/command.rs +++ /dev/null @@ -1,41 +0,0 @@ -use super::Direction; -use super::Direction::*; -use termion::event::Key; -use termion::event::Key::{Char, Ctrl}; - -pub enum Command { - /// Quit the game - Quit, - - /// Move the character in a direction - Move(Direction), - - /// Pick up any item(s) at the current position - PickUp, - - /// Display the previous message - PreviousMessage, -} - -impl Command { - pub fn from_key(k: Key) -> Option<Command> { - use Command::*; - match k { - Char('q') => Some(Quit), - - Char('h') | Char('a') | Key::Left => Some(Move(Left)), - Char('k') | Char('w') | Key::Up => Some(Move(Up)), - Char('j') | Char('s') | Key::Down => Some(Move(Down)), - Char('l') | Char('d') | Key::Right => Some(Move(Right)), - Char('y') => Some(Move(UpLeft)), - Char('u') => Some(Move(UpRight)), - Char('b') => Some(Move(DownLeft)), - Char('n') => Some(Move(DownRight)), - - Ctrl('p') => Some(PreviousMessage), - Char(',') => Some(PickUp), - - _ => None, - } - } -} diff --git a/src/types/direction.rs b/src/types/direction.rs deleted file mode 100644 index 9b5c0991da8d..000000000000 --- a/src/types/direction.rs +++ /dev/null @@ -1,13 +0,0 @@ -use proptest_derive::Arbitrary; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)] -pub enum Direction { - Left, - Up, - Down, - Right, - UpLeft, - UpRight, - DownRight, - DownLeft, -} diff --git a/src/types/entity_map.rs b/src/types/entity_map.rs deleted file mode 100644 index 202d8b593e15..000000000000 --- a/src/types/entity_map.rs +++ /dev/null @@ -1,430 +0,0 @@ -use crate::entities::entity::Identified; -use crate::entities::EntityID; -use crate::types::Neighbors; -use crate::types::Position; -use crate::types::Positioned; -use crate::types::PositionedMut; -use alga::general::{ - AbstractMagma, AbstractMonoid, AbstractSemigroup, Additive, Identity, -}; -use std::collections::{hash_map, BTreeMap, HashMap}; -use std::iter::FromIterator; - -#[derive(Debug, Clone, Default)] -pub struct EntityMap<A> { - by_position: BTreeMap<Position, Vec<EntityID>>, - by_id: HashMap<EntityID, A>, - last_id: EntityID, -} - -impl<A: PartialEq> PartialEq for EntityMap<A> { - fn eq(&self, other: &Self) -> bool { - self.by_position == other.by_position && self.by_id == other.by_id - } -} -impl<A: Eq> Eq for EntityMap<A> {} - -const BY_POS_INVARIANT: &str = - "Invariant: All references in EntityMap.by_position should point to existent references in by_id"; - -impl<A> EntityMap<A> { - pub fn new() -> EntityMap<A> { - EntityMap { - by_position: BTreeMap::new(), - by_id: HashMap::new(), - last_id: 0, - } - } - - pub fn len(&self) -> usize { - self.by_id.len() - } - - /// Returns a list of all entities at the given position - pub fn at<'a>(&'a self, pos: Position) -> Vec<&'a A> { - self.by_position - .get(&pos) - .iter() - .flat_map(|eids| { - eids.iter() - .map(|eid| self.by_id.get(eid).expect(BY_POS_INVARIANT)) - }) - .collect() - } - - /// Remove all entities at the given position - pub fn remove_all_at(&mut self, pos: Position) { - if let Some(eids) = self.by_position.remove(&pos) { - for eid in eids { - self.by_id.remove(&eid).expect(BY_POS_INVARIANT); - } - } - } - - pub fn get(&self, id: EntityID) -> Option<&A> { - self.by_id.get(&id) - } - - pub fn get_mut(&mut self, id: EntityID) -> Option<&mut A> { - self.by_id.get_mut(&id) - } - - pub fn entities(&self) -> impl Iterator<Item = &A> { - self.by_id.values() - } - - pub fn entities_mut(&mut self) -> impl Iterator<Item = &mut A> { - self.by_id.values_mut() - } - - pub fn ids(&self) -> hash_map::Keys<'_, EntityID, A> { - self.by_id.keys() - } - - pub fn drain(&mut self) -> Drain<'_, A> { - let ids = self.ids().copied().collect::<Vec<_>>(); - Drain { - map: self, - ids_iter: Box::new(ids.into_iter()), - } - } - - fn next_id(&mut self) -> EntityID { - self.last_id += 1; - self.last_id - } -} - -impl<A: Positioned + Identified<EntityID>> EntityMap<A> { - pub fn insert(&mut self, mut entity: A) -> EntityID { - let pos = entity.position(); - let entity_id = self.next_id(); - entity.set_id(entity_id); - self.by_id.entry(entity_id).or_insert(entity); - self.by_position - .entry(pos) - .or_insert_with(Vec::new) - .push(entity_id); - entity_id - } - - /// Remove the entity with the given ID - pub fn remove(&mut self, id: EntityID) -> Option<A> { - self.by_id.remove(&id).map(|e| { - let mut empty = false; - let position = e.position(); - - if let Some(es) = self.by_position.get_mut(&position) { - es.retain(|e| *e != id); - if es.is_empty() { - empty = true; - } - } - - if empty { - self.by_position.remove(&position); - } - e - }) - } - - /// Moves all elements from `other` into `Self`, leathing `other` empty. - pub fn append(&mut self, other: &mut Self) { - // TODO there's probably some perf opportunities here by calling - // reserve() on stuff - for (_, entity) in other.drain() { - self.insert(entity); - } - } - - /// Gets all 8 neighbors of the given position. - pub fn neighbors<'a>( - &'a self, - position: Position, - ) -> Neighbors<Vec<(EntityID, &'a A)>> { - Neighbors::of_position(position) - .map(|pos| self.at(*pos)) - .mapmap(&|e| (e.id(), *e)) - } - - pub fn neighbor_entities<'a>( - &'a self, - position: Position, - ) -> Neighbors<Vec<&'a A>> { - self.neighbors(position).mapmap(&|(_eid, ent)| *ent) - } - - pub fn check_invariants(&self) { - for (id, ent) in &self.by_id { - assert_eq!(*id, ent.id()); - } - - for (pos, ents) in &self.by_position { - for eid in ents { - let ent = self.by_id.get(eid).unwrap(); - assert_eq!(*pos, ent.position()) - } - } - } -} - -impl<'a, A: Positioned + Identified<EntityID>> IntoIterator - for &'a EntityMap<A> -{ - type Item = (&'a EntityID, &'a A); - type IntoIter = std::collections::hash_map::Iter<'a, EntityID, A>; - fn into_iter(self) -> Self::IntoIter { - (&self.by_id).iter() - } -} - -impl<A: Positioned + Identified<EntityID>> IntoIterator for EntityMap<A> { - type Item = (EntityID, A); - type IntoIter = std::collections::hash_map::IntoIter<EntityID, A>; - fn into_iter(self) -> Self::IntoIter { - self.by_id.into_iter() - } -} - -impl<A: Positioned + Identified<EntityID>> FromIterator<A> for EntityMap<A> { - fn from_iter<I: IntoIterator<Item = A>>(iter: I) -> Self { - let mut em = EntityMap::new(); - for ent in iter { - em.insert(ent); - } - em - } -} - -impl<A: Positioned + Identified<EntityID> + Eq + Clone> AbstractMagma<Additive> - for EntityMap<A> -{ - fn operate(&self, right: &Self) -> Self { - let mut by_position = self.by_position.clone(); - by_position.append(&mut right.by_position.clone()); - - let mut by_id = self.by_id.clone(); - for (k, v) in right.by_id.clone() { - by_id.insert(k, v); - } - - EntityMap { - by_position, - by_id, - last_id: self.last_id.max(right.last_id), - } - } -} - -impl<A: Positioned + Identified<EntityID> + Eq + Clone> - AbstractSemigroup<Additive> for EntityMap<A> -{ -} - -impl<A: Positioned + Identified<EntityID> + Eq> Identity<Additive> - for EntityMap<A> -{ - fn identity() -> Self { - EntityMap::new() - } -} - -impl<A: Positioned + Identified<EntityID> + Eq + Clone> AbstractMonoid<Additive> - for EntityMap<A> -{ -} - -impl<A: PositionedMut> EntityMap<A> { - pub fn update_position( - &mut self, - entity_id: EntityID, - new_position: Position, - ) { - let mut old_pos = None; - if let Some(entity) = self.by_id.get_mut(&entity_id) { - if entity.position() == new_position { - return; - } - old_pos = Some(entity.position()); - entity.set_position(new_position); - } - - if let Some(p) = old_pos { - if let Some(es) = self.by_position.get_mut(&p) { - es.retain(|e| *e != entity_id); - } - - self.by_position - .entry(new_position) - .or_insert_with(Vec::new) - .push(entity_id); - } - } -} - -pub struct Drain<'a, A> { - map: &'a mut EntityMap<A>, - ids_iter: Box<dyn Iterator<Item = EntityID> + 'a>, -} - -impl<A: Positioned + Identified<EntityID>> Iterator for Drain<'_, A> { - type Item = (EntityID, A); - - fn next(&mut self) -> Option<Self::Item> { - self.ids_iter - .next() - .map(|eid| (eid, self.map.remove(eid).expect(BY_POS_INVARIANT))) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::types::PositionedMut; - use proptest::prelude::*; - use proptest_derive::Arbitrary; - - #[derive(Debug, Arbitrary, PartialEq, Eq, Clone)] - struct TestEntity { - _id: Option<EntityID>, - position: Position, - name: String, - } - - impl Positioned for TestEntity { - fn position(&self) -> Position { - self.position - } - } - - impl PositionedMut for TestEntity { - fn set_position(&mut self, pos: Position) { - self.position = pos - } - } - - impl Identified<EntityID> for TestEntity { - fn opt_id(&self) -> Option<EntityID> { - self._id - } - - fn set_id(&mut self, id: EntityID) { - self._id = Some(id); - } - } - - fn gen_entity_map() -> BoxedStrategy<EntityMap<TestEntity>> { - any::<Vec<TestEntity>>() - .prop_map(|ents| { - ents.iter().cloned().collect::<EntityMap<TestEntity>>() - }) - .boxed() - } - - proptest! { - #![proptest_config(ProptestConfig::with_cases(10))] - - #[test] - fn test_entity_map_len(items: Vec<TestEntity>) { - let mut map = EntityMap::new(); - assert_eq!(map.len(), 0); - for ent in &items { - map.insert(ent.clone()); - } - assert_eq!(map.len(), items.len()); - } - - #[test] - fn test_entity_map_getset( - mut em in gen_entity_map(), - ent: TestEntity - ) { - em.insert(ent.clone()); - assert!(em.at(ent.position).iter().any(|e| e.name == ent.name)) - } - - #[test] - fn test_entity_map_set_iter_contains( - mut em in gen_entity_map(), - ent: TestEntity - ) { - em.insert(ent.clone()); - assert!(em.entities().any(|e| e.name == ent.name)) - } - - #[test] - fn test_update_position( - mut em in gen_entity_map(), - ent: TestEntity, - new_position: Position, - ) { - let original_position = ent.position(); - let entity_id = em.insert(ent.clone()); - em.update_position(entity_id, new_position); - - if new_position != original_position { - assert!(em.at(original_position).iter().all(|e| e.name != ent.name)); - } - assert_eq!( - em.get(entity_id).map(|e| e.position()), - Some(new_position) - ); - assert!( - em.at(new_position).iter().map( - |e| e.name.clone()).any(|en| en == ent.name), - ) - } - - #[test] - fn test_remove_all_at( - mut em in gen_entity_map(), - pos: Position, - ) { - em.remove_all_at(pos); - assert_eq!(em.at(pos).len(), 0); - } - - #[test] - fn test_entity_map_semigroup_laws( - em1 in gen_entity_map(), - em2 in gen_entity_map(), - em3 in gen_entity_map(), - ) { - assert!(AbstractSemigroup::prop_is_associative((em1, em2, em3))); - } - - fn test_entity_map_monoid_laws( - em in gen_entity_map(), - ) { - assert!( - AbstractMonoid::prop_operating_identity_element_is_noop((em,)) - ); - } - - #[test] - fn test_entity_map_append( - mut target in gen_entity_map(), - mut source in gen_entity_map(), - ) { - let orig_target = target.clone(); - let orig_source = source.clone(); - - target.append(&mut source); - target.check_invariants(); - - assert_eq!(source, EntityMap::new()); - - for ent in orig_source.entities() { - assert!( - target.at(ent.position()).iter().any(|e| e.name == ent.name) - ); - } - - for ent in orig_target.entities() { - assert!( - target.at(ent.position()).iter().any(|e| e.name == ent.name) - ); - } - } - } -} diff --git a/src/types/menu.rs b/src/types/menu.rs deleted file mode 100644 index 63abc837788e..000000000000 --- a/src/types/menu.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::types::Dimensions; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct MenuInfo { - pub prompt: String, - pub options: Vec<String>, -} - -impl MenuInfo { - pub fn new(prompt: String, options: Vec<String>) -> Self { - MenuInfo { prompt, options } - } - - /// Returns the inner dimensions of a box necessary to draw this menu. Will - /// not trim either dimension to the size of the terminal - pub fn dimensions(&self) -> Dimensions { - Dimensions { - w: self - .options - .iter() - .map(|s| s.len()) - .max() - .unwrap_or(0) - .max(self.prompt.len()) as u16 - + 4, - h: self.options.len() as u16 - + if self.prompt.is_empty() { 0 } else { 2 } - + 4, - } - } -} diff --git a/src/types/mod.rs b/src/types/mod.rs deleted file mode 100644 index d417e873d8ca..000000000000 --- a/src/types/mod.rs +++ /dev/null @@ -1,504 +0,0 @@ -#![allow(clippy::unit_arg)] -#![allow(clippy::identity_conversion)] - -use std::cmp::max; -use std::cmp::Ordering; -use std::ops; -use std::rc::Rc; - -pub mod collision; -pub mod command; -pub mod direction; -pub mod entity_map; -pub mod menu; - -pub use collision::Collision; -pub use direction::Direction; -pub use direction::Direction::*; -use proptest_derive::Arbitrary; -use termion::cursor; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)] -pub struct Dimensions { - #[proptest(strategy = "std::ops::Range::<u16>::from(0..100)")] - pub w: u16, - - #[proptest(strategy = "std::ops::Range::<u16>::from(0..100)")] - pub h: u16, -} - -pub const ZERO_DIMENSIONS: Dimensions = Dimensions { w: 0, h: 0 }; -pub const UNIT_DIMENSIONS: Dimensions = Dimensions { w: 1, h: 1 }; - -impl Default for Dimensions { - fn default() -> Self { - Dimensions { w: 80, h: 20 } - } -} - -impl ops::Sub<Dimensions> for Dimensions { - type Output = Dimensions; - fn sub(self, dims: Dimensions) -> Dimensions { - Dimensions { - w: self.w - dims.w, - h: self.h - dims.h, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)] -pub struct BoundingBox { - pub dimensions: Dimensions, - pub position: Position, -} - -impl BoundingBox { - pub fn at_origin(dimensions: Dimensions) -> BoundingBox { - BoundingBox { - dimensions, - position: ORIGIN, - } - } - - pub fn from_corners( - top_left: Position, - lower_right: Position, - ) -> BoundingBox { - BoundingBox { - position: top_left, - dimensions: Dimensions { - w: (lower_right.x - top_left.x) as u16, - h: (lower_right.y - top_left.y) as u16, - }, - } - } - - pub fn lr_corner(self) -> Position { - self.position - + (Position { - x: self.dimensions.w as i16, - y: self.dimensions.h as i16, - }) - } - - pub fn ll_corner(self) -> Position { - self.position - + (Position { - x: 0, - y: self.dimensions.h as i16, - }) - } - - /// Returns a bounding box representing the *inside* of this box if it was - /// drawn on the screen. - pub fn inner(self) -> BoundingBox { - self + UNIT_POSITION - UNIT_DIMENSIONS - UNIT_DIMENSIONS - } - - /// Moves the top right corner of the bounding box by the offset specified - /// by the given position, keeping the lower right corner in place - pub fn move_tr_corner(self, offset: Position) -> BoundingBox { - self + offset - - Dimensions { - w: offset.x as u16, - h: offset.y as u16, - } - } -} - -impl ops::Add<Position> for BoundingBox { - type Output = BoundingBox; - fn add(self, pos: Position) -> BoundingBox { - BoundingBox { - position: self.position + pos, - ..self - } - } -} - -impl ops::Sub<Dimensions> for BoundingBox { - type Output = BoundingBox; - fn sub(self, dims: Dimensions) -> BoundingBox { - BoundingBox { - dimensions: self.dimensions - dims, - ..self - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary, Hash, Ord)] -pub struct Position { - /// x (horizontal) position - #[proptest(strategy = "std::ops::Range::<i16>::from(0..100)")] - pub x: i16, - - #[proptest(strategy = "std::ops::Range::<i16>::from(0..100)")] - /// y (vertical) position - pub y: i16, -} - -pub fn pos(x: i16, y: i16) -> Position { - Position { x, y } -} - -pub const ORIGIN: Position = Position { x: 0, y: 0 }; -pub const UNIT_POSITION: Position = Position { x: 1, y: 1 }; - -impl Position { - /// Returns true if this position exists within the bounds of the given box, - /// inclusive - pub fn within(self, b: BoundingBox) -> bool { - (self > b.position - UNIT_POSITION) && self < (b.lr_corner()) - } - - /// Returns a sequence of ASCII escape characters for moving the cursor to - /// this Position - pub fn cursor_goto(self) -> cursor::Goto { - // + 1 because Goto is 1-based, but position is 0-based - cursor::Goto(self.x as u16 + 1, self.y as u16 + 1) - } - - /// Converts this position to the number of `Tiles` away from the origin it - /// represents. Usually done after subtracting two positions. Gives distance - /// as the crow flies - pub fn as_tiles(self) -> Tiles { - Tiles(max(self.x.abs(), self.y.abs()).into()) - } -} - -impl PartialOrd for Position { - fn partial_cmp(&self, other: &Position) -> Option<Ordering> { - if self.x == other.x && self.y == other.y { - Some(Ordering::Equal) - } else if self.x > other.x && self.y > other.y { - Some(Ordering::Greater) - } else if self.x < other.x && self.y < other.y { - Some(Ordering::Less) - } else { - None - } - } -} - -/// Implements (bounded) addition of a Dimension to a position. -/// -/// # Examples -/// -/// ``` -/// let pos = Position { x: 1, y: 10 } -/// -/// let left_pos = pos + Direction::Left -/// assert_eq!(left, Position { x: 0, y: 10 }) -/// -/// let right_pos = pos + Direction::Right -/// assert_eq!(right_pos, Position { x: 0, y: 10 }) -/// ``` -#[allow(clippy::suspicious_arithmetic_impl)] -impl ops::Add<Direction> for Position { - type Output = Position; - fn add(self, dir: Direction) -> Position { - match dir { - Left => { - if self.x > std::i16::MIN { - Position { - x: self.x - 1, - ..self - } - } else { - self - } - } - Right => { - if self.x < std::i16::MAX { - Position { - x: self.x + 1, - ..self - } - } else { - self - } - } - Up => { - if self.y > std::i16::MIN { - Position { - y: self.y - 1, - ..self - } - } else { - self - } - } - Down => { - if self.y < std::i16::MAX { - Position { - y: self.y + 1, - ..self - } - } else { - self - } - } - UpLeft => self + Up + Left, - UpRight => self + Up + Right, - DownLeft => self + Down + Left, - DownRight => self + Down + Right, - } - } -} - -impl ops::Add<Position> for Position { - type Output = Position; - fn add(self, pos: Position) -> Position { - Position { - x: self.x + pos.x, - y: self.y + pos.y, - } - } -} - -impl ops::Sub<Position> for Position { - type Output = Position; - fn sub(self, pos: Position) -> Position { - Position { - x: self.x - pos.x, - y: self.y - pos.y, - } - } -} - -impl Positioned for Position { - fn position(&self) -> Position { - *self - } -} - -pub trait Positioned { - fn x(&self) -> i16 { - self.position().x - } - - fn y(&self) -> i16 { - self.position().y - } - - fn position(&self) -> Position { - Position { - x: self.x(), - y: self.y(), - } - } -} - -pub trait PositionedMut: Positioned { - fn set_position(&mut self, pos: Position); -} - -// impl<A, I> Positioned for A where A : Deref<Target = I>, I: Positioned { -// fn position(&self) -> Position { -// self.position() -// } -// } - -impl<T: Positioned> Positioned for Box<T> { - fn position(&self) -> Position { - (**self).position() - } -} - -impl<'a, T: Positioned> Positioned for &'a T { - fn position(&self) -> Position { - (**self).position() - } -} - -impl<'a, T: Positioned> Positioned for &'a mut T { - fn position(&self) -> Position { - (**self).position() - } -} - -impl<'a, T: Positioned> Positioned for Rc<T> { - fn position(&self) -> Position { - (**self).position() - } -} - -impl<'a, T: PositionedMut> PositionedMut for &'a mut T { - fn set_position(&mut self, pos: Position) { - (**self).set_position(pos) - } -} - -#[macro_export] -macro_rules! positioned { - ($name:ident) => { - positioned!($name, position); - }; - ($name:ident, $attr:ident) => { - impl $crate::types::Positioned for $name { - fn position(&self) -> $crate::types::Position { - self.$attr - } - } - }; -} - -#[macro_export] -macro_rules! positioned_mut { - ($name:ident) => { - positioned_mut!($name, position); - }; - ($name:ident, $attr:ident) => { - impl crate::types::PositionedMut for $name { - fn set_position(&mut self, pos: $crate::types::Position) { - self.$attr = pos; - } - } - }; -} - -/// A number of ticks -#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)] -pub struct Ticks(pub u16); - -/// A number of tiles -/// -/// Expressed in terms of a float to allow moving partial tiles in a number of -/// ticks -#[derive(Clone, Copy, Debug, PartialEq, Arbitrary)] -pub struct Tiles(pub f32); - -/// The speed of an entity, expressed in ticks per tile -#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary, Deserialize)] -#[serde(transparent)] -pub struct Speed(pub u32); - -impl Speed { - /// Returns the number of tiles that would be moved in the given number of - /// ticks at this speed - pub fn ticks_to_tiles(self, ticks: Ticks) -> Tiles { - Tiles(f32::from(ticks.0) / self.0 as f32) - } - - /// Returns the number of ticks required to move the given number of tiles - /// at this speed - pub fn tiles_to_ticks(self, tiles: Tiles) -> Ticks { - Ticks(tiles.0 as u16 * self.0 as u16) - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)] -pub struct Neighbors<A> { - pub top_left: A, - pub top: A, - pub top_right: A, - pub left: A, - pub right: A, - pub bottom_left: A, - pub bottom: A, - pub bottom_right: A, -} - -impl Neighbors<Position> { - fn of_position(pos: Position) -> Self { - Neighbors { - top_left: pos + Direction::UpLeft, - top: pos + Direction::Up, - top_right: pos + Direction::UpRight, - left: pos + Direction::Left, - right: pos + Direction::Right, - bottom_left: pos + Direction::DownLeft, - bottom: pos + Direction::Down, - bottom_right: pos + Direction::DownRight, - } - } -} - -impl<A> Neighbors<A> { - /// it's a functor, yo - pub fn map<B, F: Fn(&A) -> B>(&self, f: F) -> Neighbors<B> { - Neighbors { - top_left: f(&self.top_left), - top: f(&self.top), - top_right: f(&self.top_right), - left: f(&self.left), - right: f(&self.right), - bottom_left: f(&self.bottom_left), - bottom: f(&self.bottom), - bottom_right: f(&self.bottom_right), - } - } -} - -impl<A> Neighbors<Vec<A>> { - pub fn mapmap<B, F: Fn(&A) -> B>(&self, f: &F) -> Neighbors<Vec<B>> { - self.map(|xs| xs.iter().map(f).collect()) - } -} - -#[cfg(test)] -mod tests { - #![allow(clippy::unnecessary_operation)] - use super::*; - use proptest::prelude::*; - - proptest! { - #[test] - fn position_partialord_lt_transitive( - a: Position, - b: Position, - c: Position - ) { - if a < b && b < c { - assert!(a < c) - } - } - - #[test] - fn position_partialord_eq_transitive( - a: Position, - b: Position, - c: Position - ) { - if a == b && b == c { - assert!(a == c) - } - } - - #[test] - fn position_partialord_gt_transitive( - a: Position, - b: Position, - c: Position, - ) { - if a > b && b > c { - assert!(a > c) - } - } - - #[test] - fn position_partialord_antisymmetric(a: Position, b: Position) { - if a < b { - assert!(!(a > b)) - } else if a > b { - assert!(!(a < b)) - } - } - - #[test] - fn test_position_plus_dimension_as_tiles_monoid_action( - pos: Position, - dir: Direction, - ) { - prop_assume!(pos.y > 0 && pos.x > 0); - assert_eq!(((pos + dir) - pos).as_tiles(), Tiles(1.0)); - } - } - - #[test] - fn test_position_as_tiles() { - assert_eq!(pos(0, 0).as_tiles(), Tiles(0.0)); - assert_eq!(pos(1, 1).as_tiles(), Tiles(1.0)); - assert_eq!(pos(1, 2).as_tiles(), Tiles(2.0)); - } -} diff --git a/src/util/mod.rs b/src/util/mod.rs deleted file mode 100644 index dd5087a55558..000000000000 --- a/src/util/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[macro_use] -pub mod static_cfg; -#[macro_use] -pub mod template; -pub mod promise; -#[macro_use] -pub mod trait_impls; diff --git a/src/util/promise.rs b/src/util/promise.rs deleted file mode 100644 index 22f1e8b47f58..000000000000 --- a/src/util/promise.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::future::Future; -use std::pin::Pin; -use std::sync::{Arc, RwLock}; -use std::task::{Context, Poll, Waker}; - -type Waiter<Env, T> = Box<dyn Fn(&mut Env, &T)>; - -pub struct Promise<Env, T> { - inner: Arc<RwLock<Inner<T>>>, - waiters: Arc<RwLock<Vec<Waiter<Env, T>>>>, -} - -pub struct Complete<T> { - inner: Arc<RwLock<Inner<T>>>, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Cancelled; - -struct Inner<T> { - value: Option<Arc<T>>, - waker: Option<Waker>, -} - -pub fn promise<Env, T>() -> (Complete<T>, Promise<Env, T>) { - let inner = Arc::new(RwLock::new(Inner { - value: None, - waker: None, - })); - let promise = Promise { - inner: inner.clone(), - waiters: Arc::new(RwLock::new(Vec::new())), - }; - let complete = Complete { inner }; - (complete, promise) -} - -impl<T> Complete<T> { - pub fn fulfill(&self, val: T) { - let mut inner = self.inner.write().unwrap(); - inner.value = Some(Arc::new(val)); - if let Some(waker) = inner.waker.take() { - waker.wake() - } - } -} - -impl<T> Complete<Result<T, Cancelled>> { - pub fn cancel(&mut self) { - self.fulfill(Err(Cancelled)) - } -} - -impl<E, T> Complete<Result<T, E>> { - pub fn ok(&mut self, val: T) { - self.fulfill(Ok(val)) - } - - pub fn err(&mut self, e: E) { - self.fulfill(Err(e)) - } -} - -impl<Env, T> Promise<Env, T> { - pub fn on_fulfill<F: Fn(&mut Env, &T) + 'static>(&mut self, f: F) { - let mut waiters = self.waiters.write().unwrap(); - waiters.push(Box::new(f)); - } -} - -impl<Env, T> Promise<Env, Result<T, Cancelled>> { - pub fn on_cancel<F: Fn(&mut Env) + 'static>(&mut self, f: F) { - self.on_err(move |env, _| f(env)) - } -} - -impl<Env, E, T> Promise<Env, Result<T, E>> { - pub fn on_ok<F: Fn(&mut Env, &T) + 'static>(&mut self, f: F) { - self.on_fulfill(move |env, r| { - if let Ok(val) = r { - f(env, val) - } - }) - } - - pub fn on_err<F: Fn(&mut Env, &E) + 'static>(&mut self, f: F) { - self.on_fulfill(move |env, r| { - if let Err(e) = r { - f(env, e) - } - }) - } -} - -pub trait Give<Env> { - fn give(&self, env: &mut Env) -> bool; -} - -impl<Env, T> Give<Env> for Promise<Env, T> { - fn give(&self, env: &mut Env) -> bool { - let inner = self.inner.read().unwrap(); - if let Some(value) = &inner.value { - let mut waiters = self.waiters.write().unwrap(); - for waiter in waiters.iter() { - waiter(env, value); - } - waiters.clear(); - true - } else { - false - } - } -} - -impl<Env, T> Clone for Promise<Env, T> { - fn clone(&self) -> Self { - Promise { - inner: self.inner.clone(), - waiters: self.waiters.clone(), - } - } -} - -impl<Env, P: Give<Env>> Give<Env> for &P { - fn give(&self, env: &mut Env) -> bool { - (*self).give(env) - } -} - -impl<Env, T> Future for Promise<Env, T> { - type Output = Arc<T>; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { - let mut inner = self.inner.write().unwrap(); - match inner.value { - Some(ref v) => Poll::Ready(v.clone()), - None => { - inner.waker = Some(cx.waker().clone()); - Poll::Pending - } - } - } -} - -pub struct Promises<'a, Env> { - ps: Vec<Box<dyn Give<Env> + 'a>>, -} - -impl<'a, Env> Promises<'a, Env> { - pub fn new() -> Self { - Promises { ps: Vec::new() } - } - - pub fn push(&mut self, p: Box<dyn Give<Env> + 'a>) { - self.ps.push(p); - } - - pub fn give_all(&mut self, env: &mut Env) { - self.ps.retain(|p| !p.give(env)); - } -} diff --git a/src/util/static_cfg.rs b/src/util/static_cfg.rs deleted file mode 100644 index b20456fb3bd4..000000000000 --- a/src/util/static_cfg.rs +++ /dev/null @@ -1,147 +0,0 @@ -use include_dir::Dir; -use serde::de; - -macro_rules! __static_cfg_include { - (toml_file, $filename:expr) => { - include_str!($filename) - }; - (toml_dir, $filename:expr) => { - include_dir!($filename) - }; - (json_file, $filename:expr) => { - include_str!($filename) - }; - (json_dir, $filename:expr) => { - include_dir!($filename) - }; - (cfg_dir, $filename:expr) => { - include_dir!($filename) - }; -} - -macro_rules! __static_cfg_type { - (toml_file) => (&'static str); - (json_file) => (&'static str); - (toml_dir) => (include_dir::Dir<'static>); - (json_dir) => (include_dir::Dir<'static>); - (cfg_dir) => (include_dir::Dir<'static>); -} - -macro_rules! __static_cfg_parse { - (toml_file, $e:expr) => { - toml::from_str($e).unwrap() - }; - - (json_file, $e:expr) => { - serde_json::from_str($e).unwrap() - }; - - (toml_dir, $e:expr) => { - crate::util::static_cfg::parse_toml_dir($e) - }; - - (json_dir, $e:expr) => { - crate::util::static_cfg::parse_json_dir($e) - }; - - (cfg_dir, $e:expr) => { - crate::util::static_cfg::parse_cfg_dir($e); - }; -} - -macro_rules! __static_cfg_inner { - ($(#[$attr:meta])* ($($vis:tt)*) static ref $N:ident : $T:ty = $kind:ident($filename:expr); $($t:tt)*) => { - // static RAW: &'static str = __static_cfg_include!($kind, $filename); - static RAW: __static_cfg_type!($kind) = __static_cfg_include!($kind, $filename); - lazy_static! { - $(#[$attr])* static ref $N: $T = __static_cfg_parse!($kind, RAW); - } - - static_cfg!($($t)*); - } -} - -#[macro_export] -macro_rules! static_cfg { - ($(#[$attr:meta])* static ref $N:ident : $T:ty = $kind:ident($filename:expr); $($t:tt)*) => { - __static_cfg_inner!($(#[$attr])* () static ref $N : $T = $kind($filename); $($t)*); - }; - - ($(#[$attr:meta])* pub static ref $N:ident : $T:ty = $kind:ident($filename:expr); $($t:tt)*) => { - __static_cfg_inner!($(#[$attr])* (pub) static ref $N : $T = $kind($filename); $($t)*); - }; - - ($(#[$attr:meta])* pub ($($vis:tt)+) static ref $N:ident : $T:ty = $kind:ident($filename:expr); $($t:tt)*) => { - __static_cfg_inner!($(#[$attr])* (pub ($($vis)+)) static ref $N : $T = $kind($filename); $($t)*); - }; - - () => () -} - -pub fn parse_cfg_dir<'a, T>(d: Dir<'a>) -> Vec<T> -where - T: de::Deserialize<'a>, -{ - d.files() - .iter() - .filter_map(|f| { - let path = f.path(); - let contents = f.contents_utf8().unwrap(); - match path.extension().and_then(|e| e.to_str()) { - Some("toml") => { - Some(toml::from_str(contents).unwrap_or_else(|e| { - panic!( - "Error parsing TOML file {}: {}", - path.display(), - e - ) - })) - } - Some("json") => { - Some(serde_json::from_str(contents).unwrap_or_else(|e| { - panic!( - "Error parsing JSON file {}: {}", - path.display(), - e - ) - })) - } - // > YAML currently does not support zero-copy deserialization - // Some("yaml") => { - // Some(serde_yaml::from_str(contents).unwrap_or_else(|e| { - // panic!( - // "Error parsing YAML file {}: {}", - // path.display(), - // e - // ) - // })) - // } - _ => None, - } - }) - .collect() -} - -pub fn parse_toml_dir<'a, T>(d: Dir<'a>) -> Vec<T> -where - T: de::Deserialize<'a>, -{ - d.files() - .iter() - .map(|f| { - toml::from_str(f.contents_utf8().unwrap()).unwrap_or_else(|e| { - panic!("Error parsing TOML file {}: {}", f.path, e) - }) - }) - .collect() -} - -pub fn parse_json_dir<'a, T>(d: Dir<'a>) -> Vec<T> -where - T: de::Deserialize<'a>, -{ - d.files() - .iter() - .map(|f| serde_json::from_str(f.contents_utf8().unwrap()).unwrap()) - .collect() -} diff --git a/src/util/template.rs b/src/util/template.rs deleted file mode 100644 index bb77f9b4d610..000000000000 --- a/src/util/template.rs +++ /dev/null @@ -1,362 +0,0 @@ -use nom::combinator::rest; -use nom::error::ErrorKind; -use nom::{Err, IResult}; -use std::collections::HashMap; -use std::fmt::{self, Display}; -use std::marker::PhantomData; - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct Path<'a> { - head: &'a str, - tail: Vec<&'a str>, -} - -impl<'a> Path<'a> { - fn new(head: &'a str, tail: Vec<&'a str>) -> Self { - Path { head, tail } - } -} - -impl<'a> Display for Path<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.head)?; - for part in &self.tail { - write!(f, ".{}", part)?; - } - Ok(()) - } -} - -// named!(path_ident, map_res!(is_not!(".}"), std::str::from_utf8)); -fn path_ident<'a>(input: &'a str) -> IResult<&'a str, &'a str> { - take_till!(input, |c| c == '.' || c == '}') -} - -fn path<'a>(input: &'a str) -> IResult<&'a str, Path<'a>> { - map!( - input, - tuple!( - path_ident, - many0!(complete!(preceded!(char!('.'), path_ident))) - ), - |(h, t)| Path::new(h, t) - ) -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum TemplateToken<'a> { - Literal(&'a str), - Substitution(Path<'a>), -} - -fn token_substitution<'a>( - input: &'a str, -) -> IResult<&'a str, TemplateToken<'a>> { - map!( - input, - delimited!(tag!("{{"), path, tag!("}}")), - TemplateToken::Substitution - ) -} - -fn template_token<'a>(input: &'a str) -> IResult<&'a str, TemplateToken<'a>> { - alt!( - input, - token_substitution - | map!( - alt!(complete!(take_until!("{{")) | complete!(rest)), - TemplateToken::Literal - ) - ) -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct Template<'a> { - tokens: Vec<TemplateToken<'a>>, -} - -impl<'a> Template<'a> { - pub fn new(tokens: Vec<TemplateToken<'a>>) -> Self { - Template { tokens } - } -} - -pub struct TemplateVisitor<'a> { - marker: PhantomData<fn() -> Template<'a>>, -} - -impl<'a> TemplateVisitor<'a> { - pub fn new() -> Self { - TemplateVisitor { - marker: PhantomData, - } - } -} - -impl<'a> serde::de::Visitor<'a> for TemplateVisitor<'a> { - type Value = Template<'a>; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("a valid template string") - } - - fn visit_borrowed_str<E: serde::de::Error>( - self, - v: &'a str, - ) -> Result<Self::Value, E> { - Template::parse(v).map_err(|_| { - serde::de::Error::invalid_value( - serde::de::Unexpected::Str(v), - &"a valid template string", - ) - }) - } -} - -impl<'a> serde::Deserialize<'a> for Template<'a> { - fn deserialize<D: serde::Deserializer<'a>>( - deserializer: D, - ) -> Result<Self, D::Error> { - deserializer.deserialize_str(TemplateVisitor::new()) - } -} - -impl<'a> Template<'a> { - pub fn parse( - input: &'a str, - ) -> Result<Template<'a>, Err<(&'a str, ErrorKind)>> { - let (remaining, res) = template(input)?; - if !remaining.is_empty() { - unreachable!(); - } - Ok(res) - } - - pub fn format( - &self, - params: &TemplateParams<'a>, - ) -> Result<String, TemplateError<'a>> { - use TemplateToken::*; - let mut res = String::new(); - for token in &self.tokens { - match token { - Literal(s) => res.push_str(s), - Substitution(p) => match params.get(p.clone()) { - Some(s) => res.push_str(s), - None => return Err(TemplateError::MissingParam(p.clone())), - }, - } - } - Ok(res) - } -} - -#[derive(Debug, PartialEq, Eq)] -pub enum TemplateError<'a> { - MissingParam(Path<'a>), -} - -impl<'a> Display for TemplateError<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use TemplateError::*; - match self { - MissingParam(path) => { - write!(f, "Missing template parameter: {}", path) - } - } - } -} - -#[derive(Debug, PartialEq, Eq)] -pub enum TemplateParams<'a> { - Direct(&'a str), - Nested(HashMap<&'a str, TemplateParams<'a>>), -} - -impl<'a> TemplateParams<'a> { - fn get(&self, path: Path<'a>) -> Option<&'a str> { - use TemplateParams::*; - match self { - Direct(_) => None, - Nested(m) => m.get(path.head).and_then(|next| { - if path.tail.is_empty() { - match next { - Direct(s) => Some(*s), - _ => None, - } - } else { - next.get(Path { - head: path.tail[0], - tail: path.tail[1..].to_vec(), - }) - } - }), - } - } -} - -#[macro_export] -macro_rules! template_params { - (@count $head: expr => $hv: tt, $($rest:tt)+) => { 1 + template_params!(@count $($rest)+) }; - (@count $one:expr => $($ov: tt)*) => { 1 }; - (@inner $ret: ident, ($key: expr => {$($v:tt)*}, $($r:tt)*)) => { - $ret.insert($key, template_params!({ $($v)* })); - template_params!(@inner $ret, ($($r)*)); - }; - (@inner $ret: ident, ($key: expr => $value: expr, $($r:tt)*)) => { - $ret.insert($key, template_params!($value)); - template_params!(@inner $ret, ($($r)*)); - }; - (@inner $ret: ident, ()) => {}; - - ({ $($body: tt)* }) => {{ - let _cap = template_params!(@count $($body)*); - let mut _m = ::std::collections::HashMap::with_capacity(_cap); - template_params!(@inner _m, ($($body)*)); - TemplateParams::Nested(_m) - }}; - - ($direct:expr) => { TemplateParams::Direct($direct) }; - - () => { TemplateParams::Nested(::std::collections::HashMap::new()) }; -} - -fn template<'a>(input: &'a str) -> IResult<&'a str, Template<'a>> { - complete!( - input, - map!(many1!(complete!(template_token)), Template::new) - ) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_path_ident() { - assert_eq!(path_ident("foo}}"), Ok(("}}", "foo"))); - assert_eq!(path_ident("foo.bar}}"), Ok((".bar}}", "foo"))); - } - - #[test] - fn test_parse_path() { - assert_eq!(path("foo}}"), Ok(("}}", Path::new("foo", vec![])))); - assert_eq!( - path("foo.bar}}"), - Ok(("}}", Path::new("foo", vec!["bar"]))) - ); - assert_eq!( - path("foo.bar.baz}}"), - Ok(("}}", Path::new("foo", vec!["bar", "baz"]))) - ); - } - - #[test] - fn test_parse_template_token() { - assert_eq!( - template_token("foo bar"), - Ok(("", TemplateToken::Literal("foo bar"))) - ); - - assert_eq!( - template_token("foo bar {{baz}}"), - Ok(("{{baz}}", TemplateToken::Literal("foo bar "))) - ); - - assert_eq!( - template_token("{{baz}}"), - Ok(( - "", - TemplateToken::Substitution(Path::new("baz", Vec::new())) - )) - ); - - assert_eq!( - template_token("{{baz}} foo bar"), - Ok(( - " foo bar", - TemplateToken::Substitution(Path::new("baz", Vec::new())) - )) - ); - } - - #[test] - fn test_parse_template() { - assert_eq!( - template("foo bar"), - Ok(( - "", - Template { - tokens: vec![TemplateToken::Literal("foo bar")] - } - )) - ); - - assert_eq!( - template("foo bar {{baz}} qux"), - Ok(( - "", - Template { - tokens: vec![ - TemplateToken::Literal("foo bar "), - TemplateToken::Substitution(Path::new( - "baz", - Vec::new() - )), - TemplateToken::Literal(" qux"), - ] - } - )) - ); - } - - #[test] - fn test_template_params_literal() { - // trace_macros!(true); - let expected = template_params!({ - "direct" => "hi", - "other" => "here", - "nested" => { - "one" => "1", - "two" => "2", - "double" => { - "three" => "3", - }, - }, - }); - // trace_macros!(false); - assert_eq!( - TemplateParams::Nested(hashmap! { - "direct" => TemplateParams::Direct("hi"), - "other" => TemplateParams::Direct("here"), - "nested" => TemplateParams::Nested(hashmap!{ - "one" => TemplateParams::Direct("1"), - "two" => TemplateParams::Direct("2"), - "double" => TemplateParams::Nested(hashmap!{ - "three" => TemplateParams::Direct("3"), - }) - }) - }), - expected, - ) - } - - #[test] - fn test_format_template() { - assert_eq!( - "foo bar baz qux", - Template::parse("foo {{x}} {{y.z}} {{y.w.z}}") - .unwrap() - .format(&template_params!({ - "x" => "bar", - "y" => { - "z" => "baz", - "w" => { - "z" => "qux", - }, - }, - })) - .unwrap() - ) - } -} diff --git a/src/util/trait_impls.rs b/src/util/trait_impls.rs deleted file mode 100644 index ba15f7119d26..000000000000 --- a/src/util/trait_impls.rs +++ /dev/null @@ -1,17 +0,0 @@ -macro_rules! ref_impl { - (impl<T: $traitb: ident $(+ $bound:ident)*> $traiti:ident for &T { - $($body:tt)* - }) => { - impl<'a, T: $traitb $(+ $bound)*> $traiti for &'a T { - $($body)* - } - - impl<'a, T: $traitb $(+ $bound)*> $traiti for &'a mut T { - $($body)* - } - - impl<T: $traitb $(+ $bound)*> $traiti for ::std::boxed::Box<T> { - $($body)* - } - }; -} |