mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-23 15:37:51 -08:00
@@ -16,3 +16,27 @@ http://www.vintage-basic.net/games.html
|
|||||||
#### Porting Notes
|
#### Porting Notes
|
||||||
|
|
||||||
(please note any difficulties or challenges in porting here)
|
(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.
|
||||||
|
|||||||
@@ -6,4 +6,12 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="Resources/*.txt" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\00_Common\dotnet\Games.Common\Games.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
104
30_Cube/csharp/Game.cs
Normal file
104
30_Cube/csharp/Game.cs
Normal file
@@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
20
30_Cube/csharp/IOExtensions.cs
Normal file
20
30_Cube/csharp/IOExtensions.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
30_Cube/csharp/Program.cs
Normal file
10
30_Cube/csharp/Program.cs
Normal file
@@ -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();
|
||||||
@@ -1,3 +1,12 @@
|
|||||||
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
|
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/)
|
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.
|
||||||
|
|||||||
14
30_Cube/csharp/RandomExtensions.cs
Normal file
14
30_Cube/csharp/RandomExtensions.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
30_Cube/csharp/Resources/Balance.txt
Normal file
1
30_Cube/csharp/Resources/Balance.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
You now have {0} dollars.
|
||||||
4
30_Cube/csharp/Resources/Bang.txt
Normal file
4
30_Cube/csharp/Resources/Bang.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
******BANG******
|
||||||
|
You lose!
|
||||||
|
|
||||||
|
|
||||||
1
30_Cube/csharp/Resources/BetAgain.txt
Normal file
1
30_Cube/csharp/Resources/BetAgain.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Tried to fool me; bet again
|
||||||
1
30_Cube/csharp/Resources/Bust.txt
Normal file
1
30_Cube/csharp/Resources/Bust.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
You bust.
|
||||||
1
30_Cube/csharp/Resources/Congratulations.txt
Normal file
1
30_Cube/csharp/Resources/Congratulations.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Congratulations!
|
||||||
3
30_Cube/csharp/Resources/Goodbye.txt
Normal file
3
30_Cube/csharp/Resources/Goodbye.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Tough luck!
|
||||||
|
|
||||||
|
Goodbye.
|
||||||
1
30_Cube/csharp/Resources/HowMuch.txt
Normal file
1
30_Cube/csharp/Resources/HowMuch.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
How much
|
||||||
2
30_Cube/csharp/Resources/IllegalMove.txt
Normal file
2
30_Cube/csharp/Resources/IllegalMove.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
Illegal move. You lose.
|
||||||
24
30_Cube/csharp/Resources/Instructions.txt
Normal file
24
30_Cube/csharp/Resources/Instructions.txt
Normal file
@@ -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!
|
||||||
6
30_Cube/csharp/Resources/Introduction.txt
Normal file
6
30_Cube/csharp/Resources/Introduction.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Cube
|
||||||
|
Creative Computing Morristown, New Jersey
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Do you want to see the instructions? (Yes--1,No--0)
|
||||||
1
30_Cube/csharp/Resources/NextMove.txt
Normal file
1
30_Cube/csharp/Resources/NextMove.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Next move:
|
||||||
44
30_Cube/csharp/Resources/Resource.cs
Normal file
44
30_Cube/csharp/Resources/Resource.cs
Normal file
@@ -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}'.");
|
||||||
|
}
|
||||||
1
30_Cube/csharp/Resources/TryAgain.txt
Normal file
1
30_Cube/csharp/Resources/TryAgain.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Do you want to try again
|
||||||
1
30_Cube/csharp/Resources/Wager.txt
Normal file
1
30_Cube/csharp/Resources/Wager.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Want to make a wager
|
||||||
2
30_Cube/csharp/Resources/YourMove.txt
Normal file
2
30_Cube/csharp/Resources/YourMove.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
It's your move:
|
||||||
10
30_Cube/csharp/ZerosGenerator.cs
Normal file
10
30_Cube/csharp/ZerosGenerator.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Cube;
|
||||||
|
|
||||||
|
internal class ZerosGenerator : IRandom
|
||||||
|
{
|
||||||
|
public float NextFloat() => 0;
|
||||||
|
|
||||||
|
public float PreviousFloat() => 0;
|
||||||
|
|
||||||
|
public void Reseed(int seed) { }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user