diff options
| author | Freya Murphy <freya@freyacat.org> | 2025-11-12 11:01:21 -0500 |
|---|---|---|
| committer | Freya Murphy <freya@freyacat.org> | 2025-11-12 23:48:49 -0500 |
| commit | ab1392bba09f8b58dc496b3a3f5b980bc5b475c8 (patch) | |
| tree | 2044f352eb84e473d037e0b74428914c04e5a754 | |
| parent | dungeon: bsp generate should return a floor to use same single rng (diff) | |
| download | DungeonCrawl-ab1392bba09f8b58dc496b3a3f5b980bc5b475c8.tar.gz DungeonCrawl-ab1392bba09f8b58dc496b3a3f5b980bc5b475c8.tar.bz2 DungeonCrawl-ab1392bba09f8b58dc496b3a3f5b980bc5b475c8.zip | |
dungeon: switch to only using small rng from rand
| -rw-r--r-- | Cargo.lock | 40 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | dungeon/src/bsp.rs | 29 | ||||
| -rw-r--r-- | dungeon/src/entity.rs | 4 | ||||
| -rw-r--r-- | dungeon/src/lib.rs | 43 | ||||
| -rw-r--r-- | dungeon/src/map.rs | 80 | ||||
| -rw-r--r-- | dungeon/tests/bsp.rs (renamed from dungeon/tests/bsp_tests.rs) | 25 | ||||
| -rw-r--r-- | game/src/main.rs | 2 |
8 files changed, 72 insertions, 153 deletions
@@ -260,15 +260,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] name = "prettyplease" version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -308,17 +299,6 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", "rand_core", ] @@ -482,23 +462,3 @@ name = "wit-bindgen" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[package]] -name = "zerocopy" -version = "0.8.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] @@ -29,7 +29,7 @@ features = ["help"] [workspace.dependencies.rand] version = "0.9" default-features = false -features = ["std_rng", "os_rng"] +features = ["small_rng", "os_rng"] [workspace.dependencies.raylib] git = "https://github.com/raylib-rs/raylib-rs" diff --git a/dungeon/src/bsp.rs b/dungeon/src/bsp.rs index e0ac001..09b4433 100644 --- a/dungeon/src/bsp.rs +++ b/dungeon/src/bsp.rs @@ -3,7 +3,7 @@ use core::panic; use rand::prelude::IndexedRandom; -use rand::{Rng, SeedableRng}; +use rand::{Rng, rngs::SmallRng}; use std::cmp; // for min/max use crate::Floor; @@ -40,7 +40,7 @@ impl Rect { } /// Returns a random point in this rectangle. - fn random_point<R: Rng>(&self, rng: &mut R) -> Pos { + fn random_point(&self, rng: &mut SmallRng) -> Pos { let rx = rng.random_range(self.x..(self.x + self.w)); let ry = rng.random_range(self.y..(self.y + self.h)); Pos::new(rx, ry).unwrap_or(self.center()) @@ -70,7 +70,7 @@ impl Node { /// Try to split the node. Returns true if split happened. /// Splitting is done either horizontally or vertically, /// depending on the dimensions of the node. - fn split<R: Rng>(&mut self, rng: &mut R) -> bool { + fn split(&mut self, rng: &mut SmallRng) -> bool { // Already split if self.left.is_some() || self.right.is_some() { return false; @@ -150,7 +150,7 @@ impl Node { /// Create a room inside this node (called for leaves). /// Room size and position chosen randomly. - fn create_room<R: Rng>(&mut self, rng: &mut R) { + fn create_room(&mut self, rng: &mut SmallRng) { if self.left.is_some() || self.right.is_some() { // This is not a leaf if let Some(left) = &mut self.left { @@ -213,7 +213,7 @@ impl Node { /// Return a random point for a room in this subtree: /// if node has a room, return a randiom point in it, else try left then right. - fn random_point_in_room<R: Rng>(&self, rng: &mut R) -> Pos { + fn random_point_in_room(&self, rng: &mut SmallRng) -> Pos { // Base case: if this node has a room, return a random point in it if let Some(room) = &self.room { return room.random_point(rng); @@ -230,7 +230,7 @@ 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)> { + fn connect_children(&self, rng: &mut SmallRng) -> Vec<(Pos, Pos)> { let mut corridors = Vec::new(); if let (Some(left), Some(right)) = (&self.left, &self.right) { @@ -288,9 +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) -> Floor { - let mut rng = rand::rngs::StdRng::seed_from_u64(seed); - +pub fn generate(seed: u64, mut rng: SmallRng) -> Floor { // Initialize all tiles to walls let mut tiles_box: Box<[Tile; TILE_COUNT]> = Box::new([Tile::Wall; TILE_COUNT]); @@ -403,6 +401,8 @@ pub fn generate(seed: u64) -> Floor { /// BSP Unit Tests #[cfg(test)] mod tests { + use rand::SeedableRng; + use super::*; use crate::map::MAP_SIZE; @@ -418,7 +418,7 @@ mod tests { fn test_node_split() { let rect = Rect::new(0, 0, 20, 20); let mut node = Node::new(rect); - let mut rng = rand::rngs::StdRng::seed_from_u64(12345); + let mut rng = SmallRng::seed_from_u64(12345); let splitted = node.split(&mut rng); assert!(splitted); assert!(node.left.is_some()); @@ -429,7 +429,7 @@ mod tests { fn test_node_create_room() { let rect = Rect::new(0, 0, 20, 20); let mut node = Node::new(rect); - let mut rng = rand::rngs::StdRng::seed_from_u64(12345); + let mut rng = SmallRng::seed_from_u64(12345); node.create_room(&mut rng); assert!(node.room.is_some()); match &node.room { @@ -447,7 +447,7 @@ mod tests { fn test_node_collect_leaves() { let rect = Rect::new(0, 0, 20, 20); let mut node = Node::new(rect); - let mut rng = rand::rngs::StdRng::seed_from_u64(12345); + let mut rng = SmallRng::seed_from_u64(12345); node.split(&mut rng); let mut leaves = Vec::new(); node.collect_leaves(&mut leaves); @@ -458,7 +458,7 @@ mod tests { fn test_node_connect_children() { let rect = Rect::new(0, 0, 20, 20); let mut node = Node::new(rect); - let mut rng = rand::rngs::StdRng::seed_from_u64(12345); + let mut rng = SmallRng::seed_from_u64(12345); node.split(&mut rng); node.create_room(&mut rng); let corridors = node.connect_children(&mut rng); @@ -468,7 +468,8 @@ mod tests { #[test] fn test_generate() { let seed = 12345u64; - let floor = generate(seed); + let rng = SmallRng::seed_from_u64(seed); + let floor = generate(seed, 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 82042b5..de21c28 100644 --- a/dungeon/src/entity.rs +++ b/dungeon/src/entity.rs @@ -93,8 +93,8 @@ impl EnemyMoveState { pub fn new_roam(starting_pos: Pos, floor: &mut Floor) -> Self { let mut loop_index = 0; loop { - let dir = Direction::random(floor.rand()); - let dist = floor.rand().random_range(MIN_ROAM_DIST..=MAX_ROAM_DIST); + let dir = floor.rng().random(); + let dist = floor.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() { diff --git a/dungeon/src/lib.rs b/dungeon/src/lib.rs index 4767942..7bc9c05 100644 --- a/dungeon/src/lib.rs +++ b/dungeon/src/lib.rs @@ -14,42 +14,35 @@ pub use map::*; pub use player_input::*; pub use pos::*; +use rand::{ + SeedableRng, TryRngCore, + rngs::{OsRng, SmallRng}, +}; + /// The `Dungeon` type represents the game state of the /// dungeon crawler. -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, Clone)] pub struct Dungeon { pub floor: Floor, pub player: Player, pub enemies: Vec<Entity>, } impl Dungeon { - /// Creates a new `Dungeon`. - /// - /// # Examples - /// - /// ```no_run - /// use dungeon::Dungeon; - /// - /// let dungeon = Dungeon::new(); - /// ``` - #[must_use] - pub fn new() -> Self { - Self::from(Floor::generate()) - } - /// Creates a new `Dungeon` with a provided seed. /// /// # Examples /// /// ```no_run /// use dungeon::Dungeon; + /// use rand::{SeedableRng, rngs::SmallRng}; /// /// let seed = 234690523482u64; - /// let dungeon = Dungeon::new_seeded(seed); + /// let rng = SmallRng::seed_from_u64(seed); + /// let dungeon = Dungeon::with_rng(seed, rng); /// ``` #[must_use] - pub fn new_seeded(seed: u64) -> Self { - Self::from(Floor::generate_seeded(seed)) + pub fn with_rng(seed: u64, rng: SmallRng) -> Self { + Self::from(bsp::generate(seed, rng)) } /// Returns the current position of the camera (viewer) @@ -110,8 +103,20 @@ impl Dungeon { } } 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 { - Self::from(Floor::default()) + 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 { diff --git a/dungeon/src/map.rs b/dungeon/src/map.rs index 7a6cc45..b1c5f16 100644 --- a/dungeon/src/map.rs +++ b/dungeon/src/map.rs @@ -1,7 +1,7 @@ //! The `map` module contains structures of the dungeon game map //! including the current `Floor`, and map `Tile`. -use rand::{Rng, SeedableRng, TryRngCore, rngs::StdRng}; +use rand::{Rng, rngs::SmallRng}; use strum::IntoEnumIterator; use strum_macros::EnumIter; @@ -11,8 +11,7 @@ use std::{ hash::{DefaultHasher, Hash, Hasher}, }; -use crate::bsp; -use crate::{const_pos, pos::Pos}; +use crate::pos::Pos; /// `MAP_SIZE` is the size of the size of the dungeon grid. pub const MAP_SIZE: u16 = 48; @@ -71,7 +70,7 @@ impl Display for Tile { /// The `Floor` type represents the current playing /// grid of the dungeon. It contains the tiles of the /// grid, and the starting position of the player. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct Floor { /// The dungeon grid tiles: Box<[Tile; TILE_COUNT]>, @@ -80,7 +79,7 @@ pub struct Floor { /// The seed used when generating the dungeon grid seed: u64, /// Seeded rng by `seed` - rng: StdRng, + rng: SmallRng, /// The computed hash of the tile map hash: RefCell<u64>, /// If the tiles are dirty (hash needs to be recomputed) @@ -92,7 +91,7 @@ impl Floor { tiles: Box<[Tile; TILE_COUNT]>, player_start: Pos, seed: u64, - rng: StdRng, + rng: SmallRng, ) -> Self { Self { tiles, @@ -104,43 +103,6 @@ impl Floor { } } - /// Generates a dungeon `Floor` using binary space partitioning. - /// - /// # Examples - /// - /// ```no_run - /// use dungeon::Floor; - /// - /// let floor = Floor::generate(); - /// ``` - #[must_use] - pub fn generate() -> Self { - let mut rng = rand::rngs::OsRng; - let seed = rng.try_next_u64().unwrap_or(0); - Self::generate_seeded(seed) - } - - /// Generates a dungeon `Floor` using binary space partitioning provided with a seed. - /// - /// The provided seed is used for randomness in the binary space partitioning - /// algorithm. - /// - /// # Examples - /// - /// ```no_run - /// use dungeon::Floor; - /// - /// /// here is our very seedy seed - /// let seed = 2893249402u64; - /// let floor_1 = Floor::generate_seeded(seed); - /// let floor_2 = Floor::generate_seeded(seed); - /// assert_eq!(floor_1, floor_2); // both floors will be identical - /// ``` - #[must_use] - pub fn generate_seeded(seed: u64) -> Self { - bsp::generate(seed) - } - /// Returns the start position of the player #[must_use] pub const fn player_start(&self) -> Pos { @@ -214,7 +176,7 @@ impl Floor { #[must_use] pub fn random_walkable_pos(&mut self) -> Pos { loop { - let pos = self.rand().random(); + let pos = self.rng().random(); if !self.get(pos).is_walkable() { continue; } @@ -224,35 +186,18 @@ impl Floor { /// Returns the random number gen for the `Floor` #[must_use] - pub fn rand(&mut self) -> &mut impl Rng { + pub fn rng(&mut self) -> &mut SmallRng { &mut self.rng } } -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::Room; TILE_COUNT]); - let seed = 0u64; - - for pos in Pos::values() { - if pos.is_border() { - tiles[pos.idx()] = Tile::Wall; - } - } - - let rng = rand::rngs::StdRng::seed_from_u64(seed); - - Self::from_parts(tiles, player_start, seed, rng) - } -} impl Display for Floor { /// Display the floor as a string for debugging /// /// # Examples /// ```no_run - /// use dungeon::Floor; - /// let floor = Floor::generate(); + /// use dungeon::Dungeon; + /// let dungeon = Dungeon::default(); + /// let floor = &dungeon.floor; /// println!("{floor}"); /// ``` fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -278,12 +223,13 @@ impl Display for Floor { /// Tests #[cfg(test)] mod tests { - use super::*; + use crate::Dungeon; // Test floor printing #[test] fn test_floor_display() { - let floor = Floor::generate(); + let dungeon = Dungeon::default(); + let floor = &dungeon.floor; // Print the display for visual inspection println!("{floor}"); } diff --git a/dungeon/tests/bsp_tests.rs b/dungeon/tests/bsp.rs index 09d2d3a..60198a5 100644 --- a/dungeon/tests/bsp_tests.rs +++ b/dungeon/tests/bsp.rs @@ -3,11 +3,11 @@ mod tests { use dungeon::*; use pos::Pos; - use rand::{Rng, SeedableRng}; + use rand::{Rng, SeedableRng, rngs::SmallRng}; /// 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); + let mut rng = SmallRng::seed_from_u64(seed); // Generate 100 random u64 seeds (0..100).map(|_| rng.random_range(0..u64::MAX)).collect() } @@ -17,7 +17,8 @@ mod tests { fn test_bsp_integration() { let test_seeds = generate_test_seeds(123456); for seed in test_seeds { - let floor = bsp::generate(seed); + let rng = SmallRng::seed_from_u64(seed); + let floor = bsp::generate(seed, rng); // Basic integration test: ensure we get valid data assert!(!floor.tiles().is_empty()); } @@ -28,7 +29,8 @@ mod tests { fn test_bsp_player_start() { let test_seeds = generate_test_seeds(654321); for seed in test_seeds { - let floor = bsp::generate(seed); + let rng = SmallRng::seed_from_u64(seed); + let floor = bsp::generate(seed, rng); // Ensure player start is a room tile let start = floor.player_start(); assert_eq!(floor.get(start), map::Tile::Room); @@ -40,7 +42,8 @@ mod tests { fn test_bsp_2_or_more_rooms() { let test_seeds = generate_test_seeds(111222); for seed in test_seeds { - let floor = bsp::generate(seed); + let rng = SmallRng::seed_from_u64(seed); + let floor = bsp::generate(seed, rng); // Ensure we have at least one room tile let room_count = floor .tiles() @@ -59,7 +62,8 @@ mod tests { fn test_bsp_walls_on_borders() { let test_seeds = generate_test_seeds(777888); for seed in test_seeds { - let floor = bsp::generate(seed); + let rng = SmallRng::seed_from_u64(seed); + let floor = bsp::generate(seed, rng); // Go through all tiles, and ensure border tiles are walls for pos in Pos::values() { if pos.is_border() { @@ -78,8 +82,10 @@ mod tests { fn test_bsp_reproducibility() { let test_seeds = generate_test_seeds(111111); for seed in test_seeds { - let floor1 = bsp::generate(seed); - let floor2 = bsp::generate(seed); + 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); assert_eq!( floor1.tiles(), floor2.tiles(), @@ -104,7 +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 floor = bsp::generate(seed); + let rng = SmallRng::seed_from_u64(seed); + let floor = bsp::generate(seed, rng); // BFS to find all reachable air tiles let mut visited = vec![false; TILE_COUNT]; diff --git a/game/src/main.rs b/game/src/main.rs index 28498dc..693e7f0 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -12,7 +12,7 @@ struct Game { impl Game { fn new(window: Window) -> Self { // Initial game state - let dungeon = Dungeon::new(); + let dungeon = Dungeon::default(); Self { window, dungeon, |