Skip to main content

terrain_forge/
rng.rs

1//! Seeded random number generator for deterministic generation
2
3use rand::{Rng as RandRng, SeedableRng};
4use rand_chacha::ChaCha8Rng;
5
6/// Seeded RNG wrapper for deterministic generation.
7///
8/// All terrain generation uses this RNG so that identical seeds produce
9/// identical output across runs and platforms.
10#[derive(Debug, Clone)]
11pub struct Rng {
12    inner: ChaCha8Rng,
13}
14
15impl Rng {
16    /// Creates a new RNG from the given seed.
17    pub fn new(seed: u64) -> Self {
18        Self {
19            inner: ChaCha8Rng::seed_from_u64(seed),
20        }
21    }
22
23    /// Returns a random `i32` in `[min, max)`.
24    pub fn range(&mut self, min: i32, max: i32) -> i32 {
25        self.inner.gen_range(min..max)
26    }
27
28    /// Returns a random `usize` in `[min, max)`.
29    pub fn range_usize(&mut self, min: usize, max: usize) -> usize {
30        self.inner.gen_range(min..max)
31    }
32
33    /// Returns a random `f64` in `[0.0, 1.0)`.
34    pub fn random(&mut self) -> f64 {
35        self.inner.gen()
36    }
37
38    /// Returns a random `u64`.
39    pub fn next_u64(&mut self) -> u64 {
40        self.inner.gen()
41    }
42
43    /// Returns `true` with the given probability (0.0–1.0).
44    pub fn chance(&mut self, probability: f64) -> bool {
45        self.random() < probability
46    }
47
48    /// Picks a random element from the slice, or `None` if empty.
49    pub fn pick<'a, T>(&mut self, slice: &'a [T]) -> Option<&'a T> {
50        if slice.is_empty() {
51            None
52        } else {
53            Some(&slice[self.range_usize(0, slice.len())])
54        }
55    }
56
57    /// Shuffles the slice in place (Fisher-Yates).
58    pub fn shuffle<T>(&mut self, slice: &mut [T]) {
59        for i in (1..slice.len()).rev() {
60            let j = self.range_usize(0, i + 1);
61            slice.swap(i, j);
62        }
63    }
64}