From 09bafa4d1fc64984e2ecf5c56875660cc9a9c46e Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Wed, 19 Jul 2023 20:15:26 +0100 Subject: [PATCH] diffusion-limited aggregation, symmetry, brushes --- src/map_builders/common.rs | 68 ++++++++++ src/map_builders/dla.rs | 241 +++++++++++++++++++++++++++++++++++ src/map_builders/drunkard.rs | 58 +++++++-- src/map_builders/mod.rs | 15 ++- 4 files changed, 365 insertions(+), 17 deletions(-) create mode 100644 src/map_builders/dla.rs diff --git a/src/map_builders/common.rs b/src/map_builders/common.rs index 5656765..50b277d 100644 --- a/src/map_builders/common.rs +++ b/src/map_builders/common.rs @@ -77,3 +77,71 @@ pub fn generate_voronoi_spawn_regions(map: &Map, rng: &mut rltk::RandomNumberGen } return noise_areas; } + +#[derive(PartialEq, Copy, Clone)] +pub enum Symmetry { + None, + Horizontal, + Vertical, + Both, +} + +pub fn paint(map: &mut Map, mode: Symmetry, brush_size: i32, x: i32, y: i32) { + match mode { + Symmetry::None => apply_paint(map, brush_size, x, y), + Symmetry::Horizontal => { + let centre_x = map.width / 2; + if x == centre_x { + apply_paint(map, brush_size, x, y); + } else { + let dist_x = i32::abs(centre_x - x); + apply_paint(map, brush_size, centre_x + dist_x, y); + apply_paint(map, brush_size, centre_x - dist_x, y); + } + } + Symmetry::Vertical => { + let centre_y = map.height / 2; + if y == centre_y { + apply_paint(map, brush_size, x, y); + } else { + let dist_y = i32::abs(centre_y - y); + apply_paint(map, brush_size, x, centre_y + dist_y); + apply_paint(map, brush_size, x, centre_y - dist_y); + } + } + Symmetry::Both => { + let centre_x = map.width / 2; + let centre_y = map.height / 2; + if x == centre_x && y == centre_y { + apply_paint(map, brush_size, x, y); + } else { + let dist_x = i32::abs(centre_x - x); + apply_paint(map, brush_size, centre_x + dist_x, y); + apply_paint(map, brush_size, centre_x - dist_x, y); + let dist_y = i32::abs(centre_y - y); + apply_paint(map, brush_size, x, centre_y + dist_y); + apply_paint(map, brush_size, x, centre_y - dist_y); + } + } + } +} + +fn apply_paint(map: &mut Map, brush_size: i32, x: i32, y: i32) { + match brush_size { + 1 => { + let digger_idx = map.xy_idx(x, y); + map.tiles[digger_idx] = TileType::Floor; + } + _ => { + let half_brush_size = brush_size / 2; + for brush_y in y - half_brush_size..y + half_brush_size { + for brush_x in x - half_brush_size..x + half_brush_size { + if brush_x > 1 && brush_x < map.width - 1 && brush_y > 1 && brush_y < map.height - 1 { + let idx = map.xy_idx(brush_x, brush_y); + map.tiles[idx] = TileType::Floor; + } + } + } + } + } +} diff --git a/src/map_builders/dla.rs b/src/map_builders/dla.rs new file mode 100644 index 0000000..2e61a92 --- /dev/null +++ b/src/map_builders/dla.rs @@ -0,0 +1,241 @@ +use super::{ + common::Symmetry, generate_voronoi_spawn_regions, paint, remove_unreachable_areas_returning_most_distant, spawner, + Map, MapBuilder, Position, TileType, SHOW_MAPGEN, +}; +use rltk::RandomNumberGenerator; +use specs::prelude::*; +use std::collections::HashMap; + +#[derive(PartialEq, Copy, Clone)] +pub enum DLAAlgorithm { + WalkInwards, + WalkOutwards, + CentralAttractor, +} + +pub struct DLABuilder { + map: Map, + starting_position: Position, + depth: i32, + history: Vec, + noise_areas: HashMap>, + algorithm: DLAAlgorithm, + brush_size: i32, + symmetry: Symmetry, + floor_percent: f32, +} + +impl MapBuilder for DLABuilder { + fn build_map(&mut self, rng: &mut RandomNumberGenerator) { + return self.build(rng); + } + fn spawn_entities(&mut self, ecs: &mut World) { + for area in self.noise_areas.iter() { + spawner::spawn_region(ecs, area.1, self.depth); + } + } + // Getters + fn get_map(&mut self) -> Map { + return self.map.clone(); + } + fn get_starting_pos(&mut self) -> Position { + return self.starting_position.clone(); + } + // Mapgen visualisation stuff + fn get_snapshot_history(&self) -> Vec { + return self.history.clone(); + } + fn take_snapshot(&mut self) { + if SHOW_MAPGEN { + let mut snapshot = self.map.clone(); + for v in snapshot.revealed_tiles.iter_mut() { + *v = true; + } + self.history.push(snapshot); + } + } +} + +impl DLABuilder { + pub fn walk_inwards(new_depth: i32) -> DLABuilder { + DLABuilder { + map: Map::new(new_depth), + starting_position: Position { x: 0, y: 0 }, + depth: new_depth, + history: Vec::new(), + noise_areas: HashMap::new(), + algorithm: DLAAlgorithm::WalkInwards, + brush_size: 1, + symmetry: Symmetry::None, + floor_percent: 0.25, + } + } + + pub fn walk_outwards(new_depth: i32) -> DLABuilder { + DLABuilder { + map: Map::new(new_depth), + starting_position: Position { x: 0, y: 0 }, + depth: new_depth, + history: Vec::new(), + noise_areas: HashMap::new(), + algorithm: DLAAlgorithm::WalkOutwards, + brush_size: 2, + symmetry: Symmetry::None, + floor_percent: 0.25, + } + } + + pub fn central_attractor(new_depth: i32) -> DLABuilder { + DLABuilder { + map: Map::new(new_depth), + starting_position: Position { x: 0, y: 0 }, + depth: new_depth, + history: Vec::new(), + noise_areas: HashMap::new(), + algorithm: DLAAlgorithm::CentralAttractor, + brush_size: 2, + symmetry: Symmetry::None, + floor_percent: 0.25, + } + } + + pub fn insectoid(new_depth: i32) -> DLABuilder { + DLABuilder { + map: Map::new(new_depth), + starting_position: Position { x: 0, y: 0 }, + depth: new_depth, + history: Vec::new(), + noise_areas: HashMap::new(), + algorithm: DLAAlgorithm::CentralAttractor, + brush_size: 2, + symmetry: Symmetry::Horizontal, + floor_percent: 0.25, + } + } + + #[allow(clippy::map_entry)] + fn build(&mut self, rng: &mut RandomNumberGenerator) { + // Carve starting seed + self.starting_position = Position { x: self.map.width / 2, y: self.map.height / 2 }; + let start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y); + self.take_snapshot(); + self.map.tiles[start_idx] = TileType::Floor; + self.map.tiles[start_idx - 1] = TileType::Floor; + self.map.tiles[start_idx + 1] = TileType::Floor; + self.map.tiles[start_idx - self.map.width as usize] = TileType::Floor; + self.map.tiles[start_idx + self.map.width as usize] = TileType::Floor; + + // Random walker + let total_tiles = self.map.width * self.map.height; + let desired_floor_tiles = (self.floor_percent * total_tiles as f32) as usize; + let mut floor_tile_count = self.map.tiles.iter().filter(|a| **a == TileType::Floor).count(); + while floor_tile_count < desired_floor_tiles { + match self.algorithm { + DLAAlgorithm::WalkInwards => { + let mut digger_x = rng.roll_dice(1, self.map.width - 3) + 1; + let mut digger_y = rng.roll_dice(1, self.map.height - 3) + 1; + let mut prev_x = digger_x; + let mut prev_y = digger_y; + let mut digger_idx = self.map.xy_idx(digger_x, digger_y); + while self.map.tiles[digger_idx] == TileType::Wall { + prev_x = digger_x; + prev_y = digger_y; + let stagger_direction = rng.roll_dice(1, 4); + match stagger_direction { + 1 => { + if digger_x > 2 { + digger_x -= 1; + } + } + 2 => { + if digger_x < self.map.width - 2 { + digger_x += 1; + } + } + 3 => { + if digger_y > 2 { + digger_y -= 1; + } + } + _ => { + if digger_y < self.map.height - 2 { + digger_y += 1; + } + } + } + digger_idx = self.map.xy_idx(digger_x, digger_y); + } + paint(&mut self.map, self.symmetry, self.brush_size, prev_x, prev_y); + } + DLAAlgorithm::WalkOutwards => { + let mut digger_x = self.starting_position.x; + let mut digger_y = self.starting_position.y; + let mut digger_idx = self.map.xy_idx(digger_x, digger_y); + while self.map.tiles[digger_idx] == TileType::Floor { + let stagger_direction = rng.roll_dice(1, 4); + match stagger_direction { + 1 => { + if digger_x > 2 { + digger_x -= 1; + } + } + 2 => { + if digger_x < self.map.width - 2 { + digger_x += 1; + } + } + 3 => { + if digger_y > 2 { + digger_y -= 1; + } + } + _ => { + if digger_y < self.map.height - 2 { + digger_y += 1; + } + } + } + digger_idx = self.map.xy_idx(digger_x, digger_y); + } + paint(&mut self.map, self.symmetry, self.brush_size, digger_x, digger_y); + } + DLAAlgorithm::CentralAttractor => { + let mut digger_x = rng.roll_dice(1, self.map.width - 3) + 1; + let mut digger_y = rng.roll_dice(1, self.map.height - 3) + 1; + let mut prev_x = digger_x; + let mut prev_y = digger_y; + let mut digger_idx = self.map.xy_idx(digger_x, digger_y); + + let mut path = rltk::line2d( + rltk::LineAlg::Bresenham, + rltk::Point::new(digger_x, digger_y), + rltk::Point::new(self.starting_position.x, self.starting_position.y), + ); + + while self.map.tiles[digger_idx] == TileType::Wall && !path.is_empty() { + prev_x = digger_x; + prev_y = digger_y; + digger_x = path[0].x; + digger_y = path[0].y; + path.remove(0); + digger_idx = self.map.xy_idx(digger_x, digger_y); + } + paint(&mut self.map, self.symmetry, self.brush_size, prev_x, prev_y); + } + _ => {} + } + self.take_snapshot(); + floor_tile_count = self.map.tiles.iter().filter(|a| **a == TileType::Floor).count(); + } + // 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::DownStair; + self.take_snapshot(); + + // Now we build a noise map for use in spawning entities later + self.noise_areas = generate_voronoi_spawn_regions(&self.map, rng); + } +} diff --git a/src/map_builders/drunkard.rs b/src/map_builders/drunkard.rs index 38f7609..8d8749f 100644 --- a/src/map_builders/drunkard.rs +++ b/src/map_builders/drunkard.rs @@ -1,6 +1,6 @@ use super::{ - generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder, - Position, TileType, SHOW_MAPGEN, + generate_voronoi_spawn_regions, paint, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder, + Position, Symmetry, TileType, SHOW_MAPGEN, }; use rltk::RandomNumberGenerator; use specs::prelude::*; @@ -16,6 +16,8 @@ pub struct DrunkardSettings { pub spawn_mode: DrunkSpawnMode, pub drunken_lifetime: i32, pub floor_percent: f32, + pub brush_size: i32, + pub symmetry: Symmetry, } pub struct DrunkardsWalkBuilder { @@ -59,17 +61,6 @@ impl MapBuilder for DrunkardsWalkBuilder { } impl DrunkardsWalkBuilder { - pub fn new(new_depth: i32, settings: DrunkardSettings) -> DrunkardsWalkBuilder { - DrunkardsWalkBuilder { - map: Map::new(new_depth), - starting_position: Position { x: 0, y: 0 }, - depth: new_depth, - history: Vec::new(), - noise_areas: HashMap::new(), - settings, - } - } - pub fn open_area(new_depth: i32) -> DrunkardsWalkBuilder { DrunkardsWalkBuilder { map: Map::new(new_depth), @@ -81,6 +72,8 @@ impl DrunkardsWalkBuilder { spawn_mode: DrunkSpawnMode::StartingPoint, drunken_lifetime: 400, floor_percent: 0.5, + brush_size: 1, + symmetry: Symmetry::None, }, } } @@ -96,6 +89,8 @@ impl DrunkardsWalkBuilder { spawn_mode: DrunkSpawnMode::Random, drunken_lifetime: 400, floor_percent: 0.5, + brush_size: 1, + symmetry: Symmetry::None, }, } } @@ -111,6 +106,42 @@ impl DrunkardsWalkBuilder { spawn_mode: DrunkSpawnMode::Random, drunken_lifetime: 100, floor_percent: 0.4, + brush_size: 1, + symmetry: Symmetry::None, + }, + } + } + + pub fn fat_passages(new_depth: i32) -> DrunkardsWalkBuilder { + DrunkardsWalkBuilder { + map: Map::new(new_depth), + starting_position: Position { x: 0, y: 0 }, + depth: new_depth, + history: Vec::new(), + noise_areas: HashMap::new(), + settings: DrunkardSettings { + spawn_mode: DrunkSpawnMode::Random, + drunken_lifetime: 100, + floor_percent: 0.4, + brush_size: 2, + symmetry: Symmetry::None, + }, + } + } + + pub fn fearful_symmetry(new_depth: i32) -> DrunkardsWalkBuilder { + DrunkardsWalkBuilder { + map: Map::new(new_depth), + starting_position: Position { x: 0, y: 0 }, + depth: new_depth, + history: Vec::new(), + noise_areas: HashMap::new(), + settings: DrunkardSettings { + spawn_mode: DrunkSpawnMode::Random, + drunken_lifetime: 100, + floor_percent: 0.4, + brush_size: 1, + symmetry: Symmetry::Both, }, } } @@ -154,6 +185,7 @@ impl DrunkardsWalkBuilder { if self.map.tiles[drunk_idx] == TileType::Wall { did_something = true; } + paint(&mut self.map, self.settings.symmetry, self.settings.brush_size, drunk_x, drunk_y); self.map.tiles[drunk_idx] = TileType::DownStair; let stagger_direction = rng.roll_dice(1, 4); diff --git a/src/map_builders/mod.rs b/src/map_builders/mod.rs index 58b722d..137bb42 100644 --- a/src/map_builders/mod.rs +++ b/src/map_builders/mod.rs @@ -3,6 +3,7 @@ mod bsp_dungeon; mod bsp_interior; mod cellular_automata; mod common; +mod dla; mod drunkard; mod maze; mod simple_map; @@ -20,8 +21,8 @@ pub trait MapBuilder { } pub fn random_builder(new_depth: i32) -> Box { - let mut rng = rltk::RandomNumberGenerator::new(); - let builder = rng.roll_dice(1, 8); + /*let mut rng = rltk::RandomNumberGenerator::new(); + let builder = rng.roll_dice(1, 14); match builder { 1 => Box::new(bsp_dungeon::BspDungeonBuilder::new(new_depth)), 2 => Box::new(bsp_interior::BspInteriorBuilder::new(new_depth)), @@ -29,8 +30,14 @@ pub fn random_builder(new_depth: i32) -> Box { 4 => Box::new(drunkard::DrunkardsWalkBuilder::open_area(new_depth)), 5 => Box::new(drunkard::DrunkardsWalkBuilder::open_halls(new_depth)), 6 => Box::new(drunkard::DrunkardsWalkBuilder::winding_passages(new_depth)), + 6 => Box::new(drunkard::DrunkardsWalkBuilder::fat_passages(new_depth)), + 6 => Box::new(drunkard::DrunkardsWalkBuilder::fearful_symmetry(new_depth)), 7 => Box::new(maze::MazeBuilder::new(new_depth)), + 8 => Box::new(dla::DLABuilder::walk_inwards(new_depth)), + 9 => Box::new(dla::DLABuilder::walk_outwards(new_depth)), + 10 => Box::new(dla::DLABuilder::central_attractor(new_depth)), + 11 => Box::new(dla::DLABuilder::insectoid(new_depth)), _ => Box::new(simple_map::SimpleMapBuilder::new(new_depth)), - } - //Box::new(drunkard::DrunkardsWalkBuilder::winding_passages(new_depth)) + }*/ + Box::new(drunkard::DrunkardsWalkBuilder::fearful_symmetry(new_depth)) }