summaryrefslogtreecommitdiff
path: root/dungeon
diff options
context:
space:
mode:
authorFreya Murphy <freya@freyacat.org>2025-11-12 11:01:21 -0500
committerFreya Murphy <freya@freyacat.org>2025-11-12 23:48:49 -0500
commitab1392bba09f8b58dc496b3a3f5b980bc5b475c8 (patch)
tree2044f352eb84e473d037e0b74428914c04e5a754 /dungeon
parentdungeon: bsp generate should return a floor to use same single rng (diff)
downloadDungeonCrawl-ab1392bba09f8b58dc496b3a3f5b980bc5b475c8.tar.gz
DungeonCrawl-ab1392bba09f8b58dc496b3a3f5b980bc5b475c8.tar.bz2
DungeonCrawl-ab1392bba09f8b58dc496b3a3f5b980bc5b475c8.zip
dungeon: switch to only using small rng from rand
Diffstat (limited to 'dungeon')
-rw-r--r--dungeon/src/bsp.rs29
-rw-r--r--dungeon/src/entity.rs4
-rw-r--r--dungeon/src/lib.rs43
-rw-r--r--dungeon/src/map.rs80
-rw-r--r--dungeon/tests/bsp.rs (renamed from dungeon/tests/bsp_tests.rs)25
5 files changed, 70 insertions, 111 deletions
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];