From aaaadc04eaf6633070d435237f4b4defe4e5326d Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Tue, 5 Apr 2022 05:06:17 +1000 Subject: [PATCH] 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); +}