From 9106c2e13c9901d402a04fbbe4979bfa8b11758f Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Fri, 25 Mar 2022 08:14:29 +1100 Subject: [PATCH] Transliterate game logic --- 07_Basketball/README.md | 18 +- 07_Basketball/csharp/Basketball.csproj | 9 + 07_Basketball/csharp/Game.cs | 352 ++++++++++++++++++ 07_Basketball/csharp/Program.cs | 7 + .../csharp/Resources/EndOfFirstHalf.txt | 5 + 07_Basketball/csharp/Resources/EndOfGame.txt | 2 + .../csharp/Resources/EndOfSecondHalf.txt | 7 + .../csharp/Resources/Introduction.txt | 12 + 07_Basketball/csharp/Resources/Resource.cs | 32 ++ 07_Basketball/csharp/Resources/Score.txt | 1 + .../csharp/Resources/TwoMinutesLeft.txt | 3 + 11 files changed, 447 insertions(+), 1 deletion(-) create mode 100644 07_Basketball/csharp/Game.cs create mode 100644 07_Basketball/csharp/Program.cs create mode 100644 07_Basketball/csharp/Resources/EndOfFirstHalf.txt create mode 100644 07_Basketball/csharp/Resources/EndOfGame.txt create mode 100644 07_Basketball/csharp/Resources/EndOfSecondHalf.txt create mode 100644 07_Basketball/csharp/Resources/Introduction.txt create mode 100644 07_Basketball/csharp/Resources/Resource.cs create mode 100644 07_Basketball/csharp/Resources/Score.txt create mode 100644 07_Basketball/csharp/Resources/TwoMinutesLeft.txt diff --git a/07_Basketball/README.md b/07_Basketball/README.md index 5200748d..ac0662dd 100644 --- a/07_Basketball/README.md +++ b/07_Basketball/README.md @@ -14,7 +14,7 @@ Both teams use the same defense, but you may call it: - Enter (7): Zone - Enter (7.5): None -To change defense, type “0” as your next shot. +To change defense, type "0" as your next shot. Note: The game is biased slightly in favor of Dartmouth. The average probability of a Dartmouth shot being good is 62.95% compared to a probability of 61.85% for their opponent. (This makes the sample run slightly remarkable in that Cornell won by a score of 45 to 42 Hooray for the Big Red!) @@ -33,3 +33,19 @@ http://www.vintage-basic.net/games.html (please note any difficulties or challenges in porting here) +##### Original bugs + +###### Initial defense selection + +If a number <6 is entered for the starting defense then the original code prompts again until a value >=6 is entered, +but then skips the opponent selection center jump. + +The C# port does not reproduce this behavior. It does prompt for a correct value, but will then go to opponent selection +followed by the center jump. + +###### Unvalidated defense selection + +The original code does not validate the value entered for the defense beyond checking that it is >=6. A large enough +defense value will guarantee that all shots are good, and the game gets rather predictable. + +This bug is preserved in the C# port. diff --git a/07_Basketball/csharp/Basketball.csproj b/07_Basketball/csharp/Basketball.csproj index d3fe4757..91e759c0 100644 --- a/07_Basketball/csharp/Basketball.csproj +++ b/07_Basketball/csharp/Basketball.csproj @@ -6,4 +6,13 @@ enable enable + + + + + + + + + diff --git a/07_Basketball/csharp/Game.cs b/07_Basketball/csharp/Game.cs new file mode 100644 index 00000000..b44278d3 --- /dev/null +++ b/07_Basketball/csharp/Game.cs @@ -0,0 +1,352 @@ +using Basketball.Resources; +using Games.Common.IO; +using Games.Common.Randomness; + +namespace Basketball; + +internal class Game +{ + private readonly TextIO _io; + private readonly IRandom _random; + + public Game(TextIO io, IRandom random) + { + _io = io; + _random = random; + } + + public void Play() + { + _io.Write(Resource.Streams.Introduction); + + var defense = _io.ReadDefense("Your starting defense will be"); + + _io.WriteLine(); + var opponent = _io.ReadString("Choose your opponent"); + + _io.WriteLine("Center jump"); + var offense = _random.NextFloat() > 0.6 ? "Dartmouth" : opponent; + + _io.WriteLine($"{offense} controls the tap."); + + var time = 0; + var score = new Dictionary { ["Dartmouth"] = 0, [opponent] = 0 }; + + _io.WriteLine(); + + if (offense == "Dartmouth") + { + var shot = _io.ReadShot("Your shot"); + + if (_random.NextFloat() >= 0.5 && time >= 100) + { + _io.WriteLine(); + if (score["Dartmouth"] == score[opponent]) + { + _io.WriteScore(Resource.Formats.EndOfSecondHalf, opponent, score); + time = 93; + // Loop back to center jump + } + else + { + _io.WriteScore(Resource.Formats.EndOfGame, opponent, score); + return; + } + } + else + { + if (shot == 0) + { + defense = _io.ReadDefense("Your new defensive alignment is"); + // go to next shot + } + + if (shot == 1 || shot == 2) + { + time++; + if (time == 50) + { + _io.WriteScore(Resource.Formats.EndOfFirstHalf, opponent, score); + // Loop back to center jump; + } + if (time == 92) + { + _io.Write(Resource.Streams.TwoMinutesLeft); + } + _io.WriteLine("Jump shot"); + if (_random.NextFloat() <= 0.341 * defense / 8) + { + _io.WriteLine("Shot is good"); + score["Dartmouth"] += 2; + _io.WriteScore(Resource.Formats.Score, opponent, score); + // over to opponent + } + else if (_random.NextFloat() <= 0.682 * defense / 8) + { + _io.WriteLine("Shot is off target"); + if (defense / 6 * _random.NextFloat() > 0.45) + { + _io.WriteLine($"Rebound to {opponent}"); + // over to opponent + } + else + { + _io.WriteLine("Dartmouth controls the rebound."); + if (_random.NextFloat() <= 0.4) + { + // fall through to 1300 + } + else + { + if (defense == 6) + { + if (_random.NextFloat() > 0.6) + { + _io.WriteLine($"Pass stolen be {opponent} easy layup."); + score[opponent] += 2; + _io.WriteScore(Resource.Formats.Score, opponent, score); + _io.WriteLine(); + } + } + _io.Write("Ball passed back to you. "); + // next shot without writeline + } + } + } + else if (_random.NextFloat() <= 0.782 * defense / 8) + { + offense = _random.NextFloat() <= 0.5 ? "Dartmouth" : opponent; + _io.WriteLine($"Shot is blocked. Ball controlled by {offense}."); + // go to next shot + } + else if (_random.NextFloat() <= 0.843 * defense / 8) + { + _io.WriteLine("Shooter is fouled. Two shots."); + FreeShots(); + // over to opponent + } + else + { + _io.WriteLine("Charging foul. Dartmouth loses ball."); + // over to opponent + } + } + // 1300 + time++; + if (time == 50) + { + _io.WriteScore(Resource.Formats.EndOfFirstHalf, opponent, score); + // Loop back to center jump; + } + if (time == 92) + { + _io.Write(Resource.Streams.TwoMinutesLeft); + } + + _io.WriteLine(shot == 3 ? "Lay up." : "Set shot."); + + if (7 / defense * _random.NextFloat() <= 0.4) + { + _io.WriteLine("Shot is good. Two points."); + score["Dartmouth"] += 2; + _io.WriteScore(Resource.Formats.Score, opponent, score); + // over to opponent + } + else if (7 / defense * _random.NextFloat() <= 0.7) + { + _io.WriteLine("Shot is off the rim."); + if (_random.NextFloat() <= 2 / 3f) + { + _io.WriteLine($"{opponent} controls the rebound."); + // over to opponent + } + else + { + _io.WriteLine("Dartmouth controls the rebound"); + if (_random.NextFloat() <= 0.4) + { + // goto 1300 + } + else + { + _io.WriteLine("Ball passed back to you."); + // go to next shot + } + } + } + else if (7 / defense * _random.NextFloat() <= 0.875) + { + _io.WriteLine("Shooter fouled. Two shots."); + FreeShots(); + // over to opponent + } + else if (7 / defense * _random.NextFloat() <= 0.925) + { + _io.WriteLine($"Shot blocked. {opponent}'s ball."); + // over to opponent + } + else + { + _io.WriteLine("Charging foul. Dartmouth loses ball."); + // over to opponent + } + } + } + else + { + time++; + if (time == 50) + { + _io.WriteScore(Resource.Formats.EndOfFirstHalf, opponent, score); + // Loop back to center jump; + } + if (time == 92) + { + _io.Write(Resource.Streams.TwoMinutesLeft); + } + + _io.WriteLine(); + var shot = _random.NextFloat(1, 3.5f); + if (shot <= 2) + { + _io.WriteLine("Jump shot."); + + if (8 / defense * _random.NextFloat() <= 0.35) + { + _io.WriteLine("Shot is good."); + score[opponent] += 2; + _io.WriteScore(Resource.Formats.Score, opponent, score); + // over to Dartmouth + } + else if (8 / defense * _random.NextFloat() <= 0.75) + { + _io.WriteLine("Shot is off the rim."); + if (defense / 6 * _random.NextFloat() <= 0.5) + { + _io.WriteLine("Dartmouth controls the rebound."); + // over to Dartmouth + } + else + { + _io.WriteLine($"{opponent} controls the rebound."); + if (defense == 6) + { + if (_random.NextFloat() > 0.75) + { + _io.WriteLine("Ball stolen. Easy lay up for Dartmouth."); + score["Dartmouth"] += 2; + _io.WriteScore(Resource.Formats.Score, opponent, score); + _io.WriteLine(); + // next opponent shot + } + } + if (_random.NextFloat() <= 0.5) + { + _io.WriteLine($"Pass back to {opponent} guard."); + // next opponent shot + } + // goto 3500 + } + } + else if (8 / defense * _random.NextFloat() <= 0.9) + { + _io.WriteLine("Player fouled. Two shots."); + FreeShots(); + // next Dartmouth shot + } + else + { + _io.WriteLine("Offensive foul. Dartmouth's ball."); + // next Dartmouth shot + } + } + + // 3500 + _io.WriteLine(shot > 3 ? "Set shot." : "Lay up."); + + if (7 / defense * _random.NextFloat() <= 0.413) + { + _io.WriteLine("Shot is good."); + score[opponent] += 2; + _io.WriteScore(Resource.Formats.Score, opponent, score); + // over to Dartmouth + } + else + { + _io.WriteLine("Shot is missed."); + if (defense / 6 * _random.NextFloat() <= 0.5) + { + _io.WriteLine("Dartmouth controls the rebound."); + // over to Dartmouth + } + else + { + _io.WriteLine($"{opponent} controls the rebound."); + if (defense == 6) + { + if (_random.NextFloat() > 0.75) + { + _io.WriteLine("Ball stolen. Easy lay up for Dartmouth."); + score["Dartmouth"] += 2; + _io.WriteScore(Resource.Formats.Score, opponent, score); + _io.WriteLine(); + // next opponent shot + } + } + if (_random.NextFloat() <= 0.5) + { + _io.WriteLine($"Pass back to {opponent} guard."); + // next opponent shot + } + // goto 3500 + } + } + } + + void FreeShots() + { + if (_random.NextFloat() <= 0.49) + { + _io.WriteLine("Shooter makes both shots."); + score[offense] += 2; + } + else if (_random.NextFloat() <= 0.75) + { + _io.WriteLine("Shooter makes one shot and misses one."); + score[offense] += 1; + } + else + { + _io.WriteLine("Both shots missed."); + } + _io.WriteScore(Resource.Formats.Score, opponent, score); + } + } +} + +internal record Team(string Name); + +internal static class IReadWriteExtensions +{ + public static float ReadDefense(this IReadWrite io, string prompt) + { + while (true) + { + var defense = io.ReadNumber(prompt); + if (defense >= 6) { return defense; } + } + } + + public static int ReadShot(this IReadWrite io, string prompt) + { + while (true) + { + var shot = io.ReadNumber(prompt); + if ((int)shot == shot && shot >= 0 && shot <= 4) { return (int)shot; } + io.Write("Incorrect answer. Retype it. "); + } + } + + public static void WriteScore(this IReadWrite io, string format, string opponent, Dictionary score) => + io.WriteLine(format, "Dartmouth", score["Dartmouth"], opponent, score[opponent]); +} \ No newline at end of file diff --git a/07_Basketball/csharp/Program.cs b/07_Basketball/csharp/Program.cs new file mode 100644 index 00000000..79b0ab96 --- /dev/null +++ b/07_Basketball/csharp/Program.cs @@ -0,0 +1,7 @@ +using Basketball; +using Games.Common.IO; +using Games.Common.Randomness; + +var game = new Game(new ConsoleIO(), new RandomNumberGenerator()); + +game.Play(); \ No newline at end of file diff --git a/07_Basketball/csharp/Resources/EndOfFirstHalf.txt b/07_Basketball/csharp/Resources/EndOfFirstHalf.txt new file mode 100644 index 00000000..6a132a0e --- /dev/null +++ b/07_Basketball/csharp/Resources/EndOfFirstHalf.txt @@ -0,0 +1,5 @@ + + ***** End of first half ***** + +Score: {0}: {1} {2}: {3} + diff --git a/07_Basketball/csharp/Resources/EndOfGame.txt b/07_Basketball/csharp/Resources/EndOfGame.txt new file mode 100644 index 00000000..fc65416c --- /dev/null +++ b/07_Basketball/csharp/Resources/EndOfGame.txt @@ -0,0 +1,2 @@ + ***** End of game ***** +Final score: {0}: {1} {2}: {3} \ No newline at end of file diff --git a/07_Basketball/csharp/Resources/EndOfSecondHalf.txt b/07_Basketball/csharp/Resources/EndOfSecondHalf.txt new file mode 100644 index 00000000..c5f73218 --- /dev/null +++ b/07_Basketball/csharp/Resources/EndOfSecondHalf.txt @@ -0,0 +1,7 @@ + + ***** End of second half ***** + +Score at end of regulation time: + {0}: {1} {2}: {3} + +Begin two minute overtime period \ No newline at end of file diff --git a/07_Basketball/csharp/Resources/Introduction.txt b/07_Basketball/csharp/Resources/Introduction.txt new file mode 100644 index 00000000..b5ad510b --- /dev/null +++ b/07_Basketball/csharp/Resources/Introduction.txt @@ -0,0 +1,12 @@ + Basketball + Creative Computing Morristown, New Jersey + + + +This is Dartmouth College basketball. You will be Dartmouth + captain and playmaker. Call shots as follows: 1. Long + (30 ft.) jump shot; 2. Short (15 ft.) jump shot; 3. Lay + up; 4. Set shot. +Both teams will use the same defense. Call defense as +follows: 6. Press; 6.5 Man-to-man; 7. Zone; 7.5 None. +To change defense, just type 0 as your next shot. \ No newline at end of file diff --git a/07_Basketball/csharp/Resources/Resource.cs b/07_Basketball/csharp/Resources/Resource.cs new file mode 100644 index 00000000..c442a3ff --- /dev/null +++ b/07_Basketball/csharp/Resources/Resource.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Basketball.Resources; + +internal static class Resource +{ + internal static class Streams + { + public static Stream Introduction => GetStream(); + public static Stream TwoMinutesLeft => GetStream(); + } + + internal static class Formats + { + public static string EndOfFirstHalf => GetString(); + public static string EndOfGame => GetString(); + public static string EndOfSecondHalf => GetString(); + public static string Score => 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($"Basketball.Resources.{name}.txt") + ?? throw new Exception($"Could not find embedded resource stream '{name}'."); +} \ No newline at end of file diff --git a/07_Basketball/csharp/Resources/Score.txt b/07_Basketball/csharp/Resources/Score.txt new file mode 100644 index 00000000..7b317cb3 --- /dev/null +++ b/07_Basketball/csharp/Resources/Score.txt @@ -0,0 +1 @@ +Score: {1} to {3} \ No newline at end of file diff --git a/07_Basketball/csharp/Resources/TwoMinutesLeft.txt b/07_Basketball/csharp/Resources/TwoMinutesLeft.txt new file mode 100644 index 00000000..0ad16993 --- /dev/null +++ b/07_Basketball/csharp/Resources/TwoMinutesLeft.txt @@ -0,0 +1,3 @@ + + *** Two minutes left in the game *** +