diff --git a/00_Common/dotnet/Games.Common/Randomness/IRandom.cs b/00_Common/dotnet/Games.Common/Randomness/IRandom.cs
new file mode 100644
index 00000000..0ed4a587
--- /dev/null
+++ b/00_Common/dotnet/Games.Common/Randomness/IRandom.cs
@@ -0,0 +1,55 @@
+namespace Games.Common.Randomness;
+
+///
+/// Provides access to a random number generator
+///
+public interface IRandom
+{
+ ///
+ /// Gets a random such that 0 <= n < 1.
+ ///
+ /// The random number.
+ float NextFloat();
+
+ ///
+ /// Gets a random such that 0 <= n < exclusiveMaximum.
+ ///
+ /// The random number.
+ float NextFloat(float exclusiveMaximum);
+
+ ///
+ /// Gets a random such that inclusiveMinimum <= n < exclusiveMaximum.
+ ///
+ /// The random number.
+ float NextFloat(float inclusiveMinimum, float exclusiveMaximum);
+
+ ///
+ /// Gets a random such that 0 <= n < exclusiveMaximum.
+ ///
+ /// The random number.
+ int Next(int exclusiveMaximum);
+
+ ///
+ /// Gets a random such that inclusiveMinimum <= n < exclusiveMaximum.
+ ///
+ /// The random number.
+ int Next(int inclusiveMinimum, int exclusiveMaximum);
+
+ ///
+ /// Gets the previous random number as a .
+ ///
+ /// The previous random number.
+ float PreviousFloat();
+
+ ///
+ /// Gets the previous random number as an .
+ ///
+ /// The previous random number.
+ int Previous();
+
+ ///
+ /// Reseeds the random number generator.
+ ///
+ /// The seed.
+ void Reseed(int seed);
+}
diff --git a/00_Common/dotnet/Games.Common/Randomness/RandomNumberGenerator.cs b/00_Common/dotnet/Games.Common/Randomness/RandomNumberGenerator.cs
new file mode 100644
index 00000000..2c8749cb
--- /dev/null
+++ b/00_Common/dotnet/Games.Common/Randomness/RandomNumberGenerator.cs
@@ -0,0 +1,50 @@
+using System;
+
+namespace Games.Common.Randomness;
+
+public class RandomNumberGenerator : IRandom
+{
+ private Random _random;
+ private float _previous;
+
+ public RandomNumberGenerator()
+ {
+ // The BASIC RNG is seeded based on time with a 1 second resolution
+ _random = new Random((int)(DateTime.UtcNow.Ticks / TimeSpan.TicksPerSecond));
+ }
+
+ public float NextFloat() => NextFloat(1);
+
+ public float NextFloat(float exclusiveMaximum)
+ {
+ if (exclusiveMaximum <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(exclusiveMaximum), "Must be greater than 0.");
+ }
+
+ return NextFloat(0, exclusiveMaximum);
+ }
+
+ public float NextFloat(float inclusiveMinimum, float exclusiveMaximum)
+ {
+ if (exclusiveMaximum <= inclusiveMinimum)
+ {
+ throw new ArgumentOutOfRangeException(nameof(exclusiveMaximum), "Must be greater than inclusiveMinimum.");
+ }
+
+ var range = exclusiveMaximum - inclusiveMinimum;
+ return _previous = ((float)_random.NextDouble()) * range + inclusiveMinimum;
+ }
+
+ public int Next(int exclusiveMaximum) => ToInt(NextFloat(exclusiveMaximum));
+
+ public int Next(int inclusiveMinimum, int exclusiveMaximum) => ToInt(NextFloat(inclusiveMinimum, exclusiveMaximum));
+
+ public float PreviousFloat() => _previous;
+
+ public int Previous() => ToInt(_previous);
+
+ private static int ToInt(float value) => (int)Math.Floor(value);
+
+ public void Reseed(int seed) => _random = new Random(seed);
+}
\ No newline at end of file