Add ability to associate arbitrary BuilderData with maps.

I'm finding that, for best results, I need to integrate everything into my map generation process. So for instance, object/monster spawns need to run as a filter so they can influence future steps.

This associates a `Clone + Default` type with maps and makes it available to filters. `NoData` exists for the current behavior.

All examples/tests/demos have been updated accordingly.
This commit is contained in:
Nolan Darilek 2022-03-12 09:45:42 -06:00
parent 1ef42b11ad
commit 957dcca447
20 changed files with 268 additions and 183 deletions

View File

@ -1,7 +1,7 @@
use wasm_bindgen::prelude::*;
use web_sys;
use rand::prelude::*;
use mapgen::{Map, MapBuilder, geometry::Point};
use mapgen::{Map, MapBuilder, NoData, geometry::Point};
use mapgen::filter::*;
use mapgen::metric;
@ -19,7 +19,7 @@ pub struct World {
width: u32,
height: u32,
tiles: Vec<Cell>,
map: Map,
map: Map<NoData>,
}
#[wasm_bindgen]
@ -32,7 +32,7 @@ pub struct Position {
#[wasm_bindgen]
impl World {
fn new(width: u32, height: u32, map: Map) -> World {
fn new(width: u32, height: u32, map: Map<NoData>) -> World {
let tiles = (0..map.tiles.len())
.map(|i| if map.tiles[i].is_walkable() {Cell::Floor} else {Cell::Wall})
.collect();
@ -176,7 +176,7 @@ impl World {
div.set_inner_html(&info);
}
fn print_map_metrics(map: &Map) {
fn print_map_metrics(map: &Map<NoData>) {
let window = web_sys::window().expect("no global `window` exists");
let document = window.document().expect("should have a document on window");
let div = document.get_element_by_id("map-metrics").expect("Need div with id: map-metrics");

View File

@ -4,7 +4,7 @@ use mapgen::*;
fn main() {
let mut rng = StdRng::seed_from_u64(907647352);
let gen = BspInterior::new();
let gen = BspInterior::<NoData>::new();
let map = gen.modify_map(&mut rng, &Map::new(20, 10));
println!("{:}", &map);
}

View File

@ -6,7 +6,7 @@ use mapgen::*;
fn main() {
let system_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Can't access system time");
let mut rng = StdRng::seed_from_u64(system_time.as_millis() as u64);
let gen = BspRooms::new();
let gen = BspRooms::<NoData>::new();
let map = gen.modify_map(&mut rng, &Map::new(80, 50));
println!("{:}", &map);
}

View File

@ -1,5 +1,6 @@
use mapgen::{
MapBuilder,
NoData,
filter::{
NoiseGenerator,
CellularAutomata,
@ -12,7 +13,7 @@ use mapgen::{
fn main() {
let map = MapBuilder::new(20, 20)
let map = MapBuilder::<NoData>::new(20, 20)
.with(NoiseGenerator::uniform())
.with(CellularAutomata::new())
.with(AreaStartingPosition::new(XStart::CENTER, YStart::CENTER))

View File

@ -25,25 +25,26 @@
//! ---
//!
use std::collections::VecDeque;
use std::{collections::VecDeque, marker::PhantomData};
use std::f32::MAX;
use super::map::Map;
use super::map::{BuilderData, Map};
/// Representation of a Dijkstra flow map.
/// map is a vector of floats, having a size equal to size_x * size_y (one per tile).
/// size_x and size_y are stored for overflow avoidance.
/// max_depth is the maximum number of iterations this search shall support.
pub struct DijkstraMap {
pub struct DijkstraMap<D> {
pub tiles: Vec<f32>,
size_x: usize,
size_y: usize,
max_depth: f32,
phantom: PhantomData<D>,
}
impl DijkstraMap {
impl<D: BuilderData> DijkstraMap<D> {
//! Construct a new Dijkstra map, ready to run.
pub fn new(map: &Map) -> DijkstraMap {
pub fn new(map: &Map<D>) -> DijkstraMap<D> {
let len = map.width * map.height;
let tiles = vec![MAX; len];
let mut d = DijkstraMap {
@ -51,6 +52,7 @@ impl DijkstraMap {
size_x: map.width,
size_y: map.height,
max_depth: len as f32,
phantom: PhantomData,
};
d.build(map);
d
@ -61,7 +63,7 @@ impl DijkstraMap {
/// depth is further than the current depth.
/// WARNING: Will give incorrect results when used with non-uniform exit costs. Much slower
/// algorithm required to support that.
fn build(&mut self, map: &Map) {
fn build(&mut self, map: &Map<D>) {
let mapsize: usize = (self.size_x * self.size_y) as usize;
let mut open_list: VecDeque<((usize, usize), f32)> = VecDeque::with_capacity(mapsize);
@ -97,7 +99,7 @@ impl DijkstraMap {
mod tests {
use super::*;
use crate::geometry::Point;
use crate::map::Map;
use crate::map::{Map, NoData};
#[test]
fn test_culling() {
@ -106,7 +108,7 @@ mod tests {
# # #
##########
";
let mut map = Map::from_string(map_str);
let mut map = Map::<NoData>::from_string(map_str);
map.starting_point = Some(Point::new(8, 1));
let dm = DijkstraMap::new(&map);
@ -134,7 +136,7 @@ mod tests {
# #
####
";
let mut map = Map::from_string(map_str);
let mut map = Map::<NoData>::from_string(map_str);
map.starting_point = Some(Point::new(2, 2));
let dm = DijkstraMap::new(&map);
let expected = [MAX, MAX, MAX, MAX,
@ -153,7 +155,7 @@ mod tests {
# # #
##########
";
let mut map = Map::from_string(map_str);
let mut map = Map::<NoData>::from_string(map_str);
map.starting_point = Some(Point::new(8, 2));
let dm = DijkstraMap::new(&map);
let expected = [MAX, MAX, MAX, MAX, MAX, MAX, MAX, MAX, MAX, MAX,

View File

@ -6,13 +6,13 @@
//! Example generator usage:
//! ```
//! use rand::prelude::*;
//! use mapgen::{Map, MapFilter};
//! use mapgen::{Map, MapFilter, NoData};
//! use mapgen::filter::{
//! BspInterior
//! };
//!
//! let mut rng = StdRng::seed_from_u64(100);
//! let gen = BspInterior::new();
//! let gen = BspInterior::<NoData>::new();
//! let map = gen.modify_map(&mut rng, &Map::new(80, 50));
//!
//! assert_eq!(map.width, 80);
@ -20,32 +20,36 @@
//! ```
//!
use std::marker::PhantomData;
use rand::prelude::*;
use crate::MapFilter;
use crate::{BuilderData, MapFilter};
use crate::geometry::{Point, Rect};
use crate::random::Rng;
use crate::Map;
pub struct BspInterior {
pub struct BspInterior<D: BuilderData> {
min_room_size: usize,
phantom: PhantomData<D>,
}
impl MapFilter for BspInterior {
fn modify_map(&self, rng: &mut StdRng, map: &Map) -> Map {
impl<D: BuilderData> MapFilter<D> for BspInterior<D> {
fn modify_map(&self, rng: &mut StdRng, map: &Map<D>) -> Map<D> {
self.build(rng, map)
}
}
impl BspInterior {
impl<D: BuilderData> BspInterior<D> {
pub fn new() -> Box<BspInterior> {
pub fn new() -> Box<BspInterior<D>> {
Box::new(BspInterior{
min_room_size: 8,
phantom: PhantomData,
})
}
fn build(&self, rng: &mut StdRng, map: &Map) -> Map {
fn build(&self, rng: &mut StdRng, map: &Map<D>) -> Map<D> {
let mut new_map = map.clone();
let mut rects: Vec<Rect> = Vec::new();
rects.push( Rect::new(1, 1, new_map.width-2, new_map.height-2) );
@ -114,12 +118,12 @@ impl BspInterior {
#[cfg(test)]
mod tests {
use super::*;
use crate::{Map};
use crate::{Map, map::NoData};
#[test]
fn no_corridors_on_borders() {
let mut rng = StdRng::seed_from_u64(907647352);
let gen = BspInterior::new();
let gen = BspInterior::<NoData>::new();
let map = gen.modify_map(&mut rng, &Map::new(80, 50));
for i in 0..80 {
assert!(map.at(i, 0).is_blocked());

View File

@ -6,11 +6,11 @@
//! Example generator usage:
//! ```
//! use rand::prelude::*;
//! use mapgen::{Map, MapFilter};
//! use mapgen::{Map, MapFilter, NoData};
//! use mapgen::filter::BspRooms;
//!
//! let mut rng = StdRng::seed_from_u64(100);
//! let gen = BspRooms::new();
//! let gen = BspRooms::<NoData>::new();
//! let map = gen.modify_map(&mut rng, &Map::new(80, 50));
//!
//! assert_eq!(map.width, 80);
@ -18,31 +18,36 @@
//! ```
//!
use std::marker::PhantomData;
use rand::prelude::*;
use crate::BuilderData;
use crate::MapFilter;
use crate::geometry::Rect;
use crate::random::Rng;
use crate::Map;
pub struct BspRooms {
pub struct BspRooms<D: BuilderData> {
max_split: usize,
phantom: PhantomData<D>,
}
impl MapFilter for BspRooms {
fn modify_map(&self, rng: &mut StdRng, map: &Map) -> Map {
impl<D: BuilderData> MapFilter<D> for BspRooms<D> {
fn modify_map(&self, rng: &mut StdRng, map: &Map<D>) -> Map<D> {
self.build_rooms(map, rng)
}
}
impl BspRooms {
pub fn new() -> Box<BspRooms> {
impl<D: BuilderData> BspRooms<D> {
pub fn new() -> Box<BspRooms<D>> {
Box::new(BspRooms {
max_split: 240,
phantom: PhantomData,
})
}
fn build_rooms(&self, map: &Map, rng : &mut StdRng) -> Map {
fn build_rooms(&self, map: &Map<D>, rng : &mut StdRng) -> Map<D> {
let mut new_map = map.clone();
let mut rects: Vec<Rect> = Vec::new();
// Start with a single map-sized rectangle
@ -102,7 +107,7 @@ impl BspRooms {
result
}
fn is_possible(&self, rect: Rect, map: &Map) -> bool {
fn is_possible(&self, rect: Rect, map: &Map<D>) -> bool {
let mut expanded = rect;
expanded.x1 -= 2;
expanded.x2 += 2;
@ -139,12 +144,12 @@ impl BspRooms {
#[cfg(test)]
mod tests {
use super::*;
use crate::map::Map;
use crate::map::{Map, NoData};
#[test]
fn no_corridors_on_borders() {
let mut rng = StdRng::seed_from_u64(907647352);
let gen = BspRooms::new();
let gen = BspRooms::<NoData>::new();
let map = gen.modify_map(&mut rng, &Map::new(80, 50));
for i in 0..80 {
assert!(map.at(i, 0).is_blocked());

View File

@ -9,11 +9,11 @@
//! Example usage:
//! ```
//! use rand::prelude::*;
//! use mapgen::{Map, MapFilter};
//! use mapgen::{Map, MapFilter, NoData};
//! use mapgen::filter::CellularAutomata;
//!
//! let mut rng = StdRng::seed_from_u64(100);
//! let gen = CellularAutomata::new();
//! let gen = CellularAutomata::<NoData>::new();
//! let map = gen.modify_map(&mut rng, &Map::new(80, 50));
//!
//! assert_eq!(map.width, 80);
@ -21,30 +21,37 @@
//! ```
//!
use std::marker::PhantomData;
use rand::prelude::*;
use crate::BuilderData;
use crate::MapFilter;
use crate::{Map, Tile};
/// Map filter
pub struct CellularAutomata {
pub struct CellularAutomata<D: BuilderData> {
num_iteraction: u32,
phantom: PhantomData<D>,
}
impl MapFilter for CellularAutomata {
fn modify_map(&self, _rng: &mut StdRng, map: &Map) -> Map {
impl<D: BuilderData> MapFilter<D> for CellularAutomata<D> {
fn modify_map(&self, _rng: &mut StdRng, map: &Map<D>) -> Map<D> {
self.build(map)
}
}
impl CellularAutomata {
impl<D: BuilderData> CellularAutomata<D> {
/// Create generator which will create map with the given dimension.
pub fn new() -> Box<CellularAutomata> {
Box::new(CellularAutomata { num_iteraction: 15})
pub fn new() -> Box<CellularAutomata<D>> {
Box::new(CellularAutomata {
num_iteraction: 15,
phantom: PhantomData,
})
}
/// Generate map
fn build(&self, map: &Map) -> Map {
fn build(&self, map: &Map<D>) -> Map<D> {
let mut new_map = map.clone();
for _ in 0..self.num_iteraction {
new_map = apply_iteration(&new_map);
@ -55,7 +62,7 @@ impl CellularAutomata {
}
fn apply_iteration(map: &Map) -> Map {
fn apply_iteration<D: BuilderData>(map: &Map<D>) -> Map<D> {
let mut new_map = map.clone();
for y in 1..map.height-1 {
@ -85,11 +92,13 @@ fn apply_iteration(map: &Map) -> Map {
/// ------------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use crate::map::NoData;
use super::*;
#[test]
fn test_iteration_wal() {
let map = Map::new(3, 3);
let map = Map::<NoData>::new(3, 3);
let new_map = apply_iteration(&map);
assert!(new_map.at(1, 1).is_blocked());
}
@ -97,7 +106,7 @@ mod tests {
#[test]
fn test_iteration_floor() {
let mut map = Map::new(3, 3);
let mut map = Map::<NoData>::new(3, 3);
for i in 0..3 {
for j in 0..2 {
map.set_tile(i, j, Tile::floor());

View File

@ -4,28 +4,34 @@
//! It will add wall on every tile which is not accessible from the starting point.
//!
use std::marker::PhantomData;
use rand::prelude::StdRng;
use crate::MapFilter;
use crate::{Map, Tile};
use crate::{BuilderData, Map, Tile};
use crate::dijkstra::DijkstraMap;
/// Remove unreachable areas from the map.
pub struct CullUnreachable {}
pub struct CullUnreachable<D: BuilderData> {
phantom: PhantomData<D>,
}
impl MapFilter for CullUnreachable {
fn modify_map(&self, _: &mut StdRng, map: &Map) -> Map {
impl<D: BuilderData> MapFilter<D> for CullUnreachable<D> {
fn modify_map(&self, _: &mut StdRng, map: &Map<D>) -> Map<D> {
self.build(map)
}
}
impl CullUnreachable {
impl<D: BuilderData> CullUnreachable<D> {
#[allow(dead_code)]
pub fn new() -> Box<CullUnreachable> {
Box::new(CullUnreachable{})
pub fn new() -> Box<CullUnreachable<D>> {
Box::new(CullUnreachable {
phantom: PhantomData,
})
}
fn build(&self, map: &Map) -> Map {
fn build(&self, map: &Map<D>) -> Map<D> {
let mut new_map = map.clone();
let dijkstra_map = DijkstraMap::new(map);
@ -51,7 +57,7 @@ mod tests {
use super::*;
use super::MapFilter;
use crate::geometry::Point;
use crate::map::Map;
use crate::map::{Map, NoData};
#[test]
fn test_culling() {
@ -60,14 +66,14 @@ mod tests {
# # #
##########
";
let mut map = Map::from_string(map_str);
let mut map = Map::<NoData>::from_string(map_str);
map.starting_point = Some(Point::new(9, 1));
let expected_map_str = "
##########
#### #
##########
";
let expected_map = Map::from_string(expected_map_str);
let expected_map = Map::<NoData>::from_string(expected_map_str);
let modifier = CullUnreachable::new();

View File

@ -5,7 +5,9 @@
//!
use std::f32;
use std::marker::PhantomData;
use rand::prelude::StdRng;
use crate::BuilderData;
use crate::geometry::Point;
use crate::MapFilter;
use crate::Map;
@ -13,21 +15,25 @@ use crate::dijkstra::DijkstraMap;
/// Add exist position to the map based on the distance from the start point.
pub struct DistantExit {}
pub struct DistantExit<D: BuilderData> {
phantom: PhantomData<D>,
}
impl MapFilter for DistantExit {
fn modify_map(&self, _: &mut StdRng, map: &Map) -> Map {
impl<D: BuilderData> MapFilter<D> for DistantExit<D> {
fn modify_map(&self, _: &mut StdRng, map: &Map<D>) -> Map<D> {
self.build(map)
}
}
impl DistantExit {
impl<D: BuilderData> DistantExit<D> {
#[allow(dead_code)]
pub fn new() -> Box<DistantExit> {
Box::new(DistantExit{})
pub fn new() -> Box<DistantExit<D>> {
Box::new(DistantExit {
phantom: PhantomData,
})
}
fn build(&self, map: &Map) -> Map {
fn build(&self, map: &Map<D>) -> Map<D> {
let mut new_map = map.clone();
let mut best_idx = 0;
@ -55,7 +61,7 @@ mod tests {
use super::*;
use super::MapFilter;
use crate::geometry::Point;
use crate::map::Map;
use crate::map::{Map, NoData};
#[test]
fn test_exit() {
@ -65,7 +71,7 @@ mod tests {
# # #
##########
";
let mut map = Map::from_string(map_str);
let mut map = Map::<NoData>::from_string(map_str);
map.starting_point = Some(Point::new(9, 2));
let modifier = DistantExit::new();

View File

@ -1,11 +1,11 @@
//! Example generator usage:
//! ```
//! use rand::prelude::*;
//! use mapgen::{Map, MapFilter};
//! use mapgen::{Map, MapFilter, NoData};
//! use mapgen::filter::DrunkardsWalk;
//!
//! let mut rng = StdRng::seed_from_u64(100);
//! let gen = DrunkardsWalk::open_area();
//! let gen = DrunkardsWalk::<NoData>::open_area();
//! let map = gen.modify_map(&mut rng, &Map::new(80, 50));
//!
//! assert_eq!(map.width, 80);
@ -13,9 +13,12 @@
//! ```
//!
use std::marker::PhantomData;
use rand::prelude::*;
use crate::MapFilter;
use crate::{
BuilderData,
map::{Map, Symmetry, Tile},
geometry::Point,
random::Rng
@ -25,57 +28,59 @@ use crate::{
#[derive(PartialEq, Copy, Clone)]
pub enum DrunkSpawnMode { StartingPoint, Random }
pub struct DrunkardsWalk {
pub struct DrunkardsWalk<D: BuilderData> {
spawn_mode : DrunkSpawnMode,
drunken_lifetime : i32,
floor_percent: f32,
brush_size: usize,
symmetry: Symmetry
symmetry: Symmetry,
phantom: PhantomData<D>,
}
impl MapFilter for DrunkardsWalk {
fn modify_map(&self, rng: &mut StdRng, map: &Map) -> Map {
impl<D: BuilderData> MapFilter<D> for DrunkardsWalk<D> {
fn modify_map(&self, rng: &mut StdRng, map: &Map<D>) -> Map<D> {
self.build(rng, map)
}
}
impl DrunkardsWalk {
impl<D: BuilderData> DrunkardsWalk<D> {
pub fn new( spawn_mode: DrunkSpawnMode,
drunken_lifetime: i32,
floor_percent: f32,
brush_size: usize,
symmetry: Symmetry) -> Box<DrunkardsWalk>
symmetry: Symmetry) -> Box<DrunkardsWalk<D>>
{
Box::new(DrunkardsWalk{
spawn_mode,
drunken_lifetime,
floor_percent,
brush_size,
symmetry
symmetry,
phantom: PhantomData,
})
}
pub fn open_area() -> Box<DrunkardsWalk> {
pub fn open_area() -> Box<DrunkardsWalk<D>> {
Self::new(DrunkSpawnMode::StartingPoint, 400, 0.5, 1, Symmetry::None)
}
pub fn open_halls() -> Box<DrunkardsWalk> {
pub fn open_halls() -> Box<DrunkardsWalk<D>> {
Self::new(DrunkSpawnMode::Random, 400, 0.5, 1, Symmetry::None)
}
pub fn winding_passages() -> Box<DrunkardsWalk> {
pub fn winding_passages() -> Box<DrunkardsWalk<D>> {
Self::new(DrunkSpawnMode::Random, 400, 0.4, 1, Symmetry::None)
}
pub fn fat_passages() -> Box<DrunkardsWalk> {
pub fn fat_passages() -> Box<DrunkardsWalk<D>> {
Self::new(DrunkSpawnMode::Random, 400, 0.4, 2, Symmetry::None)
}
pub fn fearful_symmetry() -> Box<DrunkardsWalk> {
pub fn fearful_symmetry() -> Box<DrunkardsWalk<D>> {
Self::new(DrunkSpawnMode::Random, 400, 0.4, 1, Symmetry::Both)
}
fn build(&self, rng: &mut StdRng, map: &Map) -> Map {
fn build(&self, rng: &mut StdRng, map: &Map<D>) -> Map<D> {
let mut new_map = map.clone();
// Set a central starting point
let starting_position = Point::new( new_map.width / 2, new_map.height / 2 );

View File

@ -1,11 +1,11 @@
//! Example generator usage:
//! ```
//! use rand::prelude::*;
//! use mapgen::{Map, MapFilter};
//! use mapgen::{Map, MapFilter, NoData};
//! use mapgen::filter::MazeBuilder;
//!
//! let mut rng = StdRng::seed_from_u64(100);
//! let gen = MazeBuilder::new();
//! let gen = MazeBuilder::<NoData>::new();
//! let map = gen.modify_map(&mut rng, &Map::new(80, 50));
//!
//! assert_eq!(map.width, 80);
@ -13,29 +13,35 @@
//! ```
//!
use std::marker::PhantomData;
use rand::prelude::*;
use crate::MapFilter;
use crate::{
map::{Map, Tile},
map::{BuilderData, Map, Tile},
random::Rng
};
pub struct MazeBuilder {}
pub struct MazeBuilder<D: BuilderData> {
phantom: PhantomData<D>,
}
impl MapFilter for MazeBuilder {
fn modify_map(&self, rng: &mut StdRng, map: &Map) -> Map {
impl<D: BuilderData> MapFilter<D> for MazeBuilder<D> {
fn modify_map(&self, rng: &mut StdRng, map: &Map<D>) -> Map<D> {
self.build(rng, map)
}
}
impl MazeBuilder {
pub fn new() -> Box<MazeBuilder> {
Box::new(MazeBuilder{})
impl<D: BuilderData> MazeBuilder<D> {
pub fn new() -> Box<MazeBuilder<D>> {
Box::new(MazeBuilder {
phantom: PhantomData,
})
}
#[allow(clippy::map_entry)]
fn build(&self, rng: &mut StdRng, map: &Map) -> Map {
fn build(&self, rng: &mut StdRng, map: &Map<D>) -> Map<D> {
let mut new_map = map.clone();
let mut maze = Grid::new((map.width as i32/ 2)-2, (map.height as i32/ 2)-2, rng);
maze.generate_maze(&mut new_map);
@ -91,24 +97,26 @@ impl Cell {
}
}
struct Grid<'a> {
struct Grid<'a, D: BuilderData> {
width: i32,
height: i32,
cells: Vec<Cell>,
backtrace: Vec<usize>,
current: usize,
rng : &'a mut StdRng
rng : &'a mut StdRng,
phantom: PhantomData<D>,
}
impl<'a> Grid<'a> {
fn new(width: i32, height:i32, rng: &mut StdRng) -> Grid {
impl<'a, D: BuilderData> Grid<'a, D> {
fn new(width: i32, height:i32, rng: &mut StdRng) -> Grid<D> {
let mut grid = Grid{
width,
height,
cells: Vec::new(),
backtrace: Vec::new(),
current: 0,
rng
rng,
phantom: PhantomData,
};
for row in 0..height {
@ -162,7 +170,7 @@ impl<'a> Grid<'a> {
None
}
fn generate_maze(&mut self, map: &mut Map) {
fn generate_maze(&mut self, map: &mut Map<D>) {
let mut i = 0;
loop {
self.cells[self.current].visited = true;
@ -199,7 +207,7 @@ impl<'a> Grid<'a> {
}
}
fn copy_to_map(&self, map: &mut Map) {
fn copy_to_map(&self, map: &mut Map<D>) {
// Clear the map
for i in map.tiles.iter_mut() { *i = Tile::wall(); }

View File

@ -4,11 +4,11 @@
//! Example usage:
//! ```
//! use rand::prelude::*;
//! use mapgen::{Map, MapFilter};
//! use mapgen::{Map, MapFilter, NoData};
//! use mapgen::filter::NoiseGenerator;
//!
//! let mut rng = StdRng::seed_from_u64(100);
//! let gen = NoiseGenerator::uniform();
//! let gen = NoiseGenerator::<NoData>::uniform();
//! let map = gen.modify_map(&mut rng, &Map::new(80, 50));
//!
//! assert_eq!(map.width, 80);
@ -16,39 +16,44 @@
//! ```
//!
use std::marker::PhantomData;
use rand::prelude::*;
use crate::MapFilter;
use crate::{Map, Tile};
use crate::{BuilderData, Map, Tile};
/// Map noise generator
pub struct NoiseGenerator {
pub struct NoiseGenerator<D: BuilderData> {
prob: f32,
phantom: PhantomData<D>,
}
impl MapFilter for NoiseGenerator {
fn modify_map(&self, rng: &mut StdRng, map: &Map) -> Map {
impl<D: BuilderData> MapFilter<D> for NoiseGenerator<D> {
fn modify_map(&self, rng: &mut StdRng, map: &Map<D>) -> Map<D> {
self.build(map, rng)
}
}
impl NoiseGenerator {
impl<D: BuilderData> NoiseGenerator<D> {
/// Create noise with custom probability
pub fn new(prob: f32) -> Box<NoiseGenerator> {
pub fn new(prob: f32) -> Box<NoiseGenerator<D>> {
Box::new(NoiseGenerator {
prob,
phantom: PhantomData,
})
}
/// Create uniform noise (Probablity 0.5)
pub fn uniform() -> Box<NoiseGenerator> {
pub fn uniform() -> Box<NoiseGenerator<D>> {
Box::new(NoiseGenerator {
prob: 0.5,
phantom: PhantomData,
})
}
/// Generate map
fn build(&self, map: &Map, rng: &mut StdRng) -> Map {
fn build(&self, map: &Map<D>, rng: &mut StdRng) -> Map<D> {
let mut new_map = map.clone();
let p = (self.prob * 100.0) as u32;
for y in 1..new_map.height-1 {

View File

@ -1,26 +1,32 @@
//! Connect nearest rooms on the map with corridors
//!
use rand::prelude::StdRng;
use crate::BuilderData;
use crate::MapFilter;
use crate::Map;
use std::collections::HashSet;
use std::marker::PhantomData;
pub struct NearestCorridors {}
pub struct NearestCorridors<D: BuilderData> {
phantom: PhantomData<D>,
}
impl MapFilter for NearestCorridors {
fn modify_map(&self, _: &mut StdRng, map: &Map) -> Map {
impl<D: BuilderData> MapFilter<D> for NearestCorridors<D> {
fn modify_map(&self, _: &mut StdRng, map: &Map<D>) -> Map<D> {
self.corridors(map)
}
}
impl NearestCorridors {
impl<D: BuilderData> NearestCorridors<D> {
pub fn new() -> Box<NearestCorridors> {
Box::new(NearestCorridors{})
pub fn new() -> Box<NearestCorridors<D>> {
Box::new(NearestCorridors {
phantom: PhantomData,
})
}
fn corridors(&self, map: &Map) -> Map {
fn corridors(&self, map: &Map<D>) -> Map<D> {
let mut new_map = map.clone();
let mut connected : HashSet<usize> = HashSet::new();

View File

@ -6,11 +6,11 @@
//! Example generator usage:
//! ```
//! use rand::prelude::*;
//! use mapgen::{MapFilter, Map};
//! use mapgen::{MapFilter, Map, NoData};
//! use mapgen::filter::SimpleRooms;
//!
//! let mut rng = StdRng::seed_from_u64(100);
//! let gen = SimpleRooms::new();
//! let gen = SimpleRooms::<NoData>::new();
//! let map = gen.modify_map(&mut rng, &Map::new(80, 50));
//!
//! assert_eq!(map.width, 80);
@ -18,36 +18,41 @@
//! ```
//!
use std::marker::PhantomData;
use rand::prelude::*;
use crate::BuilderData;
use crate::MapFilter;
use crate::geometry::Rect;
use crate::random::Rng;
use crate::Map;
pub struct SimpleRooms {
pub struct SimpleRooms<D: BuilderData> {
max_rooms: usize,
min_room_size: usize,
max_room_size: usize,
phantom: PhantomData<D>,
}
impl MapFilter for SimpleRooms {
fn modify_map(&self, rng: &mut StdRng, map: &Map) -> Map {
impl<D: BuilderData> MapFilter<D> for SimpleRooms<D> {
fn modify_map(&self, rng: &mut StdRng, map: &Map<D>) -> Map<D> {
self.build_rooms(map, rng)
}
}
impl SimpleRooms {
pub fn new() -> Box<SimpleRooms> {
impl<D: BuilderData> SimpleRooms<D> {
pub fn new() -> Box<SimpleRooms<D>> {
Box::new(SimpleRooms{
max_rooms: 30,
min_room_size: 6,
max_room_size: 10
max_room_size: 10,
phantom: PhantomData,
})
}
fn build_rooms(&self, map: &Map, rng : &mut StdRng) -> Map {
fn build_rooms(&self, map: &Map<D>, rng : &mut StdRng) -> Map<D> {
let mut new_map = map.clone();
// Create room dimensions

View File

@ -6,12 +6,12 @@
//! Example modifier usage:
//! ```
//! use rand::prelude::*;
//! use mapgen::{MapFilter, Map, Tile};
//! use mapgen::{MapFilter, Map, NoData, Tile};
//! use mapgen::filter::starting_point::{AreaStartingPosition, XStart, YStart};
//! use mapgen::geometry::Point;
//!
//! let mut rng = StdRng::seed_from_u64(100);
//! let mut map = Map::new(80, 50);
//! let mut map = Map::<NoData>::new(80, 50);
//! map.set_tile(10, 10, Tile::floor());
//! let modifier = AreaStartingPosition::new(XStart::LEFT, YStart::TOP);
//! let new_map = modifier.modify_map(&mut rng, &map);
@ -20,7 +20,10 @@
//! ```
//!
use std::marker::PhantomData;
use rand::prelude::StdRng;
use crate::BuilderData;
use crate::MapFilter;
use crate::geometry::Point;
use crate::Map;
@ -33,26 +36,28 @@ pub enum XStart { LEFT, CENTER, RIGHT }
pub enum YStart { TOP, CENTER, BOTTOM }
/// Add starting position to the map
pub struct AreaStartingPosition {
pub struct AreaStartingPosition<D: BuilderData> {
x : XStart,
y : YStart
y : YStart,
phantom: PhantomData<D>,
}
impl MapFilter for AreaStartingPosition {
fn modify_map(&self, _: &mut StdRng, map: &Map) -> Map {
impl<D: BuilderData> MapFilter<D> for AreaStartingPosition<D> {
fn modify_map(&self, _: &mut StdRng, map: &Map<D>) -> Map<D> {
self.build(map)
}
}
impl AreaStartingPosition {
impl<D: BuilderData> AreaStartingPosition<D> {
/// Create new modifier with given region
pub fn new(x : XStart, y : YStart) -> Box<AreaStartingPosition> {
pub fn new(x : XStart, y : YStart) -> Box<AreaStartingPosition<D>> {
Box::new(AreaStartingPosition{
x, y
x, y,
phantom: PhantomData,
})
}
fn build(&self, map : &Map) -> Map {
fn build(&self, map : &Map<D>) -> Map<D> {
let seed_x = match self.x {
XStart::LEFT => 1,
XStart::CENTER => map.width / 2,
@ -101,7 +106,7 @@ mod tests {
use super::*;
use super::MapFilter;
use crate::geometry::Point;
use crate::map::Map;
use crate::map::{Map, NoData};
#[test]
fn test_exit() {
@ -111,7 +116,7 @@ mod tests {
# # # #
##########
";
let mut map = Map::from_string(map_str);
let mut map = Map::<NoData>::from_string(map_str);
map.starting_point = Some(Point::new(9, 2));
let modifier = AreaStartingPosition::new(XStart::CENTER, YStart::TOP);

View File

@ -1,11 +1,11 @@
//! Example generator usage:
//! ```
//! use rand::prelude::*;
//! use mapgen::{Map, MapFilter};
//! use mapgen::{Map, MapFilter, NoData};
//! use mapgen::filter::VoronoiHive;
//!
//! let mut rng = StdRng::seed_from_u64(100);
//! let gen = VoronoiHive::new();
//! let gen = VoronoiHive::<NoData>::new();
//! let map = gen.modify_map(&mut rng, &Map::new(80, 50));
//!
//! assert_eq!(map.width, 80);
@ -13,34 +13,38 @@
//! ```
//!
use std::marker::PhantomData;
use rand::prelude::*;
use crate::MapFilter;
use crate::{
map::{Map, Tile},
map::{BuilderData, Map, Tile},
random::Rng,
geometry::Point,
};
pub struct VoronoiHive {
pub struct VoronoiHive<D: BuilderData> {
n_seeds: usize,
phantom: PhantomData<D>,
}
impl MapFilter for VoronoiHive {
fn modify_map(&self, rng: &mut StdRng, map: &Map) -> Map {
impl<D: BuilderData> MapFilter<D> for VoronoiHive<D> {
fn modify_map(&self, rng: &mut StdRng, map: &Map<D>) -> Map<D> {
self.build(rng, map)
}
}
impl VoronoiHive {
pub fn new() -> Box<VoronoiHive> {
impl<D: BuilderData> VoronoiHive<D> {
pub fn new() -> Box<VoronoiHive<D>> {
Box::new(VoronoiHive{
n_seeds: 64,
phantom: PhantomData,
})
}
fn build(&self, rng: &mut StdRng, map: &Map) -> Map {
fn build(&self, rng: &mut StdRng, map: &Map<D>) -> Map<D> {
let mut new_map = map.clone();
let seeds = self.generate_seeds(rng, map.width, map.height);

View File

@ -8,7 +8,7 @@
//!
//! Example
//! ```
//! use mapgen::{MapFilter, MapBuilder, Map, Tile};
//! use mapgen::{MapFilter, MapBuilder, Map, NoData, Tile};
//! use mapgen::filter::{
//! NoiseGenerator,
//! CellularAutomata,
@ -16,7 +16,7 @@
//! };
//! use mapgen::geometry::Point;
//!
//! let map = MapBuilder::new(80, 50)
//! let map = MapBuilder::<NoData>::new(80, 50)
//! .with(NoiseGenerator::uniform())
//! .with(CellularAutomata::new())
//! .with(AreaStartingPosition::new(XStart::CENTER, YStart::CENTER))
@ -33,7 +33,7 @@ pub mod geometry;
pub mod map;
pub mod metric;
pub use map::{Map, Symmetry, Tile};
pub use map::{BuilderData, Map, NoData, Symmetry, Tile};
pub use filter::*;
pub (crate) mod dijkstra;
@ -45,20 +45,20 @@ use rand::prelude::*;
/// Trait which should be implemented by map modifier.
/// Modifier takes initiall map and apply changes to it.
pub trait MapFilter {
fn modify_map(&self, rng: &mut StdRng, map: &Map) -> Map;
pub trait MapFilter<D: BuilderData> {
fn modify_map(&self, rng: &mut StdRng, map: &Map<D>) -> Map<D>;
}
/// Used to chain MapBuilder and MapModifiers to create the final map.
pub struct MapBuilder {
pub struct MapBuilder<D> {
width: usize,
height: usize,
modifiers: Vec<Box<dyn MapFilter>>,
modifiers: Vec<Box<dyn MapFilter<D>>>,
}
impl MapBuilder {
impl<D: BuilderData> MapBuilder<D> {
/// Create Map Builder with initial map generator
pub fn new(width: usize, height: usize) -> MapBuilder {
pub fn new(width: usize, height: usize) -> MapBuilder<D> {
MapBuilder {
width,
height,
@ -66,20 +66,20 @@ impl MapBuilder {
}
}
pub fn with(&mut self, modifier : Box<dyn MapFilter>) -> &mut MapBuilder {
pub fn with(&mut self, modifier : Box<dyn MapFilter<D>>) -> &mut MapBuilder<D> {
self.modifiers.push(modifier);
self
}
/// Build map using random number seeded with system time
pub fn build(&mut self) -> Map {
pub fn build(&mut self) -> Map<D> {
let system_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Can't access system time");
let mut rng = StdRng::seed_from_u64(system_time.as_millis() as u64);
self.build_with_rng(&mut rng)
}
/// Build map using provided random number generator
pub fn build_with_rng(&mut self, rng: &mut StdRng) -> Map {
pub fn build_with_rng(&mut self, rng: &mut StdRng) -> Map<D> {
let mut map = Map::new(self.width, self.height);
// Build additional layers in turn
@ -97,6 +97,8 @@ impl MapBuilder {
/// ------------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use crate::map::NoData;
use super::*;
use filter::{
CellularAutomata,
@ -106,7 +108,7 @@ mod tests {
#[test]
fn test_ca_map() {
let map = MapBuilder::new(80, 50)
let map = MapBuilder::<NoData>::new(80, 50)
.with(NoiseGenerator::new(0.55))
.with(CellularAutomata::new())
.with(AreaStartingPosition::new(XStart::CENTER, YStart::CENTER))

View File

@ -21,9 +21,18 @@ pub struct Tile {
pub enum Symmetry { None, Horizontal, Vertical, Both }
/// Arbitrary data associated with each map
pub trait BuilderData: Clone + Default {}
/// No build data
#[derive(Clone, Debug, Default)]
pub struct NoData;
impl BuilderData for NoData {}
/// Map data
#[derive(Default, Clone)]
pub struct Map {
pub struct Map<D> {
pub tiles : Vec<Tile>,
pub width : usize,
pub height : usize,
@ -31,6 +40,7 @@ pub struct Map {
pub exit_point: Option<Point>,
pub rooms: Vec<Rect>,
pub corridors: Vec<Vec<Point>>,
pub data: D,
}
impl Tile {
@ -59,10 +69,10 @@ impl Tile {
}
}
impl Map {
impl<D: BuilderData> Map<D> {
/// Generates an empty map, consisting entirely of solid walls
pub fn new(width: usize, height: usize) -> Map {
pub fn new(width: usize, height: usize) -> Map<D> {
let map_tile_count = width*height;
Map{
tiles : vec![Tile::wall(); map_tile_count],
@ -71,12 +81,13 @@ impl Map {
starting_point: None,
exit_point: None,
rooms: Vec::new(),
corridors: Vec::new()
corridors: Vec::new(),
data: Default::default()
}
}
/// Create map from given string
pub fn from_string(map_string: &str) -> Map {
pub fn from_string(map_string: &str) -> Map<D> {
let lines: Vec<&str> = map_string.split("\n")
.map(|l| l.trim())
.filter(|l| l.len() > 0)
@ -235,7 +246,7 @@ impl Map {
}
}
impl fmt::Display for Map {
impl<D: BuilderData> fmt::Display for Map<D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for y in 0..self.height {
let bytes: Vec<u8> = (0..self.width)
@ -257,7 +268,7 @@ mod tests {
#[test]
fn test_new_map() {
let map = Map::new(10, 10);
let map = Map::<NoData>::new(10, 10);
for i in 0..10 {
for j in 0..10 {
assert!(map.at(i, j).is_blocked);
@ -272,7 +283,7 @@ mod tests {
# #
##########
";
let map = Map::from_string(map_str);
let map = Map::<NoData>::from_string(map_str);
assert_eq!(map.width, 10);
assert_eq!(map.height, 3);
@ -295,7 +306,7 @@ mod tests {
# #
##########
";
let map = Map::from_string(map_str);
let map = Map::<NoData>::from_string(map_str);
let exists = map.get_available_exits(1, 1);
let expected_exists = vec![(2, 1, 1.0), (1, 2, 1.0), (2, 2, 1.45)];
assert_eq!(exists, expected_exists);
@ -303,7 +314,7 @@ mod tests {
#[test]
fn test_create_room() {
let mut map = Map::new(5, 5);
let mut map = Map::<NoData>::new(5, 5);
map.add_room(Rect::new(1, 1, 3, 3));
for x in 0..map.width {
for y in 0..map.height {
@ -323,13 +334,13 @@ mod tests {
# # #
##########
";
let mut map = Map::from_string(map_str);
let mut map = Map::<NoData>::from_string(map_str);
let expected_map_str = "
##########
# #
##########
";
let expected_map = Map::from_string(expected_map_str);
let expected_map = Map::<NoData>::from_string(expected_map_str);
map.add_corridor(Point::new(1, 1), Point::new(8, 1));
@ -344,7 +355,7 @@ mod tests {
# # #
##########
";
let map = Map::from_string(map_str);
let map = Map::<NoData>::from_string(map_str);
let exists = map.get_available_exits(0, 0);
assert_eq!(exists.len(), 1);

View File

@ -4,6 +4,7 @@
//! and the provide generator score as an average.
//!
use super::BuilderData;
use super::map::Map;
use super::dijkstra::DijkstraMap;
@ -11,7 +12,7 @@ use super::dijkstra::DijkstraMap;
/// This metric calculates the percentage of walkable cells (Floor).
/// If this number is very low (like < 10%) then it means that the map
/// is probably to degenerated and shouldn't be used
pub fn density(map: &Map) -> f32 {
pub fn density<D>(map: &Map<D>) -> f32 {
let floor_count = map.tiles.iter()
.filter(|&t| t.is_walkable())
.count();
@ -22,7 +23,7 @@ pub fn density(map: &Map) -> f32 {
/// Calculate the length of the shortes path from the starting point
/// to the exit.
/// If this path is very short, then the map is probably degenerated.
pub fn path_length(map: &Map) -> f32 {
pub fn path_length<D: BuilderData>(map: &Map<D>) -> f32 {
if map.starting_point.is_none() {
return 0.0
}
@ -43,12 +44,12 @@ pub fn path_length(map: &Map) -> f32 {
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::Point;
use crate::{geometry::Point, map::NoData};
#[test]
fn test_density_no_floor() {
let map = Map::new(10, 10);
let map = Map::<NoData>::new(10, 10);
let score = density(&map);
assert_eq!(score, 0.0);
}
@ -60,7 +61,7 @@ mod tests {
# ## #
##########
";
let map = Map::from_string(map_str);
let map = Map::<NoData>::from_string(map_str);
let score = density(&map);
assert_eq!(score, 0.2);
}
@ -72,7 +73,7 @@ mod tests {
# ## #
##########
";
let map = Map::from_string(map_str);
let map = Map::<NoData>::from_string(map_str);
let score = path_length(&map);
assert_eq!(score, 0.0);
}
@ -85,7 +86,7 @@ mod tests {
# #
##########
";
let mut map = Map::from_string(map_str);
let mut map = Map::<NoData>::from_string(map_str);
map.starting_point = Some(Point::new(1,1));
map.exit_point = Some(Point::new(8,1));