From 541421eb5b95cfbe92a1da4fc81472ab53b9c1bc Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Tue, 14 Dec 2021 16:29:36 -0500 Subject: [PATCH] Refactor simple map builder to use the Rust builder pattern --- src/main.rs | 12 +- src/map_builders.rs | 161 +++++++++++++----- src/map_builders/room_based_spawner.rs | 34 ++++ src/map_builders/room_based_stairs.rs | 30 ++++ .../room_based_starting_position.rs | 31 ++++ src/map_builders/simple_map.rs | 104 ++++------- 6 files changed, 256 insertions(+), 116 deletions(-) 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 diff --git a/src/main.rs b/src/main.rs index ad12221..106b1db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use rltk::{GameState, Point, Rltk}; +use rltk::{GameState, Point, RandomNumberGenerator, Rltk}; use specs::prelude::*; use specs::saveload::{SimpleMarker, SimpleMarkerAllocator}; @@ -461,15 +461,19 @@ impl State { self.mapgen_timer = 0.0; self.mapgen_history.clear(); - let mut builder = map_builders::random_builder(new_depth); - builder.build_map(); + 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.get_snapshot_history(); let player_start; { let mut worldmap_resource = self.ecs.write_resource::(); *worldmap_resource = builder.get_map(); - player_start = builder.get_starting_position(); + player_start = builder.get_starting_position().as_mut().unwrap().clone(); } // Spawn bad guys diff --git a/src/map_builders.rs b/src/map_builders.rs index 7754cfd..249450b 100644 --- a/src/map_builders.rs +++ b/src/map_builders.rs @@ -6,10 +6,15 @@ mod dla; mod drunkard; mod maze; mod prefab_builder; +mod room_based_spawner; +mod room_based_stairs; +mod room_based_starting_position; mod simple_map; mod voronoi; mod waveform_collapse; +use crate::{spawner, Map, Position, Rect, SHOW_MAPGEN_VISUALIZER}; +use ::rltk::RandomNumberGenerator; use bsp_dungeon::BspDungeonBuilder; use bsp_interior::BspInteriorBuilder; use cellular_automata::CellularAutomataBuilder; @@ -18,12 +23,119 @@ use dla::DLABuilder; use drunkard::DrunkardsWalkBuilder; use maze::MazeBuilder; use prefab_builder::PrefabBuilder; +use room_based_spawner::RoomBasedSpawner; +use room_based_stairs::RoomBasedStairs; +use room_based_starting_position::RoomBasedStartingPosition; use simple_map::SimpleMapBuilder; use specs::prelude::*; use voronoi::VoronoiCellBuilder; use waveform_collapse::WaveformCollapseBuilder; -use crate::{spawner, Map, Position}; +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_VISUALIZER { + 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) -> &mut Self { + match self.starter { + None => self.starter = Some(starter), + Some(_) => panic!("You can only have one starting builder."), + } + + self + } + + pub fn with(&mut self, metabuilder: Box) -> &mut Self { + self.builders.push(metabuilder); + + self + } + + pub fn build_map(&mut self, rng: &mut RandomNumberGenerator) -> &mut Self { + match &mut self.starter { + None => panic!("Cannot run a map builder chain without starting a 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); + } + + self + } + + pub fn spawn_entities(&mut self, ecs: &mut World) -> &mut Self { + for entity in self.build_data.spawn_list.iter() { + spawner::spawn_entity(ecs, &(&entity.0, &entity.1)); + } + + self + } + + pub fn get_map(&self) -> Map { + self.build_data.map.clone() + } + + pub fn get_starting_position(&self) -> Option { + self.build_data.starting_position + } + + pub fn get_snapshot_history(&self) -> Vec { + self.build_data.history.clone() + } + + pub fn get_spawn_list(&self) -> &Vec<(usize, String)> { + &self.build_data.spawn_list + } +} + +pub trait InitialMapBuilder { + fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap); +} + +pub trait MetaMapBuilder { + fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap); +} pub trait MapBuilder { fn get_map(&self) -> Map; @@ -40,44 +152,13 @@ pub trait MapBuilder { } } -pub fn random_builder(new_depth: i32) -> Box { - let mut rng = rltk::RandomNumberGenerator::new(); - let mut result: Box = match rng.roll_dice(1, 17) { - 1 => Box::new(BspDungeonBuilder::new(new_depth)), - 2 => Box::new(BspInteriorBuilder::new(new_depth)), - 3 => Box::new(CellularAutomataBuilder::new(new_depth)), - 4 => Box::new(DrunkardsWalkBuilder::open_area(new_depth)), - 5 => Box::new(DrunkardsWalkBuilder::open_halls(new_depth)), - 6 => Box::new(DrunkardsWalkBuilder::winding_passages(new_depth)), - 7 => Box::new(DrunkardsWalkBuilder::fat_passages(new_depth)), - 8 => Box::new(DrunkardsWalkBuilder::fearful_symmetry(new_depth)), - 9 => Box::new(MazeBuilder::new(new_depth)), - 10 => Box::new(DLABuilder::walk_inwards(new_depth)), - 11 => Box::new(DLABuilder::walk_outwards(new_depth)), - 12 => Box::new(DLABuilder::central_attractor(new_depth)), - 13 => Box::new(DLABuilder::insectoid(new_depth)), - 14 => Box::new(VoronoiCellBuilder::pythagoras(new_depth)), - 15 => Box::new(VoronoiCellBuilder::manhattan(new_depth)), - 16 => Box::new(PrefabBuilder::constant( - new_depth, - prefab_builder::prefab_levels::WFC_POPULATED, - )), - _ => Box::new(SimpleMapBuilder::new(new_depth)), - }; +pub fn random_builder(new_depth: i32, rng: &mut RandomNumberGenerator) -> BuilderChain { + let mut builder = BuilderChain::new(new_depth); + builder + .start_with(SimpleMapBuilder::new()) + .with(RoomBasedSpawner::new()) + .with(RoomBasedStartingPosition::new()) + .with(RoomBasedStairs::new()); - if rng.roll_dice(1, 3) == 1 { - result = Box::new(WaveformCollapseBuilder::derived_map(new_depth, result)); - } - - if rng.roll_dice(1, 20) == 1 { - result = Box::new(PrefabBuilder::sectional( - new_depth, - prefab_builder::prefab_sections::UNDERGROUND_FORT, - result, - )); - } - - result = Box::new(PrefabBuilder::vaults(new_depth, result)); - - result + builder } diff --git a/src/map_builders/room_based_spawner.rs b/src/map_builders/room_based_spawner.rs new file mode 100644 index 0000000..ba6086f --- /dev/null +++ b/src/map_builders/room_based_spawner.rs @@ -0,0 +1,34 @@ +use rltk::RandomNumberGenerator; + +use crate::map_builders::{spawner, BuilderMap, MetaMapBuilder}; + +pub struct RoomBasedSpawner {} + +impl MetaMapBuilder for RoomBasedSpawner { + fn build_map(&mut self, rng: &mut 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!("Room Based Spawning 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..7143e26 --- /dev/null +++ b/src/map_builders/room_based_stairs.rs @@ -0,0 +1,30 @@ +use rltk::RandomNumberGenerator; + +use crate::map_builders::{BuilderMap, MetaMapBuilder}; +use crate::TileType; + +pub struct RoomBasedStairs {} + +impl MetaMapBuilder for RoomBasedStairs { + fn build_map(&mut self, rng: &mut 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].center(); + let stairs_idx = build_data.map.xy_idx(stairs_position.0, stairs_position.1); + build_data.map.tiles[stairs_idx] = TileType::DownStairs; + build_data.take_snapshot(); + } else { + panic!("Room Based Stairs 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..2a14a8c --- /dev/null +++ b/src/map_builders/room_based_starting_position.rs @@ -0,0 +1,31 @@ +use rltk::RandomNumberGenerator; + +use crate::map_builders::{BuilderMap, MetaMapBuilder}; +use crate::Position; + +pub struct RoomBasedStartingPosition {} + +impl MetaMapBuilder for RoomBasedStartingPosition { + fn build_map(&mut self, rng: &mut 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].center(); + build_data.starting_position = Some(Position { + x: start_pos.0, + y: start_pos.1, + }); + } else { + panic!("Room Based Starting Position only works after rooms have been created."); + } + } +} diff --git a/src/map_builders/simple_map.rs b/src/map_builders/simple_map.rs index 215328e..5572c53 100644 --- a/src/map_builders/simple_map.rs +++ b/src/map_builders/simple_map.rs @@ -1,113 +1,73 @@ use rltk::RandomNumberGenerator; -use super::{apply_horizontal_tunnel, apply_room_to_map, apply_vertical_tunnel, MapBuilder}; +use crate::map_builders::common::{ + apply_horizontal_tunnel, apply_room_to_map, apply_vertical_tunnel, +}; +use crate::map_builders::{BuilderMap, InitialMapBuilder}; use crate::{spawner, Map, Position, Rect, TileType, SHOW_MAPGEN_VISUALIZER}; -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 get_map(&self) -> Map { - self.map.clone() - } - - fn get_starting_position(&self) -> Position { - self.starting_position - } - - fn get_snapshot_history(&self) -> Vec { - self.history.clone() - } - - fn build_map(&mut self) { - self.rooms_and_corridors(); - } - - fn take_snapshot(&mut self) { - if SHOW_MAPGEN_VISUALIZER { - let mut snapshot = self.map.clone(); - for v in snapshot.revealed_tiles.iter_mut() { - *v = true; - } - self.history.push(snapshot); - } - } - - fn get_spawn_list(&self) -> &Vec<(usize, String)> { - &self.spawn_list +impl InitialMapBuilder for SimpleMapBuilder { + #[allow(dead_code)] + fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { + self.rooms_and_corridors(rng, build_data); } } impl SimpleMapBuilder { - 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(), - } + #[allow(dead_code)] + pub fn new() -> Box { + Box::new(SimpleMapBuilder {}) } - fn rooms_and_corridors(&mut self) { + 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 rng = RandomNumberGenerator::new(); + let mut rooms: Vec = Vec::new(); for _ 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.center(); - let (prev_x, prev_y) = self.rooms[self.rooms.len() - 1].center(); + let (prev_x, prev_y) = rooms[rooms.len() - 1].center(); 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].center(); - let stairs_idx = self.map.xy_idx(stairs_position.0, stairs_position.1); - self.map.tiles[stairs_idx] = TileType::DownStairs; - - self.starting_position = Position::from(self.rooms[0].center()); - - // Spawn some entities - for room in self.rooms.iter().skip(1) { - spawner::spawn_room(&self.map, &mut rng, room, self.depth, &mut self.spawn_list); - } + build_data.rooms = Some(rooms); } }