summaryrefslogtreecommitdiff
path: root/dungeon
diff options
context:
space:
mode:
authoralf9310 <alf9310@rit.edu>2025-11-10 00:31:56 -0500
committeralf9310 <alf9310@rit.edu>2025-11-10 00:31:56 -0500
commit0d484d763a243e6b73e13d4968ccf6222b65eeca (patch)
treeeefc124a4bbe165710eac9faeb2426a8e2608747 /dungeon
parentdungeon_generation: added Hallway vs Room tiles (diff)
downloadDungeonCrawl-0d484d763a243e6b73e13d4968ccf6222b65eeca.tar.gz
DungeonCrawl-0d484d763a243e6b73e13d4968ccf6222b65eeca.tar.bz2
DungeonCrawl-0d484d763a243e6b73e13d4968ccf6222b65eeca.zip
Added many auto-gen test cases for maze connectivity
Diffstat (limited to 'dungeon')
-rw-r--r--dungeon/src/bsp.rs18
-rw-r--r--dungeon/tests/bsp_tests.rs132
2 files changed, 120 insertions, 30 deletions
diff --git a/dungeon/src/bsp.rs b/dungeon/src/bsp.rs
index 8b5fa3b..aaafa10 100644
--- a/dungeon/src/bsp.rs
+++ b/dungeon/src/bsp.rs
@@ -228,15 +228,14 @@ impl Node {
}
/// Connect rooms of child nodes recursively and collect corridors to carve.
- ///
/// 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) {
- // Get room centers for left and right children
- let left_point = left.room_center();
- let right_point = right.room_center();
+ // Get a random point in each child's room
+ let left_point = left.random_point_in_room(rng);
+ let right_point = right.random_point_in_room(rng);
corridors.push((left_point, right_point));
// Recursively connect children
@@ -265,6 +264,10 @@ 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;
+ // Don't carve if it's already a room
+ if tiles[idx as usize] == Tile::Room {
+ continue;
+ }
tiles[idx as usize] = Tile::Hallway;
}
}
@@ -274,6 +277,10 @@ 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;
+ // Don't carve if it's already a room
+ if tiles[idx as usize] == Tile::Room {
+ continue;
+ }
tiles[idx as usize] = Tile::Hallway;
}
}
@@ -374,8 +381,6 @@ pub fn generate(seed: u64) -> (Box<[Tile; TILE_COUNT]>, Pos) {
}
// Choose player start randomly in the center of one of the rooms (leaf nodes)
-
- // Choose a random leaf node (room) for the player start
let mut leaves = vec![];
root.collect_leaves(&mut leaves);
let player_room = leaves.choose(&mut rng).unwrap_or(&leaves[0]);
@@ -454,6 +459,7 @@ mod tests {
let mut node = Node::new(rect);
let mut rng = rand::rngs::StdRng::seed_from_u64(12345);
node.split(&mut rng);
+ node.create_room(&mut rng);
let corridors = node.connect_children(&mut rng);
assert!(!corridors.is_empty());
}
diff --git a/dungeon/tests/bsp_tests.rs b/dungeon/tests/bsp_tests.rs
index 16a8a77..0a494b8 100644
--- a/dungeon/tests/bsp_tests.rs
+++ b/dungeon/tests/bsp_tests.rs
@@ -2,46 +2,130 @@
#[cfg(test)]
mod tests {
use dungeon::*;
+ use pos::Pos;
+ use rand::{Rng, SeedableRng};
+
+ /// Generate a set of test seeds for reproducibility with a seeded RNG
+ fn generate_test_seeds(seed: u64) -> Vec<u64> {
+ let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
+ // Generate 100 random u64 seeds
+ (0..100).map(|_| rng.random_range(0..u64::MAX)).collect()
+ }
/// Basic integration test for BSP generation
#[test]
fn test_bsp_integration() {
- let seed = 12345u64;
- let (tiles, player_start) = bsp::generate(seed);
- // Basic integration test: ensure we get valid data
- assert!(!tiles.is_empty());
- assert!(player_start.x() < map::MAP_SIZE);
- assert!(player_start.y() < map::MAP_SIZE);
+ let test_seeds = generate_test_seeds(123456);
+ for seed in test_seeds {
+ let (tiles, player_start) = bsp::generate(seed);
+ // Basic integration test: ensure we get valid data
+ assert!(!tiles.is_empty());
+ assert!(player_start.x() < map::MAP_SIZE);
+ assert!(player_start.y() < map::MAP_SIZE);
+ }
}
/// Test that BSP-generated floors have a valid player start
#[test]
fn test_bsp_player_start() {
- let seed = 12345u64;
- let (tiles, player_start) = bsp::generate(seed);
- // Ensure player start is within bounds
- assert!(player_start.x() < map::MAP_SIZE);
- assert!(player_start.y() < map::MAP_SIZE);
- // Ensure player start is a room tile
- let idx = player_start.idx();
- assert_eq!(tiles[idx], map::Tile::Room);
+ let test_seeds = generate_test_seeds(654321);
+ for seed in test_seeds {
+ let (tiles, player_start) = bsp::generate(seed);
+ // Ensure player start is within bounds
+ assert!(player_start.x() < map::MAP_SIZE);
+ assert!(player_start.y() < map::MAP_SIZE);
+ // Ensure player start is a room tile
+ let idx = player_start.idx();
+ assert_eq!(tiles[idx], map::Tile::Room);
+ }
}
- /// Test that BSP-generated floors have at least two rooms
+ /// Test that BSP-generated floors have at least one room tile
#[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 test_seeds = generate_test_seeds(111222);
+ for seed in test_seeds {
+ let (tiles, _player_start) = bsp::generate(seed);
+ // Ensure we have at least one room tile
+ let room_count = tiles
+ .iter()
+ .filter(|&&tile| tile == map::Tile::Room)
+ .count();
+ assert!(
+ room_count >= 1,
+ "Expected at least one room tile, found {room_count}"
+ );
+ }
+ }
+
+ /// Test that BSP-generated floors have walls on the borders
+ #[test]
+ fn test_bsp_walls_on_borders() {
+ let test_seeds = generate_test_seeds(777888);
+ for seed in test_seeds {
+ let (tiles, _player_start) = bsp::generate(seed);
+ // Go through all tiles, and ensure border tiles are walls
+ for x in 0..map::MAP_SIZE {
+ for y in 0..map::MAP_SIZE {
+ let pos = Pos::new(x, y).unwrap_or(const_pos!(1, 1));
+ if pos.is_border() {
+ let idx = pos.idx();
+ assert_eq!(
+ tiles[idx],
+ map::Tile::Wall,
+ "Expected wall at border position {pos:?}"
+ );
+ }
+ }
+ }
+ }
+ }
+
+ // Test that BSP-generated floors are reproducible with the same seed
+ #[test]
+ fn test_bsp_reproducibility() {
+ let test_seeds = generate_test_seeds(111111);
+ for seed in test_seeds {
+ let (tiles1, player_start1) = bsp::generate(seed);
+ let (tiles2, player_start2) = bsp::generate(seed);
+ assert_eq!(tiles1, tiles2, "Tiles differ for same seed {seed}");
+ assert_eq!(
+ player_start1, player_start2,
+ "Player starts differ for same seed {seed}"
+ );
+ }
+ }
+
+ // Test that all `air` tiles (`Tile::Room` and `Tile::Hallway`) are reachable from the player start
+ #[test]
+ fn test_bsp_all_air_tiles_reachable() {
+ let test_seeds = generate_test_seeds(333444);
+ for seed in test_seeds {
+ check_air_tiles_reachable(seed);
+ }
+ }
+
+ // Helper function to check that all air tiles are reachable from player start
+ fn check_air_tiles_reachable(seed: u64) {
+ let (tiles, player_start) = bsp::generate(seed);
+
+ // BFS to find all reachable air tiles
let mut visited = vec![false; tiles.len()];
+ let mut queue = vec![player_start];
+ visited[player_start.idx()] = true;
+ while let Some(pos) = queue.pop() {
+ for neighbor in pos.neighbors() {
+ let idx = neighbor.idx();
+ if !visited[idx] && tiles[idx] != map::Tile::Wall {
+ visited[idx] = true;
+ queue.push(neighbor);
+ }
+ }
+ }
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;
+ if tile == map::Tile::Room || tile == map::Tile::Hallway {
+ assert!(visited[i], "Unreachable air tile at index {i}");
}
}
- assert!(room_count >= 2);
}
}