diff --git a/CHANGELOG.md b/CHANGELOG.md index 6662225..5714ffe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Additions - Added GetDownTime, GetUpTime, GetTimeSincePress, WasModifierDown and WasDown to Keybind and Combination - Added the ability for UniformTextureAtlases to have padding for each region - Added UniformTextureAtlas methods ToList and ToDictionary +- Added the SingleRandom utility class - **Added the ability to find paths to one of multiple goals using AStar** Improvements diff --git a/MLEM/Misc/SingleRandom.cs b/MLEM/Misc/SingleRandom.cs new file mode 100644 index 0000000..c662a5f --- /dev/null +++ b/MLEM/Misc/SingleRandom.cs @@ -0,0 +1,116 @@ +using System; + +namespace MLEM.Misc { + /// + /// The SingleRandom class allows generating single, one-off pseudorandom numbers based on a seed, or a set of seeds. + /// The types of numbers that can be generated are and , the former of which can be generated with specific minimum and maximum values. + /// Methods in this class are tested to be sufficiently "random", that is, to be sufficiently distributed throughout their range, as well as sufficiently different for neighboring seeds. + /// + public class SingleRandom { + + /// + /// Generates a single, one-off pseudorandom integer between 0 and based on a given . + /// This method is guaranteed to return the same result for the same . + /// + /// The seed to use. + /// The generated number. + public static int Int(int seed) { + return (int) (SingleRandom.Single(seed) * int.MaxValue); + } + + /// + /// Generates a single, one-off pseudorandom integer between 0 and based on a given set of . + /// This method is guaranteed to return the same result for the same set of . + /// + /// The seeds to use. + /// The generated number. + public static int Int(params int[] seeds) { + return (int) (SingleRandom.Single(seeds) * int.MaxValue); + } + + /// + /// Generates a single, one-off pseudorandom integer between 0 and based on a given . + /// This method is guaranteed to return the same result for the same . + /// + /// The (exclusive) maximum value. + /// The seed to use. + /// The generated number. + public static int Int(int maxValue, int seed) { + return (int) (maxValue * SingleRandom.Single(seed)); + } + + /// + /// Generates a single, one-off pseudorandom integer between 0 and based on a given set of . + /// This method is guaranteed to return the same result for the same set of . + /// + /// The (exclusive) maximum value. + /// The seeds to use. + /// The generated number. + public static int Int(int maxValue, params int[] seeds) { + return (int) (maxValue * SingleRandom.Single(seeds)); + } + + /// + /// Generates a single, one-off pseudorandom integer between and based on a given . + /// This method is guaranteed to return the same result for the same . + /// + /// The (inclusive) minimum value. + /// The (exclusive) maximum value. + /// The seed to use. + /// The generated number. + public static int Int(int minValue, int maxValue, int seed) { + return (int) ((maxValue - minValue) * SingleRandom.Single(seed)) + minValue; + } + + /// + /// Generates a single, one-off pseudorandom integer between and based on a given set of . + /// This method is guaranteed to return the same result for the same set of . + /// + /// The (inclusive) minimum value. + /// The (exclusive) maximum value. + /// The seeds to use. + /// The generated number. + public static int Int(int minValue, int maxValue, params int[] seeds) { + return (int) ((maxValue - minValue) * SingleRandom.Single(seeds)) + minValue; + } + + /// + /// Generates a single, one-off pseudorandom floating point number between 0 and 1 based on a given . + /// This method is guaranteed to return the same result for the same . + /// + /// The seed to use. + /// The generated number. + public static float Single(int seed) { + return (SingleRandom.Scramble(seed) / (float) int.MaxValue + 1) / 2; + } + + /// + /// Generates a single, one-off pseudorandom floating point number between 0 and 1 based on a given set of . + /// This method is guaranteed to return the same result for the same set of . + /// + /// The seeds to use. + /// The generated number. + public static float Single(params int[] seeds) { + return (SingleRandom.Scramble(seeds) / (float) int.MaxValue + 1) / 2; + } + + private static int Scramble(int[] seeds) { + if (seeds == null || seeds.Length <= 0) + throw new ArgumentOutOfRangeException(nameof(seeds)); + var ret = 1; + for (var i = 0; i < seeds.Length; i++) + ret *= SingleRandom.Scramble(seeds[i]); + return ret; + } + + private static int Scramble(int seed) { + seed ^= (seed << 7); + seed *= 207398809; + seed ^= (seed << 17); + seed *= 928511849; + seed ^= (seed << 12); + return seed; + } + + } +} diff --git a/Tests/SingleRandomTests.cs b/Tests/SingleRandomTests.cs new file mode 100644 index 0000000..b02955a --- /dev/null +++ b/Tests/SingleRandomTests.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Linq; +using MLEM.Misc; +using NUnit.Framework; + +namespace Tests; + +public class SingleRandomTests { + + [Test] + public void TestEquality() { + for (var i = 0; i < 1000000; i++) { + Assert.AreEqual(SingleRandom.Single(i), SingleRandom.Single(new[] {i})); + Assert.AreEqual(SingleRandom.Int(i), SingleRandom.Int(new[] {i})); + + // test if all methods that accept mins and max are identical + Assert.AreEqual(SingleRandom.Int(i), SingleRandom.Int(int.MaxValue, i)); + Assert.AreEqual(SingleRandom.Int(i), SingleRandom.Int(0, int.MaxValue, i)); + } + } + + [Test] + public void TestBounds() { + for (var i = 0; i < 1000000; i++) { + Assert.That(SingleRandom.Single(i), Is.LessThan(1).And.GreaterThanOrEqualTo(0)); + Assert.That(SingleRandom.Int(i), Is.LessThan(int.MaxValue).And.GreaterThanOrEqualTo(0)); + Assert.That(SingleRandom.Int(17, i), Is.LessThan(17).And.GreaterThanOrEqualTo(0)); + Assert.That(SingleRandom.Int(19283, 832498394, i), Is.LessThan(832498394).And.GreaterThanOrEqualTo(19283)); + } + } + + [Test] + public void TestAverages() { + var ints = new List(); + var flts = new List(); + for (var i = 0; i < 1000000; i++) { + ints.Add(SingleRandom.Int(i)); + flts.Add(SingleRandom.Single(i)); + } + // allow being off by 0.00001 of the total + Assert.AreEqual(ints.Average(), int.MaxValue / 2, 0.00001 * int.MaxValue); + Assert.AreEqual(flts.Average(), 0.5, 0.00001); + } + +}