diff options
| author | Freya Murphy <freya@freyacat.org> | 2025-11-13 09:35:30 -0500 |
|---|---|---|
| committer | Freya Murphy <freya@freyacat.org> | 2025-11-13 09:47:37 -0500 |
| commit | 018d7d4c06a354071f72c77dc03abbbe622fdc81 (patch) | |
| tree | 31c3e42c171cd2ab170260e2efb6f9a320bbc757 /dungeon | |
| parent | dungeon: switch to only using small rng from rand (diff) | |
| download | DungeonCrawl-018d7d4c06a354071f72c77dc03abbbe622fdc81.tar.gz DungeonCrawl-018d7d4c06a354071f72c77dc03abbbe622fdc81.tar.bz2 DungeonCrawl-018d7d4c06a354071f72c77dc03abbbe622fdc81.zip | |
dungeon: have Dungeon store the rng, not the Floor
Diffstat (limited to 'dungeon')
| -rw-r--r-- | dungeon/src/bsp.rs | 18 | ||||
| -rw-r--r-- | dungeon/src/entity.rs | 30 | ||||
| -rw-r--r-- | dungeon/src/lib.rs | 89 | ||||
| -rw-r--r-- | dungeon/src/map.rs | 33 | ||||
| -rw-r--r-- | dungeon/tests/bsp.rs | 28 |
5 files changed, 100 insertions, 98 deletions
diff --git a/dungeon/src/bsp.rs b/dungeon/src/bsp.rs index 09b4433..905b29b 100644 --- a/dungeon/src/bsp.rs +++ b/dungeon/src/bsp.rs @@ -288,7 +288,7 @@ 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 a `Floor` -pub fn generate(seed: u64, mut rng: SmallRng) -> Floor { +pub fn generate(rng: &mut SmallRng) -> Floor { // Initialize all tiles to walls let mut tiles_box: Box<[Tile; TILE_COUNT]> = Box::new([Tile::Wall; TILE_COUNT]); @@ -332,7 +332,7 @@ pub fn generate(seed: u64, mut rng: SmallRng) -> Floor { // TODO: Store nodes in smart pointers to avoid unsafe? let node = unsafe { &mut *node_ptr }; // Attempt to split if possible - if node.split(&mut rng) { + if node.split(rng) { splitted_any = true; } } @@ -344,7 +344,7 @@ pub fn generate(seed: u64, mut rng: SmallRng) -> Floor { } // Create rooms in all leaves - root.create_room(&mut rng); + root.create_room(rng); // Carve all rooms into the tile array let mut leaves = vec![]; @@ -356,7 +356,7 @@ pub fn generate(seed: u64, mut rng: SmallRng) -> Floor { } // Collect corridors (pairs of centers) by connecting children bottom-up - let corridors = root.connect_children(&mut rng); + let corridors = root.connect_children(rng); // Carve corridors. For each corridor (x1,y1,x2,y2), carve straight or L-shape. for (left_point, right_point) in corridors { @@ -382,20 +382,20 @@ pub fn generate(seed: u64, mut rng: SmallRng) -> Floor { // Choose player start randomly in the center of one of the rooms (leaf nodes) let mut leaves = vec![]; root.collect_leaves(&mut leaves); - let player_room = leaves.choose(&mut rng).unwrap_or(&leaves[0]); + let player_room = leaves.choose(rng).unwrap_or(&leaves[0]); let player_start = player_room.room_center(); // Set one tile to Stairs (exit) in a random room different from player start let mut exit_room = player_room; while exit_room == player_room { - exit_room = leaves.choose(&mut rng).unwrap_or(&leaves[0]); + exit_room = leaves.choose(rng).unwrap_or(&leaves[0]); } let exit_pos = exit_room.room_center(); let exit_idx = exit_pos.xy().0 + exit_pos.xy().1 * MAP_SIZE; tiles_box[exit_idx as usize] = Tile::Stairs; // Return components turned into a `Floor` - Floor::from_parts(tiles_box, player_start, seed, rng) + Floor::new(tiles_box, player_start) } /// BSP Unit Tests @@ -468,8 +468,8 @@ mod tests { #[test] fn test_generate() { let seed = 12345u64; - let rng = SmallRng::seed_from_u64(seed); - let floor = generate(seed, rng); + let mut rng = SmallRng::seed_from_u64(seed); + let floor = generate(&mut rng); // Check that tiles contain some Room tiles let room_count = floor.tiles().iter().filter(|&&t| t == Tile::Room).count(); assert!(room_count > 0); diff --git a/dungeon/src/entity.rs b/dungeon/src/entity.rs index de21c28..e3b8fbd 100644 --- a/dungeon/src/entity.rs +++ b/dungeon/src/entity.rs @@ -1,6 +1,6 @@ //! The `entity` module contains structures of all entities including players and enimies. -use rand::Rng; +use rand::{Rng, rngs::SmallRng}; use crate::{Direction, FPos, Floor, Pos, astar, const_pos}; @@ -90,11 +90,11 @@ impl EnemyMoveState { /// /// Will randomly pick a direction and number of tiles within roaming range to move. /// If an invalid tile is selected 50 times in a row, `EnemyMoveState::Idle` is returned instead. - pub fn new_roam(starting_pos: Pos, floor: &mut Floor) -> Self { + pub fn new_roam(starting_pos: Pos, floor: &Floor, rng: &mut SmallRng) -> Self { let mut loop_index = 0; loop { - let dir = floor.rng().random(); - let dist = floor.rng().random_range(MIN_ROAM_DIST..=MAX_ROAM_DIST); + let dir = rng.random(); + let dist = rng.random_range(MIN_ROAM_DIST..=MAX_ROAM_DIST); if let Some(p) = starting_pos.step_by(dir, dist) { if !floor.get(p).is_wall() { @@ -201,10 +201,22 @@ impl Entity { /// /// TODO: Merge this implementation (and Self::zombie_movement and Self::movement_helper) with the player movement /// in lib.rs. - pub fn handle_movement(&mut self, player_pos: Pos, floor: &mut Floor, delta_time: f32) { + pub fn handle_movement( + &mut self, + player_pos: Pos, + floor: &Floor, + rng: &mut SmallRng, + delta_time: f32, + ) { match &self.kind { EntityKind::Zombie(move_state) => { - self.zombie_movement(move_state.clone(), player_pos, delta_time, floor); + self.zombie_movement( + move_state.clone(), + player_pos, + delta_time, + floor, + rng, + ); } EntityKind::Player => {} _ => {} @@ -217,7 +229,8 @@ impl Entity { move_state: EnemyMoveState, player_pos: Pos, delta_time: f32, - floor: &mut Floor, + floor: &Floor, + rng: &mut SmallRng, ) { // Check if player is in range if !matches!(move_state, EnemyMoveState::Attack { .. }) @@ -235,7 +248,8 @@ impl Entity { return; } - self.kind = EntityKind::Zombie(EnemyMoveState::new_roam(self.pos, floor)); + self.kind = + EntityKind::Zombie(EnemyMoveState::new_roam(self.pos, floor, rng)); } EnemyMoveState::Roam(mut moves) => { let p = if let Some(p) = moves.last() { diff --git a/dungeon/src/lib.rs b/dungeon/src/lib.rs index 7bc9c05..0021e81 100644 --- a/dungeon/src/lib.rs +++ b/dungeon/src/lib.rs @@ -26,6 +26,8 @@ pub struct Dungeon { pub floor: Floor, pub player: Player, pub enemies: Vec<Entity>, + seed: u64, + rng: SmallRng, } impl Dungeon { /// Creates a new `Dungeon` with a provided seed. @@ -34,15 +36,40 @@ impl Dungeon { /// /// ```no_run /// use dungeon::Dungeon; - /// use rand::{SeedableRng, rngs::SmallRng}; /// /// let seed = 234690523482u64; - /// let rng = SmallRng::seed_from_u64(seed); - /// let dungeon = Dungeon::with_rng(seed, rng); + /// let dungeon = Dungeon::new(seed); /// ``` #[must_use] - pub fn with_rng(seed: u64, rng: SmallRng) -> Self { - Self::from(bsp::generate(seed, rng)) + pub fn new(seed: u64) -> Self { + let mut rng = SmallRng::seed_from_u64(seed); + let floor = bsp::generate(&mut rng); + let player = Player::new(floor.player_start()); + // TODO: Randomize enemy positions/types + let enemies = vec![Entity::zombie(floor.random_walkable_pos(&mut rng))]; + + Self { + floor, + player, + enemies, + seed, + rng, + } + } + + /// Creates a new `Dungeon` with a random seed + /// + /// # Examples + /// + /// ```no_run + /// use dungeon::Dungeon; + /// + /// let dungeon = Dungeon::random(); + /// ``` + #[must_use] + pub fn random() -> Self { + let seed = OsRng.try_next_u64().unwrap_or_default(); + Self::new(seed) } /// Returns the current position of the camera (viewer) @@ -51,6 +78,18 @@ impl Dungeon { self.player.entity.fpos } + /// Returns the seed used to generate the map + #[must_use] + pub const fn seed(&self) -> u64 { + self.seed + } + + /// Returns the random number gen for the `Floor` + #[must_use] + pub fn rng(&mut self) -> &mut SmallRng { + &mut self.rng + } + pub fn update(&mut self, player_input: PlayerInput, delta_time: f32) { self.act_player(player_input, delta_time); self.act_non_players(delta_time); @@ -98,40 +137,12 @@ impl Dungeon { fn act_non_players(&mut self, delta_time: f32) { for enemy in self.enemies.iter_mut() { - enemy.handle_movement(self.player.entity.pos, &mut self.floor, delta_time); - } - } -} -impl Default for Dungeon { - /// Creates a new `Dungeon` with a default rng. - /// - /// # Examples - /// - /// ```no_run - /// use dungeon::Dungeon; - /// - /// let dungeon = Dungeon::default(); - /// ``` - fn default() -> Self { - let seed = OsRng.try_next_u64().unwrap_or(0); - let rng = SmallRng::seed_from_u64(seed); - let floor = bsp::generate(seed, rng); - Self::from(floor) - } -} -impl From<Floor> for Dungeon { - fn from(mut floor: Floor) -> Self { - let player = Player::new(floor.player_start()); - - // TODO: initalize rest of game state - - // TODO: Randomize enemy positions/types - let enemies = vec![Entity::zombie(floor.random_walkable_pos())]; - - Self { - floor, - player, - enemies, + enemy.handle_movement( + self.player.entity.pos, + &self.floor, + &mut self.rng, + delta_time, + ); } } } diff --git a/dungeon/src/map.rs b/dungeon/src/map.rs index b1c5f16..ed92882 100644 --- a/dungeon/src/map.rs +++ b/dungeon/src/map.rs @@ -76,10 +76,6 @@ pub struct Floor { tiles: Box<[Tile; TILE_COUNT]>, /// The position the player starts at player_start: Pos, - /// The seed used when generating the dungeon grid - seed: u64, - /// Seeded rng by `seed` - rng: SmallRng, /// The computed hash of the tile map hash: RefCell<u64>, /// If the tiles are dirty (hash needs to be recomputed) @@ -87,17 +83,10 @@ pub struct Floor { } impl Floor { /// Construct a floor from its components - pub fn from_parts( - tiles: Box<[Tile; TILE_COUNT]>, - player_start: Pos, - seed: u64, - rng: SmallRng, - ) -> Self { + pub fn new(tiles: Box<[Tile; TILE_COUNT]>, player_start: Pos) -> Self { Self { tiles, player_start, - seed, - rng, hash: RefCell::new(0), dirty: RefCell::new(true), } @@ -109,12 +98,6 @@ impl Floor { self.player_start } - /// Returns the seed used to generate the map - #[must_use] - pub const fn seed(&self) -> u64 { - self.seed - } - /// Returns a `Tile` on the dungeon grid at `Pos`. #[must_use] pub const fn get(&self, pos: Pos) -> Tile { @@ -174,21 +157,15 @@ impl Floor { /// Returns a random open (no wall) position #[must_use] - pub fn random_walkable_pos(&mut self) -> Pos { + pub fn random_walkable_pos(&self, rng: &mut SmallRng) -> Pos { loop { - let pos = self.rng().random(); + let pos = rng.random(); if !self.get(pos).is_walkable() { continue; } break pos; } } - - /// Returns the random number gen for the `Floor` - #[must_use] - pub fn rng(&mut self) -> &mut SmallRng { - &mut self.rng - } } impl Display for Floor { /// Display the floor as a string for debugging @@ -196,7 +173,7 @@ impl Display for Floor { /// # Examples /// ```no_run /// use dungeon::Dungeon; - /// let dungeon = Dungeon::default(); + /// let dungeon = Dungeon::random(); /// let floor = &dungeon.floor; /// println!("{floor}"); /// ``` @@ -228,7 +205,7 @@ mod tests { // Test floor printing #[test] fn test_floor_display() { - let dungeon = Dungeon::default(); + let dungeon = Dungeon::random(); let floor = &dungeon.floor; // Print the display for visual inspection println!("{floor}"); diff --git a/dungeon/tests/bsp.rs b/dungeon/tests/bsp.rs index 60198a5..b1287ee 100644 --- a/dungeon/tests/bsp.rs +++ b/dungeon/tests/bsp.rs @@ -17,8 +17,8 @@ mod tests { fn test_bsp_integration() { let test_seeds = generate_test_seeds(123456); for seed in test_seeds { - let rng = SmallRng::seed_from_u64(seed); - let floor = bsp::generate(seed, rng); + let mut rng = SmallRng::seed_from_u64(seed); + let floor = bsp::generate(&mut rng); // Basic integration test: ensure we get valid data assert!(!floor.tiles().is_empty()); } @@ -29,8 +29,8 @@ mod tests { fn test_bsp_player_start() { let test_seeds = generate_test_seeds(654321); for seed in test_seeds { - let rng = SmallRng::seed_from_u64(seed); - let floor = bsp::generate(seed, rng); + let mut rng = SmallRng::seed_from_u64(seed); + let floor = bsp::generate(&mut rng); // Ensure player start is a room tile let start = floor.player_start(); assert_eq!(floor.get(start), map::Tile::Room); @@ -42,8 +42,8 @@ mod tests { fn test_bsp_2_or_more_rooms() { let test_seeds = generate_test_seeds(111222); for seed in test_seeds { - let rng = SmallRng::seed_from_u64(seed); - let floor = bsp::generate(seed, rng); + let mut rng = SmallRng::seed_from_u64(seed); + let floor = bsp::generate(&mut rng); // Ensure we have at least one room tile let room_count = floor .tiles() @@ -62,8 +62,8 @@ mod tests { fn test_bsp_walls_on_borders() { let test_seeds = generate_test_seeds(777888); for seed in test_seeds { - let rng = SmallRng::seed_from_u64(seed); - let floor = bsp::generate(seed, rng); + let mut rng = SmallRng::seed_from_u64(seed); + let floor = bsp::generate(&mut rng); // Go through all tiles, and ensure border tiles are walls for pos in Pos::values() { if pos.is_border() { @@ -82,10 +82,10 @@ mod tests { fn test_bsp_reproducibility() { let test_seeds = generate_test_seeds(111111); for seed in test_seeds { - let rng1 = SmallRng::seed_from_u64(seed); - let rng2 = SmallRng::seed_from_u64(seed); - let floor1 = bsp::generate(seed, rng1); - let floor2 = bsp::generate(seed, rng2); + let mut rng1 = SmallRng::seed_from_u64(seed); + let mut rng2 = SmallRng::seed_from_u64(seed); + let floor1 = bsp::generate(&mut rng1); + let floor2 = bsp::generate(&mut rng2); assert_eq!( floor1.tiles(), floor2.tiles(), @@ -110,8 +110,8 @@ mod tests { // Helper function to check that all air tiles are reachable from player start fn check_air_tiles_reachable(seed: u64) { - let rng = SmallRng::seed_from_u64(seed); - let floor = bsp::generate(seed, rng); + let mut rng = SmallRng::seed_from_u64(seed); + let floor = bsp::generate(&mut rng); // BFS to find all reachable air tiles let mut visited = vec![false; TILE_COUNT]; |