From 9106c2e13c9901d402a04fbbe4979bfa8b11758f Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Fri, 25 Mar 2022 08:14:29 +1100 Subject: [PATCH 01/15] 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 *** + From 74698c41c44bf1bd4c410630569d7361e2736c50 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Wed, 30 Mar 2022 10:30:44 +1100 Subject: [PATCH 02/15] Add Scoreboard abstraction --- 07_Basketball/csharp/Game.cs | 123 +++++++------------ 07_Basketball/csharp/IReadWriteExtensions.cs | 28 +++++ 07_Basketball/csharp/Scoreboard.cs | 43 +++++++ 07_Basketball/csharp/Team.cs | 6 + 4 files changed, 124 insertions(+), 76 deletions(-) create mode 100644 07_Basketball/csharp/IReadWriteExtensions.cs create mode 100644 07_Basketball/csharp/Scoreboard.cs create mode 100644 07_Basketball/csharp/Team.cs diff --git a/07_Basketball/csharp/Game.cs b/07_Basketball/csharp/Game.cs index b44278d3..4f405c38 100644 --- a/07_Basketball/csharp/Game.cs +++ b/07_Basketball/csharp/Game.cs @@ -21,35 +21,39 @@ internal class Game var defense = _io.ReadDefense("Your starting defense will be"); + var homeTeam = new Team("Dartmouth"); + _io.WriteLine(); - var opponent = _io.ReadString("Choose your opponent"); + var visitingTeam = new Team(_io.ReadString("Choose your opponent")); + _io.WriteLine("Center jump"); - var offense = _random.NextFloat() > 0.6 ? "Dartmouth" : opponent; + var offense = _random.NextFloat() > 0.6 ? homeTeam : visitingTeam; _io.WriteLine($"{offense} controls the tap."); var time = 0; - var score = new Dictionary { ["Dartmouth"] = 0, [opponent] = 0 }; + var scoreboard = new Scoreboard(homeTeam, visitingTeam, _io); + scoreboard.Offense = offense; _io.WriteLine(); - if (offense == "Dartmouth") + if (offense == homeTeam) { var shot = _io.ReadShot("Your shot"); if (_random.NextFloat() >= 0.5 && time >= 100) { _io.WriteLine(); - if (score["Dartmouth"] == score[opponent]) + if (scoreboard.ScoresAreEqual) { - _io.WriteScore(Resource.Formats.EndOfSecondHalf, opponent, score); + scoreboard.Display(Resource.Formats.EndOfSecondHalf); time = 93; // Loop back to center jump } else { - _io.WriteScore(Resource.Formats.EndOfGame, opponent, score); + scoreboard.Display(Resource.Formats.EndOfGame); return; } } @@ -66,7 +70,7 @@ internal class Game time++; if (time == 50) { - _io.WriteScore(Resource.Formats.EndOfFirstHalf, opponent, score); + scoreboard.Display(Resource.Formats.EndOfFirstHalf); // Loop back to center jump; } if (time == 92) @@ -76,9 +80,7 @@ internal class Game _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); + scoreboard.AddBasket("Shot is good"); // over to opponent } else if (_random.NextFloat() <= 0.682 * defense / 8) @@ -86,7 +88,8 @@ internal class Game _io.WriteLine("Shot is off target"); if (defense / 6 * _random.NextFloat() > 0.45) { - _io.WriteLine($"Rebound to {opponent}"); + _io.WriteLine($"Rebound to {visitingTeam}"); + scoreboard.Turnover(); // over to opponent } else @@ -102,9 +105,8 @@ internal class Game { if (_random.NextFloat() > 0.6) { - _io.WriteLine($"Pass stolen be {opponent} easy layup."); - score[opponent] += 2; - _io.WriteScore(Resource.Formats.Score, opponent, score); + scoreboard.Turnover(); + scoreboard.AddBasket($"Pass stolen by {visitingTeam} easy layup."); _io.WriteLine(); } } @@ -115,19 +117,21 @@ internal class Game } else if (_random.NextFloat() <= 0.782 * defense / 8) { - offense = _random.NextFloat() <= 0.5 ? "Dartmouth" : opponent; + offense = _random.NextFloat() <= 0.5 ? homeTeam : visitingTeam; + scoreboard.Offense = offense; _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(); + FreeThrows(); // over to opponent } else { _io.WriteLine("Charging foul. Dartmouth loses ball."); + scoreboard.Turnover(); // over to opponent } } @@ -135,7 +139,7 @@ internal class Game time++; if (time == 50) { - _io.WriteScore(Resource.Formats.EndOfFirstHalf, opponent, score); + scoreboard.Display(Resource.Formats.EndOfFirstHalf); // Loop back to center jump; } if (time == 92) @@ -147,9 +151,7 @@ internal class Game if (7 / defense * _random.NextFloat() <= 0.4) { - _io.WriteLine("Shot is good. Two points."); - score["Dartmouth"] += 2; - _io.WriteScore(Resource.Formats.Score, opponent, score); + scoreboard.AddBasket("Shot is good. Two points."); // over to opponent } else if (7 / defense * _random.NextFloat() <= 0.7) @@ -157,7 +159,8 @@ internal class Game _io.WriteLine("Shot is off the rim."); if (_random.NextFloat() <= 2 / 3f) { - _io.WriteLine($"{opponent} controls the rebound."); + _io.WriteLine($"{visitingTeam} controls the rebound."); + scoreboard.Turnover(); // over to opponent } else @@ -177,17 +180,19 @@ internal class Game else if (7 / defense * _random.NextFloat() <= 0.875) { _io.WriteLine("Shooter fouled. Two shots."); - FreeShots(); + FreeThrows(); // over to opponent } else if (7 / defense * _random.NextFloat() <= 0.925) { - _io.WriteLine($"Shot blocked. {opponent}'s ball."); + _io.WriteLine($"Shot blocked. {visitingTeam}'s ball."); + scoreboard.Turnover(); // over to opponent } else { _io.WriteLine("Charging foul. Dartmouth loses ball."); + scoreboard.Turnover(); // over to opponent } } @@ -197,7 +202,7 @@ internal class Game time++; if (time == 50) { - _io.WriteScore(Resource.Formats.EndOfFirstHalf, opponent, score); + scoreboard.Display(Resource.Formats.EndOfFirstHalf); // Loop back to center jump; } if (time == 92) @@ -213,9 +218,7 @@ internal class Game if (8 / defense * _random.NextFloat() <= 0.35) { - _io.WriteLine("Shot is good."); - score[opponent] += 2; - _io.WriteScore(Resource.Formats.Score, opponent, score); + scoreboard.AddBasket("Shot is good."); // over to Dartmouth } else if (8 / defense * _random.NextFloat() <= 0.75) @@ -224,25 +227,25 @@ internal class Game if (defense / 6 * _random.NextFloat() <= 0.5) { _io.WriteLine("Dartmouth controls the rebound."); + scoreboard.Turnover(); // over to Dartmouth } else { - _io.WriteLine($"{opponent} controls the rebound."); + _io.WriteLine($"{visitingTeam} 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); + scoreboard.Turnover(); + scoreboard.AddBasket("Ball stolen. Easy lay up for Dartmouth."); _io.WriteLine(); // next opponent shot } } if (_random.NextFloat() <= 0.5) { - _io.WriteLine($"Pass back to {opponent} guard."); + _io.WriteLine($"Pass back to {visitingTeam} guard."); // next opponent shot } // goto 3500 @@ -251,7 +254,7 @@ internal class Game else if (8 / defense * _random.NextFloat() <= 0.9) { _io.WriteLine("Player fouled. Two shots."); - FreeShots(); + FreeThrows(); // next Dartmouth shot } else @@ -266,9 +269,7 @@ internal class Game if (7 / defense * _random.NextFloat() <= 0.413) { - _io.WriteLine("Shot is good."); - score[opponent] += 2; - _io.WriteScore(Resource.Formats.Score, opponent, score); + scoreboard.AddBasket("Shot is good."); // over to Dartmouth } else @@ -277,25 +278,25 @@ internal class Game if (defense / 6 * _random.NextFloat() <= 0.5) { _io.WriteLine("Dartmouth controls the rebound."); + scoreboard.Turnover(); // over to Dartmouth } else { - _io.WriteLine($"{opponent} controls the rebound."); + _io.WriteLine($"{visitingTeam} 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); + scoreboard.Turnover(); + scoreboard.AddBasket("Ball stolen. Easy lay up for Dartmouth."); _io.WriteLine(); // next opponent shot } } if (_random.NextFloat() <= 0.5) { - _io.WriteLine($"Pass back to {opponent} guard."); + _io.WriteLine($"Pass back to {visitingTeam} guard."); // next opponent shot } // goto 3500 @@ -303,50 +304,20 @@ internal class Game } } - void FreeShots() + void FreeThrows() { if (_random.NextFloat() <= 0.49) { - _io.WriteLine("Shooter makes both shots."); - score[offense] += 2; + scoreboard.AddFreeThrows(2, "Shooter makes both shots."); } else if (_random.NextFloat() <= 0.75) { - _io.WriteLine("Shooter makes one shot and misses one."); - score[offense] += 1; + scoreboard.AddFreeThrows(1, "Shooter makes one shot and misses one."); } else { - _io.WriteLine("Both shots missed."); + scoreboard.AddFreeThrows(0, "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/IReadWriteExtensions.cs b/07_Basketball/csharp/IReadWriteExtensions.cs new file mode 100644 index 00000000..7b510a50 --- /dev/null +++ b/07_Basketball/csharp/IReadWriteExtensions.cs @@ -0,0 +1,28 @@ +using Games.Common.IO; + +namespace Basketball; + +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/Scoreboard.cs b/07_Basketball/csharp/Scoreboard.cs new file mode 100644 index 00000000..fc94b432 --- /dev/null +++ b/07_Basketball/csharp/Scoreboard.cs @@ -0,0 +1,43 @@ +using Basketball.Resources; +using Games.Common.IO; + +namespace Basketball; + +internal class Scoreboard +{ + private readonly Dictionary _scores; + private readonly IReadWrite _io; + + private Team _home; + private Team _visitors; + private Team _offense; + + public Scoreboard(Team home, Team visitors, IReadWrite io) + { + _scores = new() { [home] = 0, [visitors] = 0 }; + _home = home; + _visitors = visitors; + _offense = home; + _io = io; + } + + public bool ScoresAreEqual => _scores[_home] == _scores[_visitors]; + public Team Offense { set => _offense = value; } + + public void AddBasket(string message) => AddScore(2, message); + + public void AddFreeThrows(uint count, string message) => AddScore(count, message); + + private void AddScore(uint score, string message) + { + _io.WriteLine(message); + _scores[_offense] += score; + Turnover(); + Display(); + } + + public void Turnover() => _offense = _offense == _home ? _visitors : _home; + + public void Display(string? format = null) => + _io.WriteLine(format ?? Resource.Formats.Score, _home, _scores[_home], _visitors, _scores[_visitors]); +} diff --git a/07_Basketball/csharp/Team.cs b/07_Basketball/csharp/Team.cs new file mode 100644 index 00000000..91181588 --- /dev/null +++ b/07_Basketball/csharp/Team.cs @@ -0,0 +1,6 @@ +namespace Basketball; + +internal record Team(string Name) +{ + public override string ToString() => Name; +} From 741b135d9951a61d6c4b4530fee3a99201842110 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Wed, 30 Mar 2022 11:04:38 +1100 Subject: [PATCH 03/15] Simplify messaging --- 07_Basketball/csharp/Game.cs | 104 ++++++++++++++--------------- 07_Basketball/csharp/Scoreboard.cs | 14 ++-- 2 files changed, 58 insertions(+), 60 deletions(-) diff --git a/07_Basketball/csharp/Game.cs b/07_Basketball/csharp/Game.cs index 4f405c38..22e0ca48 100644 --- a/07_Basketball/csharp/Game.cs +++ b/07_Basketball/csharp/Game.cs @@ -26,23 +26,19 @@ internal class Game _io.WriteLine(); var visitingTeam = new Team(_io.ReadString("Choose your opponent")); - - _io.WriteLine("Center jump"); - var offense = _random.NextFloat() > 0.6 ? homeTeam : visitingTeam; - - _io.WriteLine($"{offense} controls the tap."); - var time = 0; var scoreboard = new Scoreboard(homeTeam, visitingTeam, _io); - scoreboard.Offense = offense; + + _io.WriteLine("Center jump"); + scoreboard.Offense = ContestBall(0.6f, visitingTeam, homeTeam, "{0} controls the tap"); _io.WriteLine(); - if (offense == homeTeam) + if (scoreboard.Offense == homeTeam) { var shot = _io.ReadShot("Your shot"); - if (_random.NextFloat() >= 0.5 && time >= 100) + if (_random.NextFloat() >= 0.5f && time >= 100) { _io.WriteLine(); if (scoreboard.ScoresAreEqual) @@ -78,24 +74,23 @@ internal class Game _io.Write(Resource.Streams.TwoMinutesLeft); } _io.WriteLine("Jump shot"); - if (_random.NextFloat() <= 0.341 * defense / 8) + if (_random.NextFloat() <= 0.341f * defense / 8) { scoreboard.AddBasket("Shot is good"); // over to opponent } - else if (_random.NextFloat() <= 0.682 * defense / 8) + else if (_random.NextFloat() <= 0.682f * defense / 8) { _io.WriteLine("Shot is off target"); - if (defense / 6 * _random.NextFloat() > 0.45) + if (defense / 6 * _random.NextFloat() > 0.45f) { - _io.WriteLine($"Rebound to {visitingTeam}"); - scoreboard.Turnover(); + scoreboard.Turnover($"Rebound to {visitingTeam}"); // over to opponent } else { _io.WriteLine("Dartmouth controls the rebound."); - if (_random.NextFloat() <= 0.4) + if (_random.NextFloat() <= 0.4f) { // fall through to 1300 } @@ -103,7 +98,7 @@ internal class Game { if (defense == 6) { - if (_random.NextFloat() > 0.6) + if (_random.NextFloat() > 0.6f) { scoreboard.Turnover(); scoreboard.AddBasket($"Pass stolen by {visitingTeam} easy layup."); @@ -115,23 +110,20 @@ internal class Game } } } - else if (_random.NextFloat() <= 0.782 * defense / 8) + else if (_random.NextFloat() <= 0.782f * defense / 8) { - offense = _random.NextFloat() <= 0.5 ? homeTeam : visitingTeam; - scoreboard.Offense = offense; - _io.WriteLine($"Shot is blocked. Ball controlled by {offense}."); + scoreboard.Offense = + ContestBall(0.5f, homeTeam, visitingTeam, "Shot is blocked. Ball controlled by {0}."); // go to next shot } - else if (_random.NextFloat() <= 0.843 * defense / 8) + else if (_random.NextFloat() <= 0.843f * defense / 8) { - _io.WriteLine("Shooter is fouled. Two shots."); - FreeThrows(); + FreeThrows("Shooter is fouled. Two shots."); // over to opponent } else { - _io.WriteLine("Charging foul. Dartmouth loses ball."); - scoreboard.Turnover(); + scoreboard.Turnover("Charging foul. Dartmouth loses ball."); // over to opponent } } @@ -149,24 +141,23 @@ internal class Game _io.WriteLine(shot == 3 ? "Lay up." : "Set shot."); - if (7 / defense * _random.NextFloat() <= 0.4) + if (_random.NextFloat() <= 0.4f * defense / 7) { scoreboard.AddBasket("Shot is good. Two points."); // over to opponent } - else if (7 / defense * _random.NextFloat() <= 0.7) + else if (_random.NextFloat() <= 0.7f * defense / 7) { _io.WriteLine("Shot is off the rim."); if (_random.NextFloat() <= 2 / 3f) { - _io.WriteLine($"{visitingTeam} controls the rebound."); - scoreboard.Turnover(); + scoreboard.Turnover($"{visitingTeam} controls the rebound."); // over to opponent } else { _io.WriteLine("Dartmouth controls the rebound"); - if (_random.NextFloat() <= 0.4) + if (_random.NextFloat() <= 0.4f) { // goto 1300 } @@ -177,22 +168,19 @@ internal class Game } } } - else if (7 / defense * _random.NextFloat() <= 0.875) + else if (_random.NextFloat() <= 0.875f * defense / 7) { - _io.WriteLine("Shooter fouled. Two shots."); - FreeThrows(); + FreeThrows("Shooter fouled. Two shots."); // over to opponent } - else if (7 / defense * _random.NextFloat() <= 0.925) + else if (_random.NextFloat() <= 0.925f * defense / 7) { - _io.WriteLine($"Shot blocked. {visitingTeam}'s ball."); - scoreboard.Turnover(); + scoreboard.Turnover($"Shot blocked. {visitingTeam}'s ball."); // over to opponent } else { - _io.WriteLine("Charging foul. Dartmouth loses ball."); - scoreboard.Turnover(); + scoreboard.Turnover("Charging foul. Dartmouth loses ball."); // over to opponent } } @@ -216,18 +204,17 @@ internal class Game { _io.WriteLine("Jump shot."); - if (8 / defense * _random.NextFloat() <= 0.35) + if (_random.NextFloat() <= 0.35f * defense / 8) { scoreboard.AddBasket("Shot is good."); // over to Dartmouth } - else if (8 / defense * _random.NextFloat() <= 0.75) + else if (_random.NextFloat() <= 0.75f * defense / 8) { _io.WriteLine("Shot is off the rim."); - if (defense / 6 * _random.NextFloat() <= 0.5) + if (_random.NextFloat() <= 0.5f / defense * 6) { - _io.WriteLine("Dartmouth controls the rebound."); - scoreboard.Turnover(); + scoreboard.Turnover("Dartmouth controls the rebound."); // over to Dartmouth } else @@ -235,7 +222,7 @@ internal class Game _io.WriteLine($"{visitingTeam} controls the rebound."); if (defense == 6) { - if (_random.NextFloat() > 0.75) + if (_random.NextFloat() <= 0.25f) { scoreboard.Turnover(); scoreboard.AddBasket("Ball stolen. Easy lay up for Dartmouth."); @@ -243,7 +230,7 @@ internal class Game // next opponent shot } } - if (_random.NextFloat() <= 0.5) + if (_random.NextFloat() <= 0.5f) { _io.WriteLine($"Pass back to {visitingTeam} guard."); // next opponent shot @@ -251,10 +238,9 @@ internal class Game // goto 3500 } } - else if (8 / defense * _random.NextFloat() <= 0.9) + else if (_random.NextFloat() <= 0.9f * defense / 8) { - _io.WriteLine("Player fouled. Two shots."); - FreeThrows(); + FreeThrows("Player fouled. Two shots."); // next Dartmouth shot } else @@ -267,7 +253,7 @@ internal class Game // 3500 _io.WriteLine(shot > 3 ? "Set shot." : "Lay up."); - if (7 / defense * _random.NextFloat() <= 0.413) + if (_random.NextFloat() <= 0.413f * defense / 7) { scoreboard.AddBasket("Shot is good."); // over to Dartmouth @@ -275,10 +261,9 @@ internal class Game else { _io.WriteLine("Shot is missed."); - if (defense / 6 * _random.NextFloat() <= 0.5) + if (_random.NextFloat() <= 0.5f * 6 / defense) { - _io.WriteLine("Dartmouth controls the rebound."); - scoreboard.Turnover(); + scoreboard.Turnover("Dartmouth controls the rebound."); // over to Dartmouth } else @@ -286,7 +271,7 @@ internal class Game _io.WriteLine($"{visitingTeam} controls the rebound."); if (defense == 6) { - if (_random.NextFloat() > 0.75) + if (_random.NextFloat() <= 0.25f) { scoreboard.Turnover(); scoreboard.AddBasket("Ball stolen. Easy lay up for Dartmouth."); @@ -294,7 +279,7 @@ internal class Game // next opponent shot } } - if (_random.NextFloat() <= 0.5) + if (_random.NextFloat() <= 0.5f) { _io.WriteLine($"Pass back to {visitingTeam} guard."); // next opponent shot @@ -304,8 +289,17 @@ internal class Game } } - void FreeThrows() + Team ContestBall(float probability, Team a, Team b, string messageFormat) { + var winner = _random.NextFloat() <= probability ? a : b; + _io.WriteLine(messageFormat, winner); + return winner; + } + + void FreeThrows(string message) + { + _io.WriteLine(message); + if (_random.NextFloat() <= 0.49) { scoreboard.AddFreeThrows(2, "Shooter makes both shots."); diff --git a/07_Basketball/csharp/Scoreboard.cs b/07_Basketball/csharp/Scoreboard.cs index fc94b432..ae6f1b56 100644 --- a/07_Basketball/csharp/Scoreboard.cs +++ b/07_Basketball/csharp/Scoreboard.cs @@ -10,19 +10,18 @@ internal class Scoreboard private Team _home; private Team _visitors; - private Team _offense; public Scoreboard(Team home, Team visitors, IReadWrite io) { _scores = new() { [home] = 0, [visitors] = 0 }; _home = home; _visitors = visitors; - _offense = home; + Offense = home; _io = io; } public bool ScoresAreEqual => _scores[_home] == _scores[_visitors]; - public Team Offense { set => _offense = value; } + public Team Offense { get; set; } public void AddBasket(string message) => AddScore(2, message); @@ -31,12 +30,17 @@ internal class Scoreboard private void AddScore(uint score, string message) { _io.WriteLine(message); - _scores[_offense] += score; + _scores[Offense] += score; Turnover(); Display(); } - public void Turnover() => _offense = _offense == _home ? _visitors : _home; + public void Turnover(string? message = null) + { + if (message is not null) { _io.WriteLine(message); } + + Offense = Offense == _home ? _visitors : _home; + } public void Display(string? format = null) => _io.WriteLine(format ?? Resource.Formats.Score, _home, _scores[_home], _visitors, _scores[_visitors]); From 1ba6dd48aa16faa835e7fdc0c4fbbfdd2aa57ea1 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Wed, 30 Mar 2022 16:50:48 +1100 Subject: [PATCH 04/15] Split out play resolution --- 07_Basketball/csharp/Clock.cs | 32 +++ 07_Basketball/csharp/Defense.cs | 12 + 07_Basketball/csharp/Game.cs | 403 ++++++++++++++--------------- 07_Basketball/csharp/Scoreboard.cs | 15 +- 07_Basketball/csharp/Team.cs | 4 +- 5 files changed, 252 insertions(+), 214 deletions(-) create mode 100644 07_Basketball/csharp/Clock.cs create mode 100644 07_Basketball/csharp/Defense.cs diff --git a/07_Basketball/csharp/Clock.cs b/07_Basketball/csharp/Clock.cs new file mode 100644 index 00000000..deb7c549 --- /dev/null +++ b/07_Basketball/csharp/Clock.cs @@ -0,0 +1,32 @@ +using Basketball.Resources; +using Games.Common.IO; + +namespace Basketball; + +internal class Clock +{ + private readonly IReadWrite _io; + private int time; + + public Clock(IReadWrite io) => _io = io; + + public bool IsHalfTime => time == 50; + public bool IsFullTime => time >= 100; + public bool TwoMinutesLeft => time == 92; + + public void Increment(Scoreboard scoreboard) + { + time += 1; + if (IsHalfTime) + { + scoreboard.Display(Resource.Formats.EndOfFirstHalf); + // Loop back to center jump; + } + if (TwoMinutesLeft) + { + _io.Write(Resource.Streams.TwoMinutesLeft); + } + } + + public void StartOvertime() => time = 93; +} \ No newline at end of file diff --git a/07_Basketball/csharp/Defense.cs b/07_Basketball/csharp/Defense.cs new file mode 100644 index 00000000..f447266f --- /dev/null +++ b/07_Basketball/csharp/Defense.cs @@ -0,0 +1,12 @@ +namespace Basketball; + +internal class Defense +{ + private float _value; + + public Defense(float value) => Set(value); + + public void Set(float value) => _value = value; + + public static implicit operator float(Defense defense) => defense._value; +} diff --git a/07_Basketball/csharp/Game.cs b/07_Basketball/csharp/Game.cs index 22e0ca48..1492cbcc 100644 --- a/07_Basketball/csharp/Game.cs +++ b/07_Basketball/csharp/Game.cs @@ -19,163 +19,109 @@ internal class Game { _io.Write(Resource.Streams.Introduction); - var defense = _io.ReadDefense("Your starting defense will be"); - - var homeTeam = new Team("Dartmouth"); - - _io.WriteLine(); - var visitingTeam = new Team(_io.ReadString("Choose your opponent")); - - var time = 0; - var scoreboard = new Scoreboard(homeTeam, visitingTeam, _io); - - _io.WriteLine("Center jump"); - scoreboard.Offense = ContestBall(0.6f, visitingTeam, homeTeam, "{0} controls the tap"); + var defense = new Defense(_io.ReadDefense("Your starting defense will be")); + var clock = new Clock(_io); _io.WriteLine(); - if (scoreboard.Offense == homeTeam) + var scoreboard = new Scoreboard( + new Team("Dartmouth", HomeTeamPlay(clock, defense)), + new Team(_io.ReadString("Choose your opponent"), VisitingTeamPlay(clock, defense)), + _io); + + while (true) { - var shot = _io.ReadShot("Your shot"); + _io.WriteLine("Center jump"); + scoreboard.Offense = ContestBall(0.4f, scoreboard, "{0} controls the tap"); - if (_random.NextFloat() >= 0.5f && time >= 100) + _io.WriteLine(); + + while (true) { - _io.WriteLine(); - if (scoreboard.ScoresAreEqual) - { - scoreboard.Display(Resource.Formats.EndOfSecondHalf); - time = 93; - // Loop back to center jump - } - else - { - scoreboard.Display(Resource.Formats.EndOfGame); - return; - } + scoreboard.Offense.ResolvePlay(scoreboard); + } + } + } + + private Action HomeTeamPlay(Clock clock, Defense defense) => scoreboard => + { + var shot = _io.ReadShot("Your shot"); + + if (_random.NextFloat() >= 0.5f && clock.IsFullTime) + { + _io.WriteLine(); + if (scoreboard.ScoresAreEqual) + { + scoreboard.Display(Resource.Formats.EndOfSecondHalf); + clock.StartOvertime(); + // Loop back to center jump } else { - if (shot == 0) - { - defense = _io.ReadDefense("Your new defensive alignment is"); - // go to next shot - } + scoreboard.Display(Resource.Formats.EndOfGame); + return; + } + } + else + { + if (shot == 0) + { + defense.Set(_io.ReadDefense("Your new defensive alignment is")); + // go to next shot + } - if (shot == 1 || shot == 2) + if (shot == 1 || shot == 2) + { + clock.Increment(scoreboard); + if (clock.IsHalfTime) { - time++; - if (time == 50) - { - scoreboard.Display(Resource.Formats.EndOfFirstHalf); - // Loop back to center jump; - } - if (time == 92) - { - _io.Write(Resource.Streams.TwoMinutesLeft); - } - _io.WriteLine("Jump shot"); - if (_random.NextFloat() <= 0.341f * defense / 8) - { - scoreboard.AddBasket("Shot is good"); - // over to opponent - } - else if (_random.NextFloat() <= 0.682f * defense / 8) - { - _io.WriteLine("Shot is off target"); - if (defense / 6 * _random.NextFloat() > 0.45f) - { - scoreboard.Turnover($"Rebound to {visitingTeam}"); - // over to opponent - } - else - { - _io.WriteLine("Dartmouth controls the rebound."); - if (_random.NextFloat() <= 0.4f) - { - // fall through to 1300 - } - else - { - if (defense == 6) - { - if (_random.NextFloat() > 0.6f) - { - scoreboard.Turnover(); - scoreboard.AddBasket($"Pass stolen by {visitingTeam} easy layup."); - _io.WriteLine(); - } - } - _io.Write("Ball passed back to you. "); - // next shot without writeline - } - } - } - else if (_random.NextFloat() <= 0.782f * defense / 8) - { - scoreboard.Offense = - ContestBall(0.5f, homeTeam, visitingTeam, "Shot is blocked. Ball controlled by {0}."); - // go to next shot - } - else if (_random.NextFloat() <= 0.843f * defense / 8) - { - FreeThrows("Shooter is fouled. Two shots."); - // over to opponent - } - else - { - scoreboard.Turnover("Charging foul. Dartmouth loses ball."); - // over to opponent - } - } - // 1300 - time++; - if (time == 50) - { - scoreboard.Display(Resource.Formats.EndOfFirstHalf); // Loop back to center jump; } - if (time == 92) + _io.WriteLine("Jump shot"); + if (_random.NextFloat() <= 0.341f * defense / 8) { - _io.Write(Resource.Streams.TwoMinutesLeft); - } - - _io.WriteLine(shot == 3 ? "Lay up." : "Set shot."); - - if (_random.NextFloat() <= 0.4f * defense / 7) - { - scoreboard.AddBasket("Shot is good. Two points."); + scoreboard.AddBasket("Shot is good"); // over to opponent } - else if (_random.NextFloat() <= 0.7f * defense / 7) + else if (_random.NextFloat() <= 0.682f * defense / 8) { - _io.WriteLine("Shot is off the rim."); - if (_random.NextFloat() <= 2 / 3f) + _io.WriteLine("Shot is off target"); + if (defense / 6 * _random.NextFloat() > 0.45f) { - scoreboard.Turnover($"{visitingTeam} controls the rebound."); + scoreboard.Turnover($"Rebound to {scoreboard.Visitors}"); // over to opponent } else { - _io.WriteLine("Dartmouth controls the rebound"); + _io.WriteLine("Dartmouth controls the rebound."); if (_random.NextFloat() <= 0.4f) { - // goto 1300 + // fall through to 1300 } else { - _io.WriteLine("Ball passed back to you."); - // go to next shot + if (defense == 6) + { + if (_random.NextFloat() > 0.6f) + { + scoreboard.Turnover(); + scoreboard.AddBasket($"Pass stolen by {scoreboard.Visitors} easy layup."); + _io.WriteLine(); + } + } + _io.Write("Ball passed back to you. "); + // next shot without writeline } } } - else if (_random.NextFloat() <= 0.875f * defense / 7) + else if (_random.NextFloat() <= 0.782f * defense / 8) { - FreeThrows("Shooter fouled. Two shots."); - // over to opponent + scoreboard.Offense = ContestBall(0.5f, scoreboard, "Shot is blocked. Ball controlled by {0}."); + // go to next shot } - else if (_random.NextFloat() <= 0.925f * defense / 7) + else if (_random.NextFloat() <= 0.843f * defense / 8) { - scoreboard.Turnover($"Shot blocked. {visitingTeam}'s ball."); + FreeThrows(scoreboard, "Shooter is fouled. Two shots."); // over to opponent } else @@ -184,91 +130,90 @@ internal class Game // over to opponent } } - } - else - { - time++; - if (time == 50) + // 1300 + clock.Increment(scoreboard); + if (clock.IsHalfTime) { - scoreboard.Display(Resource.Formats.EndOfFirstHalf); // Loop back to center jump; } - if (time == 92) + + _io.WriteLine(shot == 3 ? "Lay up." : "Set shot."); + + if (_random.NextFloat() <= 0.4f * defense / 7) { - _io.Write(Resource.Streams.TwoMinutesLeft); + scoreboard.AddBasket("Shot is good. Two points."); + // over to opponent } - - _io.WriteLine(); - var shot = _random.NextFloat(1, 3.5f); - if (shot <= 2) + else if (_random.NextFloat() <= 0.7f * defense / 7) { - _io.WriteLine("Jump shot."); - - if (_random.NextFloat() <= 0.35f * defense / 8) + _io.WriteLine("Shot is off the rim."); + if (_random.NextFloat() <= 2 / 3f) { - scoreboard.AddBasket("Shot is good."); - // over to Dartmouth - } - else if (_random.NextFloat() <= 0.75f * defense / 8) - { - _io.WriteLine("Shot is off the rim."); - if (_random.NextFloat() <= 0.5f / defense * 6) - { - scoreboard.Turnover("Dartmouth controls the rebound."); - // over to Dartmouth - } - else - { - _io.WriteLine($"{visitingTeam} controls the rebound."); - if (defense == 6) - { - if (_random.NextFloat() <= 0.25f) - { - scoreboard.Turnover(); - scoreboard.AddBasket("Ball stolen. Easy lay up for Dartmouth."); - _io.WriteLine(); - // next opponent shot - } - } - if (_random.NextFloat() <= 0.5f) - { - _io.WriteLine($"Pass back to {visitingTeam} guard."); - // next opponent shot - } - // goto 3500 - } - } - else if (_random.NextFloat() <= 0.9f * defense / 8) - { - FreeThrows("Player fouled. Two shots."); - // next Dartmouth shot + scoreboard.Turnover($"{scoreboard.Visitors} controls the rebound."); + // over to opponent } else { - _io.WriteLine("Offensive foul. Dartmouth's ball."); - // next Dartmouth shot + _io.WriteLine("Dartmouth controls the rebound"); + if (_random.NextFloat() <= 0.4f) + { + // goto 1300 + } + else + { + _io.WriteLine("Ball passed back to you."); + // go to next shot + } } } + else if (_random.NextFloat() <= 0.875f * defense / 7) + { + FreeThrows(scoreboard, "Shooter fouled. Two shots."); + // over to opponent + } + else if (_random.NextFloat() <= 0.925f * defense / 7) + { + scoreboard.Turnover($"Shot blocked. {scoreboard.Visitors}'s ball."); + // over to opponent + } + else + { + scoreboard.Turnover("Charging foul. Dartmouth loses ball."); + // over to opponent + } + } + }; - // 3500 - _io.WriteLine(shot > 3 ? "Set shot." : "Lay up."); + private Action VisitingTeamPlay(Clock clock, Defense defense) => scoreboard => + { + clock.Increment(scoreboard); + if (clock.IsHalfTime) + { + // Loop back to center jump; + } - if (_random.NextFloat() <= 0.413f * defense / 7) + _io.WriteLine(); + var shot = _random.NextFloat(1, 3.5f); + if (shot <= 2) + { + _io.WriteLine("Jump shot."); + + if (_random.NextFloat() <= 0.35f * defense / 8) { scoreboard.AddBasket("Shot is good."); // over to Dartmouth } - else + else if (_random.NextFloat() <= 0.75f * defense / 8) { - _io.WriteLine("Shot is missed."); - if (_random.NextFloat() <= 0.5f * 6 / defense) + _io.WriteLine("Shot is off the rim."); + if (_random.NextFloat() <= 0.5f / defense * 6) { scoreboard.Turnover("Dartmouth controls the rebound."); // over to Dartmouth } else { - _io.WriteLine($"{visitingTeam} controls the rebound."); + _io.WriteLine($"{scoreboard.Visitors} controls the rebound."); if (defense == 6) { if (_random.NextFloat() <= 0.25f) @@ -281,37 +226,85 @@ internal class Game } if (_random.NextFloat() <= 0.5f) { - _io.WriteLine($"Pass back to {visitingTeam} guard."); + _io.WriteLine($"Pass back to {scoreboard.Visitors} guard."); // next opponent shot } // goto 3500 } } - } - - Team ContestBall(float probability, Team a, Team b, string messageFormat) - { - var winner = _random.NextFloat() <= probability ? a : b; - _io.WriteLine(messageFormat, winner); - return winner; - } - - void FreeThrows(string message) - { - _io.WriteLine(message); - - if (_random.NextFloat() <= 0.49) + else if (_random.NextFloat() <= 0.9f * defense / 8) { - scoreboard.AddFreeThrows(2, "Shooter makes both shots."); - } - else if (_random.NextFloat() <= 0.75) - { - scoreboard.AddFreeThrows(1, "Shooter makes one shot and misses one."); + FreeThrows(scoreboard, "Player fouled. Two shots."); + // next Dartmouth shot } else { - scoreboard.AddFreeThrows(0, "Both shots missed."); + _io.WriteLine("Offensive foul. Dartmouth's ball."); + // next Dartmouth shot } } + + // 3500 + _io.WriteLine(shot > 3 ? "Set shot." : "Lay up."); + + if (_random.NextFloat() <= 0.413f * defense / 7) + { + scoreboard.AddBasket("Shot is good."); + // over to Dartmouth + } + else + { + _io.WriteLine("Shot is missed."); + if (_random.NextFloat() <= 0.5f * 6 / defense) + { + scoreboard.Turnover("Dartmouth controls the rebound."); + // over to Dartmouth + } + else + { + _io.WriteLine($"{scoreboard.Visitors} controls the rebound."); + if (defense == 6) + { + if (_random.NextFloat() <= 0.25f) + { + scoreboard.Turnover(); + scoreboard.AddBasket("Ball stolen. Easy lay up for Dartmouth."); + _io.WriteLine(); + // next opponent shot + } + } + if (_random.NextFloat() <= 0.5f) + { + _io.WriteLine($"Pass back to {scoreboard.Visitors} guard."); + // next opponent shot + } + // goto 3500 + } + } + }; + + Team ContestBall(float probability, Scoreboard scoreboard, string messageFormat) + { + var winner = _random.NextFloat() <= probability ? scoreboard.Home : scoreboard.Visitors; + _io.WriteLine(messageFormat, winner); + return winner; + } + + void FreeThrows(Scoreboard scoreboard, string message) + { + _io.WriteLine(message); + + if (_random.NextFloat() <= 0.49) + { + scoreboard.AddFreeThrows(2, "Shooter makes both shots."); + } + else if (_random.NextFloat() <= 0.75) + { + scoreboard.AddFreeThrows(1, "Shooter makes one shot and misses one."); + } + else + { + scoreboard.AddFreeThrows(0, "Both shots missed."); + } } } diff --git a/07_Basketball/csharp/Scoreboard.cs b/07_Basketball/csharp/Scoreboard.cs index ae6f1b56..31bdf34d 100644 --- a/07_Basketball/csharp/Scoreboard.cs +++ b/07_Basketball/csharp/Scoreboard.cs @@ -8,20 +8,19 @@ internal class Scoreboard private readonly Dictionary _scores; private readonly IReadWrite _io; - private Team _home; - private Team _visitors; - public Scoreboard(Team home, Team visitors, IReadWrite io) { _scores = new() { [home] = 0, [visitors] = 0 }; - _home = home; - _visitors = visitors; + Home = home; + Visitors = visitors; Offense = home; _io = io; } - public bool ScoresAreEqual => _scores[_home] == _scores[_visitors]; + public bool ScoresAreEqual => _scores[Home] == _scores[Visitors]; public Team Offense { get; set; } + public Team Home { get; } + public Team Visitors { get; } public void AddBasket(string message) => AddScore(2, message); @@ -39,9 +38,9 @@ internal class Scoreboard { if (message is not null) { _io.WriteLine(message); } - Offense = Offense == _home ? _visitors : _home; + Offense = Offense == Home ? Visitors : Home; } public void Display(string? format = null) => - _io.WriteLine(format ?? Resource.Formats.Score, _home, _scores[_home], _visitors, _scores[_visitors]); + _io.WriteLine(format ?? Resource.Formats.Score, Home, _scores[Home], Visitors, _scores[Visitors]); } diff --git a/07_Basketball/csharp/Team.cs b/07_Basketball/csharp/Team.cs index 91181588..d7dccd4e 100644 --- a/07_Basketball/csharp/Team.cs +++ b/07_Basketball/csharp/Team.cs @@ -1,6 +1,8 @@ namespace Basketball; -internal record Team(string Name) +internal record Team(string Name, Action PlayResolution) { public override string ToString() => Name; + + public void ResolvePlay(Scoreboard scoreboard) => PlayResolution.Invoke(scoreboard); } From 029e27cd2c972e4a15744c9673eb4611cad4b71d Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Thu, 31 Mar 2022 21:19:08 +1100 Subject: [PATCH 05/15] Fix game loop --- 07_Basketball/csharp/Game.cs | 236 ++++++++++++++--------------- 07_Basketball/csharp/Scoreboard.cs | 7 +- 07_Basketball/csharp/Team.cs | 4 +- 3 files changed, 121 insertions(+), 126 deletions(-) diff --git a/07_Basketball/csharp/Game.cs b/07_Basketball/csharp/Game.cs index 1492cbcc..12d80f2f 100644 --- a/07_Basketball/csharp/Game.cs +++ b/07_Basketball/csharp/Game.cs @@ -36,113 +36,107 @@ internal class Game _io.WriteLine(); - while (true) + do { - scoreboard.Offense.ResolvePlay(scoreboard); - } + var gameOver = scoreboard.Offense.ResolvePlay(scoreboard); + if (gameOver) { return; } + if (clock.IsHalfTime) { scoreboard.StartPeriod(); } + } while (scoreboard.Offense is not null); } } - private Action HomeTeamPlay(Clock clock, Defense defense) => scoreboard => + private Func HomeTeamPlay(Clock clock, Defense defense) => scoreboard => { var shot = _io.ReadShot("Your shot"); if (_random.NextFloat() >= 0.5f && clock.IsFullTime) { _io.WriteLine(); - if (scoreboard.ScoresAreEqual) - { - scoreboard.Display(Resource.Formats.EndOfSecondHalf); - clock.StartOvertime(); - // Loop back to center jump - } - else + if (!scoreboard.ScoresAreEqual) { scoreboard.Display(Resource.Formats.EndOfGame); - return; - } - } - else - { - if (shot == 0) - { - defense.Set(_io.ReadDefense("Your new defensive alignment is")); - // go to next shot + return true; } - if (shot == 1 || shot == 2) + scoreboard.Display(Resource.Formats.EndOfSecondHalf); + clock.StartOvertime(); + scoreboard.StartPeriod(); + return false; + } + + if (shot == 0) + { + defense.Set(_io.ReadDefense("Your new defensive alignment is")); + return false; + } + + var playContinues = false; + + if (shot == 1 || shot == 2) + { + clock.Increment(scoreboard); + if (clock.IsHalfTime) { return false; } + + _io.WriteLine("Jump shot"); + if (_random.NextFloat() <= 0.341f * defense / 8) { - clock.Increment(scoreboard); - if (clock.IsHalfTime) + scoreboard.AddBasket("Shot is good"); + } + else if (_random.NextFloat() <= 0.682f * defense / 8) + { + _io.WriteLine("Shot is off target"); + if (defense / 6 * _random.NextFloat() > 0.45f) { - // Loop back to center jump; - } - _io.WriteLine("Jump shot"); - if (_random.NextFloat() <= 0.341f * defense / 8) - { - scoreboard.AddBasket("Shot is good"); - // over to opponent - } - else if (_random.NextFloat() <= 0.682f * defense / 8) - { - _io.WriteLine("Shot is off target"); - if (defense / 6 * _random.NextFloat() > 0.45f) - { - scoreboard.Turnover($"Rebound to {scoreboard.Visitors}"); - // over to opponent - } - else - { - _io.WriteLine("Dartmouth controls the rebound."); - if (_random.NextFloat() <= 0.4f) - { - // fall through to 1300 - } - else - { - if (defense == 6) - { - if (_random.NextFloat() > 0.6f) - { - scoreboard.Turnover(); - scoreboard.AddBasket($"Pass stolen by {scoreboard.Visitors} easy layup."); - _io.WriteLine(); - } - } - _io.Write("Ball passed back to you. "); - // next shot without writeline - } - } - } - else if (_random.NextFloat() <= 0.782f * defense / 8) - { - scoreboard.Offense = ContestBall(0.5f, scoreboard, "Shot is blocked. Ball controlled by {0}."); - // go to next shot - } - else if (_random.NextFloat() <= 0.843f * defense / 8) - { - FreeThrows(scoreboard, "Shooter is fouled. Two shots."); - // over to opponent + scoreboard.Turnover($"Rebound to {scoreboard.Visitors}"); } else { - scoreboard.Turnover("Charging foul. Dartmouth loses ball."); - // over to opponent + _io.WriteLine("Dartmouth controls the rebound."); + if (_random.NextFloat() <= 0.4f) + { + playContinues = true; + } + else + { + if (defense == 6) + { + if (_random.NextFloat() > 0.6f) + { + scoreboard.Turnover(); + scoreboard.AddBasket($"Pass stolen by {scoreboard.Visitors} easy layup."); + _io.WriteLine(); + } + } + _io.Write("Ball passed back to you. "); + } } } - // 1300 - clock.Increment(scoreboard); - if (clock.IsHalfTime) + else if (_random.NextFloat() <= 0.782f * defense / 8) { - // Loop back to center jump; + scoreboard.Offense = ContestBall(0.5f, scoreboard, "Shot is blocked. Ball controlled by {0}."); } + else if (_random.NextFloat() <= 0.843f * defense / 8) + { + FreeThrows(scoreboard, "Shooter is fouled. Two shots."); + } + else + { + scoreboard.Turnover("Charging foul. Dartmouth loses ball."); + } + } + + while (playContinues) + { + clock.Increment(scoreboard); + if (clock.IsHalfTime) { return false; } + + playContinues = false; _io.WriteLine(shot == 3 ? "Lay up." : "Set shot."); if (_random.NextFloat() <= 0.4f * defense / 7) { scoreboard.AddBasket("Shot is good. Two points."); - // over to opponent } else if (_random.NextFloat() <= 0.7f * defense / 7) { @@ -150,47 +144,43 @@ internal class Game if (_random.NextFloat() <= 2 / 3f) { scoreboard.Turnover($"{scoreboard.Visitors} controls the rebound."); - // over to opponent } else { _io.WriteLine("Dartmouth controls the rebound"); if (_random.NextFloat() <= 0.4f) { - // goto 1300 + playContinues = true; } else { _io.WriteLine("Ball passed back to you."); - // go to next shot } } } else if (_random.NextFloat() <= 0.875f * defense / 7) { FreeThrows(scoreboard, "Shooter fouled. Two shots."); - // over to opponent } else if (_random.NextFloat() <= 0.925f * defense / 7) { scoreboard.Turnover($"Shot blocked. {scoreboard.Visitors}'s ball."); - // over to opponent } else { scoreboard.Turnover("Charging foul. Dartmouth loses ball."); - // over to opponent } } + + return false; }; - private Action VisitingTeamPlay(Clock clock, Defense defense) => scoreboard => + private Func VisitingTeamPlay(Clock clock, Defense defense) => scoreboard => { clock.Increment(scoreboard); - if (clock.IsHalfTime) - { - // Loop back to center jump; - } + if (clock.IsHalfTime) { return false; } + + var playContinues = false; _io.WriteLine(); var shot = _random.NextFloat(1, 3.5f); @@ -201,7 +191,6 @@ internal class Game if (_random.NextFloat() <= 0.35f * defense / 8) { scoreboard.AddBasket("Shot is good."); - // over to Dartmouth } else if (_random.NextFloat() <= 0.75f * defense / 8) { @@ -209,7 +198,6 @@ internal class Game if (_random.NextFloat() <= 0.5f / defense * 6) { scoreboard.Turnover("Dartmouth controls the rebound."); - // over to Dartmouth } else { @@ -221,66 +209,70 @@ internal class Game scoreboard.Turnover(); scoreboard.AddBasket("Ball stolen. Easy lay up for Dartmouth."); _io.WriteLine(); - // next opponent shot + return false; } } if (_random.NextFloat() <= 0.5f) { _io.WriteLine($"Pass back to {scoreboard.Visitors} guard."); - // next opponent shot + return false; } - // goto 3500 + + playContinues = true; } } else if (_random.NextFloat() <= 0.9f * defense / 8) { FreeThrows(scoreboard, "Player fouled. Two shots."); - // next Dartmouth shot } else { _io.WriteLine("Offensive foul. Dartmouth's ball."); - // next Dartmouth shot } } - // 3500 - _io.WriteLine(shot > 3 ? "Set shot." : "Lay up."); + while (playContinues) + { + playContinues = false; - if (_random.NextFloat() <= 0.413f * defense / 7) - { - scoreboard.AddBasket("Shot is good."); - // over to Dartmouth - } - else - { - _io.WriteLine("Shot is missed."); - if (_random.NextFloat() <= 0.5f * 6 / defense) + _io.WriteLine(shot > 3 ? "Set shot." : "Lay up."); + + if (_random.NextFloat() <= 0.413f * defense / 7) { - scoreboard.Turnover("Dartmouth controls the rebound."); - // over to Dartmouth + scoreboard.AddBasket("Shot is good."); } else { - _io.WriteLine($"{scoreboard.Visitors} controls the rebound."); - if (defense == 6) + _io.WriteLine("Shot is missed."); + if (_random.NextFloat() <= 0.5f * 6 / defense) { - if (_random.NextFloat() <= 0.25f) + scoreboard.Turnover("Dartmouth controls the rebound."); + } + else + { + _io.WriteLine($"{scoreboard.Visitors} controls the rebound."); + if (defense == 6) { - scoreboard.Turnover(); - scoreboard.AddBasket("Ball stolen. Easy lay up for Dartmouth."); - _io.WriteLine(); - // next opponent shot + if (_random.NextFloat() <= 0.25f) + { + scoreboard.Turnover(); + scoreboard.AddBasket("Ball stolen. Easy lay up for Dartmouth."); + _io.WriteLine(); + return false; + } } + if (_random.NextFloat() <= 0.5f) + { + _io.WriteLine($"Pass back to {scoreboard.Visitors} guard."); + return false; + } + + playContinues = true; } - if (_random.NextFloat() <= 0.5f) - { - _io.WriteLine($"Pass back to {scoreboard.Visitors} guard."); - // next opponent shot - } - // goto 3500 } } + + return false; }; Team ContestBall(float probability, Scoreboard scoreboard, string messageFormat) diff --git a/07_Basketball/csharp/Scoreboard.cs b/07_Basketball/csharp/Scoreboard.cs index 31bdf34d..356e50c1 100644 --- a/07_Basketball/csharp/Scoreboard.cs +++ b/07_Basketball/csharp/Scoreboard.cs @@ -13,12 +13,11 @@ internal class Scoreboard _scores = new() { [home] = 0, [visitors] = 0 }; Home = home; Visitors = visitors; - Offense = home; _io = io; } public bool ScoresAreEqual => _scores[Home] == _scores[Visitors]; - public Team Offense { get; set; } + public Team? Offense { get; set; } public Team Home { get; } public Team Visitors { get; } @@ -28,12 +27,16 @@ internal class Scoreboard private void AddScore(uint score, string message) { + if (Offense is null) { throw new InvalidOperationException("Offense must be set before adding to score."); } + _io.WriteLine(message); _scores[Offense] += score; Turnover(); Display(); } + public void StartPeriod() => Offense = null; + public void Turnover(string? message = null) { if (message is not null) { _io.WriteLine(message); } diff --git a/07_Basketball/csharp/Team.cs b/07_Basketball/csharp/Team.cs index d7dccd4e..f6a58aaf 100644 --- a/07_Basketball/csharp/Team.cs +++ b/07_Basketball/csharp/Team.cs @@ -1,8 +1,8 @@ namespace Basketball; -internal record Team(string Name, Action PlayResolution) +internal record Team(string Name, Func PlayResolution) { public override string ToString() => Name; - public void ResolvePlay(Scoreboard scoreboard) => PlayResolution.Invoke(scoreboard); + public bool ResolvePlay(Scoreboard scoreboard) => PlayResolution.Invoke(scoreboard); } From aaaadc04eaf6633070d435237f4b4defe4e5326d Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Tue, 5 Apr 2022 05:06:17 +1000 Subject: [PATCH 06/15] Add probabilistic branching helper --- 07_Basketball/csharp/Game.cs | 272 ++++++++++--------------------- 07_Basketball/csharp/Probably.cs | 32 ++++ 2 files changed, 122 insertions(+), 182 deletions(-) create mode 100644 07_Basketball/csharp/Probably.cs diff --git a/07_Basketball/csharp/Game.cs b/07_Basketball/csharp/Game.cs index 12d80f2f..d51b49c1 100644 --- a/07_Basketball/csharp/Game.cs +++ b/07_Basketball/csharp/Game.cs @@ -32,16 +32,16 @@ internal class Game while (true) { _io.WriteLine("Center jump"); - scoreboard.Offense = ContestBall(0.4f, scoreboard, "{0} controls the tap"); + ContestBall(0.4f, scoreboard, "{0} controls the tap"); _io.WriteLine(); - do + while (scoreboard.Offense is not null) { var gameOver = scoreboard.Offense.ResolvePlay(scoreboard); if (gameOver) { return; } if (clock.IsHalfTime) { scoreboard.StartPeriod(); } - } while (scoreboard.Offense is not null); + } } } @@ -67,62 +67,23 @@ internal class Game if (shot == 0) { defense.Set(_io.ReadDefense("Your new defensive alignment is")); + _io.WriteLine(); return false; } - var playContinues = false; + var playContinues = shot >= 3; if (shot == 1 || shot == 2) { clock.Increment(scoreboard); if (clock.IsHalfTime) { return false; } - _io.WriteLine("Jump shot"); - if (_random.NextFloat() <= 0.341f * defense / 8) - { - scoreboard.AddBasket("Shot is good"); - } - else if (_random.NextFloat() <= 0.682f * defense / 8) - { - _io.WriteLine("Shot is off target"); - if (defense / 6 * _random.NextFloat() > 0.45f) - { - scoreboard.Turnover($"Rebound to {scoreboard.Visitors}"); - } - else - { - _io.WriteLine("Dartmouth controls the rebound."); - if (_random.NextFloat() <= 0.4f) - { - playContinues = true; - } - else - { - if (defense == 6) - { - if (_random.NextFloat() > 0.6f) - { - scoreboard.Turnover(); - scoreboard.AddBasket($"Pass stolen by {scoreboard.Visitors} easy layup."); - _io.WriteLine(); - } - } - _io.Write("Ball passed back to you. "); - } - } - } - else if (_random.NextFloat() <= 0.782f * defense / 8) - { - scoreboard.Offense = ContestBall(0.5f, scoreboard, "Shot is blocked. Ball controlled by {0}."); - } - else if (_random.NextFloat() <= 0.843f * defense / 8) - { - FreeThrows(scoreboard, "Shooter is fouled. Two shots."); - } - else - { - scoreboard.Turnover("Charging foul. Dartmouth loses ball."); - } + Resolve("Jump shot", defense / 8) + .Do(0.341f, () => scoreboard.AddBasket("Shot is good")) + .Or(0.682f, () => ResolveShotOffTarget()) + .Or(0.782f, () => ContestBall(0.5f, scoreboard, "Shot is blocked. Ball controlled by {0}.")) + .Or(0.843f, () => ResolveFreeThrows(scoreboard, "Shooter is fouled. Two shots.")) + .Or(() => scoreboard.Turnover("Charging foul. Dartmouth loses ball.")); } while (playContinues) @@ -132,47 +93,38 @@ internal class Game playContinues = false; - _io.WriteLine(shot == 3 ? "Lay up." : "Set shot."); - - if (_random.NextFloat() <= 0.4f * defense / 7) - { - scoreboard.AddBasket("Shot is good. Two points."); - } - else if (_random.NextFloat() <= 0.7f * defense / 7) - { - _io.WriteLine("Shot is off the rim."); - if (_random.NextFloat() <= 2 / 3f) - { - scoreboard.Turnover($"{scoreboard.Visitors} controls the rebound."); - } - else - { - _io.WriteLine("Dartmouth controls the rebound"); - if (_random.NextFloat() <= 0.4f) - { - playContinues = true; - } - else - { - _io.WriteLine("Ball passed back to you."); - } - } - } - else if (_random.NextFloat() <= 0.875f * defense / 7) - { - FreeThrows(scoreboard, "Shooter fouled. Two shots."); - } - else if (_random.NextFloat() <= 0.925f * defense / 7) - { - scoreboard.Turnover($"Shot blocked. {scoreboard.Visitors}'s ball."); - } - else - { - scoreboard.Turnover("Charging foul. Dartmouth loses ball."); - } + Resolve(shot == 3 ? "Lay up." : "Set shot.", defense / 7) + .Do(0.4f, () => scoreboard.AddBasket("Shot is good. Two points.")) + .Or(0.7f, () => ResolveShotOffTheRim()) + .Or(0.875f, () => ResolveFreeThrows(scoreboard, "Shooter fouled. Two shots.")) + .Or(0.925f, () => scoreboard.Turnover($"Shot blocked. {scoreboard.Visitors}'s ball.")) + .Or(() => scoreboard.Turnover("Charging foul. Dartmouth loses ball.")); } return false; + + void ResolveShotOffTarget() => + Resolve("Shot is off target", 6 / defense) + .Do(0.45f, () => Resolve("Dartmouth controls the rebound.") + .Do(0.4f, () => playContinues = true) + .Or(() => + { + if (defense == 6 && _random.NextFloat() > 0.6f) + { + scoreboard.Turnover(); + scoreboard.AddBasket($"Pass stolen by {scoreboard.Visitors} easy layup."); + _io.WriteLine(); + } + _io.Write("Ball passed back to you. "); + })) + .Or(() => scoreboard.Turnover($"Rebound to {scoreboard.Visitors}")); + + void ResolveShotOffTheRim() => + Resolve("Shot is off the rim.") + .Do(2 / 3f, () => scoreboard.Turnover($"{scoreboard.Visitors} controls the rebound.")) + .Or(() => Resolve("Dartmouth controls the rebound") + .Do(0.4f, () => playContinues = true) + .Or(() => _io.WriteLine("Ball passed back to you."))); }; private Func VisitingTeamPlay(Clock clock, Defense defense) => scoreboard => @@ -180,123 +132,79 @@ internal class Game clock.Increment(scoreboard); if (clock.IsHalfTime) { return false; } - var playContinues = false; _io.WriteLine(); var shot = _random.NextFloat(1, 3.5f); + var playContinues = shot > 2; + if (shot <= 2) { - _io.WriteLine("Jump shot."); + Resolve("Jump shot.", defense / 8) + .Do(0.35f, () => scoreboard.AddBasket("Shot is good.")) + .Or(0.75f, () => ResolveBadShot("Shot is off the rim.", defense * 6)) + .Or(0.9f, () => ResolveFreeThrows(scoreboard, "Player fouled. Two shots.")) + .Or(() => _io.WriteLine("Offensive foul. Dartmouth's ball.")); - if (_random.NextFloat() <= 0.35f * defense / 8) - { - scoreboard.AddBasket("Shot is good."); - } - else if (_random.NextFloat() <= 0.75f * defense / 8) - { - _io.WriteLine("Shot is off the rim."); - if (_random.NextFloat() <= 0.5f / defense * 6) - { - scoreboard.Turnover("Dartmouth controls the rebound."); - } - else - { - _io.WriteLine($"{scoreboard.Visitors} controls the rebound."); - if (defense == 6) - { - if (_random.NextFloat() <= 0.25f) - { - scoreboard.Turnover(); - scoreboard.AddBasket("Ball stolen. Easy lay up for Dartmouth."); - _io.WriteLine(); - return false; - } - } - if (_random.NextFloat() <= 0.5f) - { - _io.WriteLine($"Pass back to {scoreboard.Visitors} guard."); - return false; - } - - playContinues = true; - } - } - else if (_random.NextFloat() <= 0.9f * defense / 8) - { - FreeThrows(scoreboard, "Player fouled. Two shots."); - } - else - { - _io.WriteLine("Offensive foul. Dartmouth's ball."); - } + _io.WriteLine(); } while (playContinues) { playContinues = false; - _io.WriteLine(shot > 3 ? "Set shot." : "Lay up."); - - if (_random.NextFloat() <= 0.413f * defense / 7) - { - scoreboard.AddBasket("Shot is good."); - } - else - { - _io.WriteLine("Shot is missed."); - if (_random.NextFloat() <= 0.5f * 6 / defense) - { - scoreboard.Turnover("Dartmouth controls the rebound."); - } - else - { - _io.WriteLine($"{scoreboard.Visitors} controls the rebound."); - if (defense == 6) - { - if (_random.NextFloat() <= 0.25f) - { - scoreboard.Turnover(); - scoreboard.AddBasket("Ball stolen. Easy lay up for Dartmouth."); - _io.WriteLine(); - return false; - } - } - if (_random.NextFloat() <= 0.5f) - { - _io.WriteLine($"Pass back to {scoreboard.Visitors} guard."); - return false; - } - - playContinues = true; - } - } + Resolve(shot > 3 ? "Set shot." : "Lay up.", defense / 7) + .Do(0.413f, () => scoreboard.AddBasket("Shot is good.")) + .Or(() => ResolveBadShot("Shot is missed.", 6 / defense)); + _io.WriteLine(); } return false; + + void ResolveBadShot(string message, float defenseFactor) => + Resolve("Shot is off the rim.", defense * 6) + .Do(0.5f, () => scoreboard.Turnover("Dartmouth controls the rebound.")) + .Or(() => ResolveVisitorsRebound()); + + void ResolveVisitorsRebound() + { + _io.Write($"{scoreboard.Visitors} controls the rebound."); + if (defense == 6 && _random.NextFloat() <= 0.25f) + { + _io.WriteLine(); + scoreboard.Turnover(); + scoreboard.AddBasket("Ball stolen. Easy lay up for Dartmouth."); + return; + } + + if (_random.NextFloat() <= 0.5f) + { + _io.WriteLine(); + _io.Write($"Pass back to {scoreboard.Visitors} guard."); + return; + } + + playContinues = true; + } }; - Team ContestBall(float probability, Scoreboard scoreboard, string messageFormat) + void ContestBall(float probability, Scoreboard scoreboard, string messageFormat) { var winner = _random.NextFloat() <= probability ? scoreboard.Home : scoreboard.Visitors; + scoreboard.Offense = winner; _io.WriteLine(messageFormat, winner); - return winner; } - void FreeThrows(Scoreboard scoreboard, string message) + void ResolveFreeThrows(Scoreboard scoreboard, string message) => + Resolve(message) + .Do(0.49f, () => scoreboard.AddFreeThrows(2, "Shooter makes both shots.")) + .Or(0.75f, () => scoreboard.AddFreeThrows(1, "Shooter makes one shot and misses one.")) + .Or(() => scoreboard.AddFreeThrows(0, "Both shots missed.")); + + private Probably Resolve(string message) => Resolve(message, 1f); + + private Probably Resolve(string message, float defenseFactor) { _io.WriteLine(message); - - if (_random.NextFloat() <= 0.49) - { - scoreboard.AddFreeThrows(2, "Shooter makes both shots."); - } - else if (_random.NextFloat() <= 0.75) - { - scoreboard.AddFreeThrows(1, "Shooter makes one shot and misses one."); - } - else - { - scoreboard.AddFreeThrows(0, "Both shots missed."); - } + return new Probably(defenseFactor, _random); } } diff --git a/07_Basketball/csharp/Probably.cs b/07_Basketball/csharp/Probably.cs new file mode 100644 index 00000000..a7ca403c --- /dev/null +++ b/07_Basketball/csharp/Probably.cs @@ -0,0 +1,32 @@ +using Games.Common.Randomness; + +namespace Basketball; + +internal struct Probably +{ + private readonly float _defenseFactor; + private readonly IRandom _random; + private readonly bool _done; + + internal Probably(float defenseFactor, IRandom random, bool done = false) + { + _defenseFactor = defenseFactor; + _random = random; + _done = done; + } + + public Probably Do(float probability, Action action) + { + if (!_done && _random.NextFloat() <= probability * _defenseFactor) + { + action.Invoke(); + return new Probably(_defenseFactor, _random, true); + } + + return this; + } + + public Probably Or(float probability, Action action) => Do(probability, action); + + public Probably Or(Action action) => Do(1f, action); +} From b8da97a7a3df171a15e7e05b26b5d38e77419513 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Tue, 5 Apr 2022 17:51:13 +1000 Subject: [PATCH 07/15] Break out classes for resolving plays --- 07_Basketball/csharp/Game.cs | 172 +----------------- 07_Basketball/csharp/Plays/BallContest.cs | 29 +++ 07_Basketball/csharp/Plays/HomeTeamPlay.cs | 106 +++++++++++ 07_Basketball/csharp/Plays/Play.cs | 32 ++++ .../csharp/Plays/VisitingTeamPlay.cs | 80 ++++++++ 07_Basketball/csharp/Team.cs | 6 +- 6 files changed, 257 insertions(+), 168 deletions(-) create mode 100644 07_Basketball/csharp/Plays/BallContest.cs create mode 100644 07_Basketball/csharp/Plays/HomeTeamPlay.cs create mode 100644 07_Basketball/csharp/Plays/Play.cs create mode 100644 07_Basketball/csharp/Plays/VisitingTeamPlay.cs diff --git a/07_Basketball/csharp/Game.cs b/07_Basketball/csharp/Game.cs index d51b49c1..391e70d8 100644 --- a/07_Basketball/csharp/Game.cs +++ b/07_Basketball/csharp/Game.cs @@ -1,3 +1,4 @@ +using Basketball.Plays; using Basketball.Resources; using Games.Common.IO; using Games.Common.Randomness; @@ -25,14 +26,16 @@ internal class Game _io.WriteLine(); var scoreboard = new Scoreboard( - new Team("Dartmouth", HomeTeamPlay(clock, defense)), - new Team(_io.ReadString("Choose your opponent"), VisitingTeamPlay(clock, defense)), + new Team("Dartmouth", new HomeTeamPlay(_io, _random, clock, defense)), + new Team(_io.ReadString("Choose your opponent"), new VisitingTeamPlay(_io, _random, clock, defense)), _io); + var ballContest = new BallContest(0.4f, "{0} controls the tap", _io, _random); + while (true) { _io.WriteLine("Center jump"); - ContestBall(0.4f, scoreboard, "{0} controls the tap"); + ballContest.Resolve(scoreboard); _io.WriteLine(); @@ -44,167 +47,4 @@ internal class Game } } } - - private Func HomeTeamPlay(Clock clock, Defense defense) => scoreboard => - { - var shot = _io.ReadShot("Your shot"); - - if (_random.NextFloat() >= 0.5f && clock.IsFullTime) - { - _io.WriteLine(); - if (!scoreboard.ScoresAreEqual) - { - scoreboard.Display(Resource.Formats.EndOfGame); - return true; - } - - scoreboard.Display(Resource.Formats.EndOfSecondHalf); - clock.StartOvertime(); - scoreboard.StartPeriod(); - return false; - } - - if (shot == 0) - { - defense.Set(_io.ReadDefense("Your new defensive alignment is")); - _io.WriteLine(); - return false; - } - - var playContinues = shot >= 3; - - if (shot == 1 || shot == 2) - { - clock.Increment(scoreboard); - if (clock.IsHalfTime) { return false; } - - Resolve("Jump shot", defense / 8) - .Do(0.341f, () => scoreboard.AddBasket("Shot is good")) - .Or(0.682f, () => ResolveShotOffTarget()) - .Or(0.782f, () => ContestBall(0.5f, scoreboard, "Shot is blocked. Ball controlled by {0}.")) - .Or(0.843f, () => ResolveFreeThrows(scoreboard, "Shooter is fouled. Two shots.")) - .Or(() => scoreboard.Turnover("Charging foul. Dartmouth loses ball.")); - } - - while (playContinues) - { - clock.Increment(scoreboard); - if (clock.IsHalfTime) { return false; } - - playContinues = false; - - Resolve(shot == 3 ? "Lay up." : "Set shot.", defense / 7) - .Do(0.4f, () => scoreboard.AddBasket("Shot is good. Two points.")) - .Or(0.7f, () => ResolveShotOffTheRim()) - .Or(0.875f, () => ResolveFreeThrows(scoreboard, "Shooter fouled. Two shots.")) - .Or(0.925f, () => scoreboard.Turnover($"Shot blocked. {scoreboard.Visitors}'s ball.")) - .Or(() => scoreboard.Turnover("Charging foul. Dartmouth loses ball.")); - } - - return false; - - void ResolveShotOffTarget() => - Resolve("Shot is off target", 6 / defense) - .Do(0.45f, () => Resolve("Dartmouth controls the rebound.") - .Do(0.4f, () => playContinues = true) - .Or(() => - { - if (defense == 6 && _random.NextFloat() > 0.6f) - { - scoreboard.Turnover(); - scoreboard.AddBasket($"Pass stolen by {scoreboard.Visitors} easy layup."); - _io.WriteLine(); - } - _io.Write("Ball passed back to you. "); - })) - .Or(() => scoreboard.Turnover($"Rebound to {scoreboard.Visitors}")); - - void ResolveShotOffTheRim() => - Resolve("Shot is off the rim.") - .Do(2 / 3f, () => scoreboard.Turnover($"{scoreboard.Visitors} controls the rebound.")) - .Or(() => Resolve("Dartmouth controls the rebound") - .Do(0.4f, () => playContinues = true) - .Or(() => _io.WriteLine("Ball passed back to you."))); - }; - - private Func VisitingTeamPlay(Clock clock, Defense defense) => scoreboard => - { - clock.Increment(scoreboard); - if (clock.IsHalfTime) { return false; } - - - _io.WriteLine(); - var shot = _random.NextFloat(1, 3.5f); - var playContinues = shot > 2; - - if (shot <= 2) - { - Resolve("Jump shot.", defense / 8) - .Do(0.35f, () => scoreboard.AddBasket("Shot is good.")) - .Or(0.75f, () => ResolveBadShot("Shot is off the rim.", defense * 6)) - .Or(0.9f, () => ResolveFreeThrows(scoreboard, "Player fouled. Two shots.")) - .Or(() => _io.WriteLine("Offensive foul. Dartmouth's ball.")); - - _io.WriteLine(); - } - - while (playContinues) - { - playContinues = false; - - Resolve(shot > 3 ? "Set shot." : "Lay up.", defense / 7) - .Do(0.413f, () => scoreboard.AddBasket("Shot is good.")) - .Or(() => ResolveBadShot("Shot is missed.", 6 / defense)); - _io.WriteLine(); - } - - return false; - - void ResolveBadShot(string message, float defenseFactor) => - Resolve("Shot is off the rim.", defense * 6) - .Do(0.5f, () => scoreboard.Turnover("Dartmouth controls the rebound.")) - .Or(() => ResolveVisitorsRebound()); - - void ResolveVisitorsRebound() - { - _io.Write($"{scoreboard.Visitors} controls the rebound."); - if (defense == 6 && _random.NextFloat() <= 0.25f) - { - _io.WriteLine(); - scoreboard.Turnover(); - scoreboard.AddBasket("Ball stolen. Easy lay up for Dartmouth."); - return; - } - - if (_random.NextFloat() <= 0.5f) - { - _io.WriteLine(); - _io.Write($"Pass back to {scoreboard.Visitors} guard."); - return; - } - - playContinues = true; - } - }; - - void ContestBall(float probability, Scoreboard scoreboard, string messageFormat) - { - var winner = _random.NextFloat() <= probability ? scoreboard.Home : scoreboard.Visitors; - scoreboard.Offense = winner; - _io.WriteLine(messageFormat, winner); - } - - void ResolveFreeThrows(Scoreboard scoreboard, string message) => - Resolve(message) - .Do(0.49f, () => scoreboard.AddFreeThrows(2, "Shooter makes both shots.")) - .Or(0.75f, () => scoreboard.AddFreeThrows(1, "Shooter makes one shot and misses one.")) - .Or(() => scoreboard.AddFreeThrows(0, "Both shots missed.")); - - private Probably Resolve(string message) => Resolve(message, 1f); - - private Probably Resolve(string message, float defenseFactor) - { - _io.WriteLine(message); - return new Probably(defenseFactor, _random); - } } diff --git a/07_Basketball/csharp/Plays/BallContest.cs b/07_Basketball/csharp/Plays/BallContest.cs new file mode 100644 index 00000000..a26e7c06 --- /dev/null +++ b/07_Basketball/csharp/Plays/BallContest.cs @@ -0,0 +1,29 @@ +using Games.Common.IO; +using Games.Common.Randomness; + +namespace Basketball.Plays; + +internal class BallContest : Play +{ + private readonly float _probability; + private readonly string _messageFormat; + private readonly IReadWrite _io; + private readonly IRandom _random; + + internal BallContest(float probability, string messageFormat, IReadWrite io, IRandom random) + : base(io, random) + { + _io = io; + _probability = probability; + _messageFormat = messageFormat; + _random = random; + } + + internal override bool Resolve(Scoreboard scoreboard) + { + var winner = _random.NextFloat() <= _probability ? scoreboard.Home : scoreboard.Visitors; + scoreboard.Offense = winner; + _io.WriteLine(_messageFormat, winner); + return false; + } +} diff --git a/07_Basketball/csharp/Plays/HomeTeamPlay.cs b/07_Basketball/csharp/Plays/HomeTeamPlay.cs new file mode 100644 index 00000000..f79d6388 --- /dev/null +++ b/07_Basketball/csharp/Plays/HomeTeamPlay.cs @@ -0,0 +1,106 @@ +using Basketball.Resources; +using Games.Common.IO; +using Games.Common.Randomness; + +namespace Basketball.Plays; + +internal class HomeTeamPlay : Play +{ + private readonly TextIO _io; + private readonly IRandom _random; + private readonly Clock _clock; + private readonly Defense _defense; + + public HomeTeamPlay(TextIO io, IRandom random, Clock clock, Defense defense) + : base(io, random) + { + _io = io; + _random = random; + _clock = clock; + _defense = defense; + } + + internal override bool Resolve(Scoreboard scoreboard) + { + var shot = _io.ReadShot("Your shot"); + + if (_random.NextFloat() >= 0.5f && _clock.IsFullTime) + { + _io.WriteLine(); + if (!scoreboard.ScoresAreEqual) + { + scoreboard.Display(Resource.Formats.EndOfGame); + return true; + } + + scoreboard.Display(Resource.Formats.EndOfSecondHalf); + _clock.StartOvertime(); + scoreboard.StartPeriod(); + return false; + } + + if (shot == 0) + { + _defense.Set(_io.ReadDefense("Your new defensive alignment is")); + _io.WriteLine(); + return false; + } + + var playContinues = shot >= 3; + + if (shot == 1 || shot == 2) + { + _clock.Increment(scoreboard); + if (_clock.IsHalfTime) { return false; } + + var ballContest = new BallContest(0.5f, "Shot is blocked. Ball controlled by {0}.", _io, _random); + + Resolve("Jump shot", _defense / 8) + .Do(0.341f, () => scoreboard.AddBasket("Shot is good")) + .Or(0.682f, () => ResolveShotOffTarget()) + .Or(0.782f, () => ballContest.Resolve(scoreboard)) + .Or(0.843f, () => ResolveFreeThrows(scoreboard, "Shooter is fouled. Two shots.")) + .Or(() => scoreboard.Turnover("Charging foul. Dartmouth loses ball.")); + } + + while (playContinues) + { + _clock.Increment(scoreboard); + if (_clock.IsHalfTime) { return false; } + + playContinues = false; + + Resolve(shot == 3 ? "Lay up." : "Set shot.", _defense / 7) + .Do(0.4f, () => scoreboard.AddBasket("Shot is good. Two points.")) + .Or(0.7f, () => ResolveShotOffTheRim()) + .Or(0.875f, () => ResolveFreeThrows(scoreboard, "Shooter fouled. Two shots.")) + .Or(0.925f, () => scoreboard.Turnover($"Shot blocked. {scoreboard.Visitors}'s ball.")) + .Or(() => scoreboard.Turnover("Charging foul. Dartmouth loses ball.")); + } + + return false; + + void ResolveShotOffTarget() => + Resolve("Shot is off target", 6 / _defense) + .Do(0.45f, () => Resolve("Dartmouth controls the rebound.") + .Do(0.4f, () => playContinues = true) + .Or(() => + { + if (_defense == 6 && _random.NextFloat() > 0.6f) + { + scoreboard.Turnover(); + scoreboard.AddBasket($"Pass stolen by {scoreboard.Visitors} easy layup."); + _io.WriteLine(); + } + _io.Write("Ball passed back to you. "); + })) + .Or(() => scoreboard.Turnover($"Rebound to {scoreboard.Visitors}")); + + void ResolveShotOffTheRim() => + Resolve("Shot is off the rim.") + .Do(2 / 3f, () => scoreboard.Turnover($"{scoreboard.Visitors} controls the rebound.")) + .Or(() => Resolve("Dartmouth controls the rebound") + .Do(0.4f, () => playContinues = true) + .Or(() => _io.WriteLine("Ball passed back to you."))); + } +} diff --git a/07_Basketball/csharp/Plays/Play.cs b/07_Basketball/csharp/Plays/Play.cs new file mode 100644 index 00000000..0edba943 --- /dev/null +++ b/07_Basketball/csharp/Plays/Play.cs @@ -0,0 +1,32 @@ +using Games.Common.IO; +using Games.Common.Randomness; + +namespace Basketball.Plays; + +internal abstract class Play +{ + private readonly IReadWrite _io; + private readonly IRandom _random; + + public Play(IReadWrite io, IRandom random) + { + _io = io; + _random = random; + } + + internal abstract bool Resolve(Scoreboard scoreboard); + + protected void ResolveFreeThrows(Scoreboard scoreboard, string message) => + Resolve(message) + .Do(0.49f, () => scoreboard.AddFreeThrows(2, "Shooter makes both shots.")) + .Or(0.75f, () => scoreboard.AddFreeThrows(1, "Shooter makes one shot and misses one.")) + .Or(() => scoreboard.AddFreeThrows(0, "Both shots missed.")); + + protected Probably Resolve(string message) => Resolve(message, 1f); + + protected Probably Resolve(string message, float defenseFactor) + { + _io.WriteLine(message); + return new Probably(defenseFactor, _random); + } +} diff --git a/07_Basketball/csharp/Plays/VisitingTeamPlay.cs b/07_Basketball/csharp/Plays/VisitingTeamPlay.cs new file mode 100644 index 00000000..f9e8e460 --- /dev/null +++ b/07_Basketball/csharp/Plays/VisitingTeamPlay.cs @@ -0,0 +1,80 @@ +using Games.Common.IO; +using Games.Common.Randomness; + +namespace Basketball.Plays; + +internal class VisitingTeamPlay : Play +{ + private readonly TextIO _io; + private readonly IRandom _random; + private readonly Clock _clock; + private readonly Defense _defense; + + public VisitingTeamPlay(TextIO io, IRandom random, Clock clock, Defense defense) + : base(io, random) + { + _io = io; + _random = random; + _clock = clock; + _defense = defense; + } + + internal override bool Resolve(Scoreboard scoreboard) + { + _clock.Increment(scoreboard); + if (_clock.IsHalfTime) { return false; } + + _io.WriteLine(); + var shot = _random.NextFloat(1, 3.5f); + var playContinues = shot > 2; + + if (shot <= 2) + { + Resolve("Jump shot.", _defense / 8) + .Do(0.35f, () => scoreboard.AddBasket("Shot is good.")) + .Or(0.75f, () => ResolveBadShot("Shot is off the rim.", _defense * 6)) + .Or(0.9f, () => ResolveFreeThrows(scoreboard, "Player fouled. Two shots.")) + .Or(() => _io.WriteLine("Offensive foul. Dartmouth's ball.")); + + _io.WriteLine(); + } + + while (playContinues) + { + playContinues = false; + + Resolve(shot > 3 ? "Set shot." : "Lay up.", _defense / 7) + .Do(0.413f, () => scoreboard.AddBasket("Shot is good.")) + .Or(() => ResolveBadShot("Shot is missed.", 6 / _defense)); + _io.WriteLine(); + } + + return false; + + void ResolveBadShot(string message, float defenseFactor) => + Resolve("Shot is off the rim.", _defense * 6) + .Do(0.5f, () => scoreboard.Turnover("Dartmouth controls the rebound.")) + .Or(() => ResolveVisitorsRebound()); + + void ResolveVisitorsRebound() + { + _io.Write($"{scoreboard.Visitors} controls the rebound."); + if (_defense == 6 && _random.NextFloat() <= 0.25f) + { + _io.WriteLine(); + scoreboard.Turnover(); + scoreboard.AddBasket("Ball stolen. Easy lay up for Dartmouth."); + return; + } + + if (_random.NextFloat() <= 0.5f) + { + _io.WriteLine(); + _io.Write($"Pass back to {scoreboard.Visitors} guard."); + return; + } + + playContinues = true; + } + } +} diff --git a/07_Basketball/csharp/Team.cs b/07_Basketball/csharp/Team.cs index f6a58aaf..2cf262aa 100644 --- a/07_Basketball/csharp/Team.cs +++ b/07_Basketball/csharp/Team.cs @@ -1,8 +1,10 @@ +using Basketball.Plays; + namespace Basketball; -internal record Team(string Name, Func PlayResolution) +internal record Team(string Name, Play PlayResolver) { public override string ToString() => Name; - public bool ResolvePlay(Scoreboard scoreboard) => PlayResolution.Invoke(scoreboard); + public bool ResolvePlay(Scoreboard scoreboard) => PlayResolver.Resolve(scoreboard); } From 99b52e6861ccde74e1bec1d0c516e911d35a29c4 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Wed, 6 Apr 2022 20:55:46 +1000 Subject: [PATCH 08/15] Move full time messages to game loop --- 07_Basketball/csharp/Clock.cs | 11 +--- 07_Basketball/csharp/Game.cs | 22 ++++++-- 07_Basketball/csharp/Plays/HomeTeamPlay.cs | 15 +---- .../csharp/Plays/VisitingTeamPlay.cs | 55 ++++++++++--------- 07_Basketball/csharp/Scoreboard.cs | 5 +- 5 files changed, 51 insertions(+), 57 deletions(-) diff --git a/07_Basketball/csharp/Clock.cs b/07_Basketball/csharp/Clock.cs index deb7c549..25033052 100644 --- a/07_Basketball/csharp/Clock.cs +++ b/07_Basketball/csharp/Clock.cs @@ -17,15 +17,8 @@ internal class Clock public void Increment(Scoreboard scoreboard) { time += 1; - if (IsHalfTime) - { - scoreboard.Display(Resource.Formats.EndOfFirstHalf); - // Loop back to center jump; - } - if (TwoMinutesLeft) - { - _io.Write(Resource.Streams.TwoMinutesLeft); - } + if (IsHalfTime) { scoreboard.Display(Resource.Formats.EndOfFirstHalf); } + if (TwoMinutesLeft) { _io.Write(Resource.Streams.TwoMinutesLeft); } } public void StartOvertime() => time = 93; diff --git a/07_Basketball/csharp/Game.cs b/07_Basketball/csharp/Game.cs index 391e70d8..4444fa77 100644 --- a/07_Basketball/csharp/Game.cs +++ b/07_Basketball/csharp/Game.cs @@ -39,12 +39,26 @@ internal class Game _io.WriteLine(); - while (scoreboard.Offense is not null) + while (true) { - var gameOver = scoreboard.Offense.ResolvePlay(scoreboard); - if (gameOver) { return; } - if (clock.IsHalfTime) { scoreboard.StartPeriod(); } + var isFullTime = scoreboard.Offense.ResolvePlay(scoreboard); + if (isFullTime && IsGameOver(scoreboard, clock)) { return; } + if (clock.IsHalfTime) { break; } } } } + + private bool IsGameOver(Scoreboard scoreboard, Clock clock) + { + _io.WriteLine(); + if (scoreboard.ScoresAreEqual) + { + scoreboard.Display(Resource.Formats.EndOfSecondHalf); + clock.StartOvertime(); + return false; + } + + scoreboard.Display(Resource.Formats.EndOfGame); + return true; + } } diff --git a/07_Basketball/csharp/Plays/HomeTeamPlay.cs b/07_Basketball/csharp/Plays/HomeTeamPlay.cs index f79d6388..fe30ff8f 100644 --- a/07_Basketball/csharp/Plays/HomeTeamPlay.cs +++ b/07_Basketball/csharp/Plays/HomeTeamPlay.cs @@ -24,20 +24,7 @@ internal class HomeTeamPlay : Play { var shot = _io.ReadShot("Your shot"); - if (_random.NextFloat() >= 0.5f && _clock.IsFullTime) - { - _io.WriteLine(); - if (!scoreboard.ScoresAreEqual) - { - scoreboard.Display(Resource.Formats.EndOfGame); - return true; - } - - scoreboard.Display(Resource.Formats.EndOfSecondHalf); - _clock.StartOvertime(); - scoreboard.StartPeriod(); - return false; - } + if (_random.NextFloat() >= 0.5f && _clock.IsFullTime) { return true; } if (shot == 0) { diff --git a/07_Basketball/csharp/Plays/VisitingTeamPlay.cs b/07_Basketball/csharp/Plays/VisitingTeamPlay.cs index f9e8e460..ab526562 100644 --- a/07_Basketball/csharp/Plays/VisitingTeamPlay.cs +++ b/07_Basketball/csharp/Plays/VisitingTeamPlay.cs @@ -9,6 +9,7 @@ internal class VisitingTeamPlay : Play private readonly IRandom _random; private readonly Clock _clock; private readonly Defense _defense; + private bool _playContinues; public VisitingTeamPlay(TextIO io, IRandom random, Clock clock, Defense defense) : base(io, random) @@ -26,55 +27,55 @@ internal class VisitingTeamPlay : Play _io.WriteLine(); var shot = _random.NextFloat(1, 3.5f); - var playContinues = shot > 2; + _playContinues = shot > 2; if (shot <= 2) { Resolve("Jump shot.", _defense / 8) .Do(0.35f, () => scoreboard.AddBasket("Shot is good.")) - .Or(0.75f, () => ResolveBadShot("Shot is off the rim.", _defense * 6)) + .Or(0.75f, () => ResolveBadShot(scoreboard, "Shot is off the rim.", _defense * 6)) .Or(0.9f, () => ResolveFreeThrows(scoreboard, "Player fouled. Two shots.")) .Or(() => _io.WriteLine("Offensive foul. Dartmouth's ball.")); _io.WriteLine(); } - while (playContinues) + while (_playContinues) { - playContinues = false; + _playContinues = false; Resolve(shot > 3 ? "Set shot." : "Lay up.", _defense / 7) .Do(0.413f, () => scoreboard.AddBasket("Shot is good.")) - .Or(() => ResolveBadShot("Shot is missed.", 6 / _defense)); + .Or(() => ResolveBadShot(scoreboard, "Shot is missed.", 6 / _defense)); _io.WriteLine(); } return false; + } - void ResolveBadShot(string message, float defenseFactor) => - Resolve("Shot is off the rim.", _defense * 6) - .Do(0.5f, () => scoreboard.Turnover("Dartmouth controls the rebound.")) - .Or(() => ResolveVisitorsRebound()); + void ResolveBadShot(Scoreboard scoreboard, string message, float defenseFactor) => + Resolve(message, defenseFactor) + .Do(0.5f, () => scoreboard.Turnover("Dartmouth controls the rebound.")) + .Or(() => ResolveVisitorsRebound(scoreboard)); - void ResolveVisitorsRebound() + void ResolveVisitorsRebound(Scoreboard scoreboard) + { + _io.Write($"{scoreboard.Visitors} controls the rebound."); + if (_defense == 6 && _random.NextFloat() <= 0.25f) { - _io.Write($"{scoreboard.Visitors} controls the rebound."); - if (_defense == 6 && _random.NextFloat() <= 0.25f) - { - _io.WriteLine(); - scoreboard.Turnover(); - scoreboard.AddBasket("Ball stolen. Easy lay up for Dartmouth."); - return; - } - - if (_random.NextFloat() <= 0.5f) - { - _io.WriteLine(); - _io.Write($"Pass back to {scoreboard.Visitors} guard."); - return; - } - - playContinues = true; + _io.WriteLine(); + scoreboard.Turnover(); + scoreboard.AddBasket("Ball stolen. Easy lay up for Dartmouth."); + return; } + + if (_random.NextFloat() <= 0.5f) + { + _io.WriteLine(); + _io.Write($"Pass back to {scoreboard.Visitors} guard."); + return; + } + + _playContinues = true; } } diff --git a/07_Basketball/csharp/Scoreboard.cs b/07_Basketball/csharp/Scoreboard.cs index 356e50c1..79068392 100644 --- a/07_Basketball/csharp/Scoreboard.cs +++ b/07_Basketball/csharp/Scoreboard.cs @@ -13,11 +13,12 @@ internal class Scoreboard _scores = new() { [home] = 0, [visitors] = 0 }; Home = home; Visitors = visitors; + Offense = home; // temporary value till first center jump _io = io; } public bool ScoresAreEqual => _scores[Home] == _scores[Visitors]; - public Team? Offense { get; set; } + public Team Offense { get; set; } public Team Home { get; } public Team Visitors { get; } @@ -35,8 +36,6 @@ internal class Scoreboard Display(); } - public void StartPeriod() => Offense = null; - public void Turnover(string? message = null) { if (message is not null) { _io.WriteLine(message); } From 1ce5f7b3a7b05fe9f8ce89aecac5716687caf9a4 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Wed, 6 Apr 2022 23:07:35 +1000 Subject: [PATCH 09/15] Resolve play resolution helper methods --- 07_Basketball/csharp/Plays/HomeTeamPlay.cs | 108 ++++++++++-------- 07_Basketball/csharp/Plays/Play.cs | 10 +- .../csharp/Plays/VisitingTeamPlay.cs | 45 +++++--- 3 files changed, 93 insertions(+), 70 deletions(-) diff --git a/07_Basketball/csharp/Plays/HomeTeamPlay.cs b/07_Basketball/csharp/Plays/HomeTeamPlay.cs index fe30ff8f..4f6532dd 100644 --- a/07_Basketball/csharp/Plays/HomeTeamPlay.cs +++ b/07_Basketball/csharp/Plays/HomeTeamPlay.cs @@ -1,4 +1,3 @@ -using Basketball.Resources; using Games.Common.IO; using Games.Common.Randomness; @@ -10,9 +9,10 @@ internal class HomeTeamPlay : Play private readonly IRandom _random; private readonly Clock _clock; private readonly Defense _defense; + private bool _playContinues; public HomeTeamPlay(TextIO io, IRandom random, Clock clock, Defense defense) - : base(io, random) + : base(io, random, clock) { _io = io; _random = random; @@ -33,61 +33,69 @@ internal class HomeTeamPlay : Play return false; } - var playContinues = shot >= 3; - if (shot == 1 || shot == 2) { - _clock.Increment(scoreboard); - if (_clock.IsHalfTime) { return false; } - - var ballContest = new BallContest(0.5f, "Shot is blocked. Ball controlled by {0}.", _io, _random); - - Resolve("Jump shot", _defense / 8) - .Do(0.341f, () => scoreboard.AddBasket("Shot is good")) - .Or(0.682f, () => ResolveShotOffTarget()) - .Or(0.782f, () => ballContest.Resolve(scoreboard)) - .Or(0.843f, () => ResolveFreeThrows(scoreboard, "Shooter is fouled. Two shots.")) - .Or(() => scoreboard.Turnover("Charging foul. Dartmouth loses ball.")); + if (ClockIncrementsToHalfTime(scoreboard)) { return false; } + ResolveJumpShot(scoreboard); } - while (playContinues) + _playContinues |= shot >= 3; + + while (_playContinues) { - _clock.Increment(scoreboard); - if (_clock.IsHalfTime) { return false; } - - playContinues = false; - - Resolve(shot == 3 ? "Lay up." : "Set shot.", _defense / 7) - .Do(0.4f, () => scoreboard.AddBasket("Shot is good. Two points.")) - .Or(0.7f, () => ResolveShotOffTheRim()) - .Or(0.875f, () => ResolveFreeThrows(scoreboard, "Shooter fouled. Two shots.")) - .Or(0.925f, () => scoreboard.Turnover($"Shot blocked. {scoreboard.Visitors}'s ball.")) - .Or(() => scoreboard.Turnover("Charging foul. Dartmouth loses ball.")); + if (ClockIncrementsToHalfTime(scoreboard)) { return false; } + ResolveLayupOrSetShot(scoreboard, shot); } return false; - - void ResolveShotOffTarget() => - Resolve("Shot is off target", 6 / _defense) - .Do(0.45f, () => Resolve("Dartmouth controls the rebound.") - .Do(0.4f, () => playContinues = true) - .Or(() => - { - if (_defense == 6 && _random.NextFloat() > 0.6f) - { - scoreboard.Turnover(); - scoreboard.AddBasket($"Pass stolen by {scoreboard.Visitors} easy layup."); - _io.WriteLine(); - } - _io.Write("Ball passed back to you. "); - })) - .Or(() => scoreboard.Turnover($"Rebound to {scoreboard.Visitors}")); - - void ResolveShotOffTheRim() => - Resolve("Shot is off the rim.") - .Do(2 / 3f, () => scoreboard.Turnover($"{scoreboard.Visitors} controls the rebound.")) - .Or(() => Resolve("Dartmouth controls the rebound") - .Do(0.4f, () => playContinues = true) - .Or(() => _io.WriteLine("Ball passed back to you."))); } + + private void ResolveJumpShot(Scoreboard scoreboard) + { + var ballContest = new BallContest(0.5f, "Shot is blocked. Ball controlled by {0}.", _io, _random); + + Resolve("Jump shot", _defense / 8) + .Do(0.341f, () => scoreboard.AddBasket("Shot is good")) + .Or(0.682f, () => ResolveShotOffTarget(scoreboard)) + .Or(0.782f, () => ballContest.Resolve(scoreboard)) + .Or(0.843f, () => ResolveFreeThrows(scoreboard, "Shooter is fouled. Two shots.")) + .Or(() => scoreboard.Turnover($"Charging foul. {scoreboard.Home} loses ball.")); + } + + private void ResolveLayupOrSetShot(Scoreboard scoreboard, int shot) + { + _playContinues = false; + + Resolve(shot == 3 ? "Lay up." : "Set shot.", _defense / 7) + .Do(0.4f, () => scoreboard.AddBasket("Shot is good. Two points.")) + .Or(0.7f, () => ResolveShotOffTheRim(scoreboard)) + .Or(0.875f, () => ResolveFreeThrows(scoreboard, "Shooter fouled. Two shots.")) + .Or(0.925f, () => scoreboard.Turnover($"Shot blocked. {scoreboard.Visitors}'s ball.")) + .Or(() => scoreboard.Turnover($"Charging foul. {scoreboard.Home} loses ball.")); + } + + private void ResolveShotOffTarget(Scoreboard scoreboard) => + Resolve("Shot is off target", 6 / _defense) + .Do(0.45f, () => ResolveHomeRebound(scoreboard, ResolvePossibleSteal)) + .Or(() => scoreboard.Turnover($"Rebound to {scoreboard.Visitors}")); + + private void ResolveHomeRebound(Scoreboard scoreboard, Action endOfPlayAction) => + Resolve($"{scoreboard.Home} controls the rebound.") + .Do(0.4f, () => _playContinues = true) + .Or(() => endOfPlayAction.Invoke(scoreboard)); + private void ResolvePossibleSteal(Scoreboard scoreboard) + { + if (_defense == 6 && _random.NextFloat() > 0.6f) + { + scoreboard.Turnover(); + scoreboard.AddBasket($"Pass stolen by {scoreboard.Visitors} easy layup."); + _io.WriteLine(); + } + _io.Write("Ball passed back to you. "); + } + + private void ResolveShotOffTheRim(Scoreboard scoreboard) => + Resolve("Shot is off the rim.") + .Do(2 / 3f, () => scoreboard.Turnover($"{scoreboard.Visitors} controls the rebound.")) + .Or(() => ResolveHomeRebound(scoreboard, _ => _io.WriteLine("Ball passed back to you."))); } diff --git a/07_Basketball/csharp/Plays/Play.cs b/07_Basketball/csharp/Plays/Play.cs index 0edba943..9b82a74e 100644 --- a/07_Basketball/csharp/Plays/Play.cs +++ b/07_Basketball/csharp/Plays/Play.cs @@ -7,11 +7,19 @@ internal abstract class Play { private readonly IReadWrite _io; private readonly IRandom _random; + private readonly Clock _clock; - public Play(IReadWrite io, IRandom random) + public Play(IReadWrite io, IRandom random, Clock clock) { _io = io; _random = random; + _clock = clock; + } + + protected bool ClockIncrementsToHalfTime(Scoreboard scoreboard) + { + _clock.Increment(scoreboard); + return _clock.IsHalfTime; } internal abstract bool Resolve(Scoreboard scoreboard); diff --git a/07_Basketball/csharp/Plays/VisitingTeamPlay.cs b/07_Basketball/csharp/Plays/VisitingTeamPlay.cs index ab526562..85591a97 100644 --- a/07_Basketball/csharp/Plays/VisitingTeamPlay.cs +++ b/07_Basketball/csharp/Plays/VisitingTeamPlay.cs @@ -7,52 +7,59 @@ internal class VisitingTeamPlay : Play { private readonly TextIO _io; private readonly IRandom _random; - private readonly Clock _clock; private readonly Defense _defense; private bool _playContinues; public VisitingTeamPlay(TextIO io, IRandom random, Clock clock, Defense defense) - : base(io, random) + : base(io, random, clock) { _io = io; _random = random; - _clock = clock; _defense = defense; } internal override bool Resolve(Scoreboard scoreboard) { - _clock.Increment(scoreboard); - if (_clock.IsHalfTime) { return false; } + if (ClockIncrementsToHalfTime(scoreboard)) { return false; } _io.WriteLine(); var shot = _random.NextFloat(1, 3.5f); - _playContinues = shot > 2; if (shot <= 2) { - Resolve("Jump shot.", _defense / 8) - .Do(0.35f, () => scoreboard.AddBasket("Shot is good.")) - .Or(0.75f, () => ResolveBadShot(scoreboard, "Shot is off the rim.", _defense * 6)) - .Or(0.9f, () => ResolveFreeThrows(scoreboard, "Player fouled. Two shots.")) - .Or(() => _io.WriteLine("Offensive foul. Dartmouth's ball.")); - - _io.WriteLine(); + ResolveJumpShot(scoreboard); } + _playContinues |= shot > 2; + while (_playContinues) { - _playContinues = false; - - Resolve(shot > 3 ? "Set shot." : "Lay up.", _defense / 7) - .Do(0.413f, () => scoreboard.AddBasket("Shot is good.")) - .Or(() => ResolveBadShot(scoreboard, "Shot is missed.", 6 / _defense)); - _io.WriteLine(); + ResolveLayupOrSetShot(scoreboard, shot); } return false; } + private void ResolveJumpShot(Scoreboard scoreboard) + { + Resolve("Jump shot.", _defense / 8) + .Do(0.35f, () => scoreboard.AddBasket("Shot is good.")) + .Or(0.75f, () => ResolveBadShot(scoreboard, "Shot is off the rim.", _defense * 6)) + .Or(0.9f, () => ResolveFreeThrows(scoreboard, "Player fouled. Two shots.")) + .Or(() => _io.WriteLine("Offensive foul. Dartmouth's ball.")); + _io.WriteLine(); + } + + private void ResolveLayupOrSetShot(Scoreboard scoreboard, float shot) + { + _playContinues = false; + + Resolve(shot > 3 ? "Set shot." : "Lay up.", _defense / 7) + .Do(0.413f, () => scoreboard.AddBasket("Shot is good.")) + .Or(() => ResolveBadShot(scoreboard, "Shot is missed.", 6 / _defense)); + _io.WriteLine(); + } + void ResolveBadShot(Scoreboard scoreboard, string message, float defenseFactor) => Resolve(message, defenseFactor) .Do(0.5f, () => scoreboard.Turnover("Dartmouth controls the rebound.")) From 11ee7318328d6b189c7b7e4d1f796ce42b8d264e Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Sun, 10 Apr 2022 13:10:46 +1000 Subject: [PATCH 10/15] Encapsulate shot number and name --- 07_Basketball/csharp/IRandomExtensions.cs | 8 +++++ 07_Basketball/csharp/IReadWriteExtensions.cs | 20 +++++++---- 07_Basketball/csharp/JumpShot.cs | 9 +++++ 07_Basketball/csharp/Plays/BallContest.cs | 5 ++- 07_Basketball/csharp/Plays/HomeTeamPlay.cs | 18 +++++----- .../csharp/Plays/VisitingTeamPlay.cs | 29 +++++++-------- 07_Basketball/csharp/Shot.cs | 36 +++++++++++++++++++ 7 files changed, 89 insertions(+), 36 deletions(-) create mode 100644 07_Basketball/csharp/IRandomExtensions.cs create mode 100644 07_Basketball/csharp/JumpShot.cs create mode 100644 07_Basketball/csharp/Shot.cs diff --git a/07_Basketball/csharp/IRandomExtensions.cs b/07_Basketball/csharp/IRandomExtensions.cs new file mode 100644 index 00000000..876e05a2 --- /dev/null +++ b/07_Basketball/csharp/IRandomExtensions.cs @@ -0,0 +1,8 @@ +using Games.Common.Randomness; + +namespace Basketball; + +internal static class IRandomExtensions +{ + internal static Shot NextShot(this IRandom random) => Shot.Get(random.NextFloat(1, 3.5f)); +} diff --git a/07_Basketball/csharp/IReadWriteExtensions.cs b/07_Basketball/csharp/IReadWriteExtensions.cs index 7b510a50..9a066f2e 100644 --- a/07_Basketball/csharp/IReadWriteExtensions.cs +++ b/07_Basketball/csharp/IReadWriteExtensions.cs @@ -13,16 +13,22 @@ internal static class IReadWriteExtensions } } - public static int ReadShot(this IReadWrite io, string prompt) + private static bool TryReadInteger(this IReadWrite io, string prompt, out int value) + { + var floatValue = io.ReadNumber(prompt); + value = (int)floatValue; + return value == floatValue; + } + + public static Shot? ReadShot(this IReadWrite io, string prompt) { while (true) { - var shot = io.ReadNumber(prompt); - if ((int)shot == shot && shot >= 0 && shot <= 4) { return (int)shot; } + if (io.TryReadInteger(prompt, out var value) && Shot.TryGet(value, out var shot)) + { + return 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/JumpShot.cs b/07_Basketball/csharp/JumpShot.cs new file mode 100644 index 00000000..46ffc6d8 --- /dev/null +++ b/07_Basketball/csharp/JumpShot.cs @@ -0,0 +1,9 @@ +namespace Basketball; + +public class JumpShot : Shot +{ + public JumpShot() + : base("Jump shot") + { + } +} \ No newline at end of file diff --git a/07_Basketball/csharp/Plays/BallContest.cs b/07_Basketball/csharp/Plays/BallContest.cs index a26e7c06..a12ddc36 100644 --- a/07_Basketball/csharp/Plays/BallContest.cs +++ b/07_Basketball/csharp/Plays/BallContest.cs @@ -3,7 +3,7 @@ using Games.Common.Randomness; namespace Basketball.Plays; -internal class BallContest : Play +internal class BallContest { private readonly float _probability; private readonly string _messageFormat; @@ -11,7 +11,6 @@ internal class BallContest : Play private readonly IRandom _random; internal BallContest(float probability, string messageFormat, IReadWrite io, IRandom random) - : base(io, random) { _io = io; _probability = probability; @@ -19,7 +18,7 @@ internal class BallContest : Play _random = random; } - internal override bool Resolve(Scoreboard scoreboard) + internal bool Resolve(Scoreboard scoreboard) { var winner = _random.NextFloat() <= _probability ? scoreboard.Home : scoreboard.Visitors; scoreboard.Offense = winner; diff --git a/07_Basketball/csharp/Plays/HomeTeamPlay.cs b/07_Basketball/csharp/Plays/HomeTeamPlay.cs index 4f6532dd..999a6551 100644 --- a/07_Basketball/csharp/Plays/HomeTeamPlay.cs +++ b/07_Basketball/csharp/Plays/HomeTeamPlay.cs @@ -26,35 +26,35 @@ internal class HomeTeamPlay : Play if (_random.NextFloat() >= 0.5f && _clock.IsFullTime) { return true; } - if (shot == 0) + if (shot is null) { _defense.Set(_io.ReadDefense("Your new defensive alignment is")); _io.WriteLine(); return false; } - if (shot == 1 || shot == 2) + if (shot is JumpShot jumpShot) { if (ClockIncrementsToHalfTime(scoreboard)) { return false; } - ResolveJumpShot(scoreboard); + Resolve(jumpShot, scoreboard); } - _playContinues |= shot >= 3; + _playContinues |= shot is not JumpShot; while (_playContinues) { if (ClockIncrementsToHalfTime(scoreboard)) { return false; } - ResolveLayupOrSetShot(scoreboard, shot); + Resolve(shot, scoreboard); } return false; } - private void ResolveJumpShot(Scoreboard scoreboard) + private void Resolve(JumpShot shot, Scoreboard scoreboard) { var ballContest = new BallContest(0.5f, "Shot is blocked. Ball controlled by {0}.", _io, _random); - Resolve("Jump shot", _defense / 8) + Resolve(shot.ToString(), _defense / 8) .Do(0.341f, () => scoreboard.AddBasket("Shot is good")) .Or(0.682f, () => ResolveShotOffTarget(scoreboard)) .Or(0.782f, () => ballContest.Resolve(scoreboard)) @@ -62,11 +62,11 @@ internal class HomeTeamPlay : Play .Or(() => scoreboard.Turnover($"Charging foul. {scoreboard.Home} loses ball.")); } - private void ResolveLayupOrSetShot(Scoreboard scoreboard, int shot) + private void Resolve(Shot shot, Scoreboard scoreboard) { _playContinues = false; - Resolve(shot == 3 ? "Lay up." : "Set shot.", _defense / 7) + Resolve(shot.ToString(), _defense / 7) .Do(0.4f, () => scoreboard.AddBasket("Shot is good. Two points.")) .Or(0.7f, () => ResolveShotOffTheRim(scoreboard)) .Or(0.875f, () => ResolveFreeThrows(scoreboard, "Shooter fouled. Two shots.")) diff --git a/07_Basketball/csharp/Plays/VisitingTeamPlay.cs b/07_Basketball/csharp/Plays/VisitingTeamPlay.cs index 85591a97..1722be02 100644 --- a/07_Basketball/csharp/Plays/VisitingTeamPlay.cs +++ b/07_Basketball/csharp/Plays/VisitingTeamPlay.cs @@ -23,42 +23,37 @@ internal class VisitingTeamPlay : Play if (ClockIncrementsToHalfTime(scoreboard)) { return false; } _io.WriteLine(); - var shot = _random.NextFloat(1, 3.5f); + var shot = _random.NextShot(); - if (shot <= 2) + if (shot is JumpShot jumpShot) { - ResolveJumpShot(scoreboard); + Resolve(jumpShot, scoreboard); + _io.WriteLine(); } - _playContinues |= shot > 2; + _playContinues |= shot is not JumpShot; while (_playContinues) { - ResolveLayupOrSetShot(scoreboard, shot); + _playContinues = false; + Resolve(shot, scoreboard); + _io.WriteLine(); } return false; } - private void ResolveJumpShot(Scoreboard scoreboard) - { - Resolve("Jump shot.", _defense / 8) + private void Resolve(JumpShot shot, Scoreboard scoreboard) => + Resolve(shot.ToString(), _defense / 8) .Do(0.35f, () => scoreboard.AddBasket("Shot is good.")) .Or(0.75f, () => ResolveBadShot(scoreboard, "Shot is off the rim.", _defense * 6)) .Or(0.9f, () => ResolveFreeThrows(scoreboard, "Player fouled. Two shots.")) .Or(() => _io.WriteLine("Offensive foul. Dartmouth's ball.")); - _io.WriteLine(); - } - private void ResolveLayupOrSetShot(Scoreboard scoreboard, float shot) - { - _playContinues = false; - - Resolve(shot > 3 ? "Set shot." : "Lay up.", _defense / 7) + private void Resolve(Shot shot, Scoreboard scoreboard) => + Resolve(shot.ToString(), _defense / 7) .Do(0.413f, () => scoreboard.AddBasket("Shot is good.")) .Or(() => ResolveBadShot(scoreboard, "Shot is missed.", 6 / _defense)); - _io.WriteLine(); - } void ResolveBadShot(Scoreboard scoreboard, string message, float defenseFactor) => Resolve(message, defenseFactor) diff --git a/07_Basketball/csharp/Shot.cs b/07_Basketball/csharp/Shot.cs new file mode 100644 index 00000000..94ff9b84 --- /dev/null +++ b/07_Basketball/csharp/Shot.cs @@ -0,0 +1,36 @@ +namespace Basketball; + +public class Shot +{ + private readonly string _name; + + public Shot(string name) + { + _name = name; + } + + public static bool TryGet(int shotNumber, out Shot? shot) + { + shot = shotNumber switch + { + // Although the game instructions reference two different jump shots, + // the original game code treats them both the same and just prints "Jump shot" + <= 2 => new JumpShot(), + 3 => new Shot("Lay up"), + 4 => new Shot("Set shot"), + _ => null + }; + return shot is not null; + } + + public static Shot Get(float shotNumber) => + shotNumber switch + { + <= 2 => new JumpShot(), + > 3 => new Shot("Set shot"), + > 2 => new Shot("Lay up"), + _ => throw new Exception("Unexpected value") + }; + + public override string ToString() => _name; +} From 0e1ef8fc46ccb875b532b7a4d8088c1052ef0264 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Sun, 10 Apr 2022 16:09:39 +1000 Subject: [PATCH 11/15] Cleanup Game creation and main loop --- 07_Basketball/csharp/Game.cs | 45 ++++++++++++++++++++------------- 07_Basketball/csharp/Program.cs | 2 +- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/07_Basketball/csharp/Game.cs b/07_Basketball/csharp/Game.cs index 4444fa77..5a7d6ffa 100644 --- a/07_Basketball/csharp/Game.cs +++ b/07_Basketball/csharp/Game.cs @@ -7,58 +7,67 @@ namespace Basketball; internal class Game { + private readonly Clock _clock; + private readonly Scoreboard _scoreboard; private readonly TextIO _io; private readonly IRandom _random; - public Game(TextIO io, IRandom random) + private Game(Clock clock, Scoreboard scoreboard, TextIO io, IRandom random) { + _clock = clock; + _scoreboard = scoreboard; _io = io; _random = random; } - public void Play() + public static Game Create(TextIO io, IRandom random) { - _io.Write(Resource.Streams.Introduction); + io.Write(Resource.Streams.Introduction); - var defense = new Defense(_io.ReadDefense("Your starting defense will be")); - var clock = new Clock(_io); + var defense = new Defense(io.ReadDefense("Your starting defense will be")); + var clock = new Clock(io); - _io.WriteLine(); + io.WriteLine(); var scoreboard = new Scoreboard( - new Team("Dartmouth", new HomeTeamPlay(_io, _random, clock, defense)), - new Team(_io.ReadString("Choose your opponent"), new VisitingTeamPlay(_io, _random, clock, defense)), - _io); + new Team("Dartmouth", new HomeTeamPlay(io, random, clock, defense)), + new Team(io.ReadString("Choose your opponent"), new VisitingTeamPlay(io, random, clock, defense)), + io); + return new Game(clock, scoreboard, io, random); + } + + public void Play() + { var ballContest = new BallContest(0.4f, "{0} controls the tap", _io, _random); while (true) { _io.WriteLine("Center jump"); - ballContest.Resolve(scoreboard); + ballContest.Resolve(_scoreboard); _io.WriteLine(); while (true) { - var isFullTime = scoreboard.Offense.ResolvePlay(scoreboard); - if (isFullTime && IsGameOver(scoreboard, clock)) { return; } - if (clock.IsHalfTime) { break; } + var isFullTime = _scoreboard.Offense.ResolvePlay(_scoreboard); + if (isFullTime && IsGameOver()) { return; } + if (_clock.IsHalfTime) { break; } } } } - private bool IsGameOver(Scoreboard scoreboard, Clock clock) + private bool IsGameOver() { _io.WriteLine(); - if (scoreboard.ScoresAreEqual) + if (_scoreboard.ScoresAreEqual) { - scoreboard.Display(Resource.Formats.EndOfSecondHalf); - clock.StartOvertime(); + _scoreboard.Display(Resource.Formats.EndOfSecondHalf); + _clock.StartOvertime(); return false; } - scoreboard.Display(Resource.Formats.EndOfGame); + _scoreboard.Display(Resource.Formats.EndOfGame); return true; } } diff --git a/07_Basketball/csharp/Program.cs b/07_Basketball/csharp/Program.cs index 79b0ab96..7a610336 100644 --- a/07_Basketball/csharp/Program.cs +++ b/07_Basketball/csharp/Program.cs @@ -2,6 +2,6 @@ using Basketball; using Games.Common.IO; using Games.Common.Randomness; -var game = new Game(new ConsoleIO(), new RandomNumberGenerator()); +var game = Game.Create(new ConsoleIO(), new RandomNumberGenerator()); game.Play(); \ No newline at end of file From fd159ac58248b3a5ef63ddb8a1c35ab492ddc716 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Sun, 10 Apr 2022 16:09:49 +1000 Subject: [PATCH 12/15] Some cleanup --- 07_Basketball/csharp/Plays/HomeTeamPlay.cs | 19 ++++++++----------- .../csharp/Plays/VisitingTeamPlay.cs | 8 +++++--- 07_Basketball/csharp/Probably.cs | 6 ++++++ 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/07_Basketball/csharp/Plays/HomeTeamPlay.cs b/07_Basketball/csharp/Plays/HomeTeamPlay.cs index 999a6551..cb614af2 100644 --- a/07_Basketball/csharp/Plays/HomeTeamPlay.cs +++ b/07_Basketball/csharp/Plays/HomeTeamPlay.cs @@ -9,6 +9,7 @@ internal class HomeTeamPlay : Play private readonly IRandom _random; private readonly Clock _clock; private readonly Defense _defense; + private readonly BallContest _ballContest; private bool _playContinues; public HomeTeamPlay(TextIO io, IRandom random, Clock clock, Defense defense) @@ -18,6 +19,7 @@ internal class HomeTeamPlay : Play _random = random; _clock = clock; _defense = defense; + _ballContest = new BallContest(0.5f, "Shot is blocked. Ball controlled by {0}.", _io, _random); } internal override bool Resolve(Scoreboard scoreboard) @@ -39,10 +41,13 @@ internal class HomeTeamPlay : Play Resolve(jumpShot, scoreboard); } + // Either the above resolution has transition to a lay-up + // or the chosen shot is not a jump shot and has not been resolved yet. _playContinues |= shot is not JumpShot; while (_playContinues) { + _playContinues = false; if (ClockIncrementsToHalfTime(scoreboard)) { return false; } Resolve(shot, scoreboard); } @@ -50,29 +55,21 @@ internal class HomeTeamPlay : Play return false; } - private void Resolve(JumpShot shot, Scoreboard scoreboard) - { - var ballContest = new BallContest(0.5f, "Shot is blocked. Ball controlled by {0}.", _io, _random); - + private void Resolve(JumpShot shot, Scoreboard scoreboard) => Resolve(shot.ToString(), _defense / 8) .Do(0.341f, () => scoreboard.AddBasket("Shot is good")) .Or(0.682f, () => ResolveShotOffTarget(scoreboard)) - .Or(0.782f, () => ballContest.Resolve(scoreboard)) + .Or(0.782f, () => _ballContest.Resolve(scoreboard)) .Or(0.843f, () => ResolveFreeThrows(scoreboard, "Shooter is fouled. Two shots.")) .Or(() => scoreboard.Turnover($"Charging foul. {scoreboard.Home} loses ball.")); - } - - private void Resolve(Shot shot, Scoreboard scoreboard) - { - _playContinues = false; + private void Resolve(Shot shot, Scoreboard scoreboard) => Resolve(shot.ToString(), _defense / 7) .Do(0.4f, () => scoreboard.AddBasket("Shot is good. Two points.")) .Or(0.7f, () => ResolveShotOffTheRim(scoreboard)) .Or(0.875f, () => ResolveFreeThrows(scoreboard, "Shooter fouled. Two shots.")) .Or(0.925f, () => scoreboard.Turnover($"Shot blocked. {scoreboard.Visitors}'s ball.")) .Or(() => scoreboard.Turnover($"Charging foul. {scoreboard.Home} loses ball.")); - } private void ResolveShotOffTarget(Scoreboard scoreboard) => Resolve("Shot is off target", 6 / _defense) diff --git a/07_Basketball/csharp/Plays/VisitingTeamPlay.cs b/07_Basketball/csharp/Plays/VisitingTeamPlay.cs index 1722be02..f7ac45a0 100644 --- a/07_Basketball/csharp/Plays/VisitingTeamPlay.cs +++ b/07_Basketball/csharp/Plays/VisitingTeamPlay.cs @@ -31,6 +31,8 @@ internal class VisitingTeamPlay : Play _io.WriteLine(); } + // Either the above resolution has transition to a lay-up + // or the chosen shot is not a jump shot and has not been resolved yet. _playContinues |= shot is not JumpShot; while (_playContinues) @@ -48,7 +50,7 @@ internal class VisitingTeamPlay : Play .Do(0.35f, () => scoreboard.AddBasket("Shot is good.")) .Or(0.75f, () => ResolveBadShot(scoreboard, "Shot is off the rim.", _defense * 6)) .Or(0.9f, () => ResolveFreeThrows(scoreboard, "Player fouled. Two shots.")) - .Or(() => _io.WriteLine("Offensive foul. Dartmouth's ball.")); + .Or(() => _io.WriteLine($"Offensive foul. {scoreboard.Home}'s ball.")); private void Resolve(Shot shot, Scoreboard scoreboard) => Resolve(shot.ToString(), _defense / 7) @@ -57,7 +59,7 @@ internal class VisitingTeamPlay : Play void ResolveBadShot(Scoreboard scoreboard, string message, float defenseFactor) => Resolve(message, defenseFactor) - .Do(0.5f, () => scoreboard.Turnover("Dartmouth controls the rebound.")) + .Do(0.5f, () => scoreboard.Turnover($"{scoreboard.Home} controls the rebound.")) .Or(() => ResolveVisitorsRebound(scoreboard)); void ResolveVisitorsRebound(Scoreboard scoreboard) @@ -67,7 +69,7 @@ internal class VisitingTeamPlay : Play { _io.WriteLine(); scoreboard.Turnover(); - scoreboard.AddBasket("Ball stolen. Easy lay up for Dartmouth."); + scoreboard.AddBasket($"Ball stolen. Easy lay up for {scoreboard.Home}."); return; } diff --git a/07_Basketball/csharp/Probably.cs b/07_Basketball/csharp/Probably.cs index a7ca403c..1a32674e 100644 --- a/07_Basketball/csharp/Probably.cs +++ b/07_Basketball/csharp/Probably.cs @@ -2,6 +2,12 @@ using Games.Common.Randomness; namespace Basketball; +/// +/// Supports a chain of actions to be performed based on various probabilities. The original game code gets a new +/// random number for each probability check. Evaluating a set of probabilities against a single random number is +/// much simpler, but yield a very different outcome distribution. The purpose of this class is to simplify the code +/// to for the original probabilistic branch decisions. +/// internal struct Probably { private readonly float _defenseFactor; From c45fb59747357ef30ee589473a5cf26fe778b40c Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Sun, 10 Apr 2022 16:40:08 +1000 Subject: [PATCH 13/15] Simplify play continuation logic --- 07_Basketball/csharp/Plays/HomeTeamPlay.cs | 25 +++++------- .../csharp/Plays/VisitingTeamPlay.cs | 32 +++++++-------- 07_Basketball/csharp/Probably.cs | 40 +++++++++++++------ 3 files changed, 51 insertions(+), 46 deletions(-) diff --git a/07_Basketball/csharp/Plays/HomeTeamPlay.cs b/07_Basketball/csharp/Plays/HomeTeamPlay.cs index cb614af2..48ef249e 100644 --- a/07_Basketball/csharp/Plays/HomeTeamPlay.cs +++ b/07_Basketball/csharp/Plays/HomeTeamPlay.cs @@ -10,7 +10,6 @@ internal class HomeTeamPlay : Play private readonly Clock _clock; private readonly Defense _defense; private readonly BallContest _ballContest; - private bool _playContinues; public HomeTeamPlay(TextIO io, IRandom random, Clock clock, Defense defense) : base(io, random, clock) @@ -38,24 +37,20 @@ internal class HomeTeamPlay : Play if (shot is JumpShot jumpShot) { if (ClockIncrementsToHalfTime(scoreboard)) { return false; } - Resolve(jumpShot, scoreboard); + if (!Resolve(jumpShot, scoreboard)) { return false; } } - // Either the above resolution has transition to a lay-up - // or the chosen shot is not a jump shot and has not been resolved yet. - _playContinues |= shot is not JumpShot; - - while (_playContinues) + do { - _playContinues = false; if (ClockIncrementsToHalfTime(scoreboard)) { return false; } - Resolve(shot, scoreboard); - } + } while (Resolve(shot, scoreboard)); return false; } - private void Resolve(JumpShot shot, Scoreboard scoreboard) => + // The Resolve* methods resolve the probabilistic outcome of the current game state. + // They return true if the Home team should continue the play and attempt a layup, false otherwise. + private bool Resolve(JumpShot shot, Scoreboard scoreboard) => Resolve(shot.ToString(), _defense / 8) .Do(0.341f, () => scoreboard.AddBasket("Shot is good")) .Or(0.682f, () => ResolveShotOffTarget(scoreboard)) @@ -63,7 +58,7 @@ internal class HomeTeamPlay : Play .Or(0.843f, () => ResolveFreeThrows(scoreboard, "Shooter is fouled. Two shots.")) .Or(() => scoreboard.Turnover($"Charging foul. {scoreboard.Home} loses ball.")); - private void Resolve(Shot shot, Scoreboard scoreboard) => + private bool Resolve(Shot shot, Scoreboard scoreboard) => Resolve(shot.ToString(), _defense / 7) .Do(0.4f, () => scoreboard.AddBasket("Shot is good. Two points.")) .Or(0.7f, () => ResolveShotOffTheRim(scoreboard)) @@ -71,14 +66,14 @@ internal class HomeTeamPlay : Play .Or(0.925f, () => scoreboard.Turnover($"Shot blocked. {scoreboard.Visitors}'s ball.")) .Or(() => scoreboard.Turnover($"Charging foul. {scoreboard.Home} loses ball.")); - private void ResolveShotOffTarget(Scoreboard scoreboard) => + private bool ResolveShotOffTarget(Scoreboard scoreboard) => Resolve("Shot is off target", 6 / _defense) .Do(0.45f, () => ResolveHomeRebound(scoreboard, ResolvePossibleSteal)) .Or(() => scoreboard.Turnover($"Rebound to {scoreboard.Visitors}")); - private void ResolveHomeRebound(Scoreboard scoreboard, Action endOfPlayAction) => + private bool ResolveHomeRebound(Scoreboard scoreboard, Action endOfPlayAction) => Resolve($"{scoreboard.Home} controls the rebound.") - .Do(0.4f, () => _playContinues = true) + .Do(0.4f, () => true) .Or(() => endOfPlayAction.Invoke(scoreboard)); private void ResolvePossibleSteal(Scoreboard scoreboard) { diff --git a/07_Basketball/csharp/Plays/VisitingTeamPlay.cs b/07_Basketball/csharp/Plays/VisitingTeamPlay.cs index f7ac45a0..3975a6ba 100644 --- a/07_Basketball/csharp/Plays/VisitingTeamPlay.cs +++ b/07_Basketball/csharp/Plays/VisitingTeamPlay.cs @@ -8,7 +8,6 @@ internal class VisitingTeamPlay : Play private readonly TextIO _io; private readonly IRandom _random; private readonly Defense _defense; - private bool _playContinues; public VisitingTeamPlay(TextIO io, IRandom random, Clock clock, Defense defense) : base(io, random, clock) @@ -27,42 +26,39 @@ internal class VisitingTeamPlay : Play if (shot is JumpShot jumpShot) { - Resolve(jumpShot, scoreboard); + var continuePlay = Resolve(jumpShot, scoreboard); _io.WriteLine(); + if (!continuePlay) { return false; } } - // Either the above resolution has transition to a lay-up - // or the chosen shot is not a jump shot and has not been resolved yet. - _playContinues |= shot is not JumpShot; - - while (_playContinues) + while (true) { - _playContinues = false; - Resolve(shot, scoreboard); + var continuePlay = Resolve(shot, scoreboard); _io.WriteLine(); + if (!continuePlay) { return false; } } - - return false; } - private void Resolve(JumpShot shot, Scoreboard scoreboard) => + // The Resolve* methods resolve the probabilistic outcome of the current game state. + // They return true if the Visiting team should continue the play and attempt a layup, false otherwise. + private bool Resolve(JumpShot shot, Scoreboard scoreboard) => Resolve(shot.ToString(), _defense / 8) .Do(0.35f, () => scoreboard.AddBasket("Shot is good.")) .Or(0.75f, () => ResolveBadShot(scoreboard, "Shot is off the rim.", _defense * 6)) .Or(0.9f, () => ResolveFreeThrows(scoreboard, "Player fouled. Two shots.")) .Or(() => _io.WriteLine($"Offensive foul. {scoreboard.Home}'s ball.")); - private void Resolve(Shot shot, Scoreboard scoreboard) => + private bool Resolve(Shot shot, Scoreboard scoreboard) => Resolve(shot.ToString(), _defense / 7) .Do(0.413f, () => scoreboard.AddBasket("Shot is good.")) .Or(() => ResolveBadShot(scoreboard, "Shot is missed.", 6 / _defense)); - void ResolveBadShot(Scoreboard scoreboard, string message, float defenseFactor) => + private bool ResolveBadShot(Scoreboard scoreboard, string message, float defenseFactor) => Resolve(message, defenseFactor) .Do(0.5f, () => scoreboard.Turnover($"{scoreboard.Home} controls the rebound.")) .Or(() => ResolveVisitorsRebound(scoreboard)); - void ResolveVisitorsRebound(Scoreboard scoreboard) + private bool ResolveVisitorsRebound(Scoreboard scoreboard) { _io.Write($"{scoreboard.Visitors} controls the rebound."); if (_defense == 6 && _random.NextFloat() <= 0.25f) @@ -70,16 +66,16 @@ internal class VisitingTeamPlay : Play _io.WriteLine(); scoreboard.Turnover(); scoreboard.AddBasket($"Ball stolen. Easy lay up for {scoreboard.Home}."); - return; + return false; } if (_random.NextFloat() <= 0.5f) { _io.WriteLine(); _io.Write($"Pass back to {scoreboard.Visitors} guard."); - return; + return false; } - _playContinues = true; + return true; } } diff --git a/07_Basketball/csharp/Probably.cs b/07_Basketball/csharp/Probably.cs index 1a32674e..018a8108 100644 --- a/07_Basketball/csharp/Probably.cs +++ b/07_Basketball/csharp/Probably.cs @@ -12,27 +12,41 @@ internal struct Probably { private readonly float _defenseFactor; private readonly IRandom _random; - private readonly bool _done; + private readonly bool? _result; - internal Probably(float defenseFactor, IRandom random, bool done = false) + internal Probably(float defenseFactor, IRandom random, bool? result = false) { _defenseFactor = defenseFactor; _random = random; - _done = done; + _result = result; } - public Probably Do(float probability, Action action) - { - if (!_done && _random.NextFloat() <= probability * _defenseFactor) - { - action.Invoke(); - return new Probably(_defenseFactor, _random, true); - } + public Probably Do(float probability, Func action) => + ShouldResolveAction(probability) + ? new Probably(_defenseFactor, _random, _result | Resolve(action)) + : this; - return this; - } + public Probably Do(float probability, Action action) => + ShouldResolveAction(probability) + ? new Probably(_defenseFactor, _random, _result | Resolve(action)) + : this; public Probably Or(float probability, Action action) => Do(probability, action); - public Probably Or(Action action) => Do(1f, action); + public Probably Or(float probability, Func action) => Do(probability, action); + + public bool Or(Action action) => _result | Resolve(action) ?? false; + + private bool? Resolve(Action action) + { + action.Invoke(); + return _result; + } + + private bool? Resolve(Func action) => action.Invoke(); + + private readonly bool ShouldResolveAction(float probability) + { + return _result is null && _random.NextFloat() <= probability * _defenseFactor; + } } From f4fa4f230bc2dabd4544d1e3aab6eb3a8f9c9b5c Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Tue, 12 Apr 2022 17:47:24 +1000 Subject: [PATCH 14/15] Fix action resolution --- 07_Basketball/csharp/IReadWriteExtensions.cs | 6 ++--- 07_Basketball/csharp/Probably.cs | 26 +++++++++----------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/07_Basketball/csharp/IReadWriteExtensions.cs b/07_Basketball/csharp/IReadWriteExtensions.cs index 9a066f2e..6197ff97 100644 --- a/07_Basketball/csharp/IReadWriteExtensions.cs +++ b/07_Basketball/csharp/IReadWriteExtensions.cs @@ -13,11 +13,11 @@ internal static class IReadWriteExtensions } } - private static bool TryReadInteger(this IReadWrite io, string prompt, out int value) + private static bool TryReadInteger(this IReadWrite io, string prompt, out int intValue) { var floatValue = io.ReadNumber(prompt); - value = (int)floatValue; - return value == floatValue; + intValue = (int)floatValue; + return intValue == floatValue; } public static Shot? ReadShot(this IReadWrite io, string prompt) diff --git a/07_Basketball/csharp/Probably.cs b/07_Basketball/csharp/Probably.cs index 018a8108..0ba5864a 100644 --- a/07_Basketball/csharp/Probably.cs +++ b/07_Basketball/csharp/Probably.cs @@ -14,28 +14,28 @@ internal struct Probably private readonly IRandom _random; private readonly bool? _result; - internal Probably(float defenseFactor, IRandom random, bool? result = false) + internal Probably(float defenseFactor, IRandom random, bool? result = null) { _defenseFactor = defenseFactor; _random = random; _result = result; } - public Probably Do(float probability, Func action) => - ShouldResolveAction(probability) - ? new Probably(_defenseFactor, _random, _result | Resolve(action)) - : this; - public Probably Do(float probability, Action action) => ShouldResolveAction(probability) - ? new Probably(_defenseFactor, _random, _result | Resolve(action)) + ? new Probably(_defenseFactor, _random, Resolve(action) ?? false) + : this; + + public Probably Do(float probability, Func action) => + ShouldResolveAction(probability) + ? new Probably(_defenseFactor, _random, Resolve(action) ?? false) : this; public Probably Or(float probability, Action action) => Do(probability, action); - public Probably Or(float probability, Func action) => Do(probability, action); + public Probably Or(float probability, Func action) => Do(probability, action); - public bool Or(Action action) => _result | Resolve(action) ?? false; + public bool Or(Action action) => _result ?? Resolve(action) ?? false; private bool? Resolve(Action action) { @@ -43,10 +43,8 @@ internal struct Probably return _result; } - private bool? Resolve(Func action) => action.Invoke(); + private bool? Resolve(Func action) => action.Invoke(); - private readonly bool ShouldResolveAction(float probability) - { - return _result is null && _random.NextFloat() <= probability * _defenseFactor; - } + private readonly bool ShouldResolveAction(float probability) => + _result is null && _random.NextFloat() <= probability * _defenseFactor; } From c6d018acef04a8596cdc976d2fb02e86ccd1fb08 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Tue, 12 Apr 2022 17:51:18 +1000 Subject: [PATCH 15/15] Fix 0 shot for new defense --- 07_Basketball/csharp/Shot.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/07_Basketball/csharp/Shot.cs b/07_Basketball/csharp/Shot.cs index 94ff9b84..fb7c2f3e 100644 --- a/07_Basketball/csharp/Shot.cs +++ b/07_Basketball/csharp/Shot.cs @@ -15,12 +15,13 @@ public class Shot { // Although the game instructions reference two different jump shots, // the original game code treats them both the same and just prints "Jump shot" + 0 => null, <= 2 => new JumpShot(), 3 => new Shot("Lay up"), 4 => new Shot("Set shot"), _ => null }; - return shot is not null; + return shotNumber == 0 || shot is not null; } public static Shot Get(float shotNumber) =>