diff --git a/30_Cube/README.md b/30_Cube/README.md
index f99366a2..c220c8d4 100644
--- a/30_Cube/README.md
+++ b/30_Cube/README.md
@@ -16,3 +16,27 @@ http://www.vintage-basic.net/games.html
#### Porting Notes
(please note any difficulties or challenges in porting here)
+
+##### Randomization Logic
+
+The BASIC code uses an interesting technique for choosing the random coordinates for the mines. The first coordinate is
+chosen like this:
+
+```basic
+380 LET A=INT(3*(RND(X)))
+390 IF A<>0 THEN 410
+400 LET A=3
+```
+
+where line 410 is the start of a similar block of code for the next coordinate. The behaviour of `RND(X)` depends on the
+value of `X`. If `X` is greater than zero then it returns a random value between 0 and 1. If `X` is zero it returns the
+last random value generated, or 0 if no value has yet been generated.
+
+If `X` is 1, therefore, the first line above set `A` to 0, 1, or 2. The next 2 lines replace a 0 with a 3. The
+replacement values varies for the different coordinates with the result that the random selection is biased towards a
+specific set of points. If `X` is 0, the `RND` calls all return 0, so the coordinates are the known. It appears that
+this technique was probably used to allow testing the game with a well-known set of locations for the mines. However, in
+the code as it comes to us, the value of `X` is never set and is thus 0, so the mine locations are never randomized.
+
+The C# port implements the biased randomized mine locations, as seems to be the original intent, but includes a
+command-line switch to enable the deterministic execution as well.
diff --git a/30_Cube/csharp/Cube.csproj b/30_Cube/csharp/Cube.csproj
index d3fe4757..3870320c 100644
--- a/30_Cube/csharp/Cube.csproj
+++ b/30_Cube/csharp/Cube.csproj
@@ -6,4 +6,12 @@
enable
enable
+
+
+
+
+
+
+
+
diff --git a/30_Cube/csharp/Game.cs b/30_Cube/csharp/Game.cs
new file mode 100644
index 00000000..a06ec565
--- /dev/null
+++ b/30_Cube/csharp/Game.cs
@@ -0,0 +1,104 @@
+namespace Cube;
+
+internal class Game
+{
+ private const int _initialBalance = 500;
+ private readonly IEnumerable<(int, int, int)> _seeds = new List<(int, int, int)>
+ {
+ (3, 2, 3), (1, 3, 3), (3, 3, 2), (3, 2, 3), (3, 1, 3)
+ };
+ private readonly (float, float, float) _startLocation = (1, 1, 1);
+ private readonly (float, float, float) _goalLocation = (3, 3, 3);
+
+ private readonly IReadWrite _io;
+ private readonly IRandom _random;
+
+ public Game(IReadWrite io, IRandom random)
+ {
+ _io = io;
+ _random = random;
+ }
+
+ public void Play()
+ {
+ _io.Write(Streams.Introduction);
+
+ if (_io.ReadNumber("") != 0)
+ {
+ _io.Write(Streams.Instructions);
+ }
+
+ PlaySeries(_initialBalance);
+
+ _io.Write(Streams.Goodbye);
+ }
+
+ private void PlaySeries(float balance)
+ {
+ while (true)
+ {
+ var wager = _io.ReadWager(balance);
+
+ var gameWon = PlayGame();
+
+ if (wager.HasValue)
+ {
+ balance = gameWon ? (balance + wager.Value) : (balance - wager.Value);
+ if (balance <= 0)
+ {
+ _io.Write(Streams.Bust);
+ return;
+ }
+ _io.WriteLine(Formats.Balance, balance);
+ }
+
+ if (_io.ReadNumber(Prompts.TryAgain) != 1) { return; }
+ }
+ }
+
+ private bool PlayGame()
+ {
+ var mineLocations = _seeds.Select(seed => _random.NextLocation(seed)).ToHashSet();
+ var currentLocation = _startLocation;
+ var prompt = Prompts.YourMove;
+
+ while (true)
+ {
+ var newLocation = _io.Read3Numbers(prompt);
+
+ if (!MoveIsLegal(currentLocation, newLocation)) { return Lose(Streams.IllegalMove); }
+
+ currentLocation = newLocation;
+
+ if (currentLocation == _goalLocation) { return Win(Streams.Congratulations); }
+
+ if (mineLocations.Contains(currentLocation)) { return Lose(Streams.Bang); }
+
+ prompt = Prompts.NextMove;
+ }
+ }
+
+ private bool Lose(Stream text)
+ {
+ _io.Write(text);
+ return false;
+ }
+
+ private bool Win(Stream text)
+ {
+ _io.Write(text);
+ return true;
+ }
+
+ private bool MoveIsLegal((float, float, float) from, (float, float, float) to)
+ => (to.Item1 - from.Item1, to.Item2 - from.Item2, to.Item3 - from.Item3) switch
+ {
+ ( > 1, _, _) => false,
+ (_, > 1, _) => false,
+ (_, _, > 1) => false,
+ (1, 1, _) => false,
+ (1, _, 1) => false,
+ (_, 1, 1) => false,
+ _ => true
+ };
+}
diff --git a/30_Cube/csharp/IOExtensions.cs b/30_Cube/csharp/IOExtensions.cs
new file mode 100644
index 00000000..14f2a85e
--- /dev/null
+++ b/30_Cube/csharp/IOExtensions.cs
@@ -0,0 +1,20 @@
+namespace Cube;
+
+internal static class IOExtensions
+{
+ internal static float? ReadWager(this IReadWrite io, float balance)
+ {
+ io.Write(Streams.Wager);
+ if (io.ReadNumber("") == 0) { return null; }
+
+ var prompt = Prompts.HowMuch;
+
+ while(true)
+ {
+ var wager = io.ReadNumber(prompt);
+ if (wager <= balance) { return wager; }
+
+ prompt = Prompts.BetAgain;
+ }
+ }
+}
diff --git a/30_Cube/csharp/Program.cs b/30_Cube/csharp/Program.cs
new file mode 100644
index 00000000..803a569a
--- /dev/null
+++ b/30_Cube/csharp/Program.cs
@@ -0,0 +1,10 @@
+global using Games.Common.IO;
+global using Games.Common.Randomness;
+
+global using static Cube.Resources.Resource;
+
+using Cube;
+
+IRandom random = args.Contains("--non-random") ? new ZerosGenerator() : new RandomNumberGenerator();
+
+new Game(new ConsoleIO(), random).Play();
diff --git a/30_Cube/csharp/README.md b/30_Cube/csharp/README.md
index 4daabb5c..7bea3d88 100644
--- a/30_Cube/csharp/README.md
+++ b/30_Cube/csharp/README.md
@@ -1,3 +1,12 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Microsoft C#](https://docs.microsoft.com/en-us/dotnet/csharp/)
+
+#### Execution
+
+As noted in the main Readme file, the randomization code in the BASIC program has a switch (the variable `X`) that
+allows the game to be run in a deterministic (non-random) mode.
+
+Running the C# port without command-line parameters will play the game with random mine locations.
+
+Running the port with a `--non-random` command-line switch will run the game with non-random mine locations.
diff --git a/30_Cube/csharp/RandomExtensions.cs b/30_Cube/csharp/RandomExtensions.cs
new file mode 100644
index 00000000..ac05108e
--- /dev/null
+++ b/30_Cube/csharp/RandomExtensions.cs
@@ -0,0 +1,14 @@
+namespace Cube;
+
+internal static class RandomExtensions
+{
+ internal static (float, float, float) NextLocation(this IRandom random, (int, int, int) bias)
+ => (random.NextCoordinate(bias.Item1), random.NextCoordinate(bias.Item2), random.NextCoordinate(bias.Item3));
+
+ private static float NextCoordinate(this IRandom random, int bias)
+ {
+ var value = random.Next(3);
+ if (value == 0) { value = bias; }
+ return value;
+ }
+}
\ No newline at end of file
diff --git a/30_Cube/csharp/Resources/Balance.txt b/30_Cube/csharp/Resources/Balance.txt
new file mode 100644
index 00000000..1f6adffd
--- /dev/null
+++ b/30_Cube/csharp/Resources/Balance.txt
@@ -0,0 +1 @@
+You now have {0} dollars.
\ No newline at end of file
diff --git a/30_Cube/csharp/Resources/Bang.txt b/30_Cube/csharp/Resources/Bang.txt
new file mode 100644
index 00000000..1d924788
--- /dev/null
+++ b/30_Cube/csharp/Resources/Bang.txt
@@ -0,0 +1,4 @@
+******BANG******
+You lose!
+
+
diff --git a/30_Cube/csharp/Resources/BetAgain.txt b/30_Cube/csharp/Resources/BetAgain.txt
new file mode 100644
index 00000000..47c9fb8c
--- /dev/null
+++ b/30_Cube/csharp/Resources/BetAgain.txt
@@ -0,0 +1 @@
+Tried to fool me; bet again
\ No newline at end of file
diff --git a/30_Cube/csharp/Resources/Bust.txt b/30_Cube/csharp/Resources/Bust.txt
new file mode 100644
index 00000000..cd753d98
--- /dev/null
+++ b/30_Cube/csharp/Resources/Bust.txt
@@ -0,0 +1 @@
+You bust.
diff --git a/30_Cube/csharp/Resources/Congratulations.txt b/30_Cube/csharp/Resources/Congratulations.txt
new file mode 100644
index 00000000..3319c833
--- /dev/null
+++ b/30_Cube/csharp/Resources/Congratulations.txt
@@ -0,0 +1 @@
+Congratulations!
diff --git a/30_Cube/csharp/Resources/Goodbye.txt b/30_Cube/csharp/Resources/Goodbye.txt
new file mode 100644
index 00000000..0aa64192
--- /dev/null
+++ b/30_Cube/csharp/Resources/Goodbye.txt
@@ -0,0 +1,3 @@
+Tough luck!
+
+Goodbye.
diff --git a/30_Cube/csharp/Resources/HowMuch.txt b/30_Cube/csharp/Resources/HowMuch.txt
new file mode 100644
index 00000000..ff2bea20
--- /dev/null
+++ b/30_Cube/csharp/Resources/HowMuch.txt
@@ -0,0 +1 @@
+How much
\ No newline at end of file
diff --git a/30_Cube/csharp/Resources/IllegalMove.txt b/30_Cube/csharp/Resources/IllegalMove.txt
new file mode 100644
index 00000000..ca8f96ba
--- /dev/null
+++ b/30_Cube/csharp/Resources/IllegalMove.txt
@@ -0,0 +1,2 @@
+
+Illegal move. You lose.
diff --git a/30_Cube/csharp/Resources/Instructions.txt b/30_Cube/csharp/Resources/Instructions.txt
new file mode 100644
index 00000000..e82ae51e
--- /dev/null
+++ b/30_Cube/csharp/Resources/Instructions.txt
@@ -0,0 +1,24 @@
+This is a game in which you will be playing against the
+random decision od the computer. The field of play is a
+cube of side 3. Any of the 27 locations can be designated
+by inputing three numbers such as 2,3,1. At the start,
+you are automatically at location 1,1,1. The object of
+the game is to get to location 3,3,3. One minor detail:
+the computer will pick, at random, 5 locations at which
+it will play land mines. If you hit one of these locations
+you lose. One other details: you may move only one space
+in one direction each move. For example: from 1,1,2 you
+may move to 2,1,2 or 1,1,3. You may not change
+two of the numbers on the same move. If you make an illegal
+move, you lose and the computer takes the money you may
+have bet on that round.
+
+
+All Yes or No questions will be answered by a 1 for Yes
+or a 0 (zero) for no.
+
+When stating the amount of a wager, print only the number
+of dollars (example: 250) You are automatically started with
+500 dollars in your account.
+
+Good luck!
diff --git a/30_Cube/csharp/Resources/Introduction.txt b/30_Cube/csharp/Resources/Introduction.txt
new file mode 100644
index 00000000..6299d19b
--- /dev/null
+++ b/30_Cube/csharp/Resources/Introduction.txt
@@ -0,0 +1,6 @@
+ Cube
+ Creative Computing Morristown, New Jersey
+
+
+
+Do you want to see the instructions? (Yes--1,No--0)
diff --git a/30_Cube/csharp/Resources/NextMove.txt b/30_Cube/csharp/Resources/NextMove.txt
new file mode 100644
index 00000000..4cbe5496
--- /dev/null
+++ b/30_Cube/csharp/Resources/NextMove.txt
@@ -0,0 +1 @@
+Next move:
\ No newline at end of file
diff --git a/30_Cube/csharp/Resources/Resource.cs b/30_Cube/csharp/Resources/Resource.cs
new file mode 100644
index 00000000..c3ed10c7
--- /dev/null
+++ b/30_Cube/csharp/Resources/Resource.cs
@@ -0,0 +1,44 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+namespace Cube.Resources;
+
+internal static class Resource
+{
+ internal static class Streams
+ {
+ public static Stream Introduction => GetStream();
+ public static Stream Instructions => GetStream();
+ public static Stream Wager => GetStream();
+ public static Stream IllegalMove => GetStream();
+ public static Stream Bang => GetStream();
+ public static Stream Bust => GetStream();
+ public static Stream Congratulations => GetStream();
+ public static Stream Goodbye => GetStream();
+ }
+
+ internal static class Prompts
+ {
+ public static string HowMuch => GetString();
+ public static string BetAgain => GetString();
+ public static string YourMove => GetString();
+ public static string NextMove => GetString();
+ public static string TryAgain => GetString();
+ }
+
+ internal static class Formats
+ {
+ public static string Balance => GetString();
+ }
+
+ private static string GetString([CallerMemberName] string? name = null)
+ {
+ using var stream = GetStream(name);
+ using var reader = new StreamReader(stream);
+ return reader.ReadToEnd();
+ }
+
+ private static Stream GetStream([CallerMemberName] string? name = null) =>
+ Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(Resource).Namespace}.{name}.txt")
+ ?? throw new Exception($"Could not find embedded resource stream '{name}'.");
+}
\ No newline at end of file
diff --git a/30_Cube/csharp/Resources/TryAgain.txt b/30_Cube/csharp/Resources/TryAgain.txt
new file mode 100644
index 00000000..9ccf358a
--- /dev/null
+++ b/30_Cube/csharp/Resources/TryAgain.txt
@@ -0,0 +1 @@
+Do you want to try again
\ No newline at end of file
diff --git a/30_Cube/csharp/Resources/Wager.txt b/30_Cube/csharp/Resources/Wager.txt
new file mode 100644
index 00000000..04720a7a
--- /dev/null
+++ b/30_Cube/csharp/Resources/Wager.txt
@@ -0,0 +1 @@
+Want to make a wager
diff --git a/30_Cube/csharp/Resources/YourMove.txt b/30_Cube/csharp/Resources/YourMove.txt
new file mode 100644
index 00000000..5ea0c544
--- /dev/null
+++ b/30_Cube/csharp/Resources/YourMove.txt
@@ -0,0 +1,2 @@
+
+It's your move:
\ No newline at end of file
diff --git a/30_Cube/csharp/ZerosGenerator.cs b/30_Cube/csharp/ZerosGenerator.cs
new file mode 100644
index 00000000..4490f606
--- /dev/null
+++ b/30_Cube/csharp/ZerosGenerator.cs
@@ -0,0 +1,10 @@
+namespace Cube;
+
+internal class ZerosGenerator : IRandom
+{
+ public float NextFloat() => 0;
+
+ public float PreviousFloat() => 0;
+
+ public void Reseed(int seed) { }
+}
\ No newline at end of file