Add probabilistic branching helper

This commit is contained in:
Andrew Cooper
2022-04-05 05:06:17 +10:00
parent 029e27cd2c
commit aaaadc04ea
2 changed files with 122 additions and 182 deletions

View File

@@ -32,16 +32,16 @@ internal class Game
while (true) while (true)
{ {
_io.WriteLine("Center jump"); _io.WriteLine("Center jump");
scoreboard.Offense = ContestBall(0.4f, scoreboard, "{0} controls the tap"); ContestBall(0.4f, scoreboard, "{0} controls the tap");
_io.WriteLine(); _io.WriteLine();
do while (scoreboard.Offense is not null)
{ {
var gameOver = scoreboard.Offense.ResolvePlay(scoreboard); var gameOver = scoreboard.Offense.ResolvePlay(scoreboard);
if (gameOver) { return; } if (gameOver) { return; }
if (clock.IsHalfTime) { scoreboard.StartPeriod(); } if (clock.IsHalfTime) { scoreboard.StartPeriod(); }
} while (scoreboard.Offense is not null); }
} }
} }
@@ -67,62 +67,23 @@ internal class Game
if (shot == 0) if (shot == 0)
{ {
defense.Set(_io.ReadDefense("Your new defensive alignment is")); defense.Set(_io.ReadDefense("Your new defensive alignment is"));
_io.WriteLine();
return false; return false;
} }
var playContinues = false; var playContinues = shot >= 3;
if (shot == 1 || shot == 2) if (shot == 1 || shot == 2)
{ {
clock.Increment(scoreboard); clock.Increment(scoreboard);
if (clock.IsHalfTime) { return false; } if (clock.IsHalfTime) { return false; }
_io.WriteLine("Jump shot"); Resolve("Jump shot", defense / 8)
if (_random.NextFloat() <= 0.341f * defense / 8) .Do(0.341f, () => scoreboard.AddBasket("Shot is good"))
{ .Or(0.682f, () => ResolveShotOffTarget())
scoreboard.AddBasket("Shot is good"); .Or(0.782f, () => ContestBall(0.5f, scoreboard, "Shot is blocked. Ball controlled by {0}."))
} .Or(0.843f, () => ResolveFreeThrows(scoreboard, "Shooter is fouled. Two shots."))
else if (_random.NextFloat() <= 0.682f * defense / 8) .Or(() => scoreboard.Turnover("Charging foul. Dartmouth loses ball."));
{
_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.");
}
} }
while (playContinues) while (playContinues)
@@ -132,47 +93,38 @@ internal class Game
playContinues = false; playContinues = false;
_io.WriteLine(shot == 3 ? "Lay up." : "Set shot."); Resolve(shot == 3 ? "Lay up." : "Set shot.", defense / 7)
.Do(0.4f, () => scoreboard.AddBasket("Shot is good. Two points."))
if (_random.NextFloat() <= 0.4f * defense / 7) .Or(0.7f, () => ResolveShotOffTheRim())
{ .Or(0.875f, () => ResolveFreeThrows(scoreboard, "Shooter fouled. Two shots."))
scoreboard.AddBasket("Shot is good. Two points."); .Or(0.925f, () => scoreboard.Turnover($"Shot blocked. {scoreboard.Visitors}'s ball."))
} .Or(() => scoreboard.Turnover("Charging foul. Dartmouth loses ball."));
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.");
}
} }
return false; 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<Scoreboard, bool> VisitingTeamPlay(Clock clock, Defense defense) => scoreboard => private Func<Scoreboard, bool> VisitingTeamPlay(Clock clock, Defense defense) => scoreboard =>
@@ -180,123 +132,79 @@ internal class Game
clock.Increment(scoreboard); clock.Increment(scoreboard);
if (clock.IsHalfTime) { return false; } if (clock.IsHalfTime) { return false; }
var playContinues = false;
_io.WriteLine(); _io.WriteLine();
var shot = _random.NextFloat(1, 3.5f); var shot = _random.NextFloat(1, 3.5f);
var playContinues = shot > 2;
if (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) _io.WriteLine();
{
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.");
}
} }
while (playContinues) while (playContinues)
{ {
playContinues = false; playContinues = false;
_io.WriteLine(shot > 3 ? "Set shot." : "Lay up."); Resolve(shot > 3 ? "Set shot." : "Lay up.", defense / 7)
.Do(0.413f, () => scoreboard.AddBasket("Shot is good."))
if (_random.NextFloat() <= 0.413f * defense / 7) .Or(() => ResolveBadShot("Shot is missed.", 6 / defense));
{ _io.WriteLine();
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;
}
}
} }
return false; 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; var winner = _random.NextFloat() <= probability ? scoreboard.Home : scoreboard.Visitors;
scoreboard.Offense = winner;
_io.WriteLine(messageFormat, 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); _io.WriteLine(message);
return new Probably(defenseFactor, _random);
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.");
}
} }
} }

View File

@@ -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);
}