Refactor voronoi map builder to use Rust builder pattern

This commit is contained in:
Timothy Warren 2021-12-15 10:08:05 -05:00
parent 3f692846f8
commit a6d37bf0dc
8 changed files with 321 additions and 136 deletions

View File

@ -1,7 +1,10 @@
mod area_starting_points;
mod bsp_dungeon; mod bsp_dungeon;
mod bsp_interior; mod bsp_interior;
mod cellular_automata; mod cellular_automata;
mod common; mod common;
mod cull_unreachable;
mod distant_exit;
mod dla; mod dla;
mod drunkard; mod drunkard;
mod maze; mod maze;
@ -11,14 +14,17 @@ mod room_based_stairs;
mod room_based_starting_position; mod room_based_starting_position;
mod simple_map; mod simple_map;
mod voronoi; mod voronoi;
mod voronoi_spawning;
mod waveform_collapse; mod waveform_collapse;
use crate::{spawner, Map, Position, Rect, SHOW_MAPGEN_VISUALIZER};
use ::rltk::RandomNumberGenerator; use ::rltk::RandomNumberGenerator;
use area_starting_points::{AreaStartingPosition, XStart, YStart};
use bsp_dungeon::BspDungeonBuilder; use bsp_dungeon::BspDungeonBuilder;
use bsp_interior::BspInteriorBuilder; use bsp_interior::BspInteriorBuilder;
use cellular_automata::CellularAutomataBuilder; use cellular_automata::CellularAutomataBuilder;
use common::*; use common::*;
use cull_unreachable::CullUnreachable;
use distant_exit::DistantExit;
use dla::DLABuilder; use dla::DLABuilder;
use drunkard::DrunkardsWalkBuilder; use drunkard::DrunkardsWalkBuilder;
use maze::MazeBuilder; use maze::MazeBuilder;
@ -29,8 +35,11 @@ use room_based_starting_position::RoomBasedStartingPosition;
use simple_map::SimpleMapBuilder; use simple_map::SimpleMapBuilder;
use specs::prelude::*; use specs::prelude::*;
use voronoi::VoronoiCellBuilder; use voronoi::VoronoiCellBuilder;
use voronoi_spawning::VoronoiSpawning;
use waveform_collapse::WaveformCollapseBuilder; use waveform_collapse::WaveformCollapseBuilder;
use crate::{spawner, Map, Position, Rect, SHOW_MAPGEN_VISUALIZER};
pub struct BuilderMap { pub struct BuilderMap {
pub spawn_list: Vec<(usize, String)>, pub spawn_list: Vec<(usize, String)>,
pub map: Map, pub map: Map,
@ -40,6 +49,16 @@ pub struct BuilderMap {
} }
impl BuilderMap { impl BuilderMap {
fn new(new_depth: i32) -> BuilderMap {
BuilderMap {
spawn_list: Vec::new(),
map: Map::new(new_depth),
starting_position: None,
rooms: None,
history: Vec::new(),
}
}
fn take_snapshot(&mut self) { fn take_snapshot(&mut self) {
if SHOW_MAPGEN_VISUALIZER { if SHOW_MAPGEN_VISUALIZER {
let mut snapshot = self.map.clone(); let mut snapshot = self.map.clone();
@ -62,13 +81,7 @@ impl BuilderChain {
BuilderChain { BuilderChain {
starter: None, starter: None,
builders: Vec::new(), builders: Vec::new(),
build_data: BuilderMap { build_data: BuilderMap::new(new_depth),
spawn_list: Vec::new(),
map: Map::new(new_depth),
starting_position: None,
rooms: None,
history: Vec::new(),
},
} }
} }
@ -155,10 +168,11 @@ pub trait MapBuilder {
pub fn random_builder(new_depth: i32, rng: &mut RandomNumberGenerator) -> BuilderChain { pub fn random_builder(new_depth: i32, rng: &mut RandomNumberGenerator) -> BuilderChain {
let mut builder = BuilderChain::new(new_depth); let mut builder = BuilderChain::new(new_depth);
builder builder
.start_with(SimpleMapBuilder::new()) .start_with(CellularAutomataBuilder::new())
.with(RoomBasedSpawner::new()) .with(AreaStartingPosition::new(XStart::Center, YStart::Center))
.with(RoomBasedStartingPosition::new()) .with(CullUnreachable::new())
.with(RoomBasedStairs::new()); .with(VoronoiSpawning::new())
.with(DistantExit::new());
builder builder
} }

View File

@ -0,0 +1,79 @@
use rltk::RandomNumberGenerator;
use crate::map_builders::{BuilderMap, MetaMapBuilder};
use crate::{Position, TileType};
#[allow(dead_code)]
pub enum XStart {
Left,
Center,
Right,
}
#[allow(dead_code)]
pub enum YStart {
Top,
Center,
Bottom,
}
pub struct AreaStartingPosition {
x: XStart,
y: YStart,
}
impl MetaMapBuilder for AreaStartingPosition {
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
self.build(rng, build_data);
}
}
impl AreaStartingPosition {
#[allow(dead_code)]
pub fn new(x: XStart, y: YStart) -> Box<AreaStartingPosition> {
Box::new(AreaStartingPosition { x, y })
}
fn build(&mut self, _rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
let seed_x = match self.x {
XStart::Left => 1,
XStart::Center => build_data.map.width / 2,
XStart::Right => build_data.map.width - 2,
};
let seed_y = match self.y {
YStart::Top => 1,
YStart::Center => build_data.map.height / 2,
YStart::Bottom => build_data.map.height - 2,
};
let mut available_floors: Vec<(usize, f32)> = Vec::new();
for (idx, tiletype) in build_data.map.tiles.iter().enumerate() {
if *tiletype == TileType::Floor {
available_floors.push((
idx,
rltk::DistanceAlg::PythagorasSquared.distance2d(
rltk::Point::new(
idx as i32 % build_data.map.width,
idx as i32 / build_data.map.width,
),
rltk::Point::new(seed_x, seed_y),
),
));
}
}
if available_floors.is_empty() {
panic!("No valid floors to start on");
}
available_floors.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
let start_x = available_floors[0].0 as i32 % build_data.map.width;
let start_y = available_floors[0].0 as i32 / build_data.map.width;
build_data.starting_position = Some(Position {
x: start_x,
y: start_y,
});
}
}

View File

@ -2,8 +2,8 @@ use rltk::RandomNumberGenerator;
use crate::components::Position; use crate::components::Position;
use crate::map_builders::common::{apply_room_to_map, draw_corridor}; use crate::map_builders::common::{apply_room_to_map, draw_corridor};
use crate::{spawner, Map, Rect, TileType, SHOW_MAPGEN_VISUALIZER};
use crate::map_builders::{BuilderMap, InitialMapBuilder}; use crate::map_builders::{BuilderMap, InitialMapBuilder};
use crate::{spawner, Map, Rect, TileType, SHOW_MAPGEN_VISUALIZER};
pub struct BspDungeonBuilder { pub struct BspDungeonBuilder {
rects: Vec<Rect>, rects: Vec<Rect>,
@ -18,9 +18,7 @@ impl InitialMapBuilder for BspDungeonBuilder {
impl BspDungeonBuilder { impl BspDungeonBuilder {
pub fn new() -> Box<BspDungeonBuilder> { pub fn new() -> Box<BspDungeonBuilder> {
Box::new(BspDungeonBuilder { Box::new(BspDungeonBuilder { rects: Vec::new() })
rects: Vec::new(),
})
} }
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
@ -28,8 +26,12 @@ impl BspDungeonBuilder {
self.rects.clear(); self.rects.clear();
// Start with a single map-sized rectangle // Start with a single map-sized rectangle
self.rects self.rects.push(Rect::new(
.push(Rect::new(2, 2, build_data.map.width - 5, build_data.map.height - 5)); 2,
2,
build_data.map.width - 5,
build_data.map.height - 5,
));
let first_room = self.rects[0]; let first_room = self.rects[0];
self.add_subrects(first_room); // Divide the first room self.add_subrects(first_room); // Divide the first room

View File

@ -2,9 +2,9 @@ use rltk::RandomNumberGenerator;
use super::MapBuilder; use super::MapBuilder;
use crate::components::Position; use crate::components::Position;
use crate::{spawner, Map, Rect, TileType, SHOW_MAPGEN_VISUALIZER};
use crate::map_builders::{BuilderMap, InitialMapBuilder};
use crate::map_builders::common::draw_corridor; use crate::map_builders::common::draw_corridor;
use crate::map_builders::{BuilderMap, InitialMapBuilder};
use crate::{spawner, Map, Rect, TileType, SHOW_MAPGEN_VISUALIZER};
const MIN_ROOM_SIZE: i32 = 8; const MIN_ROOM_SIZE: i32 = 8;
@ -21,9 +21,7 @@ impl InitialMapBuilder for BspInteriorBuilder {
impl BspInteriorBuilder { impl BspInteriorBuilder {
pub fn new() -> Box<BspInteriorBuilder> { pub fn new() -> Box<BspInteriorBuilder> {
Box::new(BspInteriorBuilder { Box::new(BspInteriorBuilder { rects: Vec::new() })
rects: Vec::new(),
})
} }
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
@ -31,8 +29,12 @@ impl BspInteriorBuilder {
self.rects.clear(); self.rects.clear();
// Start with a single map-sized rectangle // Start with a single map-sized rectangle
self.rects self.rects.push(Rect::new(
.push(Rect::new(1, 1, build_data.map.width - 2, build_data.map.height - 2)); 1,
1,
build_data.map.width - 2,
build_data.map.height - 2,
));
let first_room = self.rects[0]; let first_room = self.rects[0];
self.add_subrects(first_room, rng); // Divide the first room self.add_subrects(first_room, rng); // Divide the first room
@ -45,7 +47,9 @@ impl BspInteriorBuilder {
for y in room.y1..room.y2 { for y in room.y1..room.y2 {
for x in room.x1..room.x2 { for x in room.x1..room.x2 {
let idx = build_data.map.xy_idx(x, y); let idx = build_data.map.xy_idx(x, y);
if idx > 0 && idx < ((build_data.map.width * build_data.map.height) - 1) as usize { if idx > 0
&& idx < ((build_data.map.width * build_data.map.height) - 1) as usize
{
build_data.map.tiles[idx] = TileType::Floor; build_data.map.tiles[idx] = TileType::Floor;
} }
} }

View File

@ -2,117 +2,83 @@ use std::collections::HashMap;
use rltk::RandomNumberGenerator; use rltk::RandomNumberGenerator;
use super::common::{ use crate::components::Position;
use crate::map_builders::common::{
generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant, generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant,
}; };
use super::MapBuilder; use crate::map_builders::{BuilderMap, InitialMapBuilder};
use crate::components::Position;
use crate::{spawner, Map, TileType, SHOW_MAPGEN_VISUALIZER}; use crate::{spawner, Map, TileType, SHOW_MAPGEN_VISUALIZER};
pub struct CellularAutomataBuilder { pub struct CellularAutomataBuilder {}
map: Map,
starting_position: Position,
depth: i32,
history: Vec<Map>,
noise_areas: HashMap<i32, Vec<usize>>,
spawn_list: Vec<(usize, String)>,
}
impl MapBuilder for CellularAutomataBuilder { impl InitialMapBuilder for CellularAutomataBuilder {
fn get_map(&self) -> Map { #[allow(dead_code)]
self.map.clone() fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
} self.build(rng, build_data);
fn get_starting_position(&self) -> Position {
self.starting_position
}
fn get_snapshot_history(&self) -> Vec<Map> {
self.history.clone()
}
fn build_map(&mut self) {
self.build();
}
fn take_snapshot(&mut self) {
if SHOW_MAPGEN_VISUALIZER {
let mut snapshot = self.map.clone();
for v in snapshot.revealed_tiles.iter_mut() {
*v = true;
}
self.history.push(snapshot);
}
}
fn get_spawn_list(&self) -> &Vec<(usize, String)> {
&self.spawn_list
} }
} }
impl CellularAutomataBuilder { impl CellularAutomataBuilder {
pub fn new(new_depth: i32) -> CellularAutomataBuilder { pub fn new() -> Box<CellularAutomataBuilder> {
CellularAutomataBuilder { Box::new(CellularAutomataBuilder {})
map: Map::new(new_depth),
starting_position: Position::default(),
depth: new_depth,
history: Vec::new(),
noise_areas: HashMap::new(),
spawn_list: Vec::new(),
}
} }
fn build(&mut self) { fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
let mut rng = RandomNumberGenerator::new();
// First we completely randomize the map, setting 55% of it to be floor. // First we completely randomize the map, setting 55% of it to be floor.
for y in 1..self.map.height - 1 { for y in 1..build_data.map.height - 1 {
for x in 1..self.map.width - 1 { for x in 1..build_data.map.width - 1 {
let roll = rng.roll_dice(1, 100); let roll = rng.roll_dice(1, 100);
let idx = self.map.xy_idx(x, y); let idx = build_data.map.xy_idx(x, y);
if roll > 55 { if roll > 55 {
self.map.tiles[idx] = TileType::Floor build_data.map.tiles[idx] = TileType::Floor
} else { } else {
self.map.tiles[idx] = TileType::Wall build_data.map.tiles[idx] = TileType::Wall
} }
} }
} }
self.take_snapshot(); build_data.take_snapshot();
// Now we iteratively apply cellular automata rules // Now we iteratively apply cellular automata rules
for _i in 0..15 { for _i in 0..15 {
let mut newtiles = self.map.tiles.clone(); let mut newtiles = build_data.map.tiles.clone();
for y in 1..self.map.height - 1 { for y in 1..build_data.map.height - 1 {
for x in 1..self.map.width - 1 { for x in 1..build_data.map.width - 1 {
let idx = self.map.xy_idx(x, y); let idx = build_data.map.xy_idx(x, y);
let mut neighbors = 0; let mut neighbors = 0;
if self.map.tiles[idx - 1] == TileType::Wall { if build_data.map.tiles[idx - 1] == TileType::Wall {
neighbors += 1; neighbors += 1;
} }
if self.map.tiles[idx + 1] == TileType::Wall { if build_data.map.tiles[idx + 1] == TileType::Wall {
neighbors += 1; neighbors += 1;
} }
if self.map.tiles[idx - self.map.width as usize] == TileType::Wall { if build_data.map.tiles[idx - build_data.map.width as usize] == TileType::Wall {
neighbors += 1; neighbors += 1;
} }
if self.map.tiles[idx + self.map.width as usize] == TileType::Wall { if build_data.map.tiles[idx + build_data.map.width as usize] == TileType::Wall {
neighbors += 1; neighbors += 1;
} }
if self.map.tiles[idx - (self.map.width as usize - 1)] == TileType::Wall { if build_data.map.tiles[idx - (build_data.map.width as usize - 1)]
== TileType::Wall
{
neighbors += 1; neighbors += 1;
} }
if self.map.tiles[idx - (self.map.width as usize + 1)] == TileType::Wall { if build_data.map.tiles[idx - (build_data.map.width as usize + 1)]
== TileType::Wall
{
neighbors += 1; neighbors += 1;
} }
if self.map.tiles[idx + (self.map.width as usize - 1)] == TileType::Wall { if build_data.map.tiles[idx + (build_data.map.width as usize - 1)]
== TileType::Wall
{
neighbors += 1; neighbors += 1;
} }
if self.map.tiles[idx + (self.map.width as usize + 1)] == TileType::Wall { if build_data.map.tiles[idx + (build_data.map.width as usize + 1)]
== TileType::Wall
{
neighbors += 1; neighbors += 1;
} }
@ -124,45 +90,8 @@ impl CellularAutomataBuilder {
} }
} }
self.map.tiles = newtiles.clone(); build_data.map.tiles = newtiles.clone();
self.take_snapshot(); build_data.take_snapshot();
}
// Find a starting point; start at the middle and walk left until we find an open tile
self.starting_position = Position {
x: self.map.width / 2,
y: self.map.height / 2,
};
let mut start_idx = self
.map
.xy_idx(self.starting_position.x, self.starting_position.y);
while self.map.tiles[start_idx] != TileType::Floor {
self.starting_position.x -= 1;
start_idx = self
.map
.xy_idx(self.starting_position.x, self.starting_position.y)
}
// Find all tiles we can reach from the starting point
let exit_tile = remove_unreachable_areas_returning_most_distant(&mut self.map, start_idx);
self.take_snapshot();
// Place the stairs
self.map.tiles[exit_tile] = TileType::DownStairs;
self.take_snapshot();
// Now we build a noise map for use in spawning entities later
self.noise_areas = generate_voronoi_spawn_regions(&self.map, &mut rng);
// Spawn the entities
for area in self.noise_areas.iter() {
spawner::spawn_region(
&self.map,
&mut rng,
area.1,
self.depth,
&mut self.spawn_list,
);
} }
} }
} }

View File

@ -0,0 +1,46 @@
use rltk::RandomNumberGenerator;
use crate::map_builders::{BuilderMap, MetaMapBuilder};
use crate::TileType;
pub struct CullUnreachable {}
impl MetaMapBuilder for CullUnreachable {
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
self.build(rng, build_data);
}
}
impl CullUnreachable {
#[allow(dead_code)]
pub fn new() -> Box<CullUnreachable> {
Box::new(CullUnreachable {})
}
fn build(&mut self, _rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
let starting_pos = build_data.starting_position.as_ref().unwrap().clone();
let start_idx = build_data.map.xy_idx(starting_pos.x, starting_pos.y);
build_data.map.populate_blocked();
let map_starts: Vec<usize> = vec![start_idx];
let dijkstra_map = rltk::DijkstraMap::new(
build_data.map.width as usize,
build_data.map.height as usize,
&map_starts,
&build_data.map,
1000.0,
);
for (i, tile) in build_data.map.tiles.iter_mut().enumerate() {
if *tile == TileType::Floor {
let distance_to_start = dijkstra_map.map[i];
// We can't go to this tile - so we'll make it a wall
if distance_to_start == f32::MAX {
*tile = TileType::Wall;
}
}
}
}
}

View File

@ -0,0 +1,55 @@
use rltk::RandomNumberGenerator;
use crate::map_builders::{BuilderMap, MetaMapBuilder};
use crate::TileType;
pub struct DistantExit {}
impl MetaMapBuilder for DistantExit {
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
self.build(rng, build_data);
}
}
impl DistantExit {
#[allow(dead_code)]
pub fn new() -> Box<DistantExit> {
Box::new(DistantExit {})
}
fn build(&mut self, _rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
let starting_pos = build_data.starting_position.as_ref().unwrap().clone();
let start_idx = build_data.map.xy_idx(starting_pos.x, starting_pos.y);
build_data.map.populate_blocked();
let map_starts: Vec<usize> = vec![start_idx];
let dijkstra_map = rltk::DijkstraMap::new(
build_data.map.width as usize,
build_data.map.height as usize,
&map_starts,
&build_data.map,
1000.0,
);
let mut exit_tile = (0, 0.0f32);
for (i, tile) in build_data.map.tiles.iter_mut().enumerate() {
if *tile == TileType::Floor {
let distance_to_start = dijkstra_map.map[i];
// We can't get to this tile - so we'll make it a wall
if distance_to_start != f32::MAX {
// If it is further away than our current exit candidate, move the exit
if distance_to_start > exit_tile.1 {
exit_tile.0 = i;
exit_tile.1 = distance_to_start;
}
}
}
}
// Place a staircase
let stairs_idx = exit_tile.0;
build_data.map.tiles[stairs_idx] = TileType::DownStairs;
build_data.take_snapshot();
}
}

View File

@ -0,0 +1,56 @@
use std::collections::HashMap;
use rltk::RandomNumberGenerator;
use crate::map_builders::{BuilderMap, MetaMapBuilder};
use crate::{spawner, TileType};
pub struct VoronoiSpawning {}
impl MetaMapBuilder for VoronoiSpawning {
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
self.build(rng, build_data);
}
}
impl VoronoiSpawning {
#[allow(dead_code)]
pub fn new() -> Box<VoronoiSpawning> {
Box::new(VoronoiSpawning {})
}
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
let mut noise_areas: HashMap<i32, Vec<usize>> = HashMap::new();
let mut noise = rltk::FastNoise::seeded(rng.roll_dice(1, 65536) as u64);
noise.set_noise_type(rltk::NoiseType::Cellular);
noise.set_frequency(0.08);
noise.set_cellular_distance_function(rltk::CellularDistanceFunction::Manhattan);
for y in 1..build_data.map.height - 1 {
for x in 1..build_data.map.width - 1 {
let idx = build_data.map.xy_idx(x, y);
if build_data.map.tiles[idx] == TileType::Floor {
let cell_value_f = noise.get_noise(x as f32, y as f32) * 10240.0;
let cell_value = cell_value_f as i32;
if noise_areas.contains_key(&cell_value) {
noise_areas.get_mut(&cell_value).unwrap().push(idx);
} else {
noise_areas.insert(cell_value, vec![idx]);
}
}
}
}
// Spawn the entities
for area in noise_areas.iter() {
spawner::spawn_region(
&build_data.map,
rng,
area.1,
build_data.map.depth,
&mut build_data.spawn_list,
);
}
}
}