diff options
| author | alf9310 <alf9310@rit.edu> | 2025-11-10 00:31:56 -0500 |
|---|---|---|
| committer | alf9310 <alf9310@rit.edu> | 2025-11-10 00:31:56 -0500 |
| commit | 0d484d763a243e6b73e13d4968ccf6222b65eeca (patch) | |
| tree | eefc124a4bbe165710eac9faeb2426a8e2608747 /dungeon | |
| parent | dungeon_generation: added Hallway vs Room tiles (diff) | |
| download | DungeonCrawl-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.rs | 18 | ||||
| -rw-r--r-- | dungeon/tests/bsp_tests.rs | 132 |
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); } } |