mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-23 07:29:02 -08:00
@@ -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.
|
||||
|
||||
@@ -6,4 +6,12 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources/*.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\00_Common\dotnet\Games.Common\Games.Common.csproj" />
|
||||
</ItemGroup>
|
||||
</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)
|
||||
|
||||
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