#[derive(Debug, Clone)]
pub struct SplitMix64(u64);
impl SplitMix64 {
pub fn new(seed: u64) -> Self {
Self(seed)
}
pub fn from_key(s: &str) -> Self {
Self::new(fnv1a(s))
}
pub fn next_u64(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
pub fn below(&mut self, n: usize) -> usize {
debug_assert!(n > 0);
(self.next_u64() % n as u64) as usize
}
pub fn shuffle<T>(&mut self, items: &mut [T]) {
for i in (1..items.len()).rev() {
let j = self.below(i + 1);
items.swap(i, j);
}
}
pub fn uuid_v4(&mut self) -> String {
let mut b = [0u8; 16];
for chunk in b.chunks_mut(8) {
let v = self.next_u64().to_le_bytes();
chunk.copy_from_slice(&v[..chunk.len()]);
}
b[6] = (b[6] & 0x0f) | 0x40;
b[8] = (b[8] & 0x3f) | 0x80;
let h = |r: &[u8]| r.iter().map(|x| format!("{x:02x}")).collect::<String>();
format!(
"{}-{}-{}-{}-{}",
h(&b[0..4]),
h(&b[4..6]),
h(&b[6..8]),
h(&b[8..10]),
h(&b[10..16])
)
}
}
pub fn fnv1a(s: &str) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for byte in s.as_bytes() {
h ^= u64::from(*byte);
h = h.wrapping_mul(0x100_0000_01b3);
}
h
}
pub fn entropy() -> u64 {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let mut r = SplitMix64::new(nanos ^ (u64::from(std::process::id()) << 32));
r.next_u64()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn same_seed_same_shuffle() {
let mut a = SplitMix64::new(42);
let mut b = SplitMix64::new(42);
let mut xs = [0, 1, 2, 3, 4, 5, 6, 7];
let mut ys = xs;
a.shuffle(&mut xs);
b.shuffle(&mut ys);
assert_eq!(xs, ys);
}
#[test]
fn shuffle_is_a_permutation() {
let mut r = SplitMix64::new(7);
let mut xs: Vec<usize> = (0..64).collect();
r.shuffle(&mut xs);
let mut sorted = xs.clone();
sorted.sort_unstable();
assert_eq!(sorted, (0..64).collect::<Vec<_>>());
assert_ne!(xs, sorted, "a 64-element shuffle should move something");
}
#[test]
fn shuffle_handles_degenerate_lengths() {
let mut r = SplitMix64::new(1);
let mut empty: [u8; 0] = [];
r.shuffle(&mut empty);
let mut one = [9];
r.shuffle(&mut one);
assert_eq!(one, [9]);
}
#[test]
fn uuid_v4_shape_and_variant_bits() {
let mut r = SplitMix64::new(99);
let id = r.uuid_v4();
let parts: Vec<&str> = id.split('-').collect();
assert_eq!(
parts.iter().map(|p| p.len()).collect::<Vec<_>>(),
[8, 4, 4, 4, 12]
);
assert!(id.chars().all(|c| c.is_ascii_hexdigit() || c == '-'));
assert_eq!(&parts[2][..1], "4", "version nibble");
assert!(
matches!(&parts[3][..1], "8" | "9" | "a" | "b"),
"variant nibble: {id}"
);
assert_ne!(id, SplitMix64::new(100).uuid_v4());
}
#[test]
fn seat_seeds_differ_per_seat() {
assert_ne!(
SplitMix64::from_key("judge-1").uuid_v4(),
SplitMix64::from_key("judge-2").uuid_v4()
);
}
}