about summary refs log tree commit diff
path: root/src/types
diff options
context:
space:
mode:
authorGriffin Smith <root@gws.fyi>2019-07-09T00·58-0400
committerGriffin Smith <root@gws.fyi>2019-07-09T00·58-0400
commit5af2429ecb5742383cf0798ce23682d316bdb24d (patch)
tree0fba959d9a5bce5c749c8529b3f2ea7b557c5767 /src/types
parent20f1ccb4600b88ac01768e912e6d5837534ca852 (diff)
Implement a global map of entities
Implement a global map of entities, which allows referencing by either
position or ID and updating the positions of existent entities, and put
the character in there.
Diffstat (limited to 'src/types')
-rw-r--r--src/types/collision.rs8
-rw-r--r--src/types/entity_map.rs242
-rw-r--r--src/types/mod.rs78
3 files changed, 324 insertions, 4 deletions
diff --git a/src/types/collision.rs b/src/types/collision.rs
new file mode 100644
index 000000000000..f41e30fc516a
--- /dev/null
+++ b/src/types/collision.rs
@@ -0,0 +1,8 @@
+/// Describes a kind of game collision
+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/entity_map.rs b/src/types/entity_map.rs
new file mode 100644
index 000000000000..2d9f033a79d4
--- /dev/null
+++ b/src/types/entity_map.rs
@@ -0,0 +1,242 @@
+use crate::types::Position;
+use crate::types::Positioned;
+use crate::types::PositionedMut;
+use std::collections::hash_map::HashMap;
+use std::collections::BTreeMap;
+use std::iter::FromIterator;
+
+pub type EntityID = u32;
+
+#[derive(Debug)]
+pub struct EntityMap<A> {
+    by_position: BTreeMap<Position, Vec<EntityID>>,
+    by_id: HashMap<EntityID, A>,
+    last_id: EntityID,
+}
+
+// impl<A: Debug> ArbitraryF1<A> for EntityMap<A> {
+//     type Parameters = ();
+//     fn lift1_with<AS>(base: AS, _: Self::Parameters) -> BoxedStrategy<Self>
+//     where
+//         AS: Strategy<Value = A> + 'static,
+//     {
+//         unimplemented!()
+//     }
+//     // type Strategy = strategy::Just<Self>;
+//     // fn arbitrary_with(params : Self::Parameters) -> Self::Strategy;
+// }
+
+// impl<A: Arbitrary> Arbitrary for EntityMap<A> {
+//     type Parameters = A::Parameters;
+//     type Strategy = BoxedStrategy<Self>;
+//     fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
+//         let a_strat: A::Strategy = Arbitrary::arbitrary_with(params);
+//         ArbitraryF1::lift1::<A::Strategy>(a_strat)
+//     }
+// }
+
+const BY_POS_INVARIANT: &'static 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))
+        // })
+        // gross.
+        match self.by_position.get(&pos) {
+            None => Vec::new(),
+            Some(eids) => {
+                let mut res = Vec::new();
+                for eid in eids {
+                    res.push(self.by_id.get(eid).expect(BY_POS_INVARIANT));
+                }
+                res
+            }
+        }
+    }
+
+    /// Remove all entities at the given position
+    pub fn remove_all_at(&mut self, pos: Position) {
+        self.by_position.remove(&pos).map(|eids| {
+            eids.iter()
+                .map(|eid| self.by_id.remove(&eid).expect(BY_POS_INVARIANT));
+        });
+    }
+
+    pub fn get<'a>(&'a self, id: EntityID) -> Option<&'a A> {
+        self.by_id.get(&id)
+    }
+
+    pub fn entities<'a>(&'a self) -> impl Iterator<Item = &'a A> {
+        self.by_id.values()
+    }
+
+    pub fn entities_mut<'a>(&'a mut self) -> impl Iterator<Item = &'a mut A> {
+        self.by_id.values_mut()
+    }
+
+    pub fn ids(&self) -> impl Iterator<Item = &EntityID> {
+        self.by_id.keys()
+    }
+
+    fn next_id(&mut self) -> EntityID {
+        self.last_id += 1;
+        self.last_id
+    }
+}
+
+impl<A: Positioned> EntityMap<A> {
+    pub fn insert(&mut self, entity: A) -> EntityID {
+        let pos = entity.position();
+        let entity_id = self.next_id();
+        self.by_id.entry(entity_id).or_insert(entity);
+        self.by_position
+            .entry(pos)
+            .or_insert(Vec::new())
+            .push(entity_id);
+        entity_id
+    }
+}
+
+impl<A: Positioned> 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: 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);
+        }
+        old_pos.map(|p| {
+            self.by_position
+                .get_mut(&p)
+                .map(|es| es.retain(|e| *e != entity_id));
+
+            self.by_position
+                .entry(new_position)
+                .or_insert(Vec::new())
+                .push(entity_id);
+        });
+    }
+}
+
+#[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 {
+        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
+        }
+    }
+
+    fn gen_entity_map() -> BoxedStrategy<EntityMap<TestEntity>> {
+        any::<Vec<TestEntity>>()
+            .prop_map(|ents| {
+                ents.iter()
+                    .map(|e| e.clone())
+                    .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);
+            }
+            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 == ent))
+        }
+
+        #[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 == ent))
+        }
+
+        #[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_eq!(em.at(original_position).len(), 0);
+            }
+            assert_eq!(
+                em.get(entity_id).map(|e| e.position()),
+                Some(new_position)
+            );
+            assert_eq!(
+                em.at(new_position).iter().map(|e| e.name.clone()).collect::<Vec<_>>(),
+                vec![ent.name]
+            )
+        }
+    }
+}
diff --git a/src/types/mod.rs b/src/types/mod.rs
index ab66a50cc218..c0375a382fe2 100644
--- a/src/types/mod.rs
+++ b/src/types/mod.rs
@@ -1,7 +1,11 @@
 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 use collision::Collision;
 pub use direction::Direction;
 pub use direction::Direction::{Down, Left, Right, Up};
 use proptest_derive::Arbitrary;
@@ -43,13 +47,16 @@ impl BoundingBox {
         }
     }
 
-    pub fn from_corners(top_left: Position, lower_right: Position) -> BoundingBox {
+    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,
-            }
+            },
         }
     }
 
@@ -70,7 +77,11 @@ impl BoundingBox {
     /// 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 }
+        self + offset
+            - Dimensions {
+                w: offset.x as u16,
+                h: offset.y as u16,
+            }
     }
 }
 
@@ -94,7 +105,7 @@ impl ops::Sub<Dimensions> for BoundingBox {
     }
 }
 
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary, Hash, Ord)]
 pub struct Position {
     /// x (horizontal) position
     #[proptest(strategy = "std::ops::Range::<i16>::from(0..100)")]
@@ -105,6 +116,10 @@ pub struct 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 };
 
@@ -241,6 +256,47 @@ pub trait Positioned {
     }
 }
 
+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);
@@ -254,6 +310,20 @@ macro_rules! positioned {
     };
 }
 
+#[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: Position) {
+                self.$attr = pos;
+            }
+        }
+    };
+}
+
 /// A number of ticks
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)]
 pub struct Ticks(pub u16);