Simplify play continuation logic

This commit is contained in:
Andrew Cooper
2022-04-10 16:40:08 +10:00
parent fd159ac582
commit c45fb59747
3 changed files with 51 additions and 46 deletions

View File

@@ -10,7 +10,6 @@ internal class HomeTeamPlay : Play
private readonly Clock _clock; private readonly Clock _clock;
private readonly Defense _defense; private readonly Defense _defense;
private readonly BallContest _ballContest; private readonly BallContest _ballContest;
private bool _playContinues;
public HomeTeamPlay(TextIO io, IRandom random, Clock clock, Defense defense) public HomeTeamPlay(TextIO io, IRandom random, Clock clock, Defense defense)
: base(io, random, clock) : base(io, random, clock)
@@ -38,24 +37,20 @@ internal class HomeTeamPlay : Play
if (shot is JumpShot jumpShot) if (shot is JumpShot jumpShot)
{ {
if (ClockIncrementsToHalfTime(scoreboard)) { return false; } if (ClockIncrementsToHalfTime(scoreboard)) { return false; }
Resolve(jumpShot, scoreboard); if (!Resolve(jumpShot, scoreboard)) { return false; }
} }
// Either the above resolution has transition to a lay-up do
// 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; } if (ClockIncrementsToHalfTime(scoreboard)) { return false; }
Resolve(shot, scoreboard); } while (Resolve(shot, scoreboard));
}
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 Home team should continue the play and attempt a layup, false otherwise.
private bool Resolve(JumpShot shot, Scoreboard scoreboard) =>
Resolve(shot.ToString(), _defense / 8) Resolve(shot.ToString(), _defense / 8)
.Do(0.341f, () => scoreboard.AddBasket("Shot is good")) .Do(0.341f, () => scoreboard.AddBasket("Shot is good"))
.Or(0.682f, () => ResolveShotOffTarget(scoreboard)) .Or(0.682f, () => ResolveShotOffTarget(scoreboard))
@@ -63,7 +58,7 @@ internal class HomeTeamPlay : Play
.Or(0.843f, () => ResolveFreeThrows(scoreboard, "Shooter is fouled. Two shots.")) .Or(0.843f, () => ResolveFreeThrows(scoreboard, "Shooter is fouled. Two shots."))
.Or(() => scoreboard.Turnover($"Charging foul. {scoreboard.Home} loses ball.")); .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) Resolve(shot.ToString(), _defense / 7)
.Do(0.4f, () => scoreboard.AddBasket("Shot is good. Two points.")) .Do(0.4f, () => scoreboard.AddBasket("Shot is good. Two points."))
.Or(0.7f, () => ResolveShotOffTheRim(scoreboard)) .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(0.925f, () => scoreboard.Turnover($"Shot blocked. {scoreboard.Visitors}'s ball."))
.Or(() => scoreboard.Turnover($"Charging foul. {scoreboard.Home} loses 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) Resolve("Shot is off target", 6 / _defense)
.Do(0.45f, () => ResolveHomeRebound(scoreboard, ResolvePossibleSteal)) .Do(0.45f, () => ResolveHomeRebound(scoreboard, ResolvePossibleSteal))
.Or(() => scoreboard.Turnover($"Rebound to {scoreboard.Visitors}")); .Or(() => scoreboard.Turnover($"Rebound to {scoreboard.Visitors}"));
private void ResolveHomeRebound(Scoreboard scoreboard, Action<Scoreboard> endOfPlayAction) => private bool ResolveHomeRebound(Scoreboard scoreboard, Action<Scoreboard> endOfPlayAction) =>
Resolve($"{scoreboard.Home} controls the rebound.") Resolve($"{scoreboard.Home} controls the rebound.")
.Do(0.4f, () => _playContinues = true) .Do(0.4f, () => true)
.Or(() => endOfPlayAction.Invoke(scoreboard)); .Or(() => endOfPlayAction.Invoke(scoreboard));
private void ResolvePossibleSteal(Scoreboard scoreboard) private void ResolvePossibleSteal(Scoreboard scoreboard)
{ {

View File

@@ -8,7 +8,6 @@ internal class VisitingTeamPlay : Play
private readonly TextIO _io; private readonly TextIO _io;
private readonly IRandom _random; private readonly IRandom _random;
private readonly Defense _defense; private readonly Defense _defense;
private bool _playContinues;
public VisitingTeamPlay(TextIO io, IRandom random, Clock clock, Defense defense) public VisitingTeamPlay(TextIO io, IRandom random, Clock clock, Defense defense)
: base(io, random, clock) : base(io, random, clock)
@@ -27,42 +26,39 @@ internal class VisitingTeamPlay : Play
if (shot is JumpShot jumpShot) if (shot is JumpShot jumpShot)
{ {
Resolve(jumpShot, scoreboard); var continuePlay = Resolve(jumpShot, scoreboard);
_io.WriteLine(); _io.WriteLine();
if (!continuePlay) { return false; }
} }
// Either the above resolution has transition to a lay-up while (true)
// or the chosen shot is not a jump shot and has not been resolved yet.
_playContinues |= shot is not JumpShot;
while (_playContinues)
{ {
_playContinues = false; var continuePlay = Resolve(shot, scoreboard);
Resolve(shot, scoreboard);
_io.WriteLine(); _io.WriteLine();
if (!continuePlay) { return false; }
}
} }
return false; // 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) =>
private void Resolve(JumpShot shot, Scoreboard scoreboard) =>
Resolve(shot.ToString(), _defense / 8) Resolve(shot.ToString(), _defense / 8)
.Do(0.35f, () => scoreboard.AddBasket("Shot is good.")) .Do(0.35f, () => scoreboard.AddBasket("Shot is good."))
.Or(0.75f, () => ResolveBadShot(scoreboard, "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(0.9f, () => ResolveFreeThrows(scoreboard, "Player fouled. Two shots."))
.Or(() => _io.WriteLine($"Offensive foul. {scoreboard.Home}'s ball.")); .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) Resolve(shot.ToString(), _defense / 7)
.Do(0.413f, () => scoreboard.AddBasket("Shot is good.")) .Do(0.413f, () => scoreboard.AddBasket("Shot is good."))
.Or(() => ResolveBadShot(scoreboard, "Shot is missed.", 6 / _defense)); .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) Resolve(message, defenseFactor)
.Do(0.5f, () => scoreboard.Turnover($"{scoreboard.Home} controls the rebound.")) .Do(0.5f, () => scoreboard.Turnover($"{scoreboard.Home} controls the rebound."))
.Or(() => ResolveVisitorsRebound(scoreboard)); .Or(() => ResolveVisitorsRebound(scoreboard));
void ResolveVisitorsRebound(Scoreboard scoreboard) private bool ResolveVisitorsRebound(Scoreboard scoreboard)
{ {
_io.Write($"{scoreboard.Visitors} controls the rebound."); _io.Write($"{scoreboard.Visitors} controls the rebound.");
if (_defense == 6 && _random.NextFloat() <= 0.25f) if (_defense == 6 && _random.NextFloat() <= 0.25f)
@@ -70,16 +66,16 @@ internal class VisitingTeamPlay : Play
_io.WriteLine(); _io.WriteLine();
scoreboard.Turnover(); scoreboard.Turnover();
scoreboard.AddBasket($"Ball stolen. Easy lay up for {scoreboard.Home}."); scoreboard.AddBasket($"Ball stolen. Easy lay up for {scoreboard.Home}.");
return; return false;
} }
if (_random.NextFloat() <= 0.5f) if (_random.NextFloat() <= 0.5f)
{ {
_io.WriteLine(); _io.WriteLine();
_io.Write($"Pass back to {scoreboard.Visitors} guard."); _io.Write($"Pass back to {scoreboard.Visitors} guard.");
return; return false;
} }
_playContinues = true; return true;
} }
} }

View File

@@ -12,27 +12,41 @@ internal struct Probably
{ {
private readonly float _defenseFactor; private readonly float _defenseFactor;
private readonly IRandom _random; 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; _defenseFactor = defenseFactor;
_random = random; _random = random;
_done = done; _result = result;
} }
public Probably Do(float probability, Action action) public Probably Do(float probability, Func<bool?> action) =>
{ ShouldResolveAction(probability)
if (!_done && _random.NextFloat() <= probability * _defenseFactor) ? new Probably(_defenseFactor, _random, _result | Resolve(action))
{ : this;
action.Invoke();
return new Probably(_defenseFactor, _random, true);
}
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(float probability, Action action) => Do(probability, action);
public Probably Or(Action action) => Do(1f, action); public Probably Or(float probability, Func<bool?> 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<bool?> action) => action.Invoke();
private readonly bool ShouldResolveAction(float probability)
{
return _result is null && _random.NextFloat() <= probability * _defenseFactor;
}
} }