summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock12
-rw-r--r--Cargo.toml4
-rw-r--r--dungeon/src/bsp.rs33
-rw-r--r--dungeon/src/entity.rs7
-rw-r--r--dungeon/src/lib.rs17
-rw-r--r--dungeon/src/map.rs6
-rw-r--r--dungeon/src/rng.rs74
-rw-r--r--dungeon/tests/bsp.rs19
8 files changed, 124 insertions, 48 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d558689..8e7846b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -320,21 +320,19 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
-version = "0.9.2"
+version = "0.10.0-rc.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
+checksum = "be866deebbade98028b705499827ad6967c8bb1e21f96a2609913c8c076e9307"
dependencies = [
+ "getrandom",
"rand_core",
]
[[package]]
name = "rand_core"
-version = "0.9.3"
+version = "0.10.0-rc-2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
-dependencies = [
- "getrandom",
-]
+checksum = "104a23e4e8b77312a823b6b5613edbac78397e2f34320bc7ac4277013ec4478e"
[[package]]
name = "raylib"
diff --git a/Cargo.toml b/Cargo.toml
index 8b2140e..86909dc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -28,9 +28,9 @@ default-features = false
features = ["help"]
[workspace.dependencies.rand]
-version = "0.9"
+version = "0.10.0-rc.5"
default-features = false
-features = ["small_rng", "os_rng"]
+features = ["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 971f5fe..6fb2e51 100644
--- a/dungeon/src/bsp.rs
+++ b/dungeon/src/bsp.rs
@@ -2,13 +2,16 @@
//! Produces a tile array and a player start position for a given seed.
use core::panic;
+use rand::Rng;
use rand::prelude::IndexedRandom;
-use rand::{Rng, rngs::SmallRng};
use std::cmp::{self, Ordering}; // for min/max
-use crate::Floor;
-use crate::map::{MAP_SIZE, TILE_COUNT, Tile};
-use crate::{const_pos, pos::Pos};
+use crate::{
+ const_pos,
+ map::{Floor, MAP_SIZE, TILE_COUNT, Tile},
+ pos::Pos,
+ rng::DungeonRng,
+};
/// `MIN_LEAF_SIZE` is the minimum width/height for a leaf to be considered "splittable".
const MIN_LEAF_SIZE: u16 = 8;
@@ -40,7 +43,7 @@ impl Rect {
}
/// Returns a random point in this rectangle.
- fn random_point(self, rng: &mut SmallRng) -> Pos {
+ fn random_point(self, rng: &mut DungeonRng) -> 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 +73,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(&mut self, rng: &mut SmallRng) -> bool {
+ fn split(&mut self, rng: &mut DungeonRng) -> bool {
// Already split
if self.left.is_some() || self.right.is_some() {
return false;
@@ -148,7 +151,7 @@ impl Node {
/// Create a room inside this node (called for leaves).
/// Room size and position chosen randomly.
- fn create_room(&mut self, rng: &mut SmallRng) {
+ fn create_room(&mut self, rng: &mut DungeonRng) {
if self.left.is_some() || self.right.is_some() {
// This is not a leaf
if let Some(left) = &mut self.left {
@@ -211,7 +214,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(&self, rng: &mut SmallRng) -> Pos {
+ fn random_point_in_room(&self, rng: &mut DungeonRng) -> 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);
@@ -228,7 +231,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 SmallRng) -> Vec<(Pos, Pos)> {
+ fn connect_children(&self, rng: &mut DungeonRng) -> Vec<(Pos, Pos)> {
let mut corridors = Vec::new();
if let (Some(left), Some(right)) = (&self.left, &self.right) {
@@ -286,7 +289,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(rng: &mut SmallRng) -> Floor {
+pub fn generate(rng: &mut DungeonRng) -> Floor {
// Initialize all tiles to walls
let mut tiles_box: Box<[Tile; TILE_COUNT]> = Box::new([Tile::Wall; TILE_COUNT]);
@@ -413,7 +416,7 @@ mod tests {
fn test_node_split() {
let rect = Rect::new(0, 0, 20, 20);
let mut node = Node::new(rect);
- let mut rng = SmallRng::seed_from_u64(12345);
+ let mut rng = DungeonRng::seed_from_u64(12345);
let splitted = node.split(&mut rng);
assert!(splitted);
assert!(node.left.is_some());
@@ -424,7 +427,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 = SmallRng::seed_from_u64(12345);
+ let mut rng = DungeonRng::seed_from_u64(12345);
node.create_room(&mut rng);
assert!(node.room.is_some());
match &node.room {
@@ -442,7 +445,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 = SmallRng::seed_from_u64(12345);
+ let mut rng = DungeonRng::seed_from_u64(12345);
node.split(&mut rng);
let mut leaves = Vec::new();
node.collect_leaves(&mut leaves);
@@ -453,7 +456,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 = SmallRng::seed_from_u64(12345);
+ let mut rng = DungeonRng::seed_from_u64(12345);
node.split(&mut rng);
node.create_room(&mut rng);
let corridors = node.connect_children(&mut rng);
@@ -463,7 +466,7 @@ mod tests {
#[test]
fn test_generate() {
let seed = 12345u64;
- let mut rng = SmallRng::seed_from_u64(seed);
+ let mut rng = DungeonRng::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();
diff --git a/dungeon/src/entity.rs b/dungeon/src/entity.rs
index b290a2f..3a8f131 100644
--- a/dungeon/src/entity.rs
+++ b/dungeon/src/entity.rs
@@ -2,13 +2,14 @@
use std::mem::take;
-use rand::{Rng, rngs::SmallRng};
+use rand::Rng;
use crate::{
Dungeon, astar, const_pos,
map::Floor,
player_input::PlayerInput,
pos::{Direction, FPos, Pos},
+ rng::DungeonRng,
};
/// `PLAYER_FULL_HEALTH` is the starting health of the player entity
@@ -97,7 +98,7 @@ 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 roam(starting_pos: Pos, floor: &Floor, rng: &mut SmallRng) -> Self {
+ pub fn roam(starting_pos: Pos, floor: &Floor, rng: &mut DungeonRng) -> Self {
let mut loop_index = 0;
loop {
let dir = rng.random();
@@ -274,7 +275,7 @@ impl Default for Player {
struct Updater<'a> {
floor: &'a Floor,
- rng: &'a mut SmallRng,
+ rng: &'a mut DungeonRng,
player_pos: Pos,
input: PlayerInput,
delta_time: f32,
diff --git a/dungeon/src/lib.rs b/dungeon/src/lib.rs
index 76a2bda..4e48b68 100644
--- a/dungeon/src/lib.rs
+++ b/dungeon/src/lib.rs
@@ -8,11 +8,9 @@ pub mod map;
pub mod msg;
pub mod player_input;
pub mod pos;
+pub mod rng;
-use rand::{
- Rng, SeedableRng, TryRngCore,
- rngs::{OsRng, SmallRng},
-};
+use rand::{Rng, SeedableRng, TryRngCore, rngs::OsRng};
use crate::{
entity::{Entity, Player},
@@ -20,6 +18,7 @@ use crate::{
msg::Message,
player_input::PlayerInput,
pos::FPos,
+ rng::DungeonRng,
};
/// Lets the caller know what has
@@ -45,8 +44,8 @@ pub struct Dungeon {
pub enemies: Vec<Entity>,
pub msg: Message,
seed: u64,
- level_rng: SmallRng,
- game_rng: SmallRng,
+ level_rng: DungeonRng,
+ game_rng: DungeonRng,
}
impl Dungeon {
/// Creates a new `Dungeon` with a provided seed.
@@ -61,8 +60,8 @@ impl Dungeon {
/// ```
#[must_use]
pub fn new(seed: u64) -> Self {
- let mut game_rng = SmallRng::seed_from_u64(seed);
- let mut level_rng = SmallRng::seed_from_u64(game_rng.random());
+ let mut game_rng = DungeonRng::seed_from_u64(seed);
+ let mut level_rng = DungeonRng::seed_from_u64(game_rng.random());
let floor = bsp::generate(&mut level_rng);
let player = Player::new(floor.player_start());
let enemies = vec![];
@@ -110,7 +109,7 @@ impl Dungeon {
/// Returns the runtime random number gen for the `Floor`
#[must_use]
- pub const fn rng(&mut self) -> &mut SmallRng {
+ pub const fn rng(&mut self) -> &mut DungeonRng {
&mut self.game_rng
}
diff --git a/dungeon/src/map.rs b/dungeon/src/map.rs
index 4928a29..0d05b73 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, rngs::SmallRng};
+use rand::Rng;
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
@@ -11,7 +11,7 @@ use std::{
hash::{DefaultHasher, Hash, Hasher},
};
-use crate::pos::Pos;
+use crate::{pos::Pos, rng::DungeonRng};
/// `MAP_SIZE` is the size of the size of the dungeon grid.
pub const MAP_SIZE: u16 = 48;
@@ -155,7 +155,7 @@ impl Floor {
/// Returns a random open (no wall) position
#[must_use]
- pub fn random_walkable_pos(&self, rng: &mut SmallRng) -> Pos {
+ pub fn random_walkable_pos(&self, rng: &mut DungeonRng) -> Pos {
loop {
let pos = rng.random();
if !self.get(pos).is_walkable() {
diff --git a/dungeon/src/rng.rs b/dungeon/src/rng.rs
new file mode 100644
index 0000000..bb9151b
--- /dev/null
+++ b/dungeon/src/rng.rs
@@ -0,0 +1,74 @@
+//! Implements rng using the xoshiro256++ algorithm
+
+use rand::{
+ SeedableRng,
+ rand_core::{RngCore, le},
+};
+
+/// Deterministic pseudo random number generator.
+///
+/// Is is a copy of `SmallRng` from `rand`, but is gurenteed
+/// to be reproducible across versions and platforms.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct DungeonRng {
+ s: [u64; 4],
+}
+impl SeedableRng for DungeonRng {
+ // Fix to 256 bits. Changing this is a breaking change!
+ type Seed = [u8; 32];
+
+ #[inline]
+ fn from_seed(seed: Self::Seed) -> Self {
+ let mut state = [0; 4];
+ le::read_u64_into(&seed, &mut state);
+ Self { s: state }
+ }
+
+ #[inline]
+ fn seed_from_u64(mut state: u64) -> Self {
+ const PHI: u64 = 0x9e3779b97f4a7c15;
+ let mut s = [0; 4];
+ for i in &mut s {
+ state = state.wrapping_add(PHI);
+ let mut z = state;
+ z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
+ z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
+ z = z ^ (z >> 31);
+ *i = z;
+ }
+ Self { s }
+ }
+}
+impl RngCore for DungeonRng {
+ #[inline]
+ fn next_u32(&mut self) -> u32 {
+ let val = self.next_u64();
+ (val >> 32) as u32
+ }
+
+ #[inline]
+ fn next_u64(&mut self) -> u64 {
+ let res = self.s[0]
+ .wrapping_add(self.s[3])
+ .rotate_left(23)
+ .wrapping_add(self.s[0]);
+
+ let t = self.s[1] << 17;
+
+ self.s[2] ^= self.s[0];
+ self.s[3] ^= self.s[1];
+ self.s[1] ^= self.s[2];
+ self.s[0] ^= self.s[3];
+
+ self.s[2] ^= t;
+
+ self.s[3] = self.s[3].rotate_left(45);
+
+ res
+ }
+
+ #[inline]
+ fn fill_bytes(&mut self, dst: &mut [u8]) {
+ le::fill_bytes_via_next(self, dst);
+ }
+}
diff --git a/dungeon/tests/bsp.rs b/dungeon/tests/bsp.rs
index f7919c5..8ec7b3a 100644
--- a/dungeon/tests/bsp.rs
+++ b/dungeon/tests/bsp.rs
@@ -5,12 +5,13 @@ mod tests {
bsp,
map::{self, TILE_COUNT},
pos::Pos,
+ rng::DungeonRng,
};
- use rand::{Rng, SeedableRng, rngs::SmallRng};
+ use rand::{Rng, SeedableRng};
/// Generate a set of test seeds for reproducibility with a seeded RNG
fn generate_test_seeds(seed: u64) -> Vec<u64> {
- let mut rng = SmallRng::seed_from_u64(seed);
+ let mut rng = DungeonRng::seed_from_u64(seed);
// Generate 100 random u64 seeds
(0..100).map(|_| rng.random_range(0..u64::MAX)).collect()
}
@@ -20,7 +21,7 @@ mod tests {
fn test_bsp_integration() {
let test_seeds = generate_test_seeds(123456);
for seed in test_seeds {
- let mut rng = SmallRng::seed_from_u64(seed);
+ let mut rng = DungeonRng::seed_from_u64(seed);
let floor = bsp::generate(&mut rng);
// Basic integration test: ensure we get valid data
assert!(!floor.tiles().is_empty());
@@ -32,7 +33,7 @@ mod tests {
fn test_bsp_player_start() {
let test_seeds = generate_test_seeds(654321);
for seed in test_seeds {
- let mut rng = SmallRng::seed_from_u64(seed);
+ let mut rng = DungeonRng::seed_from_u64(seed);
let floor = bsp::generate(&mut rng);
// Ensure player start is a room tile
let start = floor.player_start();
@@ -45,7 +46,7 @@ mod tests {
fn test_bsp_2_or_more_rooms() {
let test_seeds = generate_test_seeds(111222);
for seed in test_seeds {
- let mut rng = SmallRng::seed_from_u64(seed);
+ let mut rng = DungeonRng::seed_from_u64(seed);
let floor = bsp::generate(&mut rng);
// Ensure we have at least one room tile
let room_count = floor
@@ -65,7 +66,7 @@ mod tests {
fn test_bsp_walls_on_borders() {
let test_seeds = generate_test_seeds(777888);
for seed in test_seeds {
- let mut rng = SmallRng::seed_from_u64(seed);
+ let mut rng = DungeonRng::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() {
@@ -85,8 +86,8 @@ mod tests {
fn test_bsp_reproducibility() {
let test_seeds = generate_test_seeds(111111);
for seed in test_seeds {
- let mut rng1 = SmallRng::seed_from_u64(seed);
- let mut rng2 = SmallRng::seed_from_u64(seed);
+ let mut rng1 = DungeonRng::seed_from_u64(seed);
+ let mut rng2 = DungeonRng::seed_from_u64(seed);
let floor1 = bsp::generate(&mut rng1);
let floor2 = bsp::generate(&mut rng2);
assert_eq!(
@@ -113,7 +114,7 @@ mod tests {
// Helper function to check that all air tiles are reachable from player start
fn check_air_tiles_reachable(seed: u64) {
- let mut rng = SmallRng::seed_from_u64(seed);
+ let mut rng = DungeonRng::seed_from_u64(seed);
let floor = bsp::generate(&mut rng);
// BFS to find all reachable air tiles