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; +}