From a4e9c27c8faea1dd83eb485fb13518139bb94c5e Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Fri, 24 Dec 2021 11:30:52 -0500 Subject: [PATCH] Finish generating the empty town map, completing section 5.4 --- src/camera.rs | 1 + src/map/tiletype.rs | 2 + src/map_builders/town.rs | 235 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 227 insertions(+), 11 deletions(-) diff --git a/src/camera.rs b/src/camera.rs index ecf7639..eb268d4 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -143,6 +143,7 @@ fn get_tile_glyph(idx: usize, map: &Map) -> (rltk::FontCharType, RGB, RGB) { TileType::Grass => (rltk::to_cp437('"'), RGB::named(rltk::GREEN)), TileType::ShallowWater => (rltk::to_cp437('≈'), RGB::named(rltk::CYAN)), TileType::DeepWater => (rltk::to_cp437('≈'), RGB::named(rltk::NAVY_BLUE)), + TileType::Gravel => (rltk::to_cp437(';'), RGB::named(rltk::GRAY)), }; if map.bloodstains.contains(&idx) { diff --git a/src/map/tiletype.rs b/src/map/tiletype.rs index c71852e..3d23a75 100644 --- a/src/map/tiletype.rs +++ b/src/map/tiletype.rs @@ -11,6 +11,7 @@ pub enum TileType { DeepWater, WoodFloor, Bridge, + Gravel, } pub fn tile_walkable(tt: TileType) -> bool { @@ -23,6 +24,7 @@ pub fn tile_walkable(tt: TileType) -> bool { | TileType::ShallowWater | TileType::WoodFloor | TileType::Bridge + | TileType::Gravel ) } diff --git a/src/map_builders/town.rs b/src/map_builders/town.rs index f7cf060..9a7d102 100644 --- a/src/map_builders/town.rs +++ b/src/map_builders/town.rs @@ -1,23 +1,18 @@ -use ::rltk::RandomNumberGenerator; +use std::collections::HashSet; + +use ::rltk::{Point, RandomNumberGenerator}; use super::{BuilderChain, BuilderMap, InitialMapBuilder}; -use crate::map_builders::area_starting_points::AreaStartingPosition; -use crate::map_builders::distant_exit::DistantExit; -use crate::TileType; +use crate::{Position, TileType}; pub fn town_builder( new_depth: i32, - rng: &mut RandomNumberGenerator, + _rng: &mut RandomNumberGenerator, width: i32, height: i32, ) -> BuilderChain { - let (start_x, start_y) = super::random_start_position(rng); let mut chain = BuilderChain::new(new_depth, width, height); - - chain - .start_with(TownBuilder::new()) - .with(AreaStartingPosition::new(start_x, start_y)) - .with(DistantExit::new()); + chain.start_with(TownBuilder::new()); chain } @@ -45,6 +40,29 @@ impl TownBuilder { *t = true; } build_data.take_snapshot(); + + let (mut available_building_tiles, wall_gap_y) = self.town_walls(rng, build_data); + let mut buildings = self.buildings(rng, build_data, &mut available_building_tiles); + let doors = self.add_doors(rng, build_data, &mut buildings, wall_gap_y); + + self.add_paths(build_data, &doors); + + let exit_idx = build_data.map.xy_idx(build_data.width - 5, wall_gap_y); + build_data.map.tiles[exit_idx] = TileType::DownStairs; + + // Sort buildings by size + let mut building_size: Vec<(usize, i32)> = Vec::new(); + for (i, building) in buildings.iter().enumerate() { + building_size.push((i, building.2 * building.3)); + } + building_size.sort_by(|a, b| b.1.cmp(&a.1)); + + // Start in the pub + let the_pub = &buildings[building_size[0].0]; + build_data.starting_position = Some(Position { + x: the_pub.0 + (the_pub.2 / 2), + y: the_pub.1 + (the_pub.3 / 2), + }) } fn grass_layer(&mut self, build_data: &mut BuilderMap) { @@ -86,4 +104,199 @@ impl TownBuilder { } build_data.take_snapshot(); } + + fn town_walls( + &mut self, + rng: &mut RandomNumberGenerator, + build_data: &mut BuilderMap, + ) -> (HashSet, i32) { + let mut available_building_tiles: HashSet = HashSet::new(); + let wall_gap_y = rng.roll_dice(1, build_data.height - 9) + 5; + for y in 1..build_data.height - 2 { + if !(y > wall_gap_y - 4 && y < wall_gap_y + 4) { + let idx = build_data.map.xy_idx(30, y); + build_data.map.tiles[idx] = TileType::Wall; + build_data.map.tiles[idx - 1] = TileType::Floor; + + let idx_right = build_data.map.xy_idx(build_data.width - 2, y); + build_data.map.tiles[idx_right] = TileType::Wall; + + for x in 31..build_data.width - 2 { + let gravel_idx = build_data.map.xy_idx(x, y); + build_data.map.tiles[gravel_idx] = TileType::Gravel; + if y > 2 && y < build_data.height - 1 { + available_building_tiles.insert(gravel_idx); + } + } + } else { + for x in 30..build_data.width { + let road_idx = build_data.map.xy_idx(x, y); + build_data.map.tiles[road_idx] = TileType::Road; + } + } + } + build_data.take_snapshot(); + + for x in 30..build_data.width - 1 { + let idx_top = build_data.map.xy_idx(x, 1); + let idx_bot = build_data.map.xy_idx(x, build_data.height - 2); + build_data.map.tiles[idx_top] = TileType::Wall; + build_data.map.tiles[idx_bot] = TileType::Wall; + } + build_data.take_snapshot(); + + (available_building_tiles, wall_gap_y) + } + + fn buildings( + &mut self, + rng: &mut RandomNumberGenerator, + build_data: &mut BuilderMap, + available_building_tiles: &mut HashSet, + ) -> Vec<(i32, i32, i32, i32)> { + let mut buildings = Vec::new(); + let mut n_buildings = 0; + + while n_buildings < 12 { + let bx = rng.roll_dice(1, build_data.map.width - 32) + 30; + let by = rng.roll_dice(1, build_data.map.height) - 2; + let bw = rng.roll_dice(1, 8) + 4; + let bh = rng.roll_dice(1, 8) + 4; + let mut possible = true; + + for y in by..by + bh { + for x in bx..bx + bw { + if x < 0 || x > build_data.width - 1 || y < 0 || y > build_data.height - 1 { + possible = false; + } else { + let idx = build_data.map.xy_idx(x, y); + if !available_building_tiles.contains(&idx) { + possible = false; + } + } + } + } + + if possible { + n_buildings += 1; + buildings.push((bx, by, bw, bh)); + for y in by..by + bh { + for x in bx..bx + bw { + let idx = build_data.map.xy_idx(x, y); + build_data.map.tiles[idx] = TileType::WoodFloor; + + available_building_tiles.remove(&idx); + available_building_tiles.remove(&(idx + 1)); + available_building_tiles.remove(&(idx + build_data.width as usize)); + available_building_tiles.remove(&(idx - 1)); + available_building_tiles.remove(&(idx - build_data.width as usize)); + } + } + build_data.take_snapshot(); + } + } + + // Outline buildings + let mut mapclone = build_data.map.clone(); + for y in 2..build_data.height - 2 { + for x in 32..build_data.width - 2 { + let idx = build_data.map.xy_idx(x, y); + if build_data.map.tiles[idx] == TileType::WoodFloor { + let mut neighbors = 0; + if build_data.map.tiles[idx - 1] != TileType::WoodFloor { + neighbors += 1; + } + if build_data.map.tiles[idx + 1] != TileType::WoodFloor { + neighbors += 1; + } + if build_data.map.tiles[idx - build_data.width as usize] != TileType::WoodFloor + { + neighbors += 1; + } + if build_data.map.tiles[idx + build_data.width as usize] != TileType::WoodFloor + { + neighbors += 1; + } + if neighbors > 0 { + mapclone.tiles[idx] = TileType::Wall; + } + } + } + } + build_data.map = mapclone; + build_data.take_snapshot(); + + buildings + } + + fn add_doors( + &mut self, + rng: &mut RandomNumberGenerator, + build_data: &mut BuilderMap, + buildings: &mut Vec<(i32, i32, i32, i32)>, + wall_gap_y: i32, + ) -> Vec { + let mut doors = Vec::new(); + for building in buildings.iter() { + let door_x = building.0 + 1 + rng.roll_dice(1, building.2 - 3); + let cy = building.1 + (building.3 / 2); + let idx = if cy > wall_gap_y { + // Door on the north wall + build_data.map.xy_idx(door_x, building.1) + } else { + build_data.map.xy_idx(door_x, building.1 + building.3 - 1) + }; + build_data.map.tiles[idx] = TileType::Floor; + build_data.spawn_list.push((idx, "Door".to_string())); + doors.push(idx); + } + build_data.take_snapshot(); + + doors + } + + fn add_paths(&mut self, build_data: &mut BuilderMap, doors: &[usize]) { + let mut roads = Vec::new(); + for y in 0..build_data.height { + for x in 0..build_data.width { + let idx = build_data.map.xy_idx(x, y); + if build_data.map.tiles[idx] == TileType::Road { + roads.push(idx); + } + } + } + + build_data.map.populate_blocked(); + for door_idx in doors.iter() { + let mut nearest_roads: Vec<(usize, f32)> = Vec::new(); + let door_pt = Point::new( + *door_idx as i32 % build_data.map.width as i32, + *door_idx as i32 / build_data.map.width as i32, + ); + for r in roads.iter() { + nearest_roads.push(( + *r, + ::rltk::DistanceAlg::PythagorasSquared.distance2d( + door_pt, + Point::new( + *r as i32 % build_data.map.width, + *r as i32 / build_data.map.width as i32, + ), + ), + )); + } + nearest_roads.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap()); + + let destination = nearest_roads[0].0; + let path = ::rltk::a_star_search(*door_idx, destination, &build_data.map); + if path.success { + for step in path.steps.iter() { + let idx = *step as usize; + build_data.map.tiles[idx] = TileType::Road; + roads.push(idx); + } + } + build_data.take_snapshot(); + } + } }