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);
+ }
+
+}