diff --git a/src/main.rs b/src/main.rs index a6b4073..54b1e51 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,11 +42,9 @@ impl State { spawn_player(&mut ecs, map_builder.player_start); spawn_amulet_of_yala(&mut ecs, map_builder.amulet_start); map_builder - .rooms + .monster_spawns .iter() - .skip(1) - .map(|r| r.center()) - .for_each(|pos| spawn_monster(&mut ecs, &mut rng, pos)); + .for_each(|pos| spawn_monster(&mut ecs, &mut rng, *pos)); resources.insert(map_builder.map); resources.insert(Camera::new(map_builder.player_start)); @@ -94,11 +92,9 @@ impl State { spawn_player(&mut self.ecs, map_builder.player_start); spawn_amulet_of_yala(&mut self.ecs, map_builder.amulet_start); map_builder - .rooms + .monster_spawns .iter() - .skip(1) - .map(|r| r.center()) - .for_each(|pos| spawn_monster(&mut self.ecs, &mut rng, pos)); + .for_each(|pos| spawn_monster(&mut self.ecs, &mut rng, *pos)); self.resources.insert(map_builder.map); self.resources.insert(Camera::new(map_builder.player_start)); self.resources.insert(TurnState::AwaitingInput); diff --git a/src/map_builder/automata.rs b/src/map_builder/automata.rs new file mode 100644 index 0000000..317aa9d --- /dev/null +++ b/src/map_builder/automata.rs @@ -0,0 +1,88 @@ +use super::MapArchitect; +use crate::prelude::*; + +pub struct AutomataArchitect {} + +impl MapArchitect for AutomataArchitect { + fn new(&mut self, rng: &mut RandomNumberGenerator) -> MapBuilder { + let mut mb = MapBuilder { + map: Map::new(), + rooms: Vec::new(), + monster_spawns: Vec::new(), + player_start: Point::zero(), + amulet_start: Point::zero(), + }; + self.random_noise_map(rng, &mut mb.map); + for _ in 0..10 { + self.iteration(&mut mb.map); + } + let start = self.find_start(&mb.map); + mb.monster_spawns = mb.spawn_monsters(&start, rng); + mb.player_start = start; + mb.amulet_start = mb.find_most_distant(); + + mb + } +} + +impl AutomataArchitect { + fn random_noise_map(&mut self, rng: &mut RandomNumberGenerator, map: &mut Map) { + map.tiles.iter_mut().for_each(|t| { + let roll = rng.range(0, 100); + if roll > 55 { + *t = TileType::Floor; + } else { + *t = TileType::Wall; + } + }) + } + fn count_neighbors(&self, x: i32, y: i32, map: &Map) -> usize { + let mut neighbors = 0; + for iy in -1..=1 { + for ix in -1..=1 { + if !(ix == 0 && iy == 0) && map.tiles[map_idx(x + ix, y + iy)] == TileType::Wall { + neighbors += 1; + } + } + } + + neighbors + } + + fn iteration(&mut self, map: &mut Map) { + let mut new_tiles = map.tiles.clone(); + for y in 1..SCREEN_HEIGHT - 1 { + for x in 1..SCREEN_WIDTH - 1 { + let neighbors = self.count_neighbors(x, y, map); + let idx = map_idx(x, y); + if neighbors > 4 || neighbors == 0 { + new_tiles[idx] = TileType::Wall; + } else { + new_tiles[idx] = TileType::Floor; + } + } + } + + map.tiles = new_tiles; + } + + fn find_start(&self, map: &Map) -> Point { + let center = Point::new(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2); + let closest_point = map + .tiles + .iter() + .enumerate() + .filter(|(_, t)| **t == TileType::Floor) + .map(|(idx, _)| { + ( + idx, + DistanceAlg::Pythagoras.distance2d(center, map.index_to_point2d(idx)), + ) + }) + .min_by(|(_, distance), (_, distance2)| distance.partial_cmp(&distance2).unwrap()) + .map(|(idx, _)| idx) + .unwrap(); + + map.index_to_point2d(closest_point) + } +} diff --git a/src/map_builder/empty.rs b/src/map_builder/empty.rs new file mode 100644 index 0000000..dc6102f --- /dev/null +++ b/src/map_builder/empty.rs @@ -0,0 +1,27 @@ +use super::MapArchitect; +use crate::prelude::*; + +pub struct EmptyArchitect {} + +impl MapArchitect for EmptyArchitect { + fn new(&mut self, rng: &mut RandomNumberGenerator) -> MapBuilder { + let mut mb = MapBuilder { + map: Map::new(), + rooms: Vec::new(), + monster_spawns: Vec::new(), + player_start: Point::zero(), + amulet_start: Point::zero(), + }; + mb.fill(TileType::Floor); + mb.player_start = Point::new(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2); + mb.amulet_start = mb.find_most_distant(); + for _ in 0..50 { + mb.monster_spawns.push(Point::new( + rng.range(1, SCREEN_WIDTH), + rng.range(1, SCREEN_HEIGHT), + )) + } + + mb + } +} diff --git a/src/map_builder.rs b/src/map_builder/mod.rs similarity index 68% rename from src/map_builder.rs rename to src/map_builder/mod.rs index 6a7a089..b63df17 100644 --- a/src/map_builder.rs +++ b/src/map_builder/mod.rs @@ -1,35 +1,47 @@ +mod automata; +mod empty; +mod rooms; + +use automata::AutomataArchitect; +use empty::EmptyArchitect; +use rooms::RoomsArchitect; + use crate::prelude::*; +trait MapArchitect { + fn new(&mut self, rng: &mut RandomNumberGenerator) -> MapBuilder; +} + const NUM_ROOMS: usize = 20; pub struct MapBuilder { pub map: Map, pub rooms: Vec, + pub monster_spawns: Vec, pub player_start: Point, pub amulet_start: Point, } impl MapBuilder { pub fn new(rng: &mut RandomNumberGenerator) -> Self { - let mut mb = MapBuilder { - map: Map::new(), - rooms: Vec::new(), - player_start: Point::zero(), - amulet_start: Point::zero(), - }; - mb.fill(TileType::Wall); - mb.build_random_rooms(rng); - mb.build_corridors(rng); - mb.player_start = mb.rooms[0].center(); + let mut architecht = AutomataArchitect {}; + architecht.new(rng) + } + + fn fill(&mut self, tile: TileType) { + self.map.tiles.iter_mut().for_each(|t| *t = tile); + } + + fn find_most_distant(&self) -> Point { let dijkstra_map = DijkstraMap::new( SCREEN_WIDTH, SCREEN_HEIGHT, - &vec![mb.map.point2d_to_index(mb.player_start)], - &mb.map, + &vec![self.map.point2d_to_index(self.player_start)], + &self.map, 1024.0, ); const UNREACHABLE: &f32 = &f32::MAX; - mb.amulet_start = mb.map.index_to_point2d( + self.map.index_to_point2d( dijkstra_map .map .iter() @@ -38,13 +50,7 @@ impl MapBuilder { .max_by(|a, b| a.1.partial_cmp(b.1).unwrap()) .unwrap() .0, - ); - - mb - } - - fn fill(&mut self, tile: TileType) { - self.map.tiles.iter_mut().for_each(|t| *t = tile); + ) } fn build_random_rooms(&mut self, rng: &mut RandomNumberGenerator) { @@ -109,4 +115,28 @@ impl MapBuilder { } } } + + fn spawn_monsters(&self, start: &Point, rng: &mut RandomNumberGenerator) -> Vec { + const NUM_MONSTERS: usize = 50; + let mut spawnable_tiles: Vec = self + .map + .tiles + .iter() + .enumerate() + .filter(|(idx, t)| { + **t == TileType::Floor + && DistanceAlg::Pythagoras.distance2d(*start, self.map.index_to_point2d(*idx)) + > 10.0 + }) + .map(|(idx, _)| self.map.index_to_point2d(idx)) + .collect(); + let mut spawns = Vec::new(); + for _ in 0..NUM_MONSTERS { + let target_index = rng.random_slice_index(&spawnable_tiles).unwrap(); + spawns.push(spawnable_tiles[target_index].clone()); + spawnable_tiles.remove(target_index); + } + + spawns + } } diff --git a/src/map_builder/rooms.rs b/src/map_builder/rooms.rs new file mode 100644 index 0000000..1d9de39 --- /dev/null +++ b/src/map_builder/rooms.rs @@ -0,0 +1,26 @@ +use super::MapArchitect; +use crate::prelude::*; + +pub struct RoomsArchitect {} + +impl MapArchitect for RoomsArchitect { + fn new(&mut self, rng: &mut RandomNumberGenerator) -> MapBuilder { + let mut mb = MapBuilder { + map: Map::new(), + rooms: Vec::new(), + monster_spawns: Vec::new(), + player_start: Point::zero(), + amulet_start: Point::zero(), + }; + mb.fill(TileType::Wall); + mb.build_random_rooms(rng); + mb.build_corridors(rng); + mb.player_start = mb.rooms[0].center(); + mb.amulet_start = mb.find_most_distant(); + for room in mb.rooms.iter().skip(1) { + mb.monster_spawns.push(room.center()); + } + + mb + } +}