From dd367dc39bb6dce5032363353981c702c70d9a4f Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Fri, 21 Jul 2023 18:34:08 +0100 Subject: [PATCH] refactors mapgen into chained builders --- src/main.rs | 18 +- src/map_builders/area_starting_points.rs | 73 +++ src/map_builders/bsp_dungeon.rs | 161 ++--- src/map_builders/bsp_interior.rs | 145 ++--- src/map_builders/cellular_automata.rs | 151 ++--- src/map_builders/common.rs | 62 +- src/map_builders/cull_unreachable.rs | 40 ++ src/map_builders/distant_exit.rs | 49 ++ src/map_builders/dla.rs | 196 +++--- src/map_builders/drunkard.rs | 184 ++---- src/map_builders/maze.rs | 334 +++++------ src/map_builders/mod.rs | 192 ++++-- src/map_builders/prefab_builder/mod.rs | 562 +++++++++--------- .../prefab_builder/prefab_vaults.rs | 152 ++++- src/map_builders/room_based_spawner.rs | 27 + src/map_builders/room_based_stairs.rs | 28 + .../room_based_starting_position.rs | 26 + src/map_builders/simple_map.rs | 96 +-- src/map_builders/voronoi.rs | 155 ++--- src/map_builders/voronoi_spawning.rs | 48 ++ src/map_builders/wfc/mod.rs | 160 ++--- src/spawner.rs | 2 +- 22 files changed, 1381 insertions(+), 1480 deletions(-) create mode 100644 src/map_builders/area_starting_points.rs create mode 100644 src/map_builders/cull_unreachable.rs create mode 100644 src/map_builders/distant_exit.rs create mode 100644 src/map_builders/room_based_spawner.rs create mode 100644 src/map_builders/room_based_stairs.rs create mode 100644 src/map_builders/room_based_starting_position.rs create mode 100644 src/map_builders/voronoi_spawning.rs diff --git a/src/main.rs b/src/main.rs index 9c62c33..21af86c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -78,19 +78,19 @@ impl State { self.mapgen_index = 0; self.mapgen_timer = 0.0; self.mapgen_history.clear(); + let mut rng = self.ecs.write_resource::(); + let mut builder = map_builders::random_builder(new_depth, &mut rng); + builder.build_map(&mut rng); + std::mem::drop(rng); + self.mapgen_history = builder.build_data.history.clone(); let player_start; - // Scope for borrow checker - let mut builder = map_builders::random_builder(new_depth); { - // Build a new map using RNG (to retain seed) - let mut rng = self.ecs.write_resource::(); - builder.build_map(&mut rng); - self.mapgen_history = builder.get_snapshot_history(); let mut worldmap_resource = self.ecs.write_resource::(); - *worldmap_resource = builder.get_map(); - player_start = builder.get_starting_pos(); - // Spawn entities + *worldmap_resource = builder.build_data.map.clone(); + // Unwrap so we get a CTD if there's no starting pos. + player_start = builder.build_data.starting_position.as_mut().unwrap().clone(); } + // Spawn entities builder.spawn_entities(&mut self.ecs); // Place player and update resources diff --git a/src/map_builders/area_starting_points.rs b/src/map_builders/area_starting_points.rs new file mode 100644 index 0000000..f890577 --- /dev/null +++ b/src/map_builders/area_starting_points.rs @@ -0,0 +1,73 @@ +use super::{BuilderMap, MetaMapBuilder, Position, TileType}; +use rltk::RandomNumberGenerator; + +#[allow(dead_code)] +pub enum XStart { + LEFT, + CENTRE, + RIGHT, +} + +#[allow(dead_code)] +pub enum YStart { + TOP, + CENTRE, + BOTTOM, +} + +pub struct AreaStartingPosition { + x: XStart, + y: YStart, +} + +impl MetaMapBuilder for AreaStartingPosition { + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build(rng, build_data); + } +} + +impl AreaStartingPosition { + #[allow(dead_code)] + pub fn new(x: XStart, y: YStart) -> Box { + Box::new(AreaStartingPosition { x, y }) + } + + fn build(&mut self, _rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + let seed_x; + let seed_y; + + match self.x { + XStart::LEFT => seed_x = 1, + XStart::CENTRE => seed_x = build_data.map.width / 2, + XStart::RIGHT => seed_x = build_data.map.width - 2, + } + match self.y { + YStart::TOP => seed_y = 1, + YStart::CENTRE => seed_y = build_data.map.height / 2, + YStart::BOTTOM => seed_y = 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 }); + } +} diff --git a/src/map_builders/bsp_dungeon.rs b/src/map_builders/bsp_dungeon.rs index da12718..a251ccb 100644 --- a/src/map_builders/bsp_dungeon.rs +++ b/src/map_builders/bsp_dungeon.rs @@ -1,119 +1,74 @@ -use super::{apply_room_to_map, spawner, Map, MapBuilder, Position, Rect, TileType, SHOW_MAPGEN}; +use super::{apply_room_to_map, draw_corridor, BuilderMap, InitialMapBuilder, Map, Rect, TileType}; use rltk::RandomNumberGenerator; pub struct BspDungeonBuilder { - map: Map, - starting_position: Position, - depth: i32, - rooms: Vec, - history: Vec, rects: Vec, - spawn_list: Vec<(usize, String)>, } -impl MapBuilder for BspDungeonBuilder { - fn build_map(&mut self, rng: &mut RandomNumberGenerator) { - return self.build(rng); - } - // Getters - fn get_map(&mut self) -> Map { - return self.map.clone(); - } - fn get_starting_pos(&mut self) -> Position { - return self.starting_position.clone(); - } - fn get_spawn_list(&self) -> &Vec<(usize, String)> { - return &self.spawn_list; - } - // 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 InitialMapBuilder for BspDungeonBuilder { + #[allow(dead_code)] + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build(rng, build_data); } } impl BspDungeonBuilder { #[allow(dead_code)] - pub fn new(new_depth: i32) -> BspDungeonBuilder { - BspDungeonBuilder { - map: Map::new(new_depth), - starting_position: Position { x: 0, y: 0 }, - depth: new_depth, - rooms: Vec::new(), - history: Vec::new(), - rects: Vec::new(), - spawn_list: Vec::new(), - } + pub fn new() -> Box { + Box::new(BspDungeonBuilder { rects: Vec::new() }) } - fn build(&mut self, mut rng: &mut RandomNumberGenerator) { + fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + let mut rooms: Vec = Vec::new(); self.rects.clear(); - self.rects.push(Rect::new(2, 2, self.map.width - 5, self.map.height - 5)); + self.rects.push(Rect::new(2, 2, build_data.map.width - 5, build_data.map.height - 5)); // Start with a single map-sized rectangle let first_room = self.rects[0]; - self.add_subrects(first_room); // Divide first room + self.add_subrects(first_room); // Divide the first room - // Up to 240 times, get a random rect and divide it. If it's possible - // to place a room in there, place it and add it to the rooms list. + // Up to 240 times, we get a random rectangle and divide it. If its possible to squeeze a + // room in there, we place it and add it to the rooms list. let mut n_rooms = 0; while n_rooms < 240 { - let rect = self.get_random_rect(&mut rng); - let candidate = self.get_random_subrect(rect, &mut rng); + let rect = self.get_random_rect(rng); + let candidate = self.get_random_sub_rect(rect, rng); - if self.is_possible(candidate) { - apply_room_to_map(&mut self.map, &candidate); - self.rooms.push(candidate); + if self.is_possible(candidate, &build_data.map) { + apply_room_to_map(&mut build_data.map, &candidate); + rooms.push(candidate); self.add_subrects(rect); - self.take_snapshot(); + build_data.take_snapshot(); } + n_rooms += 1; } - let start = self.rooms[0].centre(); - self.starting_position = Position { x: start.0, y: start.1 }; - // Sort rooms by left co-ordinate. Optional, but helps to make connected rooms line up. - self.rooms.sort_by(|a, b| a.x1.cmp(&b.x1)); + // Now we sort the rooms + rooms.sort_by(|a, b| a.x1.cmp(&b.x1)); - // Corridors - for i in 0..self.rooms.len() - 1 { - let room = self.rooms[i]; - let next_room = self.rooms[i + 1]; + // Now we want corridors + for i in 0..rooms.len() - 1 { + let room = rooms[i]; + let next_room = rooms[i + 1]; let start_x = room.x1 + (rng.roll_dice(1, i32::abs(room.x1 - room.x2)) - 1); let start_y = room.y1 + (rng.roll_dice(1, i32::abs(room.y1 - room.y2)) - 1); let end_x = next_room.x1 + (rng.roll_dice(1, i32::abs(next_room.x1 - next_room.x2)) - 1); let end_y = next_room.y1 + (rng.roll_dice(1, i32::abs(next_room.y1 - next_room.y2)) - 1); - self.draw_corridor(start_x, start_y, end_x, end_y); - self.take_snapshot(); - } - - // Stairs - let stairs = self.rooms[self.rooms.len() - 1].centre(); - let stairs_idx = self.map.xy_idx(stairs.0, stairs.1); - self.map.tiles[stairs_idx] = TileType::DownStair; - - // Spawn entities - for room in self.rooms.iter().skip(1) { - spawner::spawn_room(&self.map, rng, room, self.depth, &mut self.spawn_list); + draw_corridor(&mut build_data.map, start_x, start_y, end_x, end_y); + build_data.take_snapshot(); } + build_data.rooms = Some(rooms); } fn add_subrects(&mut self, rect: Rect) { - let w = i32::abs(rect.x1 - rect.x2); - let h = i32::abs(rect.y1 - rect.y2); - let half_w = i32::max(w / 2, 1); - let half_h = i32::max(h / 2, 1); + let width = i32::abs(rect.x1 - rect.x2); + let height = i32::abs(rect.y1 - rect.y2); + let half_width = i32::max(width / 2, 1); + let half_height = i32::max(height / 2, 1); - self.rects.push(Rect::new(rect.x1, rect.y1, half_w, half_h)); - self.rects.push(Rect::new(rect.x1, rect.y1 + half_h, half_w, half_h)); - self.rects.push(Rect::new(rect.x1 + half_w, rect.y1, half_w, half_h)); - self.rects.push(Rect::new(rect.x1 + half_w, rect.y1 + half_h, half_w, half_h)); + self.rects.push(Rect::new(rect.x1, rect.y1, half_width, half_height)); + self.rects.push(Rect::new(rect.x1, rect.y1 + half_height, half_width, half_height)); + self.rects.push(Rect::new(rect.x1 + half_width, rect.y1, half_width, half_height)); + self.rects.push(Rect::new(rect.x1 + half_width, rect.y1 + half_height, half_width, half_height)); } fn get_random_rect(&mut self, rng: &mut RandomNumberGenerator) -> Rect { @@ -121,26 +76,26 @@ impl BspDungeonBuilder { return self.rects[0]; } let idx = (rng.roll_dice(1, self.rects.len() as i32) - 1) as usize; - return self.rects[idx]; + self.rects[idx] } - fn get_random_subrect(&self, rect: Rect, rng: &mut RandomNumberGenerator) -> Rect { + fn get_random_sub_rect(&self, rect: Rect, rng: &mut RandomNumberGenerator) -> Rect { let mut result = rect; let rect_width = i32::abs(rect.x1 - rect.x2); let rect_height = i32::abs(rect.y1 - rect.y2); - let w = i32::max(3, rng.roll_dice(1, i32::min(rect_width, 14)) - 1) + 1; - let h = i32::max(3, rng.roll_dice(1, i32::min(rect_height, 14)) - 1) + 1; + let w = i32::max(3, rng.roll_dice(1, i32::min(rect_width, 10)) - 1) + 1; + let h = i32::max(3, rng.roll_dice(1, i32::min(rect_height, 10)) - 1) + 1; result.x1 += rng.roll_dice(1, 6) - 1; result.y1 += rng.roll_dice(1, 6) - 1; result.x2 = result.x1 + w; result.y2 = result.y1 + h; - return result; + result } - fn is_possible(&self, rect: Rect) -> bool { + fn is_possible(&self, rect: Rect, map: &Map) -> bool { let mut expanded = rect; expanded.x1 -= 2; expanded.x2 += 2; @@ -151,10 +106,10 @@ impl BspDungeonBuilder { for y in expanded.y1..=expanded.y2 { for x in expanded.x1..=expanded.x2 { - if x > self.map.width - 2 { + if x > map.width - 2 { can_build = false; } - if y > self.map.height - 2 { + if y > map.height - 2 { can_build = false; } if x < 1 { @@ -164,34 +119,14 @@ impl BspDungeonBuilder { can_build = false; } if can_build { - let idx = self.map.xy_idx(x, y); - if self.map.tiles[idx] != TileType::Wall { + let idx = map.xy_idx(x, y); + if map.tiles[idx] != TileType::Wall { can_build = false; } } } } - return can_build; - } - - fn draw_corridor(&mut self, x1: i32, y1: i32, x2: i32, y2: i32) { - let mut x = x1; - let mut y = y1; - - while x != x2 || y != y2 { - if x < x2 { - x += 1; - } else if x > x2 { - x -= 1; - } else if y < y2 { - y += 1; - } else if y > y2 { - y -= 1; - } - - let idx = self.map.xy_idx(x, y); - self.map.tiles[idx] = TileType::Floor; - } + can_build } } diff --git a/src/map_builders/bsp_interior.rs b/src/map_builders/bsp_interior.rs index c8855bc..a2ab03d 100644 --- a/src/map_builders/bsp_interior.rs +++ b/src/map_builders/bsp_interior.rs @@ -1,165 +1,102 @@ -use super::{spawner, Map, MapBuilder, Position, Rect, TileType, SHOW_MAPGEN}; +use super::{draw_corridor, BuilderMap, InitialMapBuilder, Rect, TileType}; use rltk::RandomNumberGenerator; +const MIN_ROOM_SIZE: i32 = 8; + pub struct BspInteriorBuilder { - map: Map, - starting_position: Position, - depth: i32, - rooms: Vec, - history: Vec, rects: Vec, - spawn_list: Vec<(usize, String)>, } -impl MapBuilder for BspInteriorBuilder { - fn build_map(&mut self, rng: &mut RandomNumberGenerator) { - return self.build(rng); - } - // Getters - fn get_map(&mut self) -> Map { - return self.map.clone(); - } - fn get_starting_pos(&mut self) -> Position { - return self.starting_position.clone(); - } - fn get_spawn_list(&self) -> &Vec<(usize, String)> { - return &self.spawn_list; - } - // 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 InitialMapBuilder for BspInteriorBuilder { + #[allow(dead_code)] + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build(rng, build_data); } } impl BspInteriorBuilder { #[allow(dead_code)] - pub fn new(new_depth: i32) -> BspInteriorBuilder { - BspInteriorBuilder { - map: Map::new(new_depth), - starting_position: Position { x: 0, y: 0 }, - depth: new_depth, - rooms: Vec::new(), - history: Vec::new(), - rects: Vec::new(), - spawn_list: Vec::new(), - } + pub fn new() -> Box { + Box::new(BspInteriorBuilder { rects: Vec::new() }) } - fn build(&mut self, rng: &mut RandomNumberGenerator) { + fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + let mut rooms: Vec = Vec::new(); self.rects.clear(); - self.rects.push(Rect::new(1, 1, self.map.width - 2, self.map.height - 2)); // Start with a single map-sized rectangle + self.rects.push(Rect::new(1, 1, build_data.map.width - 2, build_data.map.height - 2)); // Start with a single map-sized rectangle let first_room = self.rects[0]; self.add_subrects(first_room, rng); // Divide the first room - let rooms = self.rects.clone(); - for r in rooms.iter() { + let rooms_copy = self.rects.clone(); + for r in rooms_copy.iter() { let room = *r; - self.rooms.push(room); + //room.x2 -= 1; + //room.y2 -= 1; + rooms.push(room); for y in room.y1..room.y2 { for x in room.x1..room.x2 { - let idx = self.map.xy_idx(x, y); - if idx > 0 && idx < ((self.map.width * self.map.height) - 1) as usize { - self.map.tiles[idx] = TileType::Floor; + let idx = build_data.map.xy_idx(x, y); + if idx > 0 && idx < ((build_data.map.width * build_data.map.height) - 1) as usize { + build_data.map.tiles[idx] = TileType::Floor; } } } - self.take_snapshot(); + build_data.take_snapshot(); } - let start = self.rooms[0].centre(); - self.starting_position = Position { x: start.0, y: start.1 }; - // Now we want corridors - for i in 0..self.rooms.len() - 1 { - let room = self.rooms[i]; - let next_room = self.rooms[i + 1]; + for i in 0..rooms.len() - 1 { + let room = rooms[i]; + let next_room = rooms[i + 1]; let start_x = room.x1 + (rng.roll_dice(1, i32::abs(room.x1 - room.x2)) - 1); let start_y = room.y1 + (rng.roll_dice(1, i32::abs(room.y1 - room.y2)) - 1); let end_x = next_room.x1 + (rng.roll_dice(1, i32::abs(next_room.x1 - next_room.x2)) - 1); let end_y = next_room.y1 + (rng.roll_dice(1, i32::abs(next_room.y1 - next_room.y2)) - 1); - self.draw_corridor(start_x, start_y, end_x, end_y); - self.take_snapshot(); + draw_corridor(&mut build_data.map, start_x, start_y, end_x, end_y); + build_data.take_snapshot(); } - // Don't forget the stairs - let stairs = self.rooms[self.rooms.len() - 1].centre(); - let stairs_idx = self.map.xy_idx(stairs.0, stairs.1); - self.map.tiles[stairs_idx] = TileType::DownStair; - - // Spawn entities - for room in self.rooms.iter().skip(1) { - spawner::spawn_room(&self.map, rng, room, self.depth, &mut self.spawn_list); - } + build_data.rooms = Some(rooms); } fn add_subrects(&mut self, rect: Rect, rng: &mut RandomNumberGenerator) { - const MIN_ROOM_SIZE: i32 = 6; - // Remove last rect + // Remove the last rect from the list if !self.rects.is_empty() { self.rects.remove(self.rects.len() - 1); } - // Calc bounds - let w = rect.x2 - rect.x1; - let h = rect.y2 - rect.y1; - let half_w = w / 2; - let half_h = h / 2; + // Calculate boundaries + let width = rect.x2 - rect.x1; + let height = rect.y2 - rect.y1; + let half_width = width / 2; + let half_height = height / 2; let split = rng.roll_dice(1, 4); if split <= 2 { // Horizontal split - let h1 = Rect::new(rect.x1, rect.y1, half_w - 1, h); + let h1 = Rect::new(rect.x1, rect.y1, half_width - 1, height); self.rects.push(h1); - if half_w > MIN_ROOM_SIZE { + if half_width > MIN_ROOM_SIZE { self.add_subrects(h1, rng); } - let h2 = Rect::new(rect.x1 + half_w, rect.y1, half_w, h); + let h2 = Rect::new(rect.x1 + half_width, rect.y1, half_width, height); self.rects.push(h2); - if half_w > MIN_ROOM_SIZE { + if half_width > MIN_ROOM_SIZE { self.add_subrects(h2, rng); } } else { // Vertical split - let v1 = Rect::new(rect.x1, rect.y1, w, half_h - 1); + let v1 = Rect::new(rect.x1, rect.y1, width, half_height - 1); self.rects.push(v1); - if half_h > MIN_ROOM_SIZE { + if half_height > MIN_ROOM_SIZE { self.add_subrects(v1, rng); } - let v2 = Rect::new(rect.x1, rect.y1 + half_h, w, half_h); + let v2 = Rect::new(rect.x1, rect.y1 + half_height, width, half_height); self.rects.push(v2); - if half_h > MIN_ROOM_SIZE { + if half_height > MIN_ROOM_SIZE { self.add_subrects(v2, rng); } } } - - fn draw_corridor(&mut self, x1: i32, y1: i32, x2: i32, y2: i32) { - let mut x = x1; - let mut y = y1; - - while x != x2 || y != y2 { - if x < x2 { - x += 1; - } else if x > x2 { - x -= 1; - } else if y < y2 { - y += 1; - } else if y > y2 { - y -= 1; - } - - let idx = self.map.xy_idx(x, y); - self.map.tiles[idx] = TileType::Floor; - } - } } diff --git a/src/map_builders/cellular_automata.rs b/src/map_builders/cellular_automata.rs index 4d34e3b..a7b5860 100644 --- a/src/map_builders/cellular_automata.rs +++ b/src/map_builders/cellular_automata.rs @@ -1,143 +1,80 @@ -use super::{ - generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder, - Position, TileType, SHOW_MAPGEN, -}; +use super::{BuilderMap, InitialMapBuilder, TileType}; use rltk::RandomNumberGenerator; -use std::collections::HashMap; -const PASSES: i32 = 15; +pub struct CellularAutomataBuilder {} -pub struct CellularAutomataBuilder { - map: Map, - starting_position: Position, - depth: i32, - history: Vec, - noise_areas: HashMap>, - spawn_list: Vec<(usize, String)>, -} - -impl MapBuilder for CellularAutomataBuilder { - fn build_map(&mut self, rng: &mut RandomNumberGenerator) { - return self.build(rng); - } - // Getters - fn get_map(&mut self) -> Map { - return self.map.clone(); - } - fn get_starting_pos(&mut self) -> Position { - return self.starting_position.clone(); - } - fn get_spawn_list(&self) -> &Vec<(usize, String)> { - return &self.spawn_list; - } - // 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 InitialMapBuilder for CellularAutomataBuilder { + #[allow(dead_code)] + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build(rng, build_data); } } impl CellularAutomataBuilder { - pub fn new(new_depth: i32) -> CellularAutomataBuilder { - CellularAutomataBuilder { - map: Map::new(new_depth), - starting_position: Position { x: 0, y: 0 }, - depth: new_depth, - history: Vec::new(), - noise_areas: HashMap::new(), - spawn_list: Vec::new(), - } + #[allow(dead_code)] + pub fn new() -> Box { + Box::new(CellularAutomataBuilder {}) } - fn build(&mut self, rng: &mut RandomNumberGenerator) { - // Set 55% of map to floor - for y in 1..self.map.height - 1 { - for x in 1..self.map.width - 1 { + #[allow(clippy::map_entry)] + fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + // First we completely randomize the map, setting 55% of it to be floor. + for y in 1..build_data.map.height - 1 { + for x in 1..build_data.map.width - 1 { 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 { - self.map.tiles[idx] = TileType::Floor + build_data.map.tiles[idx] = TileType::Floor } else { - self.map.tiles[idx] = TileType::Wall + build_data.map.tiles[idx] = TileType::Wall } } } - self.take_snapshot(); + build_data.take_snapshot(); - // Iteratively apply cellular automata rules - for _i in 0..PASSES { - let mut newtiles = self.map.tiles.clone(); + // Now we iteratively apply cellular automata rules + for _i in 0..15 { + let mut newtiles = build_data.map.tiles.clone(); - for y in 1..self.map.height - 1 { - for x in 1..self.map.width - 1 { - let idx = self.map.xy_idx(x, y); - let mut neighbours = 0; - if self.map.tiles[idx - 1] == TileType::Wall { - neighbours += 1; + 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); + let mut neighbors = 0; + if build_data.map.tiles[idx - 1] == TileType::Wall { + neighbors += 1; } - if self.map.tiles[idx + 1] == TileType::Wall { - neighbours += 1; + if build_data.map.tiles[idx + 1] == TileType::Wall { + neighbors += 1; } - if self.map.tiles[idx - self.map.width as usize] == TileType::Wall { - neighbours += 1; + if build_data.map.tiles[idx - build_data.map.width as usize] == TileType::Wall { + neighbors += 1; } - if self.map.tiles[idx + self.map.width as usize] == TileType::Wall { - neighbours += 1; + if build_data.map.tiles[idx + build_data.map.width as usize] == TileType::Wall { + neighbors += 1; } - if self.map.tiles[idx - (self.map.width as usize - 1)] == TileType::Wall { - neighbours += 1; + if build_data.map.tiles[idx - (build_data.map.width as usize - 1)] == TileType::Wall { + neighbors += 1; } - if self.map.tiles[idx - (self.map.width as usize + 1)] == TileType::Wall { - neighbours += 1; + if build_data.map.tiles[idx - (build_data.map.width as usize + 1)] == TileType::Wall { + neighbors += 1; } - if self.map.tiles[idx + (self.map.width as usize - 1)] == TileType::Wall { - neighbours += 1; + if build_data.map.tiles[idx + (build_data.map.width as usize - 1)] == TileType::Wall { + neighbors += 1; } - if self.map.tiles[idx + (self.map.width as usize + 1)] == TileType::Wall { - neighbours += 1; + if build_data.map.tiles[idx + (build_data.map.width as usize + 1)] == TileType::Wall { + neighbors += 1; } - if neighbours > 4 || neighbours == 0 { + if neighbors > 4 || neighbors == 0 { newtiles[idx] = TileType::Wall; } else { newtiles[idx] = TileType::Floor; } } } - self.map.tiles = newtiles.clone(); - 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); - } - - // 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); - - // Spawn the entities - for area in self.noise_areas.iter() { - spawner::spawn_region(&self.map, rng, area.1, self.depth, &mut self.spawn_list); + build_data.map.tiles = newtiles.clone(); + build_data.take_snapshot(); } } } diff --git a/src/map_builders/common.rs b/src/map_builders/common.rs index 1bc87c7..85698ee 100644 --- a/src/map_builders/common.rs +++ b/src/map_builders/common.rs @@ -1,6 +1,5 @@ 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 { @@ -29,53 +28,24 @@ 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; - } - } +pub fn draw_corridor(map: &mut Map, x1: i32, y1: i32, x2: i32, y2: i32) { + let mut x = x1; + let mut y = y1; + + while x != x2 || y != y2 { + if x < x2 { + x += 1; + } else if x > x2 { + x -= 1; + } else if y < y2 { + y += 1; + } else if y > y2 { + y -= 1; } + + let idx = map.xy_idx(x, y); + map.tiles[idx] = TileType::Floor; } - 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; } #[allow(dead_code)] diff --git a/src/map_builders/cull_unreachable.rs b/src/map_builders/cull_unreachable.rs new file mode 100644 index 0000000..4b2213b --- /dev/null +++ b/src/map_builders/cull_unreachable.rs @@ -0,0 +1,40 @@ +use super::{BuilderMap, MetaMapBuilder, TileType}; +use rltk::RandomNumberGenerator; + +pub struct CullUnreachable {} + +impl MetaMapBuilder for CullUnreachable { + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build(rng, build_data); + } +} + +impl CullUnreachable { + #[allow(dead_code)] + pub fn new() -> Box { + 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 = 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 get to this tile - so we'll make it a wall + if distance_to_start == std::f32::MAX { + *tile = TileType::Wall; + } + } + } + } +} diff --git a/src/map_builders/distant_exit.rs b/src/map_builders/distant_exit.rs new file mode 100644 index 0000000..74873ec --- /dev/null +++ b/src/map_builders/distant_exit.rs @@ -0,0 +1,49 @@ +use super::{BuilderMap, MetaMapBuilder, TileType}; +use rltk::RandomNumberGenerator; + +pub struct DistantExit {} + +impl MetaMapBuilder for DistantExit { + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build(rng, build_data); + } +} + +impl DistantExit { + #[allow(dead_code)] + pub fn new() -> Box { + 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 = 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]; + if distance_to_start != std::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::DownStair; + build_data.take_snapshot(); + } +} diff --git a/src/map_builders/dla.rs b/src/map_builders/dla.rs index 699dc15..1f96b4a 100644 --- a/src/map_builders/dla.rs +++ b/src/map_builders/dla.rs @@ -1,12 +1,8 @@ -use super::{ - common::Symmetry, generate_voronoi_spawn_regions, paint, remove_unreachable_areas_returning_most_distant, spawner, - Map, MapBuilder, Position, TileType, SHOW_MAPGEN, -}; +use super::{paint, BuilderMap, InitialMapBuilder, Position, Symmetry, TileType}; use rltk::RandomNumberGenerator; -use std::collections::HashMap; -#[allow(dead_code)] #[derive(PartialEq, Copy, Clone)] +#[allow(dead_code)] pub enum DLAAlgorithm { WalkInwards, WalkOutwards, @@ -14,134 +10,95 @@ pub enum DLAAlgorithm { } 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, - spawn_list: Vec<(usize, String)>, } -impl MapBuilder for DLABuilder { - fn build_map(&mut self, rng: &mut RandomNumberGenerator) { - return self.build(rng); - } - // Getters - fn get_map(&mut self) -> Map { - return self.map.clone(); - } - fn get_starting_pos(&mut self) -> Position { - return self.starting_position.clone(); - } - fn get_spawn_list(&self) -> &Vec<(usize, String)> { - return &self.spawn_list; - } - // 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 InitialMapBuilder for DLABuilder { + #[allow(dead_code)] + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build(rng, build_data); } } -#[allow(dead_code)] 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(), + #[allow(dead_code)] + pub fn new() -> Box { + Box::new(DLABuilder { + algorithm: DLAAlgorithm::WalkInwards, + brush_size: 2, + symmetry: Symmetry::None, + floor_percent: 0.25, + }) + } + + #[allow(dead_code)] + pub fn walk_inwards() -> Box { + Box::new(DLABuilder { algorithm: DLAAlgorithm::WalkInwards, brush_size: 1, symmetry: Symmetry::None, floor_percent: 0.25, - spawn_list: Vec::new(), - } + }) } - 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(), + #[allow(dead_code)] + pub fn walk_outwards() -> Box { + Box::new(DLABuilder { algorithm: DLAAlgorithm::WalkOutwards, brush_size: 2, symmetry: Symmetry::None, floor_percent: 0.25, - spawn_list: Vec::new(), - } + }) } - 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(), + #[allow(dead_code)] + pub fn central_attractor() -> Box { + Box::new(DLABuilder { algorithm: DLAAlgorithm::CentralAttractor, brush_size: 2, symmetry: Symmetry::None, floor_percent: 0.25, - spawn_list: Vec::new(), - } + }) } - 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(), + #[allow(dead_code)] + pub fn insectoid() -> Box { + Box::new(DLABuilder { algorithm: DLAAlgorithm::CentralAttractor, brush_size: 2, symmetry: Symmetry::Horizontal, floor_percent: 0.25, - spawn_list: Vec::new(), - } + }) } #[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; + fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + // Carve a starting seed + let starting_position = Position { x: build_data.map.width / 2, y: build_data.map.height / 2 }; + let start_idx = build_data.map.xy_idx(starting_position.x, starting_position.y); + build_data.take_snapshot(); + build_data.map.tiles[start_idx] = TileType::Floor; + build_data.map.tiles[start_idx - 1] = TileType::Floor; + build_data.map.tiles[start_idx + 1] = TileType::Floor; + build_data.map.tiles[start_idx - build_data.map.width as usize] = TileType::Floor; + build_data.map.tiles[start_idx + build_data.map.width as usize] = TileType::Floor; // Random walker - let total_tiles = self.map.width * self.map.height; + let total_tiles = build_data.map.width * build_data.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(); + let mut floor_tile_count = build_data.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 digger_x = rng.roll_dice(1, build_data.map.width - 3) + 1; + let mut digger_y = rng.roll_dice(1, build_data.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 { + let mut digger_idx = build_data.map.xy_idx(digger_x, digger_y); + while build_data.map.tiles[digger_idx] == TileType::Wall { prev_x = digger_x; prev_y = digger_y; let stagger_direction = rng.roll_dice(1, 4); @@ -152,7 +109,7 @@ impl DLABuilder { } } 2 => { - if digger_x < self.map.width - 2 { + if digger_x < build_data.map.width - 2 { digger_x += 1; } } @@ -162,20 +119,21 @@ impl DLABuilder { } } _ => { - if digger_y < self.map.height - 2 { + if digger_y < build_data.map.height - 2 { digger_y += 1; } } } - digger_idx = self.map.xy_idx(digger_x, digger_y); + digger_idx = build_data.map.xy_idx(digger_x, digger_y); } - paint(&mut self.map, self.symmetry, self.brush_size, prev_x, prev_y); + paint(&mut build_data.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 mut digger_x = starting_position.x; + let mut digger_y = starting_position.y; + let mut digger_idx = build_data.map.xy_idx(digger_x, digger_y); + while build_data.map.tiles[digger_idx] == TileType::Floor { let stagger_direction = rng.roll_dice(1, 4); match stagger_direction { 1 => { @@ -184,7 +142,7 @@ impl DLABuilder { } } 2 => { - if digger_x < self.map.width - 2 { + if digger_x < build_data.map.width - 2 { digger_x += 1; } } @@ -194,56 +152,44 @@ impl DLABuilder { } } _ => { - if digger_y < self.map.height - 2 { + if digger_y < build_data.map.height - 2 { digger_y += 1; } } } - digger_idx = self.map.xy_idx(digger_x, digger_y); + digger_idx = build_data.map.xy_idx(digger_x, digger_y); } - paint(&mut self.map, self.symmetry, self.brush_size, digger_x, digger_y); + paint(&mut build_data.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 digger_x = rng.roll_dice(1, build_data.map.width - 3) + 1; + let mut digger_y = rng.roll_dice(1, build_data.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 digger_idx = build_data.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), + rltk::Point::new(starting_position.x, starting_position.y), ); - while self.map.tiles[digger_idx] == TileType::Wall && !path.is_empty() { + while build_data.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); + digger_idx = build_data.map.xy_idx(digger_x, digger_y); } - paint(&mut self.map, self.symmetry, self.brush_size, prev_x, prev_y); + paint(&mut build_data.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(); + build_data.take_snapshot(); - // Now we build a noise map for use in spawning entities later - self.noise_areas = generate_voronoi_spawn_regions(&self.map, rng); - - // Spawn the entities - for area in self.noise_areas.iter() { - spawner::spawn_region(&self.map, rng, area.1, self.depth, &mut self.spawn_list); + floor_tile_count = build_data.map.tiles.iter().filter(|a| **a == TileType::Floor).count(); } } } diff --git a/src/map_builders/drunkard.rs b/src/map_builders/drunkard.rs index dd56eac..5057bc1 100644 --- a/src/map_builders/drunkard.rs +++ b/src/map_builders/drunkard.rs @@ -1,11 +1,8 @@ -use super::{ - generate_voronoi_spawn_regions, paint, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder, - Position, Symmetry, TileType, SHOW_MAPGEN, -}; +use super::{paint, BuilderMap, InitialMapBuilder, Position, Symmetry, TileType}; use rltk::RandomNumberGenerator; -use std::collections::HashMap; #[derive(PartialEq, Copy, Clone)] +#[allow(dead_code)] pub enum DrunkSpawnMode { StartingPoint, Random, @@ -20,53 +17,25 @@ pub struct DrunkardSettings { } pub struct DrunkardsWalkBuilder { - map: Map, - starting_position: Position, - depth: i32, - history: Vec, - noise_areas: HashMap>, settings: DrunkardSettings, - spawn_list: Vec<(usize, String)>, } -impl MapBuilder for DrunkardsWalkBuilder { - fn build_map(&mut self, rng: &mut RandomNumberGenerator) { - return self.build(rng); - } - // Getters - fn get_map(&mut self) -> Map { - return self.map.clone(); - } - fn get_starting_pos(&mut self) -> Position { - return self.starting_position.clone(); - } - fn get_spawn_list(&self) -> &Vec<(usize, String)> { - return &self.spawn_list; - } - // 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 InitialMapBuilder for DrunkardsWalkBuilder { + #[allow(dead_code)] + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build(rng, build_data); } } -#[allow(dead_code)] impl DrunkardsWalkBuilder { - 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(), + #[allow(dead_code)] + pub fn new(settings: DrunkardSettings) -> DrunkardsWalkBuilder { + DrunkardsWalkBuilder { settings } + } + + #[allow(dead_code)] + pub fn open_area() -> Box { + Box::new(DrunkardsWalkBuilder { settings: DrunkardSettings { spawn_mode: DrunkSpawnMode::StartingPoint, drunken_lifetime: 400, @@ -74,17 +43,12 @@ impl DrunkardsWalkBuilder { brush_size: 1, symmetry: Symmetry::None, }, - spawn_list: Vec::new(), - } + }) } - 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(), + #[allow(dead_code)] + pub fn open_halls() -> Box { + Box::new(DrunkardsWalkBuilder { settings: DrunkardSettings { spawn_mode: DrunkSpawnMode::Random, drunken_lifetime: 400, @@ -92,17 +56,12 @@ impl DrunkardsWalkBuilder { brush_size: 1, symmetry: Symmetry::None, }, - spawn_list: Vec::new(), - } + }) } - 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(), + #[allow(dead_code)] + pub fn winding_passages() -> Box { + Box::new(DrunkardsWalkBuilder { settings: DrunkardSettings { spawn_mode: DrunkSpawnMode::Random, drunken_lifetime: 100, @@ -110,17 +69,12 @@ impl DrunkardsWalkBuilder { brush_size: 1, symmetry: Symmetry::None, }, - spawn_list: Vec::new(), - } + }) } - 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(), + #[allow(dead_code)] + pub fn fat_passages() -> Box { + Box::new(DrunkardsWalkBuilder { settings: DrunkardSettings { spawn_mode: DrunkSpawnMode::Random, drunken_lifetime: 100, @@ -128,17 +82,12 @@ impl DrunkardsWalkBuilder { brush_size: 2, symmetry: Symmetry::None, }, - spawn_list: Vec::new(), - } + }) } - 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(), + #[allow(dead_code)] + pub fn fearful_symmetry() -> Box { + Box::new(DrunkardsWalkBuilder { settings: DrunkardSettings { spawn_mode: DrunkSpawnMode::Random, drunken_lifetime: 100, @@ -146,51 +95,47 @@ impl DrunkardsWalkBuilder { brush_size: 1, symmetry: Symmetry::Both, }, - spawn_list: Vec::new(), - } + }) } - #[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; + fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + // Set a central starting point + let starting_position = Position { x: build_data.map.width / 2, y: build_data.map.height / 2 }; + let start_idx = build_data.map.xy_idx(starting_position.x, starting_position.y); + build_data.map.tiles[start_idx] = TileType::Floor; - let total_tiles = self.map.width * self.map.height; + let total_tiles = build_data.map.width * build_data.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 floor_tile_count = build_data.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; + drunk_x = starting_position.x; + drunk_y = starting_position.y; } DrunkSpawnMode::Random => { if digger_count == 0 { - drunk_x = self.starting_position.x; - drunk_y = self.starting_position.y; + drunk_x = starting_position.x; + drunk_y = 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; + drunk_x = rng.roll_dice(1, build_data.map.width - 3) + 1; + drunk_y = rng.roll_dice(1, build_data.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 { + let drunk_idx = build_data.map.xy_idx(drunk_x, drunk_y); + if build_data.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; + paint(&mut build_data.map, self.settings.symmetry, self.settings.brush_size, drunk_x, drunk_y); + build_data.map.tiles[drunk_idx] = TileType::DownStair; let stagger_direction = rng.roll_dice(1, 4); match stagger_direction { @@ -200,7 +145,7 @@ impl DrunkardsWalkBuilder { } } 2 => { - if drunk_x < self.map.width - 2 { + if drunk_x < build_data.map.width - 2 { drunk_x += 1; } } @@ -210,46 +155,25 @@ impl DrunkardsWalkBuilder { } } _ => { - if drunk_y < self.map.height - 2 { + if drunk_y < build_data.map.height - 2 { drunk_y += 1; } } } + drunk_life -= 1; } if did_something { - self.take_snapshot(); - active_digger_count += 1; + build_data.take_snapshot(); } digger_count += 1; - for t in self.map.tiles.iter_mut() { + for t in build_data.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); - - // Spawn the entities - for area in self.noise_areas.iter() { - spawner::spawn_region(&self.map, rng, area.1, self.depth, &mut self.spawn_list); + floor_tile_count = build_data.map.tiles.iter().filter(|a| **a == TileType::Floor).count(); } } } diff --git a/src/map_builders/maze.rs b/src/map_builders/maze.rs index a06e44e..5d37496 100644 --- a/src/map_builders/maze.rs +++ b/src/map_builders/maze.rs @@ -1,209 +1,30 @@ -use super::{ - generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder, - Position, TileType, SHOW_MAPGEN, -}; +use super::{BuilderMap, InitialMapBuilder, Map, TileType}; use rltk::RandomNumberGenerator; -use std::collections::HashMap; -pub struct MazeBuilder { - map: Map, - starting_position: Position, - depth: i32, - history: Vec, - noise_areas: HashMap>, - spawn_list: Vec<(usize, String)>, -} +pub struct MazeBuilder {} -impl MapBuilder for MazeBuilder { - fn build_map(&mut self, rng: &mut RandomNumberGenerator) { - return self.build(rng); - } - // Getters - fn get_map(&mut self) -> Map { - return self.map.clone(); - } - fn get_starting_pos(&mut self) -> Position { - return self.starting_position.clone(); - } - fn get_spawn_list(&self) -> &Vec<(usize, String)> { - return &self.spawn_list; - } - // 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 InitialMapBuilder for MazeBuilder { + #[allow(dead_code)] + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build(rng, build_data); } } impl MazeBuilder { #[allow(dead_code)] - pub fn new(new_depth: i32) -> MazeBuilder { - MazeBuilder { - map: Map::new(new_depth), - starting_position: Position { x: 0, y: 0 }, - depth: new_depth, - history: Vec::new(), - noise_areas: HashMap::new(), - spawn_list: Vec::new(), - } + pub fn new() -> Box { + Box::new(MazeBuilder {}) } #[allow(clippy::map_entry)] - fn build(&mut self, rng: &mut RandomNumberGenerator) { + fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { // Maze gen - let mut maze = Grid::new((self.map.width / 2) - 2, (self.map.height / 2) - 2, rng); - maze.generate_maze(self); - - // Starting point @ top left - self.starting_position = Position { x: 2, y: 2 }; - let 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::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); - - // Spawn the entities - for area in self.noise_areas.iter() { - spawner::spawn_region(&self.map, rng, area.1, self.depth, &mut self.spawn_list); - } + let mut maze = Grid::new((build_data.map.width / 2) - 2, (build_data.map.height / 2) - 2, rng); + maze.generate_maze(build_data); } } -struct Grid<'a> { - width: i32, - height: i32, - cells: Vec, - backtrace: Vec, - current: usize, - rng: &'a mut RandomNumberGenerator, -} - -impl<'a> Grid<'a> { - fn new(width: i32, height: i32, rng: &mut RandomNumberGenerator) -> Grid { - let mut grid = Grid { width, height, cells: Vec::new(), backtrace: Vec::new(), current: 0, rng }; - - for row in 0..height { - for column in 0..width { - grid.cells.push(Cell::new(row, column)) - } - } - - return grid; - } - - fn calculate_index(&self, row: i32, column: i32) -> i32 { - if row < 0 || column < 0 || column > self.width - 1 || row > self.height - 1 { - -1 - } else { - column + (row * self.width) - } - } - - fn get_available_neighbours(&self) -> Vec { - let mut neighbours: Vec = Vec::new(); - let current_row = self.cells[self.current].row; - let current_column = self.cells[self.current].column; - - let neighbour_indices: [i32; 4] = [ - self.calculate_index(current_row - 1, current_column), - self.calculate_index(current_row, current_column + 1), - self.calculate_index(current_row + 1, current_column), - self.calculate_index(current_row, current_column - 1), - ]; - - for i in neighbour_indices.iter() { - if *i != -1 && !self.cells[*i as usize].visited { - neighbours.push(*i as usize); - } - } - - return neighbours; - } - - fn find_next_cell(&mut self) -> Option { - let neighbours = self.get_available_neighbours(); - if neighbours.is_empty() { - return None; - } - if neighbours.len() == 1 { - return Some(neighbours[0]); - } - return Some(neighbours[(self.rng.roll_dice(1, neighbours.len() as i32) - 1) as usize]); - } - - fn generate_maze(&mut self, generator: &mut MazeBuilder) { - let mut i = 0; - loop { - self.cells[self.current].visited = true; - let next = self.find_next_cell(); - - match next { - Some(next) => { - self.cells[next].visited = true; - self.backtrace.push(self.current); - let (lower_part, higher_part) = self.cells.split_at_mut(std::cmp::max(self.current, next)); - let cell1 = &mut lower_part[std::cmp::min(self.current, next)]; - let cell2 = &mut higher_part[0]; - cell1.remove_walls(cell2); - self.current = next; - } - None => { - if self.backtrace.is_empty() { - break; - } - self.current = self.backtrace[0]; - self.backtrace.remove(0); - } - } - if i % 50 == 0 { - self.copy_to_map(&mut generator.map); - generator.take_snapshot(); - } - i += 1; - } - } - - fn copy_to_map(&self, map: &mut Map) { - // Clear map - for i in map.tiles.iter_mut() { - *i = TileType::Wall; - } - - for cell in self.cells.iter() { - let x = cell.column + 1; - let y = cell.row + 1; - let idx = map.xy_idx(x * 2, y * 2); - - map.tiles[idx] = TileType::Floor; - if !cell.walls[TOP] { - map.tiles[idx - map.width as usize] = TileType::Floor - } - if !cell.walls[RIGHT] { - map.tiles[idx + 1] = TileType::Floor - } - if !cell.walls[BOTTOM] { - map.tiles[idx + map.width as usize] = TileType::Floor - } - if !cell.walls[LEFT] { - map.tiles[idx - 1] = TileType::Floor - } - } - } -} +/* Maze code taken under MIT from https://github.com/cyucelen/mazeGenerator/ */ const TOP: usize = 0; const RIGHT: usize = 1; @@ -235,10 +56,139 @@ impl Cell { next.walls[LEFT] = false; } else if y == 1 { self.walls[TOP] = false; - next.walls[BOTTOM] = false + next.walls[BOTTOM] = false; } else if y == -1 { self.walls[BOTTOM] = false; next.walls[TOP] = false; } } } + +struct Grid<'a> { + width: i32, + height: i32, + cells: Vec, + backtrace: Vec, + current: usize, + rng: &'a mut RandomNumberGenerator, +} + +impl<'a> Grid<'a> { + fn new(width: i32, height: i32, rng: &mut RandomNumberGenerator) -> Grid { + let mut grid = Grid { width, height, cells: Vec::new(), backtrace: Vec::new(), current: 0, rng }; + + for row in 0..height { + for column in 0..width { + grid.cells.push(Cell::new(row, column)); + } + } + + grid + } + + fn calculate_index(&self, row: i32, column: i32) -> i32 { + if row < 0 || column < 0 || column > self.width - 1 || row > self.height - 1 { + -1 + } else { + column + (row * self.width) + } + } + + fn get_available_neighbors(&self) -> Vec { + let mut neighbors: Vec = Vec::new(); + + let current_row = self.cells[self.current].row; + let current_column = self.cells[self.current].column; + + let neighbor_indices: [i32; 4] = [ + self.calculate_index(current_row - 1, current_column), + self.calculate_index(current_row, current_column + 1), + self.calculate_index(current_row + 1, current_column), + self.calculate_index(current_row, current_column - 1), + ]; + + for i in neighbor_indices.iter() { + if *i != -1 && !self.cells[*i as usize].visited { + neighbors.push(*i as usize); + } + } + + neighbors + } + + fn find_next_cell(&mut self) -> Option { + let neighbors = self.get_available_neighbors(); + if !neighbors.is_empty() { + if neighbors.len() == 1 { + return Some(neighbors[0]); + } else { + return Some(neighbors[(self.rng.roll_dice(1, neighbors.len() as i32) - 1) as usize]); + } + } + None + } + + fn generate_maze(&mut self, build_data: &mut BuilderMap) { + let mut i = 0; + loop { + self.cells[self.current].visited = true; + let next = self.find_next_cell(); + + match next { + Some(next) => { + self.cells[next].visited = true; + self.backtrace.push(self.current); + // __lower_part__ __higher_part_ + // / \ / \ + // --------cell1------ | cell2----------- + let (lower_part, higher_part) = self.cells.split_at_mut(std::cmp::max(self.current, next)); + let cell1 = &mut lower_part[std::cmp::min(self.current, next)]; + let cell2 = &mut higher_part[0]; + cell1.remove_walls(cell2); + self.current = next; + } + None => { + if !self.backtrace.is_empty() { + self.current = self.backtrace[0]; + self.backtrace.remove(0); + } else { + break; + } + } + } + + if i % 50 == 0 { + self.copy_to_map(&mut build_data.map); + build_data.take_snapshot(); + } + i += 1; + } + } + + fn copy_to_map(&self, map: &mut Map) { + // Clear the map + for i in map.tiles.iter_mut() { + *i = TileType::Wall; + } + + for cell in self.cells.iter() { + let x = cell.column + 1; + let y = cell.row + 1; + let idx = map.xy_idx(x * 2, y * 2); + + map.tiles[idx] = TileType::Floor; + if !cell.walls[TOP] { + map.tiles[idx - map.width as usize] = TileType::Floor + } + if !cell.walls[RIGHT] { + map.tiles[idx + 1] = TileType::Floor + } + if !cell.walls[BOTTOM] { + map.tiles[idx + map.width as usize] = TileType::Floor + } + if !cell.walls[LEFT] { + map.tiles[idx - 1] = TileType::Floor + } + } + } +} diff --git a/src/map_builders/mod.rs b/src/map_builders/mod.rs index 9d348ce..e15a595 100644 --- a/src/map_builders/mod.rs +++ b/src/map_builders/mod.rs @@ -17,63 +17,157 @@ mod voronoi; use voronoi::VoronoiBuilder; mod prefab_builder; use prefab_builder::PrefabBuilder; +mod room_based_spawner; mod wfc; +use room_based_spawner::*; +mod room_based_stairs; +use room_based_stairs::*; +mod room_based_starting_position; +use room_based_starting_position::*; +mod area_starting_points; +use area_starting_points::{AreaStartingPosition, XStart, YStart}; +mod cull_unreachable; +use cull_unreachable::CullUnreachable; +mod distant_exit; +use distant_exit::DistantExit; +mod voronoi_spawning; use common::*; -use rltk::RandomNumberGenerator; use specs::prelude::*; +use voronoi_spawning::VoronoiSpawning; use wfc::WaveFunctionCollapseBuilder; -pub trait MapBuilder { - fn build_map(&mut self, rng: &mut RandomNumberGenerator); - fn spawn_entities(&mut self, ecs: &mut World) { - for entity in self.get_spawn_list().iter() { +// Shared data to be passed around build chain +pub struct BuilderMap { + pub spawn_list: Vec<(usize, String)>, + pub map: Map, + pub starting_position: Option, + pub rooms: Option>, + pub history: Vec, +} + +impl BuilderMap { + 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); + } + } +} + +pub struct BuilderChain { + starter: Option>, + builders: Vec>, + pub build_data: BuilderMap, +} + +impl BuilderChain { + pub fn new(new_depth: i32) -> BuilderChain { + BuilderChain { + starter: None, + builders: Vec::new(), + build_data: BuilderMap { + spawn_list: Vec::new(), + map: Map::new(new_depth), + starting_position: None, + rooms: None, + history: Vec::new(), + }, + } + } + + pub fn start_with(&mut self, starter: Box) { + match self.starter { + None => self.starter = Some(starter), + Some(_) => panic!("You can only have one starting builder."), + }; + } + + pub fn with(&mut self, metabuilder: Box) { + self.builders.push(metabuilder); + } + + pub fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator) { + match &mut self.starter { + None => panic!("Cannot run a map builder chain without a starting build system"), + Some(starter) => { + // Build the starting map + starter.build_map(rng, &mut self.build_data); + } + } + + // Build additional layers in turn + for metabuilder in self.builders.iter_mut() { + metabuilder.build_map(rng, &mut self.build_data); + } + } + + pub fn spawn_entities(&mut self, ecs: &mut World) { + for entity in self.build_data.spawn_list.iter() { spawner::spawn_entity(ecs, &(&entity.0, &entity.1)); } } - fn get_map(&mut self) -> Map; - fn get_starting_pos(&mut self) -> Position; - fn get_spawn_list(&self) -> &Vec<(usize, String)>; - fn get_snapshot_history(&self) -> Vec; - fn take_snapshot(&mut self); } -#[rustfmt::skip] -pub fn random_builder(new_depth: i32) -> Box { - let mut rng = rltk::RandomNumberGenerator::new(); - let builder = rng.roll_dice(1, 17); - let mut result : Box; - rltk::console::log("Picking procgen type ->"); - match builder { - 1 => { result = Box::new(BspDungeonBuilder::new(new_depth)); } - 2 => { result = Box::new(BspInteriorBuilder::new(new_depth)); } - 3 => { result = Box::new(CellularAutomataBuilder::new(new_depth)); } - 4 => { result = Box::new(DrunkardsWalkBuilder::open_area(new_depth)); } - 5 => { result = Box::new(DrunkardsWalkBuilder::open_halls(new_depth)); } - 6 => { result = Box::new(DrunkardsWalkBuilder::winding_passages(new_depth)); } - 7 => { result = Box::new(DrunkardsWalkBuilder::fat_passages(new_depth)); } - 8 => { result = Box::new(DrunkardsWalkBuilder::fearful_symmetry(new_depth)); } - 9 => { result = Box::new(MazeBuilder::new(new_depth)); } - 10 => { result = Box::new(DLABuilder::walk_inwards(new_depth)); } - 11 => { result = Box::new(DLABuilder::walk_outwards(new_depth)); } - 12 => { result = Box::new(DLABuilder::central_attractor(new_depth)); } - 13 => { result = Box::new(DLABuilder::insectoid(new_depth)); } - 14 => { result = Box::new(VoronoiBuilder::pythagoras(new_depth)); } - 15 => { result = Box::new(VoronoiBuilder::manhattan(new_depth)); } - 16 => { result = Box::new(PrefabBuilder::constant(new_depth, prefab_builder::prefab_levels::WFC_POPULATED)) }, - _ => { result = Box::new(simple_map::SimpleMapBuilder::new(new_depth)); } - } - - if rng.roll_dice(1, 3)==1 { - result = Box::new(WaveFunctionCollapseBuilder::derived_map(new_depth, result)); - rltk::console::log("-> wfc"); - } - - if rng.roll_dice(1, 20)==1 { - result = Box::new(PrefabBuilder::sectional(new_depth, prefab_builder::prefab_sections::UNDERGROUND_FORT ,result)); - rltk::console::log("-> sectional"); - } - - result = Box::new(PrefabBuilder::vaults(new_depth, result)); - - result +pub trait InitialMapBuilder { + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap); +} + +pub trait MetaMapBuilder { + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap); +} + +fn random_initial_builder(rng: &mut rltk::RandomNumberGenerator) -> (Box, bool) { + let builder = rng.roll_dice(1, 17); + let result: (Box, bool); + match builder { + 1 => result = (BspDungeonBuilder::new(), true), + 2 => result = (BspInteriorBuilder::new(), true), + 3 => result = (CellularAutomataBuilder::new(), false), + 4 => result = (DrunkardsWalkBuilder::open_area(), false), + 5 => result = (DrunkardsWalkBuilder::open_halls(), false), + 6 => result = (DrunkardsWalkBuilder::winding_passages(), false), + 7 => result = (DrunkardsWalkBuilder::fat_passages(), false), + 8 => result = (DrunkardsWalkBuilder::fearful_symmetry(), false), + 9 => result = (MazeBuilder::new(), false), + 10 => result = (DLABuilder::walk_inwards(), false), + 11 => result = (DLABuilder::walk_outwards(), false), + 12 => result = (DLABuilder::central_attractor(), false), + 13 => result = (DLABuilder::insectoid(), false), + 14 => result = (VoronoiBuilder::pythagoras(), false), + 15 => result = (VoronoiBuilder::manhattan(), false), + 16 => result = (PrefabBuilder::constant(prefab_builder::prefab_levels::WFC_POPULATED), false), + _ => result = (simple_map::SimpleMapBuilder::new(), true), } + result +} + +pub fn random_builder(new_depth: i32, rng: &mut rltk::RandomNumberGenerator) -> BuilderChain { + let mut builder = BuilderChain::new(new_depth); + let (random_starter, has_rooms) = random_initial_builder(rng); + builder.start_with(random_starter); + if has_rooms { + builder.with(RoomBasedSpawner::new()); + builder.with(RoomBasedStairs::new()); + builder.with(RoomBasedStartingPosition::new()); + } else { + builder.with(AreaStartingPosition::new(XStart::CENTRE, YStart::CENTRE)); + builder.with(CullUnreachable::new()); + builder.with(VoronoiSpawning::new()); + builder.with(DistantExit::new()); + } + + if rng.roll_dice(1, 3) == 1 { + builder.with(WaveFunctionCollapseBuilder::new()); + } + + if rng.roll_dice(1, 20) == 1 { + builder.with(PrefabBuilder::sectional(prefab_builder::prefab_sections::UNDERGROUND_FORT)); + } + + builder.with(PrefabBuilder::vaults()); + + builder +} diff --git a/src/map_builders/prefab_builder/mod.rs b/src/map_builders/prefab_builder/mod.rs index b4d6b65..b285029 100644 --- a/src/map_builders/prefab_builder/mod.rs +++ b/src/map_builders/prefab_builder/mod.rs @@ -1,193 +1,290 @@ -use super::{remove_unreachable_areas_returning_most_distant, Map, MapBuilder, Position, TileType, SHOW_MAPGEN}; +use super::{BuilderMap, InitialMapBuilder, MetaMapBuilder, Position, TileType}; use rltk::RandomNumberGenerator; pub mod prefab_levels; pub mod prefab_sections; pub mod prefab_vaults; use std::collections::HashSet; +#[derive(PartialEq, Copy, Clone)] #[allow(dead_code)] -#[derive(PartialEq, Clone)] pub enum PrefabMode { RexLevel { template: &'static str }, Constant { level: prefab_levels::PrefabLevel }, Sectional { section: prefab_sections::PrefabSection }, - Vaults, -} - -pub struct PrefabBuilder { - map: Map, - starting_position: Position, - depth: i32, - history: Vec, - mode: PrefabMode, - spawn_list: Vec<(usize, String)>, - previous_builder: Option>, -} - -impl MapBuilder for PrefabBuilder { - fn build_map(&mut self, rng: &mut RandomNumberGenerator) { - return self.build(rng); - } - // Getters - fn get_map(&mut self) -> Map { - return self.map.clone(); - } - fn get_starting_pos(&mut self) -> Position { - return self.starting_position.clone(); - } - fn get_spawn_list(&self) -> &Vec<(usize, String)> { - return &self.spawn_list; - } - // 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); - } - } + RoomVaults, } #[allow(dead_code)] +pub struct PrefabBuilder { + mode: PrefabMode, +} + +impl MetaMapBuilder for PrefabBuilder { + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build(rng, build_data); + } +} + +impl InitialMapBuilder for PrefabBuilder { + #[allow(dead_code)] + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build(rng, build_data); + } +} + impl PrefabBuilder { - pub fn rex_level(new_depth: i32, template: &'static str) -> PrefabBuilder { - PrefabBuilder { - map: Map::new(new_depth), - starting_position: Position { x: 0, y: 0 }, - depth: new_depth, - history: Vec::new(), - mode: PrefabMode::RexLevel { template }, - spawn_list: Vec::new(), - previous_builder: None, - } - } - pub fn constant(new_depth: i32, level: prefab_levels::PrefabLevel) -> PrefabBuilder { - PrefabBuilder { - map: Map::new(new_depth), - starting_position: Position { x: 0, y: 0 }, - depth: new_depth, - history: Vec::new(), - mode: PrefabMode::Constant { level }, - spawn_list: Vec::new(), - previous_builder: None, - } - } - pub fn sectional( - new_depth: i32, - section: prefab_sections::PrefabSection, - previous_builder: Box, - ) -> PrefabBuilder { - PrefabBuilder { - map: Map::new(new_depth), - starting_position: Position { x: 0, y: 0 }, - depth: new_depth, - history: Vec::new(), - mode: PrefabMode::Sectional { section }, - spawn_list: Vec::new(), - previous_builder: Some(previous_builder), - } - } - pub fn vaults(new_depth: i32, previous_builder: Box) -> PrefabBuilder { - PrefabBuilder { - map: Map::new(new_depth), - starting_position: Position { x: 0, y: 0 }, - depth: new_depth, - history: Vec::new(), - mode: PrefabMode::Vaults, - spawn_list: Vec::new(), - previous_builder: Some(previous_builder), - } + #[allow(dead_code)] + pub fn new() -> Box { + Box::new(PrefabBuilder { mode: PrefabMode::RoomVaults }) } - fn build(&mut self, rng: &mut RandomNumberGenerator) { + #[allow(dead_code)] + pub fn rex_level(template: &'static str) -> Box { + Box::new(PrefabBuilder { mode: PrefabMode::RexLevel { template } }) + } + + #[allow(dead_code)] + pub fn constant(level: prefab_levels::PrefabLevel) -> Box { + Box::new(PrefabBuilder { mode: PrefabMode::Constant { level } }) + } + + #[allow(dead_code)] + pub fn sectional(section: prefab_sections::PrefabSection) -> Box { + Box::new(PrefabBuilder { mode: PrefabMode::Sectional { section } }) + } + + #[allow(dead_code)] + pub fn vaults() -> Box { + Box::new(PrefabBuilder { mode: PrefabMode::RoomVaults }) + } + + fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { match self.mode { - PrefabMode::RexLevel { template } => self.load_rex_map(&template), - PrefabMode::Constant { level } => self.load_ascii_map(&level), - PrefabMode::Sectional { section } => self.apply_sectional(§ion, rng), - PrefabMode::Vaults => self.apply_room_vaults(rng), + PrefabMode::RexLevel { template } => self.load_rex_map(&template, build_data), + PrefabMode::Constant { level } => self.load_ascii_map(&level, build_data), + PrefabMode::Sectional { section } => self.apply_sectional(§ion, rng, build_data), + PrefabMode::RoomVaults => self.apply_room_vaults(rng, build_data), } - self.take_snapshot(); + build_data.take_snapshot(); + } - // Find starting pos by starting at middle and walking left until finding a floor tile - if self.starting_position.x == 0 { - 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); + fn char_to_map(&mut self, ch: char, idx: usize, build_data: &mut BuilderMap) { + match ch { + ' ' => build_data.map.tiles[idx] = TileType::Floor, + '#' => build_data.map.tiles[idx] = TileType::Wall, + '>' => build_data.map.tiles[idx] = TileType::DownStair, + '≈' => build_data.map.tiles[idx] = TileType::Floor, // Placeholder for vines/brush + '@' => { + let x = idx as i32 % build_data.map.width; + let y = idx as i32 / build_data.map.width; + build_data.map.tiles[idx] = TileType::Floor; + build_data.starting_position = Some(Position { x: x as i32, y: y as i32 }); + } + 'g' => { + build_data.map.tiles[idx] = TileType::Floor; + build_data.spawn_list.push((idx, "goblin".to_string())); + } + 'G' => { + build_data.map.tiles[idx] = TileType::Floor; + build_data.spawn_list.push((idx, "goblin chieftain".to_string())); + } + 'o' => { + build_data.map.tiles[idx] = TileType::Floor; + build_data.spawn_list.push((idx, "orc".to_string())); + } + '^' => { + build_data.map.tiles[idx] = TileType::Floor; + build_data.spawn_list.push((idx, "bear trap".to_string())); + } + '%' => { + build_data.map.tiles[idx] = TileType::Floor; + build_data.spawn_list.push((idx, "rations".to_string())); + } + '!' => { + build_data.map.tiles[idx] = TileType::Floor; + build_data.spawn_list.push((idx, "health potion".to_string())); + } + '/' => { + build_data.map.tiles[idx] = TileType::Floor; + build_data.spawn_list.push((idx, "magic missile wand".to_string())); + // Placeholder for wand spawn + } + '?' => { + build_data.map.tiles[idx] = TileType::Floor; + build_data.spawn_list.push((idx, "fireball scroll".to_string())); + // Placeholder for scroll spawn + } + _ => { + rltk::console::log(format!("Unknown glyph '{}' when loading prefab", (ch as u8) as char)); } - 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::DownStair; - self.take_snapshot(); } } - fn apply_previous_iteration(&mut self, rng: &mut RandomNumberGenerator, mut filter: F) - where - F: FnMut(i32, i32, &(usize, String)) -> bool, + #[allow(dead_code)] + fn load_rex_map(&mut self, path: &str, build_data: &mut BuilderMap) { + let xp_file = rltk::rex::XpFile::from_resource(path).unwrap(); + + for layer in &xp_file.layers { + for y in 0..layer.height { + for x in 0..layer.width { + let cell = layer.get(x, y).unwrap(); + if x < build_data.map.width as usize && y < build_data.map.height as usize { + let idx = build_data.map.xy_idx(x as i32, y as i32); + // We're doing some nasty casting to make it easier to type things like '#' in the match + self.char_to_map(cell.ch as u8 as char, idx, build_data); + } + } + } + } + } + + fn read_ascii_to_vec(template: &str) -> Vec { + let mut string_vec: Vec = template.chars().filter(|a| *a != '\r' && *a != '\n').collect(); + for c in string_vec.iter_mut() { + if *c as u8 == 160u8 { + *c = ' '; + } + } + string_vec + } + + #[allow(dead_code)] + fn load_ascii_map(&mut self, level: &prefab_levels::PrefabLevel, build_data: &mut BuilderMap) { + let string_vec = PrefabBuilder::read_ascii_to_vec(level.template); + + let mut i = 0; + for ty in 0..level.height { + for tx in 0..level.width { + if tx < build_data.map.width as usize && ty < build_data.map.height as usize { + let idx = build_data.map.xy_idx(tx as i32, ty as i32); + if i < string_vec.len() { + self.char_to_map(string_vec[i], idx, build_data); + } + } + i += 1; + } + } + } + + fn apply_previous_iteration( + &mut self, + mut filter: F, + _rng: &mut RandomNumberGenerator, + build_data: &mut BuilderMap, + ) where + F: FnMut(i32, i32) -> bool, { - // Build the map - let prev_builder = self.previous_builder.as_mut().unwrap(); - prev_builder.build_map(rng); - self.starting_position = prev_builder.get_starting_pos(); - self.map = prev_builder.get_map().clone(); - self.history = prev_builder.get_snapshot_history(); - for e in prev_builder.get_spawn_list().iter() { - let idx = e.0; - let x = idx as i32 % self.map.width; - let y = idx as i32 / self.map.width; - if filter(x, y, e) { - self.spawn_list.push((idx, e.1.to_string())) - } - } - self.take_snapshot(); + let width = build_data.map.width; + build_data.spawn_list.retain(|(idx, _name)| { + let x = *idx as i32 % width; + let y = *idx as i32 / width; + filter(x, y) + }); + build_data.take_snapshot(); } - fn apply_room_vaults(&mut self, rng: &mut RandomNumberGenerator) { + #[allow(dead_code)] + fn apply_sectional( + &mut self, + section: &prefab_sections::PrefabSection, + rng: &mut RandomNumberGenerator, + build_data: &mut BuilderMap, + ) { + use prefab_sections::*; + + let string_vec = PrefabBuilder::read_ascii_to_vec(section.template); + + // Place the new section + let chunk_x; + match section.placement.0 { + HorizontalPlacement::Left => chunk_x = 0, + HorizontalPlacement::Center => chunk_x = (build_data.map.width / 2) - (section.width as i32 / 2), + HorizontalPlacement::Right => chunk_x = (build_data.map.width - 1) - section.width as i32, + } + + let chunk_y; + match section.placement.1 { + VerticalPlacement::Top => chunk_y = 0, + VerticalPlacement::Center => chunk_y = (build_data.map.height / 2) - (section.height as i32 / 2), + VerticalPlacement::Bottom => chunk_y = (build_data.map.height - 1) - section.height as i32, + } + + // Build the map + self.apply_previous_iteration( + |x, y| { + x < chunk_x + || x > (chunk_x + section.width as i32) + || y < chunk_y + || y > (chunk_y + section.height as i32) + }, + rng, + build_data, + ); + + let mut i = 0; + for ty in 0..section.height { + for tx in 0..section.width { + if tx > 0 && tx < build_data.map.width as usize - 1 && ty < build_data.map.height as usize - 1 && ty > 0 + { + let idx = build_data.map.xy_idx(tx as i32 + chunk_x, ty as i32 + chunk_y); + if i < string_vec.len() { + self.char_to_map(string_vec[i], idx, build_data); + } + } + i += 1; + } + } + build_data.take_snapshot(); + } + + fn apply_room_vaults(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { use prefab_vaults::*; - // Apply prev builder, and keep all entities - self.apply_previous_iteration(rng, |_x, _y, _e| true); + // Apply the previous builder, and keep all entities it spawns (for now) + self.apply_previous_iteration(|_x, _y| true, rng, build_data); - // Roll for a vault - let vault_roll = rng.roll_dice(1, 6); + // Do we want a vault at all? + let vault_roll = rng.roll_dice(1, 6) + build_data.map.depth; if vault_roll < 4 { return; } - // Get all vaults - let master_vault_list = vec![GOBLINS_4X4, GOBLINS2_4X4, CLASSIC_TRAP_5X5]; - // Filter out vaults from outside the current depth - let mut possible_vaults: Vec<&PrefabVault> = - master_vault_list.iter().filter(|v| self.depth >= v.first_depth && self.depth <= v.last_depth).collect(); - // Return if there's no possible vaults + // Note that this is a place-holder and will be moved out of this function + let master_vault_list = vec![ + CLASSIC_TRAP_5X5, + CLASSIC_TRAP_DIAGONALGAP_5X5, + CLASSIC_TRAP_CARDINALGAP_5X5, + GOBLINS_4X4, + GOBLINS2_4X4, + GOBLINS_5X5, + GOBLINS_6X6, + FLUFF_6X3, + FLUFF2_6X3, + HOUSE_NOTRAP_7X7, + HOUSE_TRAP_7X7, + ORC_HOUSE_8X8, + ]; + + // Filter the vault list down to ones that are applicable to the current depth + let mut possible_vaults: Vec<&PrefabVault> = master_vault_list + .iter() + .filter(|v| build_data.map.depth >= v.first_depth && build_data.map.depth <= v.last_depth) + .collect(); + if possible_vaults.is_empty() { return; - } + } // Bail out if there's nothing to build - // Pick number of vaults let n_vaults = i32::min(rng.roll_dice(1, 3), possible_vaults.len() as i32); let mut used_tiles: HashSet = HashSet::new(); for _i in 0..n_vaults { - // Select a vault - let vault_idx = if possible_vaults.len() == 1 { + let vault_index = if possible_vaults.len() == 1 { 0 } else { (rng.roll_dice(1, possible_vaults.len() as i32) - 1) as usize }; - let vault = possible_vaults[vault_idx]; + let vault = possible_vaults[vault_index]; // Decide if we want to flip the vault let mut flip_x: bool = false; let mut flip_y: bool = false; @@ -214,23 +311,25 @@ impl PrefabBuilder { } } - // Make a list of all places the vault can fit + // We'll make a list of places in which the vault could fit let mut vault_positions: Vec = Vec::new(); + let mut idx = 0usize; loop { - let x = (idx % self.map.width as usize) as i32; - let y = (idx / self.map.width as usize) as i32; - // Check for overflow + let x = (idx % build_data.map.width as usize) as i32; + let y = (idx / build_data.map.width as usize) as i32; + + // Check that we won't overflow the map if x > 1 - && (x + vault.width as i32) < self.map.width - 2 + && (x + vault.width as i32) < build_data.map.width - 2 && y > 1 - && (y + vault.height as i32) < self.map.height - 2 + && (y + vault.height as i32) < build_data.map.height - 2 { let mut possible = true; - for tile_y in 0..vault.height as i32 { - for tile_x in 0..vault.width as i32 { - let idx = self.map.xy_idx(tile_x + x, tile_y + y); - if self.map.tiles[idx] != TileType::Floor { + for ty in 0..vault.height as i32 { + for tx in 0..vault.width as i32 { + let idx = build_data.map.xy_idx(tx + x, ty + y); + if build_data.map.tiles[idx] != TileType::Floor { possible = false; } if used_tiles.contains(&idx) { @@ -238,19 +337,19 @@ impl PrefabBuilder { } } } - // If we find a position that works, push it + if possible { vault_positions.push(Position { x, y }); + break; } } - // Once we reach the end of the map, break + idx += 1; - if idx >= self.map.tiles.len() - 1 { + if idx >= build_data.map.tiles.len() - 1 { break; } } - // If we have a position, make the vault if !vault_positions.is_empty() { let pos_idx = if vault_positions.len() == 1 { 0 @@ -258,17 +357,19 @@ impl PrefabBuilder { (rng.roll_dice(1, vault_positions.len() as i32) - 1) as usize }; let pos = &vault_positions[pos_idx]; + let chunk_x = pos.x; let chunk_y = pos.y; - // Filter out entities from our spawn list that would have spawned inside this vault - let width = self.map.width; // For borrow checker. - let height = self.map.height; // As above. - self.spawn_list.retain(|e| { + + let width = build_data.map.width; // The borrow checker really doesn't like it + let height = build_data.map.height; // when we access `self` inside the `retain` + build_data.spawn_list.retain(|e| { let idx = e.0 as i32; let x = idx % width; let y = idx / height; x < chunk_x || x > chunk_x + vault.width as i32 || y < chunk_y || y > chunk_y + vault.height as i32 }); + let string_vec = PrefabBuilder::read_ascii_to_vec(vault.template); let mut i = 0; for tile_y in 0..vault.height { @@ -282,140 +383,17 @@ impl PrefabBuilder { if flip_y { y_ = vault.width as i32 - 1 - y_; } - self.map.xy_idx(x_ + chunk_x, y_ as i32 + chunk_y); - self.char_to_map(string_vec[i], idx); + let idx = build_data.map.xy_idx(x_ + chunk_x, y_ + chunk_y); + if i < string_vec.len() { + self.char_to_map(string_vec[i], idx, build_data); + } used_tiles.insert(idx); i += 1; } } - rltk::console::log("-> adding vault"); - self.take_snapshot(); - possible_vaults.remove(vault_idx); - } - } - } + build_data.take_snapshot(); - pub fn apply_sectional(&mut self, section: &prefab_sections::PrefabSection, rng: &mut RandomNumberGenerator) { - use prefab_sections::*; - let string_vec = PrefabBuilder::read_ascii_to_vec(section.template); - - // Place the new section - let chunk_x; - match section.placement.0 { - HorizontalPlacement::Left => chunk_x = 0, - HorizontalPlacement::Center => chunk_x = (self.map.width / 2) - (section.width as i32 / 2), - HorizontalPlacement::Right => chunk_x = (self.map.width - 1) - section.width as i32, - } - - let chunk_y; - match section.placement.1 { - VerticalPlacement::Top => chunk_y = 0, - VerticalPlacement::Center => chunk_y = (self.map.height / 2) - (section.height as i32 / 2), - VerticalPlacement::Bottom => chunk_y = (self.map.height - 1) - section.height as i32, - } - - // Build the map - self.apply_previous_iteration(rng, |x, y, _e| { - x < chunk_x || x > (chunk_x + section.width as i32) || y < chunk_y || y > (chunk_y + section.height as i32) - }); - - let mut i = 0; - for ty in 0..section.height { - for tx in 0..section.width { - if tx < self.map.width as usize && ty < self.map.height as usize { - let idx = self.map.xy_idx(tx as i32 + chunk_x, ty as i32 + chunk_y); - self.char_to_map(string_vec[i], idx); - } - i += 1; - } - } - self.take_snapshot(); - } - - fn char_to_map(&mut self, ch: char, idx: usize) { - match ch { - ' ' => self.map.tiles[idx] = TileType::Floor, - '#' => self.map.tiles[idx] = TileType::Wall, - '>' => self.map.tiles[idx] = TileType::DownStair, - '@' => { - let x = idx as i32 % self.map.width; - let y = idx as i32 / self.map.width; - self.map.tiles[idx] = TileType::Floor; - self.starting_position = Position { x: x as i32, y: y as i32 }; - } - 'g' => { - self.map.tiles[idx] = TileType::Floor; - self.spawn_list.push((idx, "goblin".to_string())); - } - 'G' => { - self.map.tiles[idx] = TileType::Floor; - self.spawn_list.push((idx, "goblin chieftain".to_string())); - } - 'o' => { - self.map.tiles[idx] = TileType::Floor; - self.spawn_list.push((idx, "orc".to_string())); - } - '^' => { - self.map.tiles[idx] = TileType::Floor; - self.spawn_list.push((idx, "bear trap".to_string())); - } - '%' => { - self.map.tiles[idx] = TileType::Floor; - self.spawn_list.push((idx, "rations".to_string())); - } - '!' => { - self.map.tiles[idx] = TileType::Floor; - self.spawn_list.push((idx, "health potion".to_string())); - } - _ => { - rltk::console::log(format!("Unknown glyph loading map: {}", (ch as u8) as char)); - } - } - } - - #[allow(dead_code)] - fn load_rex_map(&mut self, path: &str) { - let xp_file = rltk::rex::XpFile::from_resource(path).unwrap(); - - for layer in &xp_file.layers { - for y in 0..layer.height { - for x in 0..layer.width { - let cell = layer.get(x, y).unwrap(); - if x < self.map.width as usize && y < self.map.height as usize { - // Saving these for later, for flipping the pref horizontally/vertically/both. - // let flipped_x = (self.map.width - 1) - x as i32; - // let flipped_y = (self.map.height - 1) - y as i32; - let idx = self.map.xy_idx(x as i32, y as i32); - // We're doing some nasty casting to make it easier to type things like '#' in the match - self.char_to_map(cell.ch as u8 as char, idx); - } - } - } - } - } - - fn read_ascii_to_vec(template: &str) -> Vec { - let mut string_vec: Vec = template.chars().filter(|a| *a != '\r' && *a != '\n').collect(); - for c in string_vec.iter_mut() { - if *c as u8 == 160u8 { - *c = ' '; - } - } - return string_vec; - } - - #[allow(dead_code)] - fn load_ascii_map(&mut self, level: &prefab_levels::PrefabLevel) { - let string_vec = PrefabBuilder::read_ascii_to_vec(level.template); - - let mut i = 0; - for y in 0..level.height { - for x in 0..level.width { - if x < self.map.width as usize && y < self.map.height as usize { - let idx = self.map.xy_idx(x as i32, y as i32); - self.char_to_map(string_vec[i], idx); - } - i += 1; + possible_vaults.remove(vault_index); } } } diff --git a/src/map_builders/prefab_builder/prefab_vaults.rs b/src/map_builders/prefab_builder/prefab_vaults.rs index 2f0fde2..a16ff05 100644 --- a/src/map_builders/prefab_builder/prefab_vaults.rs +++ b/src/map_builders/prefab_builder/prefab_vaults.rs @@ -17,7 +17,6 @@ pub struct PrefabVault { pub can_flip: Flipping, } -#[allow(dead_code)] pub const CLASSIC_TRAP_5X5: PrefabVault = PrefabVault { template: CLASSIC_TRAP_5X5_V, width: 5, @@ -26,7 +25,6 @@ pub const CLASSIC_TRAP_5X5: PrefabVault = PrefabVault { last_depth: 100, can_flip: Flipping::None, }; -#[allow(dead_code)] const CLASSIC_TRAP_5X5_V: &str = " ^^^ @@ -35,7 +33,38 @@ const CLASSIC_TRAP_5X5_V: &str = " "; -#[allow(dead_code)] +pub const CLASSIC_TRAP_CARDINALGAP_5X5: PrefabVault = PrefabVault { + template: CLASSIC_TRAP_CARDINALGAP_5X5_V, + width: 5, + height: 5, + first_depth: 0, + last_depth: 100, + can_flip: Flipping::Both, +}; +const CLASSIC_TRAP_CARDINALGAP_5X5_V: &str = " +      + ^ ^  + ^!^  + ^^^  +      +"; + +pub const CLASSIC_TRAP_DIAGONALGAP_5X5: PrefabVault = PrefabVault { + template: CLASSIC_TRAP_DIAGONALGAP_5X5_V, + width: 5, + height: 5, + first_depth: 0, + last_depth: 100, + can_flip: Flipping::Both, +}; +const CLASSIC_TRAP_DIAGONALGAP_5X5_V: &str = " +      + ^^   + ^!^  + ^^^  +      +"; + pub const GOBLINS_4X4: PrefabVault = PrefabVault { template: GOBLINS_4X4_V, width: 4, @@ -51,7 +80,6 @@ const GOBLINS_4X4_V: &str = " ^g^ "; -#[allow(dead_code)] pub const GOBLINS2_4X4: PrefabVault = PrefabVault { template: GOBLINS2_4X4_V, width: 4, @@ -66,3 +94,119 @@ G# # g#  # g^ "; + +pub const GOBLINS_5X5: PrefabVault = PrefabVault { + template: GOBLINS_5X5_V, + width: 5, + height: 5, + first_depth: 0, + last_depth: 100, + can_flip: Flipping::Both, +}; +const GOBLINS_5X5_V: &str = " + ^#g  +G#?#^ +/g g# +## ^# +^# #  +"; + +pub const GOBLINS_6X6: PrefabVault = PrefabVault { + template: GOBLINS_6X6_V, + width: 6, + height: 6, + first_depth: 0, + last_depth: 100, + can_flip: Flipping::Both, +}; +const GOBLINS_6X6_V: &str = " +   #   + #^#g  +#G#$#^ + /gGg# +g##$^ + ^ # ^ +"; + +pub const FLUFF_6X3: PrefabVault = PrefabVault { + template: FLUFF_6X3_V, + width: 6, + height: 3, + first_depth: 0, + last_depth: 100, + can_flip: Flipping::Both, +}; +const FLUFF_6X3_V: &str = " +###≈^  + ^≈ #≈ + ≈##≈  +"; + +pub const FLUFF2_6X3: PrefabVault = PrefabVault { + template: FLUFF2_6X3_V, + width: 6, + height: 3, + first_depth: 0, + last_depth: 100, + can_flip: Flipping::Both, +}; +const FLUFF2_6X3_V: &str = " + ^≈### +≈# ≈^  + ≈##≈  +"; + +pub const HOUSE_NOTRAP_7X7: PrefabVault = PrefabVault { + template: HOUSE_NOTRAP_7X7_V, + width: 7, + height: 7, + first_depth: 0, + last_depth: 100, + can_flip: Flipping::Both, +}; +const HOUSE_NOTRAP_7X7_V: &str = " +#   # +#   g # +  # #   +   ?    +  # #   +# g   # + #   # +"; + +pub const HOUSE_TRAP_7X7: PrefabVault = PrefabVault { + template: HOUSE_TRAP_7X7_V, + width: 7, + height: 7, + first_depth: 0, + last_depth: 100, + can_flip: Flipping::Both, +}; +const HOUSE_TRAP_7X7_V: &str = " +#   # +#     # +  #^#   +  ^? g  + g#^#   +#     # + #   # +"; + +pub const ORC_HOUSE_8X8: PrefabVault = PrefabVault { + template: ORC_HOUSE_8X8_V, + width: 8, + height: 8, + first_depth: 0, + last_depth: 100, + can_flip: Flipping::Both, +}; +const ORC_HOUSE_8X8_V: &str = " + +###### +#o g # +#   %# +# %o # +#   ?# +##+### + +"; diff --git a/src/map_builders/room_based_spawner.rs b/src/map_builders/room_based_spawner.rs new file mode 100644 index 0000000..6ac1696 --- /dev/null +++ b/src/map_builders/room_based_spawner.rs @@ -0,0 +1,27 @@ +use super::{spawner, BuilderMap, MetaMapBuilder}; +use rltk::RandomNumberGenerator; + +pub struct RoomBasedSpawner {} + +impl MetaMapBuilder for RoomBasedSpawner { + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build(rng, build_data); + } +} + +impl RoomBasedSpawner { + #[allow(dead_code)] + pub fn new() -> Box { + Box::new(RoomBasedSpawner {}) + } + + fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + if let Some(rooms) = &build_data.rooms { + for room in rooms.iter().skip(1) { + spawner::spawn_room(&build_data.map, rng, room, build_data.map.depth, &mut build_data.spawn_list); + } + } else { + panic!("RoomBasedSpawner only works after rooms have been created"); + } + } +} diff --git a/src/map_builders/room_based_stairs.rs b/src/map_builders/room_based_stairs.rs new file mode 100644 index 0000000..1c1a268 --- /dev/null +++ b/src/map_builders/room_based_stairs.rs @@ -0,0 +1,28 @@ +use super::{BuilderMap, MetaMapBuilder, TileType}; +use rltk::RandomNumberGenerator; + +pub struct RoomBasedStairs {} + +impl MetaMapBuilder for RoomBasedStairs { + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build(rng, build_data); + } +} + +impl RoomBasedStairs { + #[allow(dead_code)] + pub fn new() -> Box { + Box::new(RoomBasedStairs {}) + } + + fn build(&mut self, _rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + if let Some(rooms) = &build_data.rooms { + let stairs_position = rooms[rooms.len() - 1].centre(); + let stairs_idx = build_data.map.xy_idx(stairs_position.0, stairs_position.1); + build_data.map.tiles[stairs_idx] = TileType::DownStair; + build_data.take_snapshot(); + } else { + panic!("RoomBasedStairs only works after rooms have been created"); + } + } +} diff --git a/src/map_builders/room_based_starting_position.rs b/src/map_builders/room_based_starting_position.rs new file mode 100644 index 0000000..0550f03 --- /dev/null +++ b/src/map_builders/room_based_starting_position.rs @@ -0,0 +1,26 @@ +use super::{BuilderMap, MetaMapBuilder, Position}; +use rltk::RandomNumberGenerator; + +pub struct RoomBasedStartingPosition {} + +impl MetaMapBuilder for RoomBasedStartingPosition { + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build(rng, build_data); + } +} + +impl RoomBasedStartingPosition { + #[allow(dead_code)] + pub fn new() -> Box { + Box::new(RoomBasedStartingPosition {}) + } + + fn build(&mut self, _rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + if let Some(rooms) = &build_data.rooms { + let start_pos = rooms[0].centre(); + build_data.starting_position = Some(Position { x: start_pos.0, y: start_pos.1 }); + } else { + panic!("RoomBasedStartingPosition only works after rooms have been created"); + } + } +} diff --git a/src/map_builders/simple_map.rs b/src/map_builders/simple_map.rs index fa67750..1e4dcf0 100644 --- a/src/map_builders/simple_map.rs +++ b/src/map_builders/simple_map.rs @@ -1,107 +1,59 @@ -use super::{ - apply_horizontal_tunnel, apply_room_to_map, apply_vertical_tunnel, spawner, Map, MapBuilder, Position, Rect, - TileType, SHOW_MAPGEN, -}; +use super::{apply_horizontal_tunnel, apply_room_to_map, apply_vertical_tunnel, BuilderMap, InitialMapBuilder, Rect}; use rltk::RandomNumberGenerator; -pub struct SimpleMapBuilder { - map: Map, - starting_position: Position, - depth: i32, - rooms: Vec, - history: Vec, - spawn_list: Vec<(usize, String)>, -} +pub struct SimpleMapBuilder {} -impl MapBuilder for SimpleMapBuilder { - fn build_map(&mut self, rng: &mut RandomNumberGenerator) { - return self.rooms_and_corridors(rng); - } - // Getters - fn get_map(&mut self) -> Map { - return self.map.clone(); - } - fn get_starting_pos(&mut self) -> Position { - return self.starting_position.clone(); - } - fn get_spawn_list(&self) -> &Vec<(usize, String)> { - return &self.spawn_list; - } - // 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 InitialMapBuilder for SimpleMapBuilder { + #[allow(dead_code)] + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) { + self.rooms_and_corridors(rng, build_data); } } impl SimpleMapBuilder { #[allow(dead_code)] - pub fn new(new_depth: i32) -> SimpleMapBuilder { - SimpleMapBuilder { - map: Map::new(new_depth), - starting_position: Position { x: 0, y: 0 }, - depth: new_depth, - rooms: Vec::new(), - history: Vec::new(), - spawn_list: Vec::new(), - } + pub fn new() -> Box { + Box::new(SimpleMapBuilder {}) } - fn rooms_and_corridors(&mut self, rng: &mut RandomNumberGenerator) { + fn rooms_and_corridors(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { const MAX_ROOMS: i32 = 30; const MIN_SIZE: i32 = 6; const MAX_SIZE: i32 = 10; + let mut rooms: Vec = Vec::new(); for _i in 0..MAX_ROOMS { let w = rng.range(MIN_SIZE, MAX_SIZE); let h = rng.range(MIN_SIZE, MAX_SIZE); - let x = rng.roll_dice(1, self.map.width - w - 1) - 1; - let y = rng.roll_dice(1, self.map.height - h - 1) - 1; + let x = rng.roll_dice(1, build_data.map.width - w - 1) - 1; + let y = rng.roll_dice(1, build_data.map.height - h - 1) - 1; let new_room = Rect::new(x, y, w, h); let mut ok = true; - for other_room in self.rooms.iter() { + for other_room in rooms.iter() { if new_room.intersect(other_room) { ok = false } } if ok { - apply_room_to_map(&mut self.map, &new_room); + apply_room_to_map(&mut build_data.map, &new_room); + build_data.take_snapshot(); - if !self.rooms.is_empty() { + if !rooms.is_empty() { let (new_x, new_y) = new_room.centre(); - let (prev_x, prev_y) = self.rooms[self.rooms.len() - 1].centre(); + let (prev_x, prev_y) = rooms[rooms.len() - 1].centre(); if rng.range(0, 2) == 1 { - apply_horizontal_tunnel(&mut self.map, prev_x, new_x, prev_y); - apply_vertical_tunnel(&mut self.map, prev_y, new_y, new_x); + apply_horizontal_tunnel(&mut build_data.map, prev_x, new_x, prev_y); + apply_vertical_tunnel(&mut build_data.map, prev_y, new_y, new_x); } else { - apply_vertical_tunnel(&mut self.map, prev_y, new_y, prev_x); - apply_horizontal_tunnel(&mut self.map, prev_x, new_x, new_y); + apply_vertical_tunnel(&mut build_data.map, prev_y, new_y, prev_x); + apply_horizontal_tunnel(&mut build_data.map, prev_x, new_x, new_y); } } - self.rooms.push(new_room); - self.take_snapshot(); + rooms.push(new_room); + build_data.take_snapshot(); } } - - let stairs_position = self.rooms[self.rooms.len() - 1].centre(); - let stairs_idx = self.map.xy_idx(stairs_position.0, stairs_position.1); - self.map.tiles[stairs_idx] = TileType::DownStair; - - let start_pos = self.rooms[0].centre(); - self.starting_position = Position { x: start_pos.0, y: start_pos.1 }; - - // Spawn entities - for room in self.rooms.iter().skip(1) { - spawner::spawn_room(&self.map, rng, room, self.depth, &mut self.spawn_list); - } + build_data.rooms = Some(rooms); } } diff --git a/src/map_builders/voronoi.rs b/src/map_builders/voronoi.rs index b1d7491..b48bcb7 100644 --- a/src/map_builders/voronoi.rs +++ b/src/map_builders/voronoi.rs @@ -1,12 +1,8 @@ -use super::{ - generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder, - Position, TileType, SHOW_MAPGEN, -}; +use super::{BuilderMap, InitialMapBuilder, TileType}; use rltk::RandomNumberGenerator; -use std::collections::HashMap; -#[allow(dead_code)] #[derive(PartialEq, Copy, Clone)] +#[allow(dead_code)] pub enum DistanceAlgorithm { Pythagoras, Manhattan, @@ -14,94 +10,42 @@ pub enum DistanceAlgorithm { } pub struct VoronoiBuilder { - map: Map, - starting_position: Position, - depth: i32, - history: Vec, - noise_areas: HashMap>, n_seeds: usize, distance_algorithm: DistanceAlgorithm, - spawn_list: Vec<(usize, String)>, } -impl MapBuilder for VoronoiBuilder { - fn build_map(&mut self, rng: &mut RandomNumberGenerator) { - return self.build(rng); - } - // Getters - fn get_map(&mut self) -> Map { - return self.map.clone(); - } - fn get_starting_pos(&mut self) -> Position { - return self.starting_position.clone(); - } - fn get_spawn_list(&self) -> &Vec<(usize, String)> { - return &self.spawn_list; - } - // 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); - } - } -} - -#[allow(dead_code)] -impl VoronoiBuilder { - pub fn pythagoras(new_depth: i32) -> VoronoiBuilder { - VoronoiBuilder { - map: Map::new(new_depth), - starting_position: Position { x: 0, y: 0 }, - depth: new_depth, - history: Vec::new(), - noise_areas: HashMap::new(), - n_seeds: 64, - distance_algorithm: DistanceAlgorithm::Pythagoras, - spawn_list: Vec::new(), - } - } - pub fn manhattan(new_depth: i32) -> VoronoiBuilder { - VoronoiBuilder { - map: Map::new(new_depth), - starting_position: Position { x: 0, y: 0 }, - depth: new_depth, - history: Vec::new(), - noise_areas: HashMap::new(), - n_seeds: 64, - distance_algorithm: DistanceAlgorithm::Manhattan, - spawn_list: Vec::new(), - } - } +impl InitialMapBuilder for VoronoiBuilder { #[allow(dead_code)] - pub fn chebyshev(new_depth: i32) -> VoronoiBuilder { - VoronoiBuilder { - map: Map::new(new_depth), - starting_position: Position { x: 0, y: 0 }, - depth: new_depth, - history: Vec::new(), - noise_areas: HashMap::new(), - n_seeds: 64, - distance_algorithm: DistanceAlgorithm::Chebyshev, - spawn_list: Vec::new(), - } + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build(rng, build_data); + } +} + +impl VoronoiBuilder { + #[allow(dead_code)] + pub fn new() -> Box { + Box::new(VoronoiBuilder { n_seeds: 64, distance_algorithm: DistanceAlgorithm::Pythagoras }) + } + + #[allow(dead_code)] + pub fn pythagoras() -> Box { + Box::new(VoronoiBuilder { n_seeds: 64, distance_algorithm: DistanceAlgorithm::Pythagoras }) + } + + #[allow(dead_code)] + pub fn manhattan() -> Box { + Box::new(VoronoiBuilder { n_seeds: 64, distance_algorithm: DistanceAlgorithm::Manhattan }) } #[allow(clippy::map_entry)] - fn build(&mut self, rng: &mut RandomNumberGenerator) { + fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { // 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 vx = rng.roll_dice(1, build_data.map.width - 1); + let vy = rng.roll_dice(1, build_data.map.height - 1); + let vidx = build_data.map.xy_idx(vx, vy); let candidate = (vidx, rltk::Point::new(vx, vy)); if !voronoi_seeds.contains(&candidate) { voronoi_seeds.push(candidate); @@ -109,10 +53,10 @@ impl VoronoiBuilder { } 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]; + let mut voronoi_membership: Vec = vec![0; build_data.map.width as usize * build_data.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; + let x = i as i32 % build_data.map.width; + let y = i as i32 / build_data.map.width; for (seed, pos) in voronoi_seeds.iter().enumerate() { let distance; @@ -135,52 +79,29 @@ impl VoronoiBuilder { *vid = voronoi_distance[0].0 as i32; } - for y in 1..self.map.height - 1 { - for x in 1..self.map.width - 1 { + for y in 1..build_data.map.height - 1 { + for x in 1..build_data.map.width - 1 { let mut neighbors = 0; - let my_idx = self.map.xy_idx(x, y); + let my_idx = build_data.map.xy_idx(x, y); let my_seed = voronoi_membership[my_idx]; - if voronoi_membership[self.map.xy_idx(x - 1, y)] != my_seed { + if voronoi_membership[build_data.map.xy_idx(x - 1, y)] != my_seed { neighbors += 1; } - if voronoi_membership[self.map.xy_idx(x + 1, y)] != my_seed { + if voronoi_membership[build_data.map.xy_idx(x + 1, y)] != my_seed { neighbors += 1; } - if voronoi_membership[self.map.xy_idx(x, y - 1)] != my_seed { + if voronoi_membership[build_data.map.xy_idx(x, y - 1)] != my_seed { neighbors += 1; } - if voronoi_membership[self.map.xy_idx(x, y + 1)] != my_seed { + if voronoi_membership[build_data.map.xy_idx(x, y + 1)] != my_seed { neighbors += 1; } if neighbors < 2 { - self.map.tiles[my_idx] = TileType::Floor; + build_data.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::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); - - // Spawn the entities - for area in self.noise_areas.iter() { - spawner::spawn_region(&self.map, rng, area.1, self.depth, &mut self.spawn_list); + build_data.take_snapshot(); } } } diff --git a/src/map_builders/voronoi_spawning.rs b/src/map_builders/voronoi_spawning.rs new file mode 100644 index 0000000..844ab8a --- /dev/null +++ b/src/map_builders/voronoi_spawning.rs @@ -0,0 +1,48 @@ +use super::{spawner, BuilderMap, MetaMapBuilder, TileType}; +use rltk::RandomNumberGenerator; +use std::collections::HashMap; + +pub struct VoronoiSpawning {} + +impl MetaMapBuilder for VoronoiSpawning { + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build(rng, build_data); + } +} + +impl VoronoiSpawning { + #[allow(dead_code)] + pub fn new() -> Box { + Box::new(VoronoiSpawning {}) + } + + #[allow(clippy::map_entry)] + fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + 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..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); + } + } +} diff --git a/src/map_builders/wfc/mod.rs b/src/map_builders/wfc/mod.rs index ee42828..d2f6159 100644 --- a/src/map_builders/wfc/mod.rs +++ b/src/map_builders/wfc/mod.rs @@ -1,154 +1,76 @@ -use super::{ - generate_voronoi_spawn_regions, remove_unreachable_areas_returning_most_distant, spawner, Map, MapBuilder, - Position, TileType, SHOW_MAPGEN, -}; - -mod common; -use common::MapChunk; -mod constraints; -mod solver; +use super::{BuilderMap, Map, MetaMapBuilder, TileType}; use rltk::RandomNumberGenerator; -use solver::Solver; -use std::collections::HashMap; +mod common; +use common::*; +mod constraints; +use constraints::*; +mod solver; +use solver::*; -pub struct WaveFunctionCollapseBuilder { - map: Map, - starting_position: Position, - depth: i32, - history: Vec, - noise_areas: HashMap>, - derive_from: Option>, - spawn_list: Vec<(usize, String)>, -} +/// Provides a map builder using the Wave Function Collapse algorithm. +pub struct WaveFunctionCollapseBuilder {} -impl MapBuilder for WaveFunctionCollapseBuilder { - fn build_map(&mut self, rng: &mut RandomNumberGenerator) { - return self.build(rng); - } - // Getters - fn get_map(&mut self) -> Map { - return self.map.clone(); - } - fn get_starting_pos(&mut self) -> Position { - return self.starting_position.clone(); - } - fn get_spawn_list(&self) -> &Vec<(usize, String)> { - return &self.spawn_list; - } - // 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 MetaMapBuilder for WaveFunctionCollapseBuilder { + fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) { + self.build(rng, build_data); } } -#[allow(dead_code)] impl WaveFunctionCollapseBuilder { - pub fn new(new_depth: i32, derive_from: Option>) -> WaveFunctionCollapseBuilder { - WaveFunctionCollapseBuilder { - map: Map::new(new_depth), - starting_position: Position { x: 0, y: 0 }, - depth: new_depth, - history: Vec::new(), - noise_areas: HashMap::new(), - derive_from, - spawn_list: Vec::new(), - } + /// Constructor for wfc. + #[allow(dead_code)] + pub fn new() -> Box { + Box::new(WaveFunctionCollapseBuilder {}) } - pub fn derived_map(new_depth: i32, builder: Box) -> WaveFunctionCollapseBuilder { - WaveFunctionCollapseBuilder::new(new_depth, Some(builder)) - } - - fn build(&mut self, rng: &mut RandomNumberGenerator) { + fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { const CHUNK_SIZE: i32 = 8; + build_data.take_snapshot(); - let prebuilder = &mut self.derive_from.as_mut().unwrap(); - prebuilder.build_map(rng); - self.map = prebuilder.get_map(); - self.history = prebuilder.get_snapshot_history(); - for t in self.map.tiles.iter_mut() { - if *t == TileType::DownStair { - *t = TileType::Floor; - } - } - self.take_snapshot(); + let patterns = build_patterns(&build_data.map, CHUNK_SIZE, true, true); + let constraints = patterns_to_constraints(patterns, CHUNK_SIZE); + self.render_tile_gallery(&constraints, CHUNK_SIZE, build_data); - let patterns = constraints::build_patterns(&self.map, CHUNK_SIZE, true, true); - let constraints = common::patterns_to_constraints(patterns, CHUNK_SIZE); - self.render_tile_gallery(&constraints, CHUNK_SIZE); - - // Call solver - self.map = Map::new(self.depth); + build_data.map = Map::new(build_data.map.depth); loop { - let mut solver = Solver::new(constraints.clone(), CHUNK_SIZE, &self.map); - while !solver.iteration(&mut self.map, rng) { - self.take_snapshot(); + let mut solver = Solver::new(constraints.clone(), CHUNK_SIZE, &build_data.map); + while !solver.iteration(&mut build_data.map, rng) { + build_data.take_snapshot(); } - self.take_snapshot(); + build_data.take_snapshot(); if solver.possible { break; - } - } - - // 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::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); - - // Spawn the entities - for area in self.noise_areas.iter() { - spawner::spawn_region(&self.map, rng, area.1, self.depth, &mut self.spawn_list); + } // If it has hit an impossible condition, try again } + build_data.spawn_list.clear(); } - fn render_tile_gallery(&mut self, constraints: &Vec, chunk_size: i32) { - self.map = Map::new(0); + fn render_tile_gallery(&mut self, constraints: &[MapChunk], chunk_size: i32, build_data: &mut BuilderMap) { + build_data.map = Map::new(0); let mut counter = 0; let mut x = 1; let mut y = 1; while counter < constraints.len() { - constraints::render_pattern_to_map(&mut self.map, &constraints[counter], chunk_size, x, y); + render_pattern_to_map(&mut build_data.map, &constraints[counter], chunk_size, x, y); + x += chunk_size + 1; - if x + chunk_size > self.map.width { - // Next row + if x + chunk_size > build_data.map.width { + // Move to the next row x = 1; y += chunk_size + 1; - self.take_snapshot(); - if y + chunk_size > self.map.height { - // Next page - self.take_snapshot(); - self.map = Map::new(0); + if y + chunk_size > build_data.map.height { + // Move to the next page + build_data.take_snapshot(); + build_data.map = Map::new(0); + x = 1; y = 1; } } + counter += 1; } - self.take_snapshot(); + build_data.take_snapshot(); } } diff --git a/src/spawner.rs b/src/spawner.rs index 8b01cb8..ab75ef9 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -170,7 +170,7 @@ pub fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) { // 20 mobs : 6 items : 2 food : 1 trap fn category_table() -> RandomTable { - return RandomTable::new().add("mob", 20).add("item", 6).add("food", 2).add("trap", 1); + return RandomTable::new().add("mob", 12).add("item", 6).add("food", 2).add("trap", 1); } fn debug_table() -> RandomTable {