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.
This commit is contained in:
parent
20f1ccb460
commit
5af2429ecb
14 changed files with 465 additions and 36 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -249,6 +249,11 @@ dependencies = [
|
|||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "0.4.4"
|
||||
|
@ -1083,8 +1088,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
name = "xanthous"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"backtrace 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"config 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"downcast-rs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1145,6 +1152,7 @@ dependencies = [
|
|||
"checksum csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37519ccdfd73a75821cac9319d4fce15a81b9fcf75f951df5b9988aa3a0af87d"
|
||||
"checksum csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9b5cadb6b25c77aeff80ba701712494213f4a8418fcda2ee11b6560c3ad0bf4c"
|
||||
"checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901"
|
||||
"checksum downcast-rs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b92dfd5c2f75260cbf750572f95d387e7ca0ba5e3fbe9e1a33f23025be020f"
|
||||
"checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e"
|
||||
"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b"
|
||||
"checksum encode_unicode 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "90b2c9496c001e8cb61827acdefad780795c42264c137744cae6f7d9e3450abd"
|
||||
|
|
|
@ -5,8 +5,10 @@ authors = ["Griffin Smith <root@gws.fyi>"]
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
backtrace = "0.3"
|
||||
clap = {version = "^2.33.0", features = ["yaml"]}
|
||||
config = "*"
|
||||
downcast-rs = "^1.0.4"
|
||||
itertools = "*"
|
||||
lazy_static = "*"
|
||||
log = "*"
|
||||
|
|
7
proptest-regressions/types/entity_map.txt
Normal file
7
proptest-regressions/types/entity_map.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Seeds for failure cases proptest has generated in the past. It is
|
||||
# automatically read and these particular cases re-run before any
|
||||
# novel cases are generated.
|
||||
#
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc 16afe2473971397314ffa77acf7bad62f0c40bc3f591aff7aa9193c29e5a0921 # shrinks to items = [(Position { x: 92, y: 60 }, ""), (Position { x: 92, y: 60 }, "")]
|
|
@ -14,5 +14,17 @@ pub fn clear<T: Write>(out: &mut T) -> io::Result<()> {
|
|||
pub trait Draw: Positioned {
|
||||
/// Draw this entity, assuming the character is already at the correct
|
||||
/// position
|
||||
fn do_draw<W: Write>(&self, out: &mut W) -> io::Result<()>;
|
||||
fn do_draw(&self, out: &mut Write) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl<T : Draw> Draw for &T {
|
||||
fn do_draw(&self, out: &mut Write) -> io::Result<()> {
|
||||
(**self).do_draw(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : Draw> Draw for Box<T> {
|
||||
fn do_draw(&self, out: &mut Write) -> io::Result<()> {
|
||||
(**self).do_draw(out)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ use super::BoxStyle;
|
|||
use super::Draw;
|
||||
use crate::display::draw_box::draw_box;
|
||||
use crate::display::utils::clone_times;
|
||||
use crate::display::utils::times;
|
||||
use crate::types::{BoundingBox, Position, Positioned};
|
||||
use std::fmt::{self, Debug};
|
||||
use std::io::{self, Write};
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use crate::display;
|
||||
use crate::entities::Entity;
|
||||
use crate::types::{Position, Speed};
|
||||
use proptest_derive::Arbitrary;
|
||||
use std::io::{self, Write};
|
||||
use termion::cursor;
|
||||
|
||||
use crate::display;
|
||||
use crate::types::{Position, Speed};
|
||||
|
||||
const DEFAULT_SPEED: Speed = Speed(100);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Arbitrary)]
|
||||
#[derive(Debug, PartialEq, Eq, Arbitrary, Clone)]
|
||||
pub struct Character {
|
||||
/// The position of the character, relative to the game
|
||||
pub position: Position,
|
||||
|
@ -26,13 +26,12 @@ impl Character {
|
|||
}
|
||||
|
||||
positioned!(Character);
|
||||
positioned_mut!(Character);
|
||||
|
||||
impl Entity for Character {}
|
||||
|
||||
impl display::Draw for Character {
|
||||
fn do_draw<W: Write>(&self, out: &mut W) -> io::Result<()> {
|
||||
write!(
|
||||
out,
|
||||
"@{}",
|
||||
cursor::Left(1),
|
||||
)
|
||||
fn do_draw(&self, out: &mut Write) -> io::Result<()> {
|
||||
write!(out, "@{}", cursor::Left(1),)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +1,16 @@
|
|||
pub mod character;
|
||||
use crate::display::Draw;
|
||||
use crate::types::{Positioned, PositionedMut};
|
||||
pub use character::Character;
|
||||
use downcast_rs::Downcast;
|
||||
use std::io::{self, Write};
|
||||
|
||||
pub trait Entity: Positioned + PositionedMut + Draw + Downcast {}
|
||||
|
||||
impl_downcast!(Entity);
|
||||
|
||||
impl Draw for Box<dyn Entity> {
|
||||
fn do_draw(&self, out: &mut Write) -> io::Result<()> {
|
||||
(**self).do_draw(out)
|
||||
}
|
||||
}
|
||||
|
|
78
src/game.rs
78
src/game.rs
|
@ -1,10 +1,14 @@
|
|||
use crate::display::{self, Viewport};
|
||||
use crate::entities::Character;
|
||||
use crate::entities::Entity;
|
||||
use crate::messages::message;
|
||||
use crate::settings::Settings;
|
||||
use crate::types::command::Command;
|
||||
use crate::types::Positioned;
|
||||
use crate::types::{BoundingBox, Dimensions, Position};
|
||||
use crate::types::entity_map::EntityID;
|
||||
use crate::types::entity_map::EntityMap;
|
||||
use crate::types::{
|
||||
BoundingBox, Collision, Dimensions, Position, Positioned, PositionedMut,
|
||||
};
|
||||
use rand::rngs::SmallRng;
|
||||
use rand::SeedableRng;
|
||||
use std::io::{self, StdinLock, StdoutLock, Write};
|
||||
|
@ -16,6 +20,20 @@ type Stdout<'a> = RawTerminal<StdoutLock<'a>>;
|
|||
|
||||
type Rng = SmallRng;
|
||||
|
||||
type AnEntity<'a> = Box<dyn Entity>;
|
||||
|
||||
impl<'a> Positioned for AnEntity<'a> {
|
||||
fn position(&self) -> Position {
|
||||
(**self).position()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PositionedMut for AnEntity<'a> {
|
||||
fn set_position(&mut self, pos: Position) {
|
||||
(**self).set_position(pos)
|
||||
}
|
||||
}
|
||||
|
||||
/// The full state of a running Game
|
||||
pub struct Game<'a> {
|
||||
settings: Settings,
|
||||
|
@ -25,8 +43,11 @@ pub struct Game<'a> {
|
|||
/// An iterator on keypresses from the user
|
||||
keys: Keys<StdinLock<'a>>,
|
||||
|
||||
/// The player character
|
||||
character: Character,
|
||||
/// The map of all the entities in the game
|
||||
entities: EntityMap<AnEntity<'a>>,
|
||||
|
||||
/// 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>,
|
||||
|
@ -51,6 +72,7 @@ impl<'a> Game<'a> {
|
|||
Some(seed) => SmallRng::seed_from_u64(seed),
|
||||
None => SmallRng::from_entropy(),
|
||||
};
|
||||
let mut entities: EntityMap<AnEntity<'a>> = EntityMap::new();
|
||||
Game {
|
||||
settings,
|
||||
rng,
|
||||
|
@ -61,19 +83,34 @@ impl<'a> Game<'a> {
|
|||
stdout,
|
||||
),
|
||||
keys: stdin.keys(),
|
||||
character: Character::new(),
|
||||
character_entity_id: entities.insert(Box::new(Character::new())),
|
||||
messages: Vec::new(),
|
||||
entities,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if there's a collision in the game at the given Position
|
||||
fn collision_at(&self, pos: Position) -> bool {
|
||||
!pos.within(self.viewport.inner)
|
||||
/// 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 {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn character(&self) -> &Character {
|
||||
debug!("ents: {:?} cid: {:?}", self.entities.ids().map(|id| *id).collect::<Vec<u32>>(), self.character_entity_id);
|
||||
(*self.entities.get(self.character_entity_id).unwrap())
|
||||
.downcast_ref()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Draw all the game entities to the screen
|
||||
fn draw_entities(&mut self) -> io::Result<()> {
|
||||
self.viewport.draw(&self.character)
|
||||
for entity in self.entities.entities() {
|
||||
self.viewport.draw(entity)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a message from the global map based on the rng in this game
|
||||
|
@ -104,7 +141,6 @@ impl<'a> Game<'a> {
|
|||
self.viewport.init()?;
|
||||
self.draw_entities()?;
|
||||
self.say("global.welcome")?;
|
||||
self.say("somethign else")?;
|
||||
self.flush()?;
|
||||
loop {
|
||||
let mut old_position = None;
|
||||
|
@ -116,10 +152,18 @@ impl<'a> Game<'a> {
|
|||
}
|
||||
|
||||
Some(Move(direction)) => {
|
||||
let new_pos = self.character.position + direction;
|
||||
if !self.collision_at(new_pos) {
|
||||
old_position = Some(self.character.position);
|
||||
self.character.position = new_pos;
|
||||
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) => unimplemented!(),
|
||||
Some(Stop) => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,12 +175,14 @@ impl<'a> Game<'a> {
|
|||
match old_position {
|
||||
Some(old_pos) => {
|
||||
self.viewport.clear(old_pos)?;
|
||||
self.viewport.draw(&self.character)?;
|
||||
self.viewport.draw(
|
||||
// TODO this clone feels unnecessary.
|
||||
&self.character().clone())?;
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
self.flush()?;
|
||||
debug!("{:?}", self.character);
|
||||
debug!("{:?}", self.character());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
18
src/main.rs
18
src/main.rs
|
@ -13,9 +13,12 @@ extern crate clap;
|
|||
extern crate prettytable;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate maplit;
|
||||
|
||||
#[macro_use]
|
||||
extern crate downcast_rs;
|
||||
extern crate backtrace;
|
||||
|
||||
mod display;
|
||||
mod game;
|
||||
|
@ -24,12 +27,14 @@ mod types;
|
|||
mod entities;
|
||||
mod messages;
|
||||
mod settings;
|
||||
mod util;
|
||||
|
||||
use clap::App;
|
||||
use game::Game;
|
||||
use prettytable::format::consts::FORMAT_BOX_CHARS;
|
||||
use settings::Settings;
|
||||
|
||||
use backtrace::Backtrace;
|
||||
use std::io::{self, StdinLock, StdoutLock};
|
||||
use std::panic;
|
||||
|
||||
|
@ -43,7 +48,16 @@ fn init(
|
|||
w: u16,
|
||||
h: u16,
|
||||
) {
|
||||
panic::set_hook(Box::new(|info| error!("{}", info)));
|
||||
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().unwrap()
|
||||
}
|
||||
|
|
|
@ -4,13 +4,16 @@ use log4rs::append::file::FileAppender;
|
|||
use log4rs::config::{Appender, Root};
|
||||
use log4rs::encode::pattern::PatternEncoder;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[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 {
|
||||
|
@ -18,6 +21,7 @@ impl Default for Logging {
|
|||
Logging {
|
||||
level: LevelFilter::Off,
|
||||
file: "debug.log".to_string(),
|
||||
print_backtrace: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,9 +48,13 @@ impl Logging {
|
|||
fn default_file() -> String {
|
||||
Logging::default().file
|
||||
}
|
||||
|
||||
fn default_print_backtrace() -> bool {
|
||||
Logging::default().print_backtrace
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Settings {
|
||||
pub seed: Option<u64>,
|
||||
pub logging: Logging,
|
||||
|
|
8
src/types/collision.rs
Normal file
8
src/types/collision.rs
Normal file
|
@ -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,
|
||||
}
|
242
src/types/entity_map.rs
Normal file
242
src/types/entity_map.rs
Normal file
|
@ -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]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
0
src/util/mod.rs
Normal file
0
src/util/mod.rs
Normal file
Loading…
Reference in a new issue