summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFreya Murphy <freya@freyacat.org>2025-11-13 09:35:30 -0500
committerFreya Murphy <freya@freyacat.org>2025-11-13 09:47:37 -0500
commit018d7d4c06a354071f72c77dc03abbbe622fdc81 (patch)
tree31c3e42c171cd2ab170260e2efb6f9a320bbc757
parentdungeon: switch to only using small rng from rand (diff)
downloadDungeonCrawl-018d7d4c06a354071f72c77dc03abbbe622fdc81.tar.gz
DungeonCrawl-018d7d4c06a354071f72c77dc03abbbe622fdc81.tar.bz2
DungeonCrawl-018d7d4c06a354071f72c77dc03abbbe622fdc81.zip
dungeon: have Dungeon store the rng, not the Floor
-rw-r--r--dungeon/src/bsp.rs18
-rw-r--r--dungeon/src/entity.rs30
-rw-r--r--dungeon/src/lib.rs89
-rw-r--r--dungeon/src/map.rs33
-rw-r--r--dungeon/tests/bsp.rs28
-rw-r--r--game/src/main.rs2
-rw-r--r--graphics/src/render.rs2
7 files changed, 102 insertions, 100 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];
diff --git a/game/src/main.rs b/game/src/main.rs
index 693e7f0..569d08a 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::default();
+ let dungeon = Dungeon::random();
Self {
window,
dungeon,
diff --git a/graphics/src/render.rs b/graphics/src/render.rs
index 501f140..d450f16 100644
--- a/graphics/src/render.rs
+++ b/graphics/src/render.rs
@@ -663,7 +663,7 @@ impl Renderer {
draw_text!(self, r, UI_COL1, UI_ROW3, "DIR {dir}");
// Draw Player Seed
- let seed = &dungeon.floor.seed();
+ let seed = &dungeon.seed();
draw_text!(self, r, UI_COL2, UI_ROW1, "{seed:016X}");
// Draw Dungeon Hash