diff --git a/src/map_builders/mod.rs b/src/map_builders/mod.rs index 48f655e..ec0c4d8 100644 --- a/src/map_builders/mod.rs +++ b/src/map_builders/mod.rs @@ -6,6 +6,7 @@ mod dla; mod drunkard; mod maze; mod simple_map; +mod voronoi; use crate::{Map, Position}; use bsp_dungeon::BspDungeonBuilder; @@ -17,6 +18,7 @@ use drunkard::DrunkardsWalkBuilder; use maze::MazeBuilder; use simple_map::SimpleMapBuilder; use specs::prelude::*; +use voronoi::VoronoiCellBuilder; pub trait MapBuilder { fn build_map(&mut self); @@ -29,7 +31,7 @@ pub trait MapBuilder { pub fn random_builder(new_depth: i32) -> Box { let mut rng = rltk::RandomNumberGenerator::new(); - match rng.roll_dice(1, 14) { + match rng.roll_dice(1, 16) { 1 => Box::new(BspDungeonBuilder::new(new_depth)), 2 => Box::new(BspInteriorBuilder::new(new_depth)), 3 => Box::new(CellularAutomataBuilder::new(new_depth)), @@ -43,6 +45,8 @@ pub fn random_builder(new_depth: i32) -> Box { 11 => Box::new(DLABuilder::walk_outwards(new_depth)), 12 => Box::new(DLABuilder::central_attractor(new_depth)), 13 => Box::new(DLABuilder::insectoid(new_depth)), + 14 => Box::new(VoronoiCellBuilder::pythagoras(new_depth)), + 15 => Box::new(VoronoiCellBuilder::manhattan(new_depth)), _ => Box::new(SimpleMapBuilder::new(new_depth)), } } diff --git a/src/map_builders/voronoi.rs b/src/map_builders/voronoi.rs new file mode 100644 index 0000000..ca4a35c --- /dev/null +++ b/src/map_builders/voronoi.rs @@ -0,0 +1,187 @@ +use super::common::{ + generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant, +}; +use super::MapBuilder; +use crate::{components::Position, spawner, Map, TileType, SHOW_MAPGEN_VISUALIZER}; +use rltk::RandomNumberGenerator; +use specs::prelude::*; +use std::collections::HashMap; + +#[derive(PartialEq, Copy, Clone)] +#[allow(dead_code)] +pub enum DistanceAlgorithm { + Pythagoras, + Manhattan, + Chebyshev, +} + +pub struct VoronoiCellBuilder { + map: Map, + starting_position: Position, + depth: i32, + history: Vec, + noise_areas: HashMap>, + n_seeds: usize, + distance_algorithm: DistanceAlgorithm, +} + +impl MapBuilder for VoronoiCellBuilder { + fn build_map(&mut self) { + self.build(); + } + + fn spawn_entities(&mut self, ecs: &mut World) { + for area in self.noise_areas.iter() { + spawner::spawn_region(ecs, area.1, self.depth); + } + } + + fn get_map(&self) -> Map { + self.map.clone() + } + + fn get_starting_position(&self) -> Position { + self.starting_position + } + + fn get_snapshot_history(&self) -> Vec { + self.history.clone() + } + + 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); + } + } +} + +impl VoronoiCellBuilder { + pub fn new(new_depth: i32) -> VoronoiCellBuilder { + VoronoiCellBuilder { + map: Map::new(new_depth), + starting_position: Position::default(), + depth: new_depth, + history: Vec::new(), + noise_areas: HashMap::new(), + n_seeds: 64, + distance_algorithm: DistanceAlgorithm::Pythagoras, + } + } + + pub fn pythagoras(new_depth: i32) -> VoronoiCellBuilder { + VoronoiCellBuilder::new(new_depth) + } + + pub fn manhattan(new_depth: i32) -> VoronoiCellBuilder { + VoronoiCellBuilder { + distance_algorithm: DistanceAlgorithm::Manhattan, + ..VoronoiCellBuilder::new(new_depth) + } + } + + #[allow(clippy::map_entry)] + fn build(&mut self) { + let mut rng = RandomNumberGenerator::new(); + + // Make a Voronoi diagram. We'll do this the hard way to learn about the technique! + let mut voronoi_seeds: Vec<(usize, rltk::Point)> = Vec::new(); + + while voronoi_seeds.len() < self.n_seeds { + let vx = rng.roll_dice(1, self.map.width - 1); + let vy = rng.roll_dice(1, self.map.height - 1); + let vidx = self.map.xy_idx(vx, vy); + let candidate = (vidx, rltk::Point::new(vx, vy)); + if !voronoi_seeds.contains(&candidate) { + voronoi_seeds.push(candidate); + } + } + + let mut voronoi_distance = vec![(0, 0.0f32); self.n_seeds]; + let mut voronoi_membership: Vec = + vec![0; self.map.width as usize * self.map.height as usize]; + for (i, vid) in voronoi_membership.iter_mut().enumerate() { + let x = i as i32 % self.map.width; + let y = i as i32 / self.map.width; + + for (seed, pos) in voronoi_seeds.iter().enumerate() { + let distance; + match self.distance_algorithm { + DistanceAlgorithm::Pythagoras => { + distance = rltk::DistanceAlg::PythagorasSquared + .distance2d(rltk::Point::new(x, y), pos.1); + } + DistanceAlgorithm::Manhattan => { + distance = + rltk::DistanceAlg::Manhattan.distance2d(rltk::Point::new(x, y), pos.1); + } + DistanceAlgorithm::Chebyshev => { + distance = + rltk::DistanceAlg::Chebyshev.distance2d(rltk::Point::new(x, y), pos.1); + } + } + voronoi_distance[seed] = (seed, distance); + } + + voronoi_distance.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap()); + + *vid = voronoi_distance[0].0 as i32; + } + + for y in 1..self.map.height - 1 { + for x in 1..self.map.width - 1 { + let mut neighbors = 0; + let my_idx = self.map.xy_idx(x, y); + let my_seed = voronoi_membership[my_idx]; + + if voronoi_membership[self.map.xy_idx(x - 1, y)] != my_seed { + neighbors += 1; + } + if voronoi_membership[self.map.xy_idx(x + 1, y)] != my_seed { + neighbors += 1; + } + if voronoi_membership[self.map.xy_idx(x, y - 1)] != my_seed { + neighbors += 1; + } + if voronoi_membership[self.map.xy_idx(x, y + 1)] != my_seed { + neighbors += 1; + } + + if neighbors < 2 { + self.map.tiles[my_idx] = TileType::Floor; + } + } + self.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); + } + self.take_snapshot(); + + // 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); + } +}