mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-25 14:08:34 +01:00
Added SeedSource
This commit is contained in:
parent
8ffd9ab48a
commit
0a62ae0036
5 changed files with 20132 additions and 50 deletions
|
@ -18,7 +18,7 @@ Additions
|
||||||
- Added GetDownTime, GetUpTime, GetTimeSincePress, WasModifierDown and WasDown to Keybind and Combination
|
- Added GetDownTime, GetUpTime, GetTimeSincePress, WasModifierDown and WasDown to Keybind and Combination
|
||||||
- Added the ability for UniformTextureAtlases to have padding for each region
|
- Added the ability for UniformTextureAtlases to have padding for each region
|
||||||
- Added UniformTextureAtlas methods ToList and ToDictionary
|
- Added UniformTextureAtlas methods ToList and ToDictionary
|
||||||
- Added the SingleRandom utility class
|
- Added SingleRandom and SeedSource
|
||||||
- **Added the ability to find paths to one of multiple goals using AStar**
|
- **Added the ability to find paths to one of multiple goals using AStar**
|
||||||
|
|
||||||
Improvements
|
Improvements
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace MLEM.Misc {
|
namespace MLEM.Misc {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The SingleRandom class allows generating single, one-off pseudorandom numbers based on a seed, or a set of seeds.
|
/// The SingleRandom class allows generating single, one-off pseudorandom numbers based on a seed or a <see cref="SeedSource"/>.
|
||||||
/// The types of numbers that can be generated are <see cref="int"/> and <see cref="float"/>, both of which can be generated with specific minimum and maximum values if desired.
|
/// The types of numbers that can be generated are <see cref="int"/> and <see cref="float"/>, both of which can be generated with specific minimum and maximum values if desired.
|
||||||
/// 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.
|
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -19,13 +19,13 @@ namespace MLEM.Misc {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a single, one-off pseudorandom integer between 0 and <see cref="int.MaxValue"/> based on a given set of <paramref name="seeds"/>.
|
/// Generates a single, one-off pseudorandom integer between 0 and <see cref="int.MaxValue"/> based on a given <paramref name="source"/>.
|
||||||
/// This method is guaranteed to return the same result for the same set of <paramref name="seeds"/>.
|
/// This method is guaranteed to return the same result for the same <paramref name="source"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="seeds">The seeds to use.</param>
|
/// <param name="source">The <see cref="SeedSource"/> to use.</param>
|
||||||
/// <returns>The generated number.</returns>
|
/// <returns>The generated number.</returns>
|
||||||
public static int Int(params int[] seeds) {
|
public static int Int(SeedSource source) {
|
||||||
return (int) (SingleRandom.Single(seeds) * int.MaxValue);
|
return (int) (SingleRandom.Single(source) * int.MaxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -40,14 +40,14 @@ namespace MLEM.Misc {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a single, one-off pseudorandom integer between 0 and <paramref name="maxValue"/> based on a given set of <paramref name="seeds"/>.
|
/// Generates a single, one-off pseudorandom integer between 0 and <paramref name="maxValue"/> based on a given <paramref name="source"/>.
|
||||||
/// This method is guaranteed to return the same result for the same set of <paramref name="seeds"/>.
|
/// This method is guaranteed to return the same result for the same <paramref name="source"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="maxValue">The (exclusive) maximum value.</param>
|
/// <param name="maxValue">The (exclusive) maximum value.</param>
|
||||||
/// <param name="seeds">The seeds to use.</param>
|
/// <param name="source">The <see cref="SeedSource"/> to use.</param>
|
||||||
/// <returns>The generated number.</returns>
|
/// <returns>The generated number.</returns>
|
||||||
public static int Int(int maxValue, params int[] seeds) {
|
public static int Int(int maxValue, SeedSource source) {
|
||||||
return (int) (maxValue * SingleRandom.Single(seeds));
|
return (int) (maxValue * SingleRandom.Single(source));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -63,15 +63,15 @@ namespace MLEM.Misc {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a single, one-off pseudorandom integer between <paramref name="minValue"/> and <paramref name="maxValue"/> based on a given set of <paramref name="seeds"/>.
|
/// Generates a single, one-off pseudorandom integer between <paramref name="minValue"/> and <paramref name="maxValue"/> based on a given <paramref name="source"/>.
|
||||||
/// This method is guaranteed to return the same result for the same set of <paramref name="seeds"/>.
|
/// This method is guaranteed to return the same result for the same <paramref name="source"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="minValue">The (inclusive) minimum value.</param>
|
/// <param name="minValue">The (inclusive) minimum value.</param>
|
||||||
/// <param name="maxValue">The (exclusive) maximum value.</param>
|
/// <param name="maxValue">The (exclusive) maximum value.</param>
|
||||||
/// <param name="seeds">The seeds to use.</param>
|
/// <param name="source">The <see cref="SeedSource"/> to use.</param>
|
||||||
/// <returns>The generated number.</returns>
|
/// <returns>The generated number.</returns>
|
||||||
public static int Int(int minValue, int maxValue, params int[] seeds) {
|
public static int Int(int minValue, int maxValue, SeedSource source) {
|
||||||
return (int) ((maxValue - minValue) * SingleRandom.Single(seeds)) + minValue;
|
return (int) ((maxValue - minValue) * SingleRandom.Single(source)) + minValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -81,17 +81,17 @@ namespace MLEM.Misc {
|
||||||
/// <param name="seed">The seed to use.</param>
|
/// <param name="seed">The seed to use.</param>
|
||||||
/// <returns>The generated number.</returns>
|
/// <returns>The generated number.</returns>
|
||||||
public static float Single(int seed) {
|
public static float Single(int seed) {
|
||||||
return (SingleRandom.Scramble(seed) / (float) int.MaxValue + 1) / 2;
|
return (new SeedSource(seed).Get() / (float) int.MaxValue + 1) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a single, one-off pseudorandom floating point number between 0 and 1 based on a given set of <paramref name="seeds"/>.
|
/// Generates a single, one-off pseudorandom floating point number between 0 and 1 based on a given <paramref name="source"/>.
|
||||||
/// This method is guaranteed to return the same result for the same set of <paramref name="seeds"/>.
|
/// This method is guaranteed to return the same result for the same <paramref name="source"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="seeds">The seeds to use.</param>
|
/// <param name="source">The <see cref="SeedSource"/> to use.</param>
|
||||||
/// <returns>The generated number.</returns>
|
/// <returns>The generated number.</returns>
|
||||||
public static float Single(params int[] seeds) {
|
public static float Single(SeedSource source) {
|
||||||
return (SingleRandom.Scramble(seeds) / (float) int.MaxValue + 1) / 2;
|
return (source.Get() / (float) int.MaxValue + 1) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -106,14 +106,14 @@ namespace MLEM.Misc {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a single, one-off pseudorandom floating point number between 0 and <paramref name="maxValue"/> based on a given set of <paramref name="seeds"/>.
|
/// Generates a single, one-off pseudorandom floating point number between 0 and <paramref name="maxValue"/> based on a given <paramref name="source"/>.
|
||||||
/// This method is guaranteed to return the same result for the same set of <paramref name="seeds"/>.
|
/// This method is guaranteed to return the same result for the same <paramref name="source"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="maxValue">The (exclusive) maximum value.</param>
|
/// <param name="maxValue">The (exclusive) maximum value.</param>
|
||||||
/// <param name="seeds">The seeds to use.</param>
|
/// <param name="source">The <see cref="SeedSource"/> to use.</param>
|
||||||
/// <returns>The generated number.</returns>
|
/// <returns>The generated number.</returns>
|
||||||
public static float Single(float maxValue, params int[] seeds) {
|
public static float Single(float maxValue, SeedSource source) {
|
||||||
return maxValue * SingleRandom.Single(seeds);
|
return maxValue * SingleRandom.Single(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -129,37 +129,104 @@ namespace MLEM.Misc {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a single, one-off pseudorandom floating point number between <paramref name="minValue"/> and <paramref name="maxValue"/> based on a given set of <paramref name="seeds"/>.
|
/// Generates a single, one-off pseudorandom floating point number between <paramref name="minValue"/> and <paramref name="maxValue"/> based on a given <paramref name="source"/>.
|
||||||
/// This method is guaranteed to return the same result for the same set of <paramref name="seeds"/>.
|
/// This method is guaranteed to return the same result for the same <paramref name="source"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="minValue">The (inclusive) minimum value.</param>
|
/// <param name="minValue">The (inclusive) minimum value.</param>
|
||||||
/// <param name="maxValue">The (exclusive) maximum value.</param>
|
/// <param name="maxValue">The (exclusive) maximum value.</param>
|
||||||
/// <param name="seeds">The seeds to use.</param>
|
/// <param name="source">The <see cref="SeedSource"/> to use.</param>
|
||||||
/// <returns>The generated number.</returns>
|
/// <returns>The generated number.</returns>
|
||||||
public static float Single(float minValue, float maxValue, params int[] seeds) {
|
public static float Single(float minValue, float maxValue, SeedSource source) {
|
||||||
return (maxValue - minValue) * SingleRandom.Single(seeds) + minValue;
|
return (maxValue - minValue) * SingleRandom.Single(source) + minValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int Scramble(int[] seeds) {
|
}
|
||||||
if (seeds == null || seeds.Length <= 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(seeds));
|
/// <summary>
|
||||||
var ret = 1623487;
|
/// A seed source contains an <see cref="int"/> value which can be used as a seed for a <see cref="System.Random"/> or <see cref="SingleRandom"/>. Seed sources feature a convenient way to add multiple seeds using <see cref="Add(int)"/>, which will be sufficiently scrambled to be deterministically semi-random and combined into a single <see cref="int"/>.
|
||||||
for (var i = 0; i < seeds.Length; i++)
|
/// This struct behaves similarly to <c>System.HashCode</c> in many ways, with an important distinction being that <see cref="SeedSource"/>'s scrambling procedure is not considered an implementation detail, and will stay consistent between process executions.
|
||||||
ret += SingleRandom.ScrambleStep(seeds[i]) * 68659;
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// For example, a seed source can be used to create a new <see cref="System.Random"/> based on an object's <c>x</c> and <c>y</c> coordinates by combining them into a <see cref="SeedSource"/> using <see cref="Add(int)"/>. The values generated by the <see cref="System.Random"/> created using <see cref="Random()"/> will then be determined by the specific pair of <c>x</c> and <c>y</c> values used.
|
||||||
|
/// </example>
|
||||||
|
public readonly struct SeedSource {
|
||||||
|
|
||||||
|
private readonly int? value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new seed source from the given seed, which will be added automatically using <see cref="Add(int)"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="seed">The initial seed to use.</param>
|
||||||
|
public SeedSource(int seed) : this() {
|
||||||
|
this = this.Add(seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new seed source from the given set of seeds, which will be added automatically using <see cref="Add(int)"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="seeds">The initial seeds to use.</param>
|
||||||
|
public SeedSource(params int[] seeds) : this() {
|
||||||
|
foreach (var seed in seeds)
|
||||||
|
this = this.Add(seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SeedSource(int? value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the given seed to this seed source's value and returns the result as a new seed source.
|
||||||
|
/// The algorithm used for adding involves various scrambling operations that sufficiently semi-randomize the seed and final value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="seed">The seed to add.</param>
|
||||||
|
/// <returns>A new seed source with the seed added.</returns>
|
||||||
|
public SeedSource Add(int seed) {
|
||||||
|
seed ^= seed << 7;
|
||||||
|
seed *= 207398809;
|
||||||
|
seed ^= seed << 17;
|
||||||
|
seed *= 928511849;
|
||||||
|
seed ^= seed << 12;
|
||||||
|
seed += 3;
|
||||||
|
return new SeedSource(new int?(this.Get() + seed));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the given seed to this seed source's value and returns the result as a new seed source.
|
||||||
|
/// Floating point values are scrambled by invoking <see cref="Add(int)"/> using a typecast version, followed by invoking <see cref="Add(int)"/> using the decimal value multiplied by <see cref="int.MaxValue"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="seed">The seed to add.</param>
|
||||||
|
/// <returns>A new seed source with the seed added.</returns>
|
||||||
|
public SeedSource Add(float seed) {
|
||||||
|
return this.Add((int) seed).Add((seed - (int) seed) * int.MaxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the given seed to this seed source's value and returns the result as a new seed source.
|
||||||
|
/// Strings are scrambled by invoking <see cref="Add(int)"/> using every character contained in the string, in order.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="seed">The seed to add.</param>
|
||||||
|
/// <returns>A new seed source with the seed added.</returns>
|
||||||
|
public SeedSource Add(string seed) {
|
||||||
|
var ret = this;
|
||||||
|
foreach (var c in seed)
|
||||||
|
ret = ret.Add(c);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int Scramble(int seed) {
|
/// <summary>
|
||||||
return SingleRandom.ScrambleStep(seed) * 68659 + 1623487;
|
/// Returns this seed source's seed value, which can then be used in <see cref="SingleRandom"/> or elsewhere.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>This seed source's value.</returns>
|
||||||
|
public int Get() {
|
||||||
|
return this.value ?? 1623487;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int ScrambleStep(int seed) {
|
/// <summary>
|
||||||
seed ^= (seed << 7);
|
/// Returns a new <see cref="Random"/> instance using this source seed's value, retrieved using <see cref="Get"/>.
|
||||||
seed *= 207398809;
|
/// </summary>
|
||||||
seed ^= (seed << 17);
|
/// <returns>A new <see cref="Random"/> using this seed source's value.</returns>
|
||||||
seed *= 928511849;
|
public Random Random() {
|
||||||
seed ^= (seed << 12);
|
return new Random(this.Get());
|
||||||
return seed + 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
10000
Tests/Content/ConsecutiveSingleRandoms.txt
Normal file
10000
Tests/Content/ConsecutiveSingleRandoms.txt
Normal file
File diff suppressed because it is too large
Load diff
10000
Tests/Content/DistributedSingleRandoms.txt
Normal file
10000
Tests/Content/DistributedSingleRandoms.txt
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MLEM.Misc;
|
using MLEM.Misc;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
@ -10,8 +12,10 @@ public class SingleRandomTests {
|
||||||
[Test]
|
[Test]
|
||||||
public void TestEquality() {
|
public void TestEquality() {
|
||||||
for (var i = 0; i < 1000000; i++) {
|
for (var i = 0; i < 1000000; i++) {
|
||||||
Assert.AreEqual(SingleRandom.Single(i), SingleRandom.Single(new[] {i}));
|
Assert.AreEqual(SingleRandom.Single(i), SingleRandom.Single(new SeedSource().Add(i)));
|
||||||
Assert.AreEqual(SingleRandom.Int(i), SingleRandom.Int(new[] {i}));
|
Assert.AreEqual(SingleRandom.Single(i), SingleRandom.Single(new SeedSource(i)));
|
||||||
|
Assert.AreEqual(SingleRandom.Int(i), SingleRandom.Int(new SeedSource().Add(i)));
|
||||||
|
Assert.AreEqual(SingleRandom.Int(i), SingleRandom.Int(new SeedSource(i)));
|
||||||
|
|
||||||
// test if all methods that accept mins and max are identical
|
// 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(int.MaxValue, i));
|
||||||
|
@ -46,4 +50,15 @@ public class SingleRandomTests {
|
||||||
Assert.AreEqual(0.5, flts.Average(), 0.001);
|
Assert.AreEqual(0.5, flts.Average(), 0.001);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestExpectedValues() {
|
||||||
|
var distributed = File.ReadAllLines("Content/DistributedSingleRandoms.txt");
|
||||||
|
for (var i = 0; i < 10000; i++)
|
||||||
|
Assert.AreEqual(SingleRandom.Single(i * 10000).ToString(CultureInfo.InvariantCulture), distributed[i]);
|
||||||
|
|
||||||
|
var consecutive = File.ReadAllLines("Content/ConsecutiveSingleRandoms.txt");
|
||||||
|
for (var i = 0; i < 10000; i++)
|
||||||
|
Assert.AreEqual(SingleRandom.Single(i).ToString(CultureInfo.InvariantCulture), consecutive[i]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue