diff --git a/src/map_builders/cellular_automata.rs b/src/map_builders/cellular_automata.rs index f3a06c1..7d7aa2d 100644 --- a/src/map_builders/cellular_automata.rs +++ b/src/map_builders/cellular_automata.rs @@ -1,4 +1,7 @@ -use super::{spawner, Map, MapBuilder, Position, TileType, SHOW_MAPGEN}; +use super::{ + generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder, + Position, TileType, SHOW_MAPGEN, +}; use rltk::RandomNumberGenerator; use specs::prelude::*; use std::collections::HashMap; @@ -122,50 +125,15 @@ impl CellularAutomataBuilder { 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 map_starts: Vec = vec![start_idx]; - let dijkstra_map = rltk::DijkstraMap::new(self.map.width, self.map.height, &map_starts, &self.map, 200.0); - let mut exit_tile = (0, 0.0f32); - for (i, tile) in self.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 == std::f32::MAX { - *tile = TileType::Wall; - } else { - // 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; - } - } - } - } + // Find all tiles reachable from starting pos + let exit_tile = remove_unreachable_areas_returning_most_distant(&mut self.map, start_idx); self.take_snapshot(); - self.map.tiles[exit_tile.0] = TileType::DownStair; + // Place stairs + self.map.tiles[exit_tile] = TileType::DownStair; self.take_snapshot(); - // Build noise map for spawning entities - 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..self.map.height - 1 { - for x in 1..self.map.width - 1 { - let idx = self.map.xy_idx(x, y); - if self.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 self.noise_areas.contains_key(&cell_value) { - self.noise_areas.get_mut(&cell_value).unwrap().push(idx); - } else { - self.noise_areas.insert(cell_value, vec![idx]); - } - } - } - } + // Noise map for spawning entities + self.noise_areas = generate_voronoi_spawn_regions(&self.map, rng); } } diff --git a/src/map_builders/common.rs b/src/map_builders/common.rs index b3d314b..5656765 100644 --- a/src/map_builders/common.rs +++ b/src/map_builders/common.rs @@ -1,5 +1,6 @@ use super::{Map, Rect, TileType}; use std::cmp::{max, min}; +use std::collections::HashMap; pub fn apply_room_to_map(map: &mut Map, room: &Rect) { for y in room.y1 + 1..=room.y2 { @@ -27,3 +28,52 @@ pub fn apply_vertical_tunnel(map: &mut Map, y1: i32, y2: i32, x: i32) { } } } + +pub fn remove_unreachable_areas_returning_most_distant(map: &mut Map, start_idx: usize) -> usize { + map.populate_blocked(); + let map_starts: Vec = vec![start_idx]; + let dijkstra_map = rltk::DijkstraMap::new(map.width as usize, map.height as usize, &map_starts, map, 200.0); + let mut exit_tile = (0, 0.0f32); + for (i, tile) in 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 == std::f32::MAX { + *tile = TileType::Wall; + } else { + // 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; + } + } + } + } + return exit_tile.0; +} + +#[allow(clippy::map_entry)] +pub fn generate_voronoi_spawn_regions(map: &Map, rng: &mut rltk::RandomNumberGenerator) -> HashMap> { + let mut noise_areas: HashMap> = 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..map.height - 1 { + for x in 1..map.width - 1 { + let idx = map.xy_idx(x, y); + if 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]); + } + } + } + } + return noise_areas; +} diff --git a/src/map_builders/drunkard.rs b/src/map_builders/drunkard.rs new file mode 100644 index 0000000..38f7609 --- /dev/null +++ b/src/map_builders/drunkard.rs @@ -0,0 +1,214 @@ +use super::{ + generate_voronoi_spawn_regions, 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 DrunkSpawnMode { + StartingPoint, + Random, +} + +pub struct DrunkardSettings { + pub spawn_mode: DrunkSpawnMode, + pub drunken_lifetime: i32, + pub floor_percent: f32, +} + +pub struct DrunkardsWalkBuilder { + map: Map, + starting_position: Position, + depth: i32, + history: Vec, + noise_areas: HashMap>, + settings: DrunkardSettings, +} + +impl MapBuilder for DrunkardsWalkBuilder { + 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 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), + starting_position: Position { x: 0, y: 0 }, + depth: new_depth, + history: Vec::new(), + noise_areas: HashMap::new(), + settings: DrunkardSettings { + spawn_mode: DrunkSpawnMode::StartingPoint, + drunken_lifetime: 400, + floor_percent: 0.5, + }, + } + } + + pub fn open_halls(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: 400, + floor_percent: 0.5, + }, + } + } + + pub fn winding_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, + }, + } + } + + #[allow(clippy::map_entry)] + fn build(&mut self, rng: &mut RandomNumberGenerator) { + // Central starting pos + 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.map.tiles[start_idx] = TileType::Floor; + + let total_tiles = self.map.width * self.map.height; + let desired_floor_tiles = (self.settings.floor_percent * total_tiles as f32) as usize; + let mut floor_tile_count = self.map.tiles.iter().filter(|a| **a == TileType::Floor).count(); + let mut digger_count = 0; + let mut active_digger_count = 0; + + while floor_tile_count < desired_floor_tiles { + let mut did_something = false; + let mut drunk_x; + let mut drunk_y; + match self.settings.spawn_mode { + DrunkSpawnMode::StartingPoint => { + drunk_x = self.starting_position.x; + drunk_y = self.starting_position.y; + } + DrunkSpawnMode::Random => { + if digger_count == 0 { + drunk_x = self.starting_position.x; + drunk_y = self.starting_position.y; + } else { + drunk_x = rng.roll_dice(1, self.map.width - 3) + 1; + drunk_y = rng.roll_dice(1, self.map.height - 3) + 1; + } + } + } + let mut drunk_life = self.settings.drunken_lifetime; + + while drunk_life > 0 { + let drunk_idx = self.map.xy_idx(drunk_x, drunk_y); + if self.map.tiles[drunk_idx] == TileType::Wall { + did_something = true; + } + self.map.tiles[drunk_idx] = TileType::DownStair; + + let stagger_direction = rng.roll_dice(1, 4); + match stagger_direction { + 1 => { + if drunk_x > 2 { + drunk_x -= 1; + } + } + 2 => { + if drunk_x < self.map.width - 2 { + drunk_x += 1; + } + } + 3 => { + if drunk_y > 2 { + drunk_y -= 1; + } + } + _ => { + if drunk_y < self.map.height - 2 { + drunk_y += 1; + } + } + } + drunk_life -= 1; + } + if did_something { + self.take_snapshot(); + active_digger_count += 1; + } + + digger_count += 1; + for t in self.map.tiles.iter_mut() { + if *t == TileType::DownStair { + *t = TileType::Floor; + } + } + floor_tile_count = self.map.tiles.iter().filter(|a| **a == TileType::Floor).count(); + } + + rltk::console::log(format!( + "{} dwarves gave up their sobriety, of whom {} actually found a wall.", + digger_count, active_digger_count + )); + + // Find all tiles reachable from starting pos + let exit_tile = remove_unreachable_areas_returning_most_distant(&mut self.map, start_idx); + self.take_snapshot(); + + // Place stairs + self.map.tiles[exit_tile] = TileType::DownStair; + self.take_snapshot(); + + // Noise map for spawning entities + self.noise_areas = generate_voronoi_spawn_regions(&self.map, rng); + } +} diff --git a/src/map_builders/mod.rs b/src/map_builders/mod.rs index 1dbb967..fa51230 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 drunkard; mod simple_map; use common::*; use rltk::RandomNumberGenerator; @@ -19,11 +20,14 @@ pub trait MapBuilder { pub fn random_builder(new_depth: i32) -> Box { let mut rng = rltk::RandomNumberGenerator::new(); - let builder = rng.roll_dice(1, 4); + let builder = rng.roll_dice(1, 7); match builder { 1 => Box::new(bsp_dungeon::BspDungeonBuilder::new(new_depth)), 2 => Box::new(bsp_interior::BspInteriorBuilder::new(new_depth)), 3 => Box::new(cellular_automata::CellularAutomataBuilder::new(new_depth)), + 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)), _ => Box::new(simple_map::SimpleMapBuilder::new(new_depth)), } } diff --git a/src/player.rs b/src/player.rs index 1ba686e..6e01b53 100644 --- a/src/player.rs +++ b/src/player.rs @@ -122,7 +122,7 @@ fn get_item(ecs: &mut World) -> bool { pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { // Player movement - let mut result = false; + let result; match ctx.key { None => return RunState::AwaitingInput, Some(key) => match key {