diff options
| author | Freya Murphy <freya@freyacat.org> | 2025-11-11 20:46:17 -0500 |
|---|---|---|
| committer | Freya Murphy <freya@freyacat.org> | 2025-11-11 20:54:28 -0500 |
| commit | 0bedd7991a7f2e3e35ae6b23d4f041c5a48454b9 (patch) | |
| tree | 73175729fc202e0cda237871d88c3822c23bed8c | |
| parent | dungeon: rewrite const_pos! (diff) | |
| download | DungeonCrawl-0bedd7991a7f2e3e35ae6b23d4f041c5a48454b9.tar.gz DungeonCrawl-0bedd7991a7f2e3e35ae6b23d4f041c5a48454b9.tar.bz2 DungeonCrawl-0bedd7991a7f2e3e35ae6b23d4f041c5a48454b9.zip | |
dungeon: bsp generate should return a floor to use same single rng
- `Floor::new` is now `Floor::from_parts` and takes in and rng
- updated bps tests to operate on a floor
- removed pos < MAP_SIZE checks, since `Pos` gurentees this
- bsp::generate now returns a floor
| -rw-r--r-- | dungeon/src/bsp.rs | 17 | ||||
| -rw-r--r-- | dungeon/src/lib.rs | 2 | ||||
| -rw-r--r-- | dungeon/src/map.rs | 26 | ||||
| -rw-r--r-- | dungeon/tests/bsp_tests.rs | 65 |
4 files changed, 56 insertions, 54 deletions
diff --git a/dungeon/src/bsp.rs b/dungeon/src/bsp.rs index 48a00e1..e0ac001 100644 --- a/dungeon/src/bsp.rs +++ b/dungeon/src/bsp.rs @@ -6,6 +6,7 @@ use rand::prelude::IndexedRandom; use rand::{Rng, SeedableRng}; use std::cmp; // for min/max +use crate::Floor; use crate::map::{MAP_SIZE, TILE_COUNT, Tile}; use crate::{const_pos, pos::Pos}; @@ -286,8 +287,8 @@ fn carve_v_corridor(tiles: &mut [Tile; TILE_COUNT], y1: u16, y2: u16, x: u16) { } /// Top-level generator function for the dungeon using BSP. -/// Returns (tiles, player_start). -pub fn generate(seed: u64) -> (Box<[Tile; TILE_COUNT]>, Pos) { +/// Returns a `Floor` +pub fn generate(seed: u64) -> Floor { let mut rng = rand::rngs::StdRng::seed_from_u64(seed); // Initialize all tiles to walls @@ -395,8 +396,8 @@ pub fn generate(seed: u64) -> (Box<[Tile; TILE_COUNT]>, Pos) { let exit_idx = exit_pos.xy().0 + exit_pos.xy().1 * MAP_SIZE; tiles_box[exit_idx as usize] = Tile::Stairs; - // Return tiles and player_start - (tiles_box, player_start) + // Return components turned into a `Floor` + Floor::from_parts(tiles_box, player_start, seed, rng) } /// BSP Unit Tests @@ -467,12 +468,12 @@ mod tests { #[test] fn test_generate() { let seed = 12345u64; - let (tiles, player_start) = generate(seed); + let floor = generate(seed); // Check that tiles contain some Room tiles - let room_count = tiles.iter().filter(|&&t| t == Tile::Room).count(); + let room_count = floor.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); + assert!(floor.player_start().xy().0 < MAP_SIZE); + assert!(floor.player_start().xy().1 < MAP_SIZE); } } diff --git a/dungeon/src/lib.rs b/dungeon/src/lib.rs index adc053e..4767942 100644 --- a/dungeon/src/lib.rs +++ b/dungeon/src/lib.rs @@ -121,7 +121,7 @@ impl From<Floor> for Dungeon { // TODO: initalize rest of game state // TODO: Randomize enemy positions/types - let enemies = vec![Entity::zombie(floor.random_pos())]; + let enemies = vec![Entity::zombie(floor.random_walkable_pos())]; Self { floor, diff --git a/dungeon/src/map.rs b/dungeon/src/map.rs index a568950..7a6cc45 100644 --- a/dungeon/src/map.rs +++ b/dungeon/src/map.rs @@ -79,24 +79,28 @@ pub struct Floor { player_start: Pos, /// The seed used when generating the dungeon grid seed: u64, + /// Seeded rng by `seed` + rng: StdRng, /// The computed hash of the tile map hash: RefCell<u64>, /// If the tiles are dirty (hash needs to be recomputed) dirty: RefCell<bool>, - /// Custom rng - rng: StdRng, } impl Floor { - /// Internal constructor for `Floor` - fn new(tiles: Box<[Tile; TILE_COUNT]>, player_start: Pos, seed: u64) -> Self { - let rng = rand::rngs::StdRng::seed_from_u64(seed); + /// Construct a floor from its components + pub fn from_parts( + tiles: Box<[Tile; TILE_COUNT]>, + player_start: Pos, + seed: u64, + rng: StdRng, + ) -> Self { Self { tiles, player_start, seed, + rng, hash: RefCell::new(0), dirty: RefCell::new(true), - rng, } } @@ -134,9 +138,7 @@ impl Floor { /// ``` #[must_use] pub fn generate_seeded(seed: u64) -> Self { - let (tiles, player_start) = bsp::generate(seed); - - Self::new(tiles, player_start, seed) + bsp::generate(seed) } /// Returns the start position of the player @@ -210,7 +212,7 @@ impl Floor { /// Returns a random open (no wall) position #[must_use] - pub fn random_pos(&mut self) -> Pos { + pub fn random_walkable_pos(&mut self) -> Pos { loop { let pos = self.rand().random(); if !self.get(pos).is_walkable() { @@ -239,7 +241,9 @@ impl Default for Floor { } } - Self::new(tiles, player_start, seed) + let rng = rand::rngs::StdRng::seed_from_u64(seed); + + Self::from_parts(tiles, player_start, seed, rng) } } impl Display for Floor { diff --git a/dungeon/tests/bsp_tests.rs b/dungeon/tests/bsp_tests.rs index 0a494b8..09d2d3a 100644 --- a/dungeon/tests/bsp_tests.rs +++ b/dungeon/tests/bsp_tests.rs @@ -17,11 +17,9 @@ mod tests { fn test_bsp_integration() { let test_seeds = generate_test_seeds(123456); for seed in test_seeds { - let (tiles, player_start) = bsp::generate(seed); + let floor = 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); + assert!(!floor.tiles().is_empty()); } } @@ -30,13 +28,10 @@ mod tests { fn test_bsp_player_start() { 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); + let floor = bsp::generate(seed); // Ensure player start is a room tile - let idx = player_start.idx(); - assert_eq!(tiles[idx], map::Tile::Room); + let start = floor.player_start(); + assert_eq!(floor.get(start), map::Tile::Room); } } @@ -45,9 +40,10 @@ mod tests { fn test_bsp_2_or_more_rooms() { let test_seeds = generate_test_seeds(111222); for seed in test_seeds { - let (tiles, _player_start) = bsp::generate(seed); + let floor = bsp::generate(seed); // Ensure we have at least one room tile - let room_count = tiles + let room_count = floor + .tiles() .iter() .filter(|&&tile| tile == map::Tile::Room) .count(); @@ -63,19 +59,15 @@ mod tests { 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); + let floor = 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:?}" - ); - } + for pos in Pos::values() { + if pos.is_border() { + assert_eq!( + floor.get(pos), + map::Tile::Wall, + "Expected wall at border position {pos:?}" + ); } } } @@ -86,11 +78,16 @@ mod tests { 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}"); + let floor1 = bsp::generate(seed); + let floor2 = bsp::generate(seed); assert_eq!( - player_start1, player_start2, + floor1.tiles(), + floor2.tiles(), + "Tiles differ for same seed {seed}" + ); + assert_eq!( + floor1.player_start(), + floor2.player_start(), "Player starts differ for same seed {seed}" ); } @@ -107,22 +104,22 @@ mod tests { // 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); + let floor = 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; + let mut visited = vec![false; TILE_COUNT]; + let mut queue = vec![floor.player_start()]; + visited[floor.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 { + if !visited[idx] && floor.get(neighbor) != map::Tile::Wall { visited[idx] = true; queue.push(neighbor); } } } - for (i, &tile) in tiles.iter().enumerate() { + for (i, &tile) in floor.tiles().iter().enumerate() { if tile == map::Tile::Room || tile == map::Tile::Hallway { assert!(visited[i], "Unreachable air tile at index {i}"); } |