mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-22 23:26:40 -08:00
Simplify play continuation logic
This commit is contained in:
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user