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:
parent
f22bcad817
commit
6c1eba6762
17 changed files with 557 additions and 62 deletions
38
Cargo.lock
generated
38
Cargo.lock
generated
|
@ -13,6 +13,17 @@ dependencies = [
|
|||
"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]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
|
@ -26,6 +37,14 @@ name = "antidote"
|
|||
version = "1.0.0"
|
||||
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]]
|
||||
name = "arc-swap"
|
||||
version = "0.3.11"
|
||||
|
@ -402,6 +421,11 @@ name = "libc"
|
|||
version = "0.2.58"
|
||||
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]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.3.0"
|
||||
|
@ -524,6 +548,15 @@ dependencies = [
|
|||
"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]]
|
||||
name = "num-integer"
|
||||
version = "0.1.41"
|
||||
|
@ -1218,6 +1251,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
name = "xanthous"
|
||||
version = "0.1.0"
|
||||
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)",
|
||||
"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)",
|
||||
|
@ -1259,8 +1293,10 @@ dependencies = [
|
|||
[metadata]
|
||||
"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 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 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 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"
|
||||
|
@ -1309,6 +1345,7 @@ dependencies = [
|
|||
"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 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.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"
|
||||
|
@ -1323,6 +1360,7 @@ dependencies = [
|
|||
"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 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-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"
|
||||
|
|
|
@ -5,6 +5,7 @@ authors = ["Griffin Smith <root@gws.fyi>"]
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
alga = "0.9.1"
|
||||
backtrace = "0.3"
|
||||
clap = {version = "^2.33.0", features = ["yaml"]}
|
||||
config = "*"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
34
src/entities/environment.rs
Normal file
34
src/entities/environment.rs
Normal 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))
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
72
src/entities/util.rs
Normal file
72
src/entities/util.rs
Normal 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)*);
|
||||
};
|
||||
|
||||
() => {};
|
||||
}
|
40
src/game.rs
40
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 => (),
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<()> {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::*;
|
||||
|
|
Loading…
Reference in a new issue