diff --git a/Cargo.lock b/Cargo.lock index d40b83a45..2d6d47cc2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -772,6 +772,12 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + [[package]] name = "memchr" version = "2.7.4" @@ -1091,6 +1097,7 @@ version = "0.1.0" dependencies = [ "colored", "fastnbt", + "md5", "serde", "uuid", ] diff --git a/pumpkin-core/Cargo.toml b/pumpkin-core/Cargo.toml index a77ec0311..6bd39248b 100644 --- a/pumpkin-core/Cargo.toml +++ b/pumpkin-core/Cargo.toml @@ -7,4 +7,5 @@ edition.workspace = true serde = { version = "1.0", features = ["derive"] } fastnbt = { git = "https://github.com/owengage/fastnbt.git" } uuid.workspace = true -colored = "2" \ No newline at end of file +colored = "2" +md5 = "0.7.0" diff --git a/pumpkin-core/src/lib.rs b/pumpkin-core/src/lib.rs index 481c63acc..4cfe1770b 100644 --- a/pumpkin-core/src/lib.rs +++ b/pumpkin-core/src/lib.rs @@ -1 +1,2 @@ +pub mod random; pub mod text; diff --git a/pumpkin-core/src/random/gaussian.rs b/pumpkin-core/src/random/gaussian.rs new file mode 100644 index 000000000..8364cb910 --- /dev/null +++ b/pumpkin-core/src/random/gaussian.rs @@ -0,0 +1,31 @@ +use super::Random; + +pub trait GaussianGenerator: Random { + fn has_next_gaussian(&self) -> bool; + + fn set_has_next_gaussian(&mut self, value: bool); + + fn stored_next_gaussian(&self) -> f64; + + fn set_stored_next_gaussian(&mut self, value: f64); + + fn calculate_gaussian(&mut self) -> f64 { + if self.has_next_gaussian() { + self.set_has_next_gaussian(false); + self.stored_next_gaussian() + } else { + loop { + let d = 2f64 * self.next_f64() - 1f64; + let e = 2f64 * self.next_f64() - 1f64; + let f = d * d + e * e; + + if f < 1f64 && f != 0f64 { + let g = (-2f64 * f.ln() / f).sqrt(); + self.set_stored_next_gaussian(e * g); + self.set_has_next_gaussian(true); + return d * g; + } + } + } + } +} diff --git a/pumpkin-core/src/random/mod.rs b/pumpkin-core/src/random/mod.rs new file mode 100644 index 000000000..f56a4c182 --- /dev/null +++ b/pumpkin-core/src/random/mod.rs @@ -0,0 +1,53 @@ +pub mod xoroshiro128; + +mod gaussian; + +pub trait Random { + fn from_seed(seed: u64) -> Self; + + fn split(&mut self) -> Self; + + fn next_splitter(&mut self) -> impl RandomSplitter; + + fn next(&mut self, bits: u64) -> u64; + + fn next_i32(&mut self) -> i32; + + fn next_bounded_i32(&mut self, bound: i32) -> i32; + + fn next_inbetween_i32(&mut self, min: i32, max: i32) -> i32 { + self.next_bounded_i32(max - min + 1) + min + } + + fn next_i64(&mut self) -> i64; + + fn next_bool(&mut self) -> bool; + + fn next_f32(&mut self) -> f32; + + fn next_f64(&mut self) -> f64; + + fn next_gaussian(&mut self) -> f64; + + fn next_triangular(&mut self, mode: f64, deviation: f64) -> f64 { + mode + deviation * (self.next_f64() - self.next_f64()) + } + + fn skip(&mut self, count: i32) { + for _ in 0..count { + self.next_i32(); + } + } + + fn next_inbetween_i32_exclusive(&mut self, min: i32, max: i32) -> i32 { + min + self.next_bounded_i32(max - min) + } +} + +pub trait RandomSplitter { + fn split_string(&self, seed: &str) -> impl Random; + + fn split_u64(&self, seed: u64) -> impl Random; + + fn split_pos(&self, x: i32, y: i32, z: i32) -> impl Random; +} diff --git a/pumpkin-core/src/random/xoroshiro128.rs b/pumpkin-core/src/random/xoroshiro128.rs new file mode 100644 index 000000000..dea1255ad --- /dev/null +++ b/pumpkin-core/src/random/xoroshiro128.rs @@ -0,0 +1,447 @@ +use super::{gaussian::GaussianGenerator, Random, RandomSplitter}; + +pub struct Xoroshiro { + lo: u64, + hi: u64, + internal_next_gaussian: f64, + internal_has_next_gaussian: bool, +} + +impl Xoroshiro { + fn new(lo: u64, hi: u64) -> Self { + let (lo, hi) = if (lo | hi) == 0 { + (0x9E3779B97F4A7C15, 0x6A09E667F3BCC909) + } else { + (lo, hi) + }; + Self { + lo, + hi, + internal_next_gaussian: 0f64, + internal_has_next_gaussian: false, + } + } + + fn mix_u64(seed: u64) -> (u64, u64) { + let l = seed ^ 0x6A09E667F3BCC909; + let m = l.wrapping_add(0x9E3779B97F4A7C15); + (l, m) + } + + pub fn from_seed_unmixed(seed: u64) -> Self { + let (lo, hi) = Self::mix_u64(seed); + Self::new(lo, hi) + } + + fn next_random(&mut self) -> u64 { + let l = self.lo; + let m = self.hi; + let n = (l.wrapping_add(m)).rotate_left(17).wrapping_add(l); + let m = m ^ l; + self.lo = l.rotate_left(49) ^ m ^ (m << 21); + self.hi = m.rotate_left(28); + n + } +} + +impl GaussianGenerator for Xoroshiro { + fn stored_next_gaussian(&self) -> f64 { + self.internal_next_gaussian + } + + fn has_next_gaussian(&self) -> bool { + self.internal_has_next_gaussian + } + + fn set_stored_next_gaussian(&mut self, value: f64) { + self.internal_next_gaussian = value; + } + + fn set_has_next_gaussian(&mut self, value: bool) { + self.internal_has_next_gaussian = value; + } +} + +fn mix_stafford_13(z: u64) -> u64 { + let z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9); + let z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB); + z ^ (z >> 31) +} + +impl Random for Xoroshiro { + fn from_seed(seed: u64) -> Self { + let (lo, hi) = Self::mix_u64(seed); + let lo = mix_stafford_13(lo); + let hi = mix_stafford_13(hi); + Self::new(lo, hi) + } + + fn split(&mut self) -> Self { + Self::new(self.next_random(), self.next_random()) + } + + fn next(&mut self, bits: u64) -> u64 { + self.next_random() >> (64 - bits) + } + + fn next_splitter(&mut self) -> impl RandomSplitter { + XoroshiroSplitter { + lo: self.next_random(), + hi: self.next_random(), + } + } + + fn next_i32(&mut self) -> i32 { + self.next_random() as i32 + } + + fn next_bounded_i32(&mut self, bound: i32) -> i32 { + let mut l = (self.next_i32() as u64) & 0xFFFFFFFF; + let mut m = l.wrapping_mul(bound as u64); + let mut n = m & 0xFFFFFFFF; + if n < bound as u64 { + let i = (((!bound).wrapping_add(1)) as u64) % (bound as u64); + while n < i { + l = (self.next_i32() as u64) & 0xFFFFFFFF; + m = l.wrapping_mul(bound as u64); + n = m & 0xFFFFFFFF; + } + } + let o = m >> 32; + o as i32 + } + + fn next_i64(&mut self) -> i64 { + self.next_random() as i64 + } + + fn next_bool(&mut self) -> bool { + (self.next_random() & 1) != 0 + } + + fn next_f32(&mut self) -> f32 { + self.next(24) as f32 * 5.9604645E-8f32 + } + + fn next_f64(&mut self) -> f64 { + self.next(53) as f64 * 1.110223E-16f32 as f64 + } + + fn next_gaussian(&mut self) -> f64 { + self.calculate_gaussian() + } + + fn skip(&mut self, count: i32) { + for _ in 0..count { + self.next_random(); + } + } +} + +pub struct XoroshiroSplitter { + lo: u64, + hi: u64, +} + +fn hash_pos(x: i32, y: i32, z: i32) -> i64 { + let l = + ((x.wrapping_mul(3129871)) as i64) ^ ((z as i64).wrapping_mul(116129781i64)) ^ (y as i64); + let l = l + .wrapping_mul(l) + .wrapping_mul(42317861i64) + .wrapping_add(l.wrapping_mul(11i64)); + l >> 16 +} + +impl RandomSplitter for XoroshiroSplitter { + fn split_pos(&self, x: i32, y: i32, z: i32) -> impl Random { + let l = hash_pos(x, y, z) as u64; + let m = l ^ self.lo; + Xoroshiro::new(m, self.hi) + } + + fn split_u64(&self, seed: u64) -> impl Random { + Xoroshiro::new(seed ^ self.lo, seed ^ self.hi) + } + + fn split_string(&self, seed: &str) -> impl Random { + let bytes = md5::compute(seed.as_bytes()); + let l = u64::from_be_bytes(bytes[0..8].try_into().expect("incorrect length")); + let m = u64::from_be_bytes(bytes[8..16].try_into().expect("incorrect length")); + + Xoroshiro::new(l ^ self.lo, m ^ self.hi) + } +} + +#[cfg(test)] +mod tests { + use crate::random::{Random, RandomSplitter}; + + use super::{hash_pos, mix_stafford_13, Xoroshiro}; + + // Values checked against results from the equivalent Java source + + #[test] + fn block_position_hash() { + let values: [((i32, i32, i32), i64); 8] = [ + ((0, 0, 0), 0), + ((1, 1, 1), 60311958971344), + ((4, 4, 4), 120566413180880), + ((25, 25, 25), 111753446486209), + ((676, 676, 676), 75210837988243), + ((458329, 458329, 458329), -43764888250), + ((-387008604, -387008604, -387008604), 8437923733503), + ((176771161, 176771161, 176771161), 18421337580760), + ]; + + for ((x, y, z), value) in values { + assert_eq!(hash_pos(x, y, z), value); + } + } + + #[test] + fn test_mix_stafford_13() { + let values: [(u64, i64); 31] = [ + (0, 0), + (1, 6238072747940578789), + (64, -8456553050427055661), + (4096, -1125827887270283392), + (262144, -120227641678947436), + (16777216, 6406066033425044679), + (1073741824, 3143522559155490559), + (16, -2773008118984693571), + (1024, 8101005175654470197), + (65536, -3551754741763842827), + (4194304, -2737109459693184599), + (2, -2606959012126976886), + (128, -5825874238589581082), + (8192, 1111983794319025228), + (524288, -7964047577924347155), + (33554432, -5634612006859462257), + (2147483648, -1436547171018572641), + (137438953472, -4514638798598940860), + (8796093022208, -610572083552328405), + (562949953421312, -263574021372026223), + (36028797018963968, 7868130499179604987), + (253, -4045451768301188906), + (127, -6873224393826578139), + (8447, 6670985465942597767), + (524543, -6228499289678716485), + (33554687, 2630391896919662492), + (2147483903, -6879633228472053040), + (137438953727, -5817997684975131823), + (8796093022463, 2384436581894988729), + (562949953421567, -5076179956679497213), + (36028797018964223, -5993365784811617721), + ]; + for (input, output) in values { + assert_eq!(mix_stafford_13(input), output as u64); + } + } + + #[test] + fn test_next_i32() { + let values = [ + -160476802, + 781697906, + 653572596, + 1337520923, + -505875771, + -47281585, + 342195906, + 1417498593, + -1478887443, + 1560080270, + ]; + + let mut xoroshiro = Xoroshiro::from_seed(0); + for value in values { + assert_eq!(xoroshiro.next_i32(), value); + } + } + + #[test] + fn test_next_bounded_i32() { + let mut xoroshiro = Xoroshiro::from_seed(0); + + let values = [9, 1, 1, 3, 8, 9, 0, 3, 6, 3]; + for value in values { + assert_eq!(xoroshiro.next_bounded_i32(10), value); + } + + let values = [ + 9784805, 470346, 13560642, 7320226, 14949645, 13460529, 2824352, 10938308, 14146127, + 4549185, + ]; + for value in values { + assert_eq!(xoroshiro.next_bounded_i32(0xFFFFFF), value); + } + } + + #[test] + fn test_next_between_i32() { + let mut xoroshiro = Xoroshiro::from_seed(0); + + let values = [99, 59, 57, 65, 94, 100, 54, 66, 83, 68]; + for value in values { + assert_eq!(xoroshiro.next_inbetween_i32(50, 100), value); + } + } + + #[test] + fn test_next_inbetween_exclusive() { + let mut xoroshiro = Xoroshiro::from_seed(0); + + let values = [98, 59, 57, 65, 94, 99, 53, 66, 82, 68]; + for value in values { + assert_eq!(xoroshiro.next_inbetween_i32_exclusive(50, 100), value); + } + } + + #[test] + fn test_next_f64() { + let mut xoroshiro = Xoroshiro::from_seed(0); + + let values: [f64; 10] = [ + 0.16474369376959186, + 0.7997457290026366, + 0.2511961888876212, + 0.11712489470639631, + 0.0997124786680137, + 0.7566797430601416, + 0.7723285712021574, + 0.9420469457586381, + 0.48056202536813664, + 0.6099690583914598, + ]; + for value in values { + assert_eq!(xoroshiro.next_f64(), value); + } + } + + #[test] + fn test_next_f32() { + let mut xoroshiro = Xoroshiro::from_seed(0); + + let values: [f32; 10] = [ + 0.16474366, + 0.7997457, + 0.25119615, + 0.117124856, + 0.09971243, + 0.7566797, + 0.77232856, + 0.94204694, + 0.48056197, + 0.609969, + ]; + for value in values { + assert_eq!(xoroshiro.next_f32(), value); + } + } + + #[test] + fn test_next_i64() { + let mut xoroshiro = Xoroshiro::from_seed(0); + + let values: [i64; 10] = [ + 3038984756725240190, + -3694039286755638414, + 4633751808701151732, + 2160572957309072155, + 1839370574944072389, + -4488466507718817201, + -4199796579929588030, + -1069045159880208415, + 8864804693509535725, + -7194800960680693874, + ]; + for value in values { + assert_eq!(xoroshiro.next_i64(), value); + } + } + + #[test] + fn test_next_bool() { + let mut xoroshiro = Xoroshiro::from_seed(0); + + let values: [bool; 10] = [ + false, false, false, true, true, true, false, true, true, false, + ]; + for value in values { + assert_eq!(xoroshiro.next_bool(), value); + } + } + + #[test] + fn test_next_gaussian() { + let mut xoroshiro = Xoroshiro::from_seed(0); + + let values: [f64; 10] = [ + -0.48540690699780015, + 0.43399227545320296, + -0.3283265251019599, + -0.5052497078202575, + -0.3772512828630807, + 0.2419080215945433, + -0.42622066207565135, + 2.411315261138953, + -1.1419147030553274, + -0.05849758093810378, + ]; + for value in values { + assert_eq!(xoroshiro.next_gaussian(), value); + } + } + + #[test] + fn test_next_triangular() { + let mut xoroshiro = Xoroshiro::from_seed(0); + + let values: [f64; 10] = [ + 6.824989823834776, + 10.670356470906125, + 6.71516367803936, + 9.151408127217596, + 9.352964834883384, + 8.291618967842293, + 8.954549938640508, + 11.833001837470519, + 10.65851306020791, + 11.684676364031647, + ]; + for value in values { + assert_eq!(xoroshiro.next_triangular(10f64, 5f64), value); + } + } + + #[test] + fn test_split() { + let mut xoroshiro = Xoroshiro::from_seed(0); + + let mut new_generator = xoroshiro.split(); + assert_eq!(new_generator.next_i32(), 542195535); + + { + // Drop splitter out of scope, so we can mut call new_generator again + let splitter = new_generator.next_splitter(); + let mut rand_1 = splitter.split_string("TEST STRING"); + assert_eq!(rand_1.next_i32(), -641435713); + + let mut rand_2 = splitter.split_u64(42069); + assert_eq!(rand_2.next_i32(), -340700677); + + let mut rand_3 = splitter.split_pos(1337, 80085, -69420); + assert_eq!(rand_3.next_i32(), 790449132); + } + // Verify we didn't mutate the originals + assert_eq!(xoroshiro.next_i32(), 653572596); + assert_eq!(new_generator.next_i32(), 435917842); + } + + #[test] + fn test_intersection() { + let mut xoroshiro = Xoroshiro::new(0, 0); + assert_eq!(xoroshiro.next_i64(), 6807859099481836695); + } +}