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