diff options
author | Griffin Smith <root@gws.fyi> | 2019-07-28T21·45-0400 |
---|---|---|
committer | Griffin Smith <root@gws.fyi> | 2019-07-28T21·45-0400 |
commit | 6c1eba67629504f10fa08ee68fb31f507c99b0d1 (patch) | |
tree | d5af8f3eb6dc32a1308a20863e5c8814a4634098 /src | |
parent | f22bcad817ee354b355d29b6b289894e2d15cfaa (diff) |
Allow converting generated levels to entities
Add a new Wall entity, and allow converting generated levels to entity maps containing them, then finally displaying them using some of the (now expanded) box drawing machinery.
Diffstat (limited to 'src')
-rw-r--r-- | src/display/draw_box.rs | 50 | ||||
-rw-r--r-- | src/display/mod.rs | 20 | ||||
-rw-r--r-- | src/display/viewport.rs | 12 | ||||
-rw-r--r-- | src/entities/entity.rs | 29 | ||||
-rw-r--r-- | src/entities/environment.rs | 34 | ||||
-rw-r--r-- | src/entities/mod.rs | 3 | ||||
-rw-r--r-- | src/entities/util.rs | 72 | ||||
-rw-r--r-- | src/game.rs | 40 | ||||
-rw-r--r-- | src/level_gen/cave_automata.rs | 4 | ||||
-rw-r--r-- | src/level_gen/display.rs | 17 | ||||
-rw-r--r-- | src/level_gen/mod.rs | 100 | ||||
-rw-r--r-- | src/level_gen/util.rs | 19 | ||||
-rw-r--r-- | src/main.rs | 2 | ||||
-rw-r--r-- | src/types/entity_map.rs | 123 | ||||
-rw-r--r-- | src/types/mod.rs | 55 |
15 files changed, 518 insertions, 62 deletions
diff --git a/src/display/draw_box.rs b/src/display/draw_box.rs index 5dc1627a298d..3b2b4aaf4f1d 100644 --- a/src/display/draw_box.rs +++ b/src/display/draw_box.rs @@ -2,6 +2,7 @@ use crate::display::utils::clone_times; use crate::display::utils::times; use crate::types::BoundingBox; use crate::types::Dimensions; +use crate::types::Neighbors; use itertools::Itertools; use proptest::prelude::Arbitrary; use proptest::strategy; @@ -22,42 +23,50 @@ use std::io::{self, Write}; 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 '╰', '╱', '╲', '╳', '╴', '╵', '╶', '╷', '╸', '╹', @@ -85,8 +94,8 @@ impl Arbitrary for BoxStyle { } } -trait Stylable { - fn style(self, style: BoxStyle) -> char; +pub trait Stylable { + fn style(&self, style: BoxStyle) -> char; } #[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)] @@ -98,7 +107,7 @@ enum Corner { } impl Stylable for Corner { - fn style(self, style: BoxStyle) -> char { + fn style(&self, style: BoxStyle) -> char { use BoxStyle::*; use Corner::*; @@ -119,7 +128,7 @@ enum Line { } impl Stylable for Line { - fn style(self, style: BoxStyle) -> char { + fn style(&self, style: BoxStyle) -> char { use BoxStyle::*; use Line::*; match (self, style) { @@ -130,6 +139,39 @@ impl Stylable for Line { } } +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 { diff --git a/src/display/mod.rs b/src/display/mod.rs index 3e30200ac723..10690284f126 100644 --- a/src/display/mod.rs +++ b/src/display/mod.rs @@ -2,6 +2,8 @@ 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}; @@ -29,3 +31,21 @@ impl<T: Draw> Draw for Box<T> { (**self).do_draw(out) } } + +pub trait DrawWithNeighbors: Positioned { + fn do_draw_with_neighbors<'a, 'b>( + &'a self, + out: &'b mut 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 Write, + _neighbors: &'a Neighbors<Vec<&'a Box<dyn Entity>>>, + ) -> io::Result<()> { + self.do_draw(out) + } +} diff --git a/src/display/viewport.rs b/src/display/viewport.rs index 372c0a2969d5..9ff7db07be20 100644 --- a/src/display/viewport.rs +++ b/src/display/viewport.rs @@ -1,7 +1,9 @@ use super::BoxStyle; -use super::Draw; +use super::DrawWithNeighbors; use crate::display::draw_box::draw_box; use crate::display::utils::clone_times; +use crate::entities::entity::Entity; +use crate::types::Neighbors; use crate::types::{pos, BoundingBox, Direction, Position, Positioned}; use std::fmt::{self, Debug}; use std::io::{self, Write}; @@ -77,12 +79,16 @@ impl<W> Debug for Viewport<W> { impl<W: Write> Viewport<W> { /// Draw the given entity to the viewport at its position, if visible - pub fn draw<T: Draw>(&mut self, entity: &T) -> io::Result<()> { + pub fn draw<'a, 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(self)?; + entity.do_draw_with_neighbors(self, neighbors)?; self.reset_cursor() } diff --git a/src/entities/entity.rs b/src/entities/entity.rs index 30f7ea9a3dae..7fedb77b2562 100644 --- a/src/entities/entity.rs +++ b/src/entities/entity.rs @@ -1,5 +1,6 @@ -use crate::display::Draw; +use crate::display::DrawWithNeighbors; use crate::entities::EntityID; +use crate::types::Neighbors; use crate::types::{Positioned, PositionedMut}; use downcast_rs::Downcast; use std::fmt::Debug; @@ -37,7 +38,7 @@ impl<ID, A: Identified<ID>> Identified<ID> for Box<A> { } pub trait Entity: - Positioned + PositionedMut + Identified<EntityID> + Draw + Downcast + Positioned + PositionedMut + Identified<EntityID> + DrawWithNeighbors + Downcast { } @@ -52,10 +53,10 @@ impl Identified<EntityID> for Box<dyn Entity> { #[macro_export] macro_rules! identified { - ($name: ident, $typ: ident) => { + ($name: ident, $typ: path) => { identified!($name, $typ, id); }; - ($name: ident, $typ: ident, $attr: ident) => { + ($name: ident, $typ: path, $attr: ident) => { impl crate::entities::entity::Identified<$typ> for $name { fn opt_id(&self) -> Option<$typ> { self.$attr @@ -68,20 +69,14 @@ macro_rules! identified { }; } -#[macro_export] -macro_rules! entity { - ($name: ident) => { - positioned!($name); - positioned_mut!($name); - identified!($name, EntityID); - impl crate::entities::entity::Entity for $name {} - }; -} - impl_downcast!(Entity); -impl Draw for Box<dyn Entity> { - fn do_draw(&self, out: &mut Write) -> io::Result<()> { - (**self).do_draw(out) +impl DrawWithNeighbors for Box<dyn Entity> { + fn do_draw_with_neighbors<'a, 'b>( + &'a self, + out: &'b mut Write, + neighbors: &'a Neighbors<Vec<&'a Box<dyn Entity>>>, + ) -> io::Result<()> { + (**self).do_draw_with_neighbors(out, neighbors) } } diff --git a/src/entities/environment.rs b/src/entities/environment.rs new file mode 100644 index 000000000000..64366a505496 --- /dev/null +++ b/src/entities/environment.rs @@ -0,0 +1,34 @@ +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 + } +} + +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 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/mod.rs b/src/entities/mod.rs index c54a587e6aba..3fe84c76f8ef 100644 --- a/src/entities/mod.rs +++ b/src/entities/mod.rs @@ -1,8 +1,11 @@ #[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; diff --git a/src/entities/util.rs b/src/entities/util.rs new file mode 100644 index 000000000000..6c11ffadf994 --- /dev/null +++ b/src/entities/util.rs @@ -0,0 +1,72 @@ +#[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 index 49068361b5d1..eee0b7c0d513 100644 --- a/src/game.rs +++ b/src/game.rs @@ -24,15 +24,15 @@ type Stdout<'a> = RawTerminal<StdoutLock<'a>>; type Rng = SmallRng; -type AnEntity<'a> = Box<dyn Entity>; +type AnEntity = Box<dyn Entity>; -impl<'a> Positioned for AnEntity<'a> { +impl Positioned for AnEntity { fn position(&self) -> Position { (**self).position() } } -impl<'a> PositionedMut for AnEntity<'a> { +impl PositionedMut for AnEntity { fn set_position(&mut self, pos: Position) { (**self).set_position(pos) } @@ -120,7 +120,7 @@ pub struct Game<'a> { input_state: InputState, /// The map of all the entities in the game - entities: EntityMap<AnEntity<'a>>, + entities: EntityMap<AnEntity>, /// The entity ID of the player character character_entity_id: EntityID, @@ -151,7 +151,7 @@ impl<'a> Game<'a> { Some(seed) => SmallRng::seed_from_u64(seed), None => SmallRng::from_entropy(), }; - let mut entities: EntityMap<AnEntity<'a>> = EntityMap::new(); + let mut entities: EntityMap<AnEntity> = EntityMap::new(); // TODO make this dynamic { @@ -219,11 +219,27 @@ impl<'a> Game<'a> { /// 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.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) + } + } + /// 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) { @@ -418,19 +434,17 @@ impl<'a> Game<'a> { match old_position { Some(old_pos) => { + let character = self.character(); + self.viewport.game_cursor_position = + character.position; + self.viewport.clear(old_pos)?; + self.draw_entity(self.character_entity_id)?; self.tick( self.character().speed().tiles_to_ticks( (old_pos - self.character().position) .as_tiles(), ), ); - self.viewport.clear(old_pos)?; - self.viewport.game_cursor_position = - self.character().position; - self.viewport.draw( - // TODO this clone feels unnecessary. - &self.character().clone(), - )?; } None => (), } diff --git a/src/level_gen/cave_automata.rs b/src/level_gen/cave_automata.rs index 6a237c0303df..de584f4111ab 100644 --- a/src/level_gen/cave_automata.rs +++ b/src/level_gen/cave_automata.rs @@ -1,3 +1,4 @@ +use crate::level_gen::util::fill_outer_edges; use crate::level_gen::util::rand_initialize; use crate::types::Dimensions; use rand::Rng; @@ -61,6 +62,9 @@ pub fn generate<R: Rng + ?Sized>( for _ in 0..params.steps { step_automata(&mut cells, dimensions, params); } + + fill_outer_edges(&mut cells); + cells } diff --git a/src/level_gen/display.rs b/src/level_gen/display.rs deleted file mode 100644 index 4472bf4fe392..000000000000 --- a/src/level_gen/display.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::io::{self, Write}; - -pub fn print_generated_level<W>( - level: &Vec<Vec<bool>>, - out: &mut W, -) -> io::Result<()> -where - W: Write, -{ - for row in level { - for cell in row { - write!(out, "{}", if *cell { "X" } else { " " })?; - } - write!(out, "\n")?; - } - Ok(()) -} diff --git a/src/level_gen/mod.rs b/src/level_gen/mod.rs index 4df57a408fa8..df742bb3a1b0 100644 --- a/src/level_gen/mod.rs +++ b/src/level_gen/mod.rs @@ -1,3 +1,101 @@ +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 display; 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.len() == 0 { + 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 index 629292c430fa..c9cd87309257 100644 --- a/src/level_gen/util.rs +++ b/src/level_gen/util.rs @@ -31,3 +31,22 @@ pub fn rand_initialize<R: Rng + ?Sized>( } 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 x in 0..xmax { + level[x][0] = true; + level[x][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 index 2f0d1c3ffb05..b322a969a1be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -95,7 +95,7 @@ fn generate_level<'a, W: io::Write>( ), Some(gen) => panic!("Unrecognized generator: {}", gen), }; - level_gen::display::print_generated_level(&level, stdout) + level_gen::draw_level(level, stdout) } fn main() -> io::Result<()> { diff --git a/src/types/entity_map.rs b/src/types/entity_map.rs index 12deaa57a6eb..3a7a982e4654 100644 --- a/src/types/entity_map.rs +++ b/src/types/entity_map.rs @@ -1,13 +1,16 @@ 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 std::collections::hash_map::HashMap; -use std::collections::BTreeMap; +use alga::general::{ + AbstractMagma, AbstractMonoid, AbstractSemigroup, Additive, Identity, +}; +use std::collections::{BTreeMap, HashMap}; use std::iter::FromIterator; -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct EntityMap<A> { by_position: BTreeMap<Position, Vec<EntityID>>, by_id: HashMap<EntityID, A>, @@ -127,6 +130,52 @@ impl<A: Positioned + Identified<EntityID>> EntityMap<A> { e }) } + + /// Moves all elements from `other` into `Self`, leathing `other` empty. + pub fn append(&mut self, other: &mut Self) { + self.by_position.append(&mut other.by_position); + self.by_id.reserve(other.len()); + for (k, v) in other.by_id.drain() { + self.by_id.insert(k, v); + } + self.last_id = self.last_id.max(other.last_id); + other.last_id = 0; + } + + /// 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) + } +} + +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).into_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> { @@ -139,6 +188,44 @@ impl<A: Positioned + Identified<EntityID>> FromIterator<A> for EntityMap<A> { } } +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, @@ -274,5 +361,35 @@ mod tests { 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_source = source.clone(); + target.append(&mut source); + assert_eq!(source, EntityMap::new()); + for (eid, e) in orig_source { + assert_eq!(target.get(eid), Some(&e)) + } + } } } diff --git a/src/types/mod.rs b/src/types/mod.rs index e656048e873c..95436fc66034 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -320,8 +320,8 @@ macro_rules! positioned { positioned!($name, position); }; ($name:ident, $attr:ident) => { - impl crate::types::Positioned for $name { - fn position(&self) -> Position { + impl $crate::types::Positioned for $name { + fn position(&self) -> $crate::types::Position { self.$attr } } @@ -335,7 +335,7 @@ macro_rules! positioned_mut { }; ($name:ident, $attr:ident) => { impl crate::types::PositionedMut for $name { - fn set_position(&mut self, pos: Position) { + fn set_position(&mut self, pos: $crate::types::Position) { self.$attr = pos; } } @@ -372,6 +372,55 @@ impl Speed { } } +#[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 { use super::*; |