diff options
| author | alf9310 <alf9310@rit.edu> | 2025-11-09 17:37:23 -0500 |
|---|---|---|
| committer | alf9310 <alf9310@rit.edu> | 2025-11-09 17:37:23 -0500 |
| commit | 096ff7a891c350da000f18f01ffb4a1f9cde0899 (patch) | |
| tree | 50132ffbfb2f5572ce4e52c0c06cc4abbb3508c8 /dungeon | |
| parent | dungeon_generation: added stair generation and fixed room center vs rabdom po... (diff) | |
| download | DungeonCrawl-096ff7a891c350da000f18f01ffb4a1f9cde0899.tar.gz DungeonCrawl-096ff7a891c350da000f18f01ffb4a1f9cde0899.tar.bz2 DungeonCrawl-096ff7a891c350da000f18f01ffb4a1f9cde0899.zip | |
dungeon_generation: added Hallway vs Room tiles
Diffstat (limited to 'dungeon')
| -rw-r--r-- | dungeon/src/bsp.rs | 68 | ||||
| -rw-r--r-- | dungeon/src/map.rs | 15 | ||||
| -rw-r--r-- | dungeon/tests/bsp_tests.rs | 22 |
3 files changed, 59 insertions, 46 deletions
diff --git a/dungeon/src/bsp.rs b/dungeon/src/bsp.rs index 9b288a2..8b5fa3b 100644 --- a/dungeon/src/bsp.rs +++ b/dungeon/src/bsp.rs @@ -1,6 +1,7 @@ //! The `bsp` module implements a Binary-Space Partitioning based dungeon generator. //! Produces a tile array and a player start position for a given seed. +use core::panic; use rand::prelude::IndexedRandom; use rand::{Rng, SeedableRng}; use std::cmp; // for min/max @@ -167,21 +168,13 @@ impl Node { let room_w = rng.random_range(MIN_ROOM_SIZE..=max_w.min(MAX_ROOM_SIZE)); let room_h = rng.random_range(MIN_ROOM_SIZE..=max_h.min(MAX_ROOM_SIZE)); - // Choose position inside rect, leaving at least 1 tile to the border + // Define a valid range for room position, leaving at least 1 tile border let x_range = (self.rect.x + 1)..=(self.rect.x + self.rect.w - 1 - room_w); let y_range = (self.rect.y + 1)..=(self.rect.y + self.rect.h - 1 - room_h); - // The ranges should be valid; if not, fallback to center - let rx = if x_range.is_empty() { - self.rect.x + (self.rect.w.saturating_sub(room_w)) / 2 - } else { - rng.random_range(x_range) - }; - let ry = if y_range.is_empty() { - self.rect.y + (self.rect.h.saturating_sub(room_h)) / 2 - } else { - rng.random_range(y_range) - }; + // Choose room position randomly within valid range + let rx = rng.random_range(x_range); + let ry = rng.random_range(y_range); self.room = Some(Rect::new(rx, ry, room_w, room_h)); } @@ -236,32 +229,33 @@ impl Node { /// Connect rooms of child nodes recursively and collect corridors to carve. /// - /// `corridors` is appended with pairs of points (x1,y1,x2,y2) describing corridors to carve. - fn connect_children( - &self, - corridors: &mut Vec<(Pos, Pos)>, - rng: &mut rand::rngs::StdRng, - ) { + /// returns corridors: output vector of (Pos, Pos) pairs to connect + fn connect_children(&self, rng: &mut rand::rngs::StdRng) -> Vec<(Pos, Pos)> { + let mut corridors = Vec::new(); + if let (Some(left), Some(right)) = (&self.left, &self.right) { - // Connect a room (or corridor endpoint) from left subtree to right subtree - // Select a random point from each side - let left_point = left.random_point_in_room(rng); - let right_point = right.random_point_in_room(rng); + // Get room centers for left and right children + let left_point = left.room_center(); + let right_point = right.room_center(); corridors.push((left_point, right_point)); - left.connect_children(corridors, rng); - right.connect_children(corridors, rng); + // Recursively connect children + let mut left_corridors = left.connect_children(rng); + let mut right_corridors = right.connect_children(rng); + corridors.append(&mut left_corridors); + corridors.append(&mut right_corridors); } - // leaf: nothing to connect! + + corridors } } -/// Carve a room rectangle into the map (tile array) by setting tiles inside to Air. +/// Carve a room rectangle into the map (tile array) by setting tiles inside to Room. fn carve_room(tiles: &mut [Tile; TILE_COUNT], room: &Rect) { for y in room.y..(room.y + room.h) { for x in room.x..(room.x + room.w) { let idx = x + y * MAP_SIZE; - tiles[idx as usize] = Tile::Air; + tiles[idx as usize] = Tile::Room; } } } @@ -271,16 +265,16 @@ fn carve_h_corridor(tiles: &mut [Tile; TILE_COUNT], x1: u16, x2: u16, y: u16) { let (sx, ex) = if x1 <= x2 { (x1, x2) } else { (x2, x1) }; for x in sx..=ex { let idx = x + y * MAP_SIZE; - tiles[idx as usize] = Tile::Air; + tiles[idx as usize] = Tile::Hallway; } } -/// Carve a vertical corridor from y1..=y2 at x +/// Carve a vertical corridor from y1..=y2 at x (inclusive) fn carve_v_corridor(tiles: &mut [Tile; TILE_COUNT], y1: u16, y2: u16, x: u16) { let (sy, ey) = if y1 <= y2 { (y1, y2) } else { (y2, y1) }; for y in sy..=ey { let idx = x + y * MAP_SIZE; - tiles[idx as usize] = Tile::Air; + tiles[idx as usize] = Tile::Hallway; } } @@ -356,8 +350,7 @@ pub fn generate(seed: u64) -> (Box<[Tile; TILE_COUNT]>, Pos) { } // Collect corridors (pairs of centers) by connecting children bottom-up - let mut corridors = Vec::new(); - root.connect_children(&mut corridors, &mut rng); + let corridors = root.connect_children(&mut rng); // Carve corridors. For each corridor (x1,y1,x2,y2), carve straight or L-shape. for (left_point, right_point) in corridors { @@ -440,7 +433,7 @@ mod tests { assert!(room.x + room.w < MAP_SIZE); assert!(room.y + room.h < MAP_SIZE); } - None => panic!("Room should be created"), + None => self::panic!("Room should exist"), } } @@ -461,8 +454,7 @@ mod tests { let mut node = Node::new(rect); let mut rng = rand::rngs::StdRng::seed_from_u64(12345); node.split(&mut rng); - let mut corridors = Vec::new(); - node.connect_children(&mut corridors, &mut rng); + let corridors = node.connect_children(&mut rng); assert!(!corridors.is_empty()); } @@ -470,9 +462,9 @@ mod tests { fn test_generate() { let seed = 12345u64; let (tiles, player_start) = generate(seed); - // Check that tiles contain some Air tiles - let air_count = tiles.iter().filter(|&&t| t == Tile::Air).count(); - assert!(air_count > 0); + // Check that tiles contain some Room tiles + let room_count = tiles.iter().filter(|&&t| t == Tile::Room).count(); + assert!(room_count > 0); // Check that player_start is within bounds assert!(player_start.xy().0 < MAP_SIZE); assert!(player_start.xy().1 < MAP_SIZE); diff --git a/dungeon/src/map.rs b/dungeon/src/map.rs index 9e43faf..5004dd7 100644 --- a/dungeon/src/map.rs +++ b/dungeon/src/map.rs @@ -28,8 +28,10 @@ pub const TILE_COUNT: usize = MAP_SIZE_USIZE * MAP_SIZE_USIZE; pub enum Tile { /// `Wall` represents an impassible wall Wall, - /// `Air` represents empty walkable space - Air, + /// `Room` represents empty walkable space for a rectangular room + Room, + /// `Hallway` represents empty walkable space for a hallway + Hallway, /// `Stairs` represents stairs to another floor Stairs, } @@ -48,7 +50,7 @@ impl Tile { } impl Default for Tile { fn default() -> Self { - Self::Air + Self::Wall } } @@ -213,7 +215,8 @@ impl Floor { let tile = self.get(pos); let char = match tile { Tile::Wall => '#', - Tile::Air => '.', + Tile::Room => '.', + Tile::Hallway => ',', Tile::Stairs => '>', }; output.push(char); @@ -233,7 +236,7 @@ impl Floor { let Some(pos) = Pos::new(x, y) else { continue; }; - if self.get(pos) != Tile::Air { + if self.get(pos) == Tile::Wall { continue; } break pos; @@ -245,7 +248,7 @@ impl Default for Floor { /// Returns a floor with a single set of walls on the map border fn default() -> Self { let player_start = const_pos!(1, 1); - let mut tiles = Box::new([Tile::Air; TILE_COUNT]); + let mut tiles = Box::new([Tile::Room; TILE_COUNT]); let seed = 0u64; for pos in Pos::values() { diff --git a/dungeon/tests/bsp_tests.rs b/dungeon/tests/bsp_tests.rs index 6bd0153..16a8a77 100644 --- a/dungeon/tests/bsp_tests.rs +++ b/dungeon/tests/bsp_tests.rs @@ -22,8 +22,26 @@ mod tests { // Ensure player start is within bounds assert!(player_start.x() < map::MAP_SIZE); assert!(player_start.y() < map::MAP_SIZE); - // Ensure player start is an air tile + // Ensure player start is a room tile let idx = player_start.idx(); - assert_eq!(tiles[idx], map::Tile::Air); + assert_eq!(tiles[idx], map::Tile::Room); + } + + /// Test that BSP-generated floors have at least two rooms + #[test] + fn test_bsp_2_or_more_rooms() { + let seed = 12345u64; + let (tiles, _player_start) = bsp::generate(seed); + // Ensure we have at least two rooms + let mut room_count = 0; + let mut visited = vec![false; tiles.len()]; + for (i, &tile) in tiles.iter().enumerate() { + if tile == map::Tile::Room && !visited[i] { + room_count += 1; + // Mark all connected tiles as visited + visited[i] = true; + } + } + assert!(room_count >= 2); } } |