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.
This commit is contained in:
Griffin Smith 2019-07-28 17:45:43 -04:00
parent f22bcad817
commit 6c1eba6762
17 changed files with 557 additions and 62 deletions

38
Cargo.lock generated
View file

@ -13,6 +13,17 @@ dependencies = [
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "alga"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"approx 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libm 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"num-complex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "ansi_term" name = "ansi_term"
version = "0.11.0" version = "0.11.0"
@ -26,6 +37,14 @@ name = "antidote"
version = "1.0.0" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "approx"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "arc-swap" name = "arc-swap"
version = "0.3.11" version = "0.3.11"
@ -402,6 +421,11 @@ name = "libc"
version = "0.2.58" version = "0.2.58"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libm"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "linked-hash-map" name = "linked-hash-map"
version = "0.3.0" version = "0.3.0"
@ -524,6 +548,15 @@ dependencies = [
"version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "num-complex"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.41" version = "0.1.41"
@ -1218,6 +1251,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "xanthous" name = "xanthous"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"alga 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
"backtrace 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "config 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1259,8 +1293,10 @@ dependencies = [
[metadata] [metadata]
"checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" "checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c"
"checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c" "checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c"
"checksum alga 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d708cb68c7106ed1844de68f50f0157a7788c2909a6926fad5a87546ef6a4ff8"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" "checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5"
"checksum approx 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3"
"checksum arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4662175ead9cd84451d5c35070517777949a2ed84551764129cedb88384841" "checksum arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4662175ead9cd84451d5c35070517777949a2ed84551764129cedb88384841"
"checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392" "checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392"
"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" "checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71"
@ -1309,6 +1345,7 @@ dependencies = [
"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
"checksum lexical-core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3f8673fab7063c2cac37d299c8a1a7beb720e78f71500098e4a3c137fdf025bf" "checksum lexical-core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3f8673fab7063c2cac37d299c8a1a7beb720e78f71500098e4a3c137fdf025bf"
"checksum libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "6281b86796ba5e4366000be6e9e18bf35580adf9e63fbe2294aadb587613a319" "checksum libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "6281b86796ba5e4366000be6e9e18bf35580adf9e63fbe2294aadb587613a319"
"checksum libm 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a"
"checksum linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" "checksum linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd"
"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" "checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
@ -1323,6 +1360,7 @@ dependencies = [
"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
"checksum nom 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e9761d859320e381010a4f7f8ed425f2c924de33ad121ace447367c713ad561b" "checksum nom 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e9761d859320e381010a4f7f8ed425f2c924de33ad121ace447367c713ad561b"
"checksum num-complex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fcb0cf31fb3ff77e6d2a6ebd6800df7fdcd106f2ad89113c9130bcd07f93dffc"
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
"checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" "checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"

View file

@ -5,6 +5,7 @@ authors = ["Griffin Smith <root@gws.fyi>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
alga = "0.9.1"
backtrace = "0.3" backtrace = "0.3"
clap = {version = "^2.33.0", features = ["yaml"]} clap = {version = "^2.33.0", features = ["yaml"]}
config = "*" config = "*"

View file

@ -2,6 +2,7 @@ use crate::display::utils::clone_times;
use crate::display::utils::times; use crate::display::utils::times;
use crate::types::BoundingBox; use crate::types::BoundingBox;
use crate::types::Dimensions; use crate::types::Dimensions;
use crate::types::Neighbors;
use itertools::Itertools; use itertools::Itertools;
use proptest::prelude::Arbitrary; use proptest::prelude::Arbitrary;
use proptest::strategy; use proptest::strategy;
@ -22,42 +23,50 @@ use std::io::{self, Write};
static BOX: char = '☐'; static BOX: char = '☐';
static BOX_CHARS: [[char; 16]; 8] = [ static BOX_CHARS: [[char; 16]; 8] = [
// 0
[ [
// 0 1 2 3 4 5 6 7 8 9 // 0 1 2 3 4 5 6 7 8 9
'─', '━', '│', '┃', '┄', '┅', '┆', '┇', '┈', '┉', '─', '━', '│', '┃', '┄', '┅', '┆', '┇', '┈', '┉',
// 10 // 10
'┊', '┋', '┌', '┍', '┎', '┏', '┊', '┋', '┌', '┍', '┎', '┏',
], ],
// 1
[ [
// 0 1 2 3 4 5 6 7 8 9 // 0 1 2 3 4 5 6 7 8 9
'┐', '┑', '┒', '┓', '└', '┕', '┖', '┗', '┘', '┙', '┐', '┑', '┒', '┓', '└', '┕', '┖', '┗', '┘', '┙',
'┚', '┛', '├', '┝', '┞', '┟', '┚', '┛', '├', '┝', '┞', '┟',
], ],
// 2
[ [
// 0 1 2 3 4 5 6 7 8 9 // 0 1 2 3 4 5 6 7 8 9
'┠', '┡', '┢', '┣', '┤', '┥', '┦', '┧', '┨', '┩', '┠', '┡', '┢', '┣', '┤', '┥', '┦', '┧', '┨', '┩',
'┪', '┫', '┬', '┭', '┮', '┯', '┪', '┫', '┬', '┭', '┮', '┯',
], ],
// 3
[ [
// 0 1 2 3 4 5 6 7 8 9 // 0 1 2 3 4 5 6 7 8 9
'┰', '┱', '┲', '┳', '┴', '┵', '┶', '┷', '┸', '┹', '┰', '┱', '┲', '┳', '┴', '┵', '┶', '┷', '┸', '┹',
'┺', '┻', '┼', '┽', '┾', '┿', '┺', '┻', '┼', '┽', '┾', '┿',
], ],
// 4
[ [
// 0 1 2 3 4 5 6 7 8 9 // 0 1 2 3 4 5 6 7 8 9
'╀', '╁', '╂', '╃', '╄', '╅', '╆', '╇', '╈', '╉', '╀', '╁', '╂', '╃', '╄', '╅', '╆', '╇', '╈', '╉',
'╊', '╋', '╌', '╍', '╎', '╏', '╊', '╋', '╌', '╍', '╎', '╏',
], ],
// 5
[ [
// 0 1 2 3 4 5 6 7 8 9 // 0 1 2 3 4 5 6 7 8 9
'═', '║', '╒', '╓', '╔', '╕', '╖', '╗', '╘', '╙', '═', '║', '╒', '╓', '╔', '╕', '╖', '╗', '╘', '╙',
'╚', '╛', '╜', '╝', '╞', '╟', '╚', '╛', '╜', '╝', '╞', '╟',
], ],
// 6
[ [
// 0 1 2 3 4 5 6 7 8 9 // 0 1 2 3 4 5 6 7 8 9
'╠', '╡', '╢', '╣', '╤', '╥', '╦', '╧', '╨', '╩', '╠', '╡', '╢', '╣', '╤', '╥', '╦', '╧', '╨', '╩',
'╪', '╫', '╬', '╭', '╮', '╯', '╪', '╫', '╬', '╭', '╮', '╯',
], ],
// 7
[ [
// 0 1 2 3 4 5 6 7 8 9 // 0 1 2 3 4 5 6 7 8 9
'╰', '', '╲', '', '╴', '╵', '╶', '╷', '╸', '╹', '╰', '', '╲', '', '╴', '╵', '╶', '╷', '╸', '╹',
@ -85,8 +94,8 @@ impl Arbitrary for BoxStyle {
} }
} }
trait Stylable { pub trait Stylable {
fn style(self, style: BoxStyle) -> char; fn style(&self, style: BoxStyle) -> char;
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)]
@ -98,7 +107,7 @@ enum Corner {
} }
impl Stylable for Corner { impl Stylable for Corner {
fn style(self, style: BoxStyle) -> char { fn style(&self, style: BoxStyle) -> char {
use BoxStyle::*; use BoxStyle::*;
use Corner::*; use Corner::*;
@ -119,7 +128,7 @@ enum Line {
} }
impl Stylable for Line { impl Stylable for Line {
fn style(self, style: BoxStyle) -> char { fn style(&self, style: BoxStyle) -> char {
use BoxStyle::*; use BoxStyle::*;
use Line::*; use Line::*;
match (self, style) { 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] #[must_use]
pub fn make_box(style: BoxStyle, dims: Dimensions) -> String { pub fn make_box(style: BoxStyle, dims: Dimensions) -> String {
if dims.h == 0 || dims.w == 0 { if dims.h == 0 || dims.w == 0 {

View file

@ -2,6 +2,8 @@ pub mod color;
pub mod draw_box; pub mod draw_box;
pub mod utils; pub mod utils;
pub mod viewport; pub mod viewport;
use crate::entities::entity::Entity;
use crate::types::Neighbors;
use crate::types::Positioned; use crate::types::Positioned;
pub use draw_box::{make_box, BoxStyle}; pub use draw_box::{make_box, BoxStyle};
use std::io::{self, Write}; use std::io::{self, Write};
@ -29,3 +31,21 @@ impl<T: Draw> Draw for Box<T> {
(**self).do_draw(out) (**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)
}
}

View file

@ -1,7 +1,9 @@
use super::BoxStyle; use super::BoxStyle;
use super::Draw; use super::DrawWithNeighbors;
use crate::display::draw_box::draw_box; use crate::display::draw_box::draw_box;
use crate::display::utils::clone_times; use crate::display::utils::clone_times;
use crate::entities::entity::Entity;
use crate::types::Neighbors;
use crate::types::{pos, BoundingBox, Direction, Position, Positioned}; use crate::types::{pos, BoundingBox, Direction, Position, Positioned};
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
use std::io::{self, Write}; use std::io::{self, Write};
@ -77,12 +79,16 @@ impl<W> Debug for Viewport<W> {
impl<W: Write> Viewport<W> { impl<W: Write> Viewport<W> {
/// Draw the given entity to the viewport at its position, if visible /// 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) { if !self.visible(entity) {
return Ok(()); return Ok(());
} }
self.cursor_goto(entity.position())?; self.cursor_goto(entity.position())?;
entity.do_draw(self)?; entity.do_draw_with_neighbors(self, neighbors)?;
self.reset_cursor() self.reset_cursor()
} }

View file

@ -1,5 +1,6 @@
use crate::display::Draw; use crate::display::DrawWithNeighbors;
use crate::entities::EntityID; use crate::entities::EntityID;
use crate::types::Neighbors;
use crate::types::{Positioned, PositionedMut}; use crate::types::{Positioned, PositionedMut};
use downcast_rs::Downcast; use downcast_rs::Downcast;
use std::fmt::Debug; use std::fmt::Debug;
@ -37,7 +38,7 @@ impl<ID, A: Identified<ID>> Identified<ID> for Box<A> {
} }
pub trait Entity: 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_export]
macro_rules! identified { macro_rules! identified {
($name: ident, $typ: ident) => { ($name: ident, $typ: path) => {
identified!($name, $typ, id); identified!($name, $typ, id);
}; };
($name: ident, $typ: ident, $attr: ident) => { ($name: ident, $typ: path, $attr: ident) => {
impl crate::entities::entity::Identified<$typ> for $name { impl crate::entities::entity::Identified<$typ> for $name {
fn opt_id(&self) -> Option<$typ> { fn opt_id(&self) -> Option<$typ> {
self.$attr 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_downcast!(Entity);
impl Draw for Box<dyn Entity> { impl DrawWithNeighbors for Box<dyn Entity> {
fn do_draw(&self, out: &mut Write) -> io::Result<()> { fn do_draw_with_neighbors<'a, 'b>(
(**self).do_draw(out) &'a self,
out: &'b mut Write,
neighbors: &'a Neighbors<Vec<&'a Box<dyn Entity>>>,
) -> io::Result<()> {
(**self).do_draw_with_neighbors(out, neighbors)
} }
} }

View file

@ -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))
}
}

View file

@ -1,8 +1,11 @@
#[macro_use] #[macro_use]
pub mod entity; pub mod entity;
#[macro_use]
pub mod util;
pub mod character; pub mod character;
pub mod creature; pub mod creature;
pub mod entity_char; pub mod entity_char;
pub mod environment;
pub mod item; pub mod item;
pub mod raw_types; pub mod raw_types;
pub mod raws; pub mod raws;

72
src/entities/util.rs Normal file
View file

@ -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)*);
};
() => {};
}

View file

@ -24,15 +24,15 @@ type Stdout<'a> = RawTerminal<StdoutLock<'a>>;
type Rng = SmallRng; 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 { fn position(&self) -> Position {
(**self).position() (**self).position()
} }
} }
impl<'a> PositionedMut for AnEntity<'a> { impl PositionedMut for AnEntity {
fn set_position(&mut self, pos: Position) { fn set_position(&mut self, pos: Position) {
(**self).set_position(pos) (**self).set_position(pos)
} }
@ -120,7 +120,7 @@ pub struct Game<'a> {
input_state: InputState, input_state: InputState,
/// The map of all the entities in the game /// The map of all the entities in the game
entities: EntityMap<AnEntity<'a>>, entities: EntityMap<AnEntity>,
/// The entity ID of the player character /// The entity ID of the player character
character_entity_id: EntityID, character_entity_id: EntityID,
@ -151,7 +151,7 @@ impl<'a> Game<'a> {
Some(seed) => SmallRng::seed_from_u64(seed), Some(seed) => SmallRng::seed_from_u64(seed),
None => SmallRng::from_entropy(), None => SmallRng::from_entropy(),
}; };
let mut entities: EntityMap<AnEntity<'a>> = EntityMap::new(); let mut entities: EntityMap<AnEntity> = EntityMap::new();
// TODO make this dynamic // TODO make this dynamic
{ {
@ -219,11 +219,27 @@ impl<'a> Game<'a> {
/// Draw all the game entities to the screen /// Draw all the game entities to the screen
fn draw_entities(&mut self) -> io::Result<()> { fn draw_entities(&mut self) -> io::Result<()> {
for entity in self.entities.entities() { for entity in self.entities.entities() {
self.viewport.draw(entity)?; self.viewport.draw(
entity,
&self.entities.neighbor_entities(entity.position()),
)?;
} }
Ok(()) 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 /// Remove the given entity from the game, drawing over it if it's visible
fn remove_entity(&mut self, entity_id: EntityID) -> io::Result<()> { fn remove_entity(&mut self, entity_id: EntityID) -> io::Result<()> {
if let Some(entity) = self.entities.remove(entity_id) { if let Some(entity) = self.entities.remove(entity_id) {
@ -418,19 +434,17 @@ impl<'a> Game<'a> {
match old_position { match old_position {
Some(old_pos) => { 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.tick(
self.character().speed().tiles_to_ticks( self.character().speed().tiles_to_ticks(
(old_pos - self.character().position) (old_pos - self.character().position)
.as_tiles(), .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 => (), None => (),
} }

View file

@ -1,3 +1,4 @@
use crate::level_gen::util::fill_outer_edges;
use crate::level_gen::util::rand_initialize; use crate::level_gen::util::rand_initialize;
use crate::types::Dimensions; use crate::types::Dimensions;
use rand::Rng; use rand::Rng;
@ -61,6 +62,9 @@ pub fn generate<R: Rng + ?Sized>(
for _ in 0..params.steps { for _ in 0..params.steps {
step_automata(&mut cells, dimensions, params); step_automata(&mut cells, dimensions, params);
} }
fill_outer_edges(&mut cells);
cells cells
} }

View file

@ -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(())
}

View file

@ -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 cave_automata;
pub mod display;
pub mod util; 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)
}

View file

@ -31,3 +31,22 @@ pub fn rand_initialize<R: Rng + ?Sized>(
} }
ret 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;
}
}

View file

@ -95,7 +95,7 @@ fn generate_level<'a, W: io::Write>(
), ),
Some(gen) => panic!("Unrecognized generator: {}", gen), Some(gen) => panic!("Unrecognized generator: {}", gen),
}; };
level_gen::display::print_generated_level(&level, stdout) level_gen::draw_level(level, stdout)
} }
fn main() -> io::Result<()> { fn main() -> io::Result<()> {

View file

@ -1,13 +1,16 @@
use crate::entities::entity::Identified; use crate::entities::entity::Identified;
use crate::entities::EntityID; use crate::entities::EntityID;
use crate::types::Neighbors;
use crate::types::Position; use crate::types::Position;
use crate::types::Positioned; use crate::types::Positioned;
use crate::types::PositionedMut; use crate::types::PositionedMut;
use std::collections::hash_map::HashMap; use alga::general::{
use std::collections::BTreeMap; AbstractMagma, AbstractMonoid, AbstractSemigroup, Additive, Identity,
};
use std::collections::{BTreeMap, HashMap};
use std::iter::FromIterator; use std::iter::FromIterator;
#[derive(Debug)] #[derive(Debug, PartialEq, Eq, Clone)]
pub struct EntityMap<A> { pub struct EntityMap<A> {
by_position: BTreeMap<Position, Vec<EntityID>>, by_position: BTreeMap<Position, Vec<EntityID>>,
by_id: HashMap<EntityID, A>, by_id: HashMap<EntityID, A>,
@ -127,6 +130,52 @@ impl<A: Positioned + Identified<EntityID>> EntityMap<A> {
e 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> { 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> { impl<A: PositionedMut> EntityMap<A> {
pub fn update_position( pub fn update_position(
&mut self, &mut self,
@ -274,5 +361,35 @@ mod tests {
em.remove_all_at(pos); em.remove_all_at(pos);
assert_eq!(em.at(pos).len(), 0); 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))
}
}
} }
} }

View file

@ -320,8 +320,8 @@ macro_rules! positioned {
positioned!($name, position); positioned!($name, position);
}; };
($name:ident, $attr:ident) => { ($name:ident, $attr:ident) => {
impl crate::types::Positioned for $name { impl $crate::types::Positioned for $name {
fn position(&self) -> Position { fn position(&self) -> $crate::types::Position {
self.$attr self.$attr
} }
} }
@ -335,7 +335,7 @@ macro_rules! positioned_mut {
}; };
($name:ident, $attr:ident) => { ($name:ident, $attr:ident) => {
impl crate::types::PositionedMut for $name { 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; 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;