summaryrefslogtreecommitdiff
path: root/dungeon
diff options
context:
space:
mode:
authoralf9310 <alf9310@rit.edu>2025-11-09 17:37:23 -0500
committeralf9310 <alf9310@rit.edu>2025-11-09 17:37:23 -0500
commit096ff7a891c350da000f18f01ffb4a1f9cde0899 (patch)
tree50132ffbfb2f5572ce4e52c0c06cc4abbb3508c8 /dungeon
parentdungeon_generation: added stair generation and fixed room center vs rabdom po... (diff)
downloadDungeonCrawl-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.rs68
-rw-r--r--dungeon/src/map.rs15
-rw-r--r--dungeon/tests/bsp_tests.rs22
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);
}
}