diff --git a/07_Basketball/csharp/Game.cs b/07_Basketball/csharp/Game.cs index d51b49c1..391e70d8 100644 --- a/07_Basketball/csharp/Game.cs +++ b/07_Basketball/csharp/Game.cs @@ -1,3 +1,4 @@ +using Basketball.Plays; using Basketball.Resources; using Games.Common.IO; using Games.Common.Randomness; @@ -25,14 +26,16 @@ internal class Game _io.WriteLine(); var scoreboard = new Scoreboard( - new Team("Dartmouth", HomeTeamPlay(clock, defense)), - new Team(_io.ReadString("Choose your opponent"), VisitingTeamPlay(clock, defense)), + new Team("Dartmouth", new HomeTeamPlay(_io, _random, clock, defense)), + new Team(_io.ReadString("Choose your opponent"), new VisitingTeamPlay(_io, _random, clock, defense)), _io); + var ballContest = new BallContest(0.4f, "{0} controls the tap", _io, _random); + while (true) { _io.WriteLine("Center jump"); - ContestBall(0.4f, scoreboard, "{0} controls the tap"); + ballContest.Resolve(scoreboard); _io.WriteLine(); @@ -44,167 +47,4 @@ internal class Game } } } - - private Func HomeTeamPlay(Clock clock, Defense defense) => scoreboard => - { - var shot = _io.ReadShot("Your shot"); - - if (_random.NextFloat() >= 0.5f && clock.IsFullTime) - { - _io.WriteLine(); - if (!scoreboard.ScoresAreEqual) - { - scoreboard.Display(Resource.Formats.EndOfGame); - return true; - } - - scoreboard.Display(Resource.Formats.EndOfSecondHalf); - clock.StartOvertime(); - scoreboard.StartPeriod(); - return false; - } - - if (shot == 0) - { - defense.Set(_io.ReadDefense("Your new defensive alignment is")); - _io.WriteLine(); - return false; - } - - var playContinues = shot >= 3; - - if (shot == 1 || shot == 2) - { - clock.Increment(scoreboard); - if (clock.IsHalfTime) { return false; } - - 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) - { - clock.Increment(scoreboard); - if (clock.IsHalfTime) { return false; } - - playContinues = false; - - 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 => - { - clock.Increment(scoreboard); - if (clock.IsHalfTime) { return false; } - - - _io.WriteLine(); - var shot = _random.NextFloat(1, 3.5f); - var playContinues = shot > 2; - - if (shot <= 2) - { - 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.")); - - _io.WriteLine(); - } - - while (playContinues) - { - playContinues = false; - - 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; - } - }; - - void ContestBall(float probability, Scoreboard scoreboard, string messageFormat) - { - var winner = _random.NextFloat() <= probability ? scoreboard.Home : scoreboard.Visitors; - scoreboard.Offense = winner; - _io.WriteLine(messageFormat, winner); - } - - 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); - return new Probably(defenseFactor, _random); - } } diff --git a/07_Basketball/csharp/Plays/BallContest.cs b/07_Basketball/csharp/Plays/BallContest.cs new file mode 100644 index 00000000..a26e7c06 --- /dev/null +++ b/07_Basketball/csharp/Plays/BallContest.cs @@ -0,0 +1,29 @@ +using Games.Common.IO; +using Games.Common.Randomness; + +namespace Basketball.Plays; + +internal class BallContest : Play +{ + private readonly float _probability; + private readonly string _messageFormat; + private readonly IReadWrite _io; + private readonly IRandom _random; + + internal BallContest(float probability, string messageFormat, IReadWrite io, IRandom random) + : base(io, random) + { + _io = io; + _probability = probability; + _messageFormat = messageFormat; + _random = random; + } + + internal override bool Resolve(Scoreboard scoreboard) + { + var winner = _random.NextFloat() <= _probability ? scoreboard.Home : scoreboard.Visitors; + scoreboard.Offense = winner; + _io.WriteLine(_messageFormat, winner); + return false; + } +} diff --git a/07_Basketball/csharp/Plays/HomeTeamPlay.cs b/07_Basketball/csharp/Plays/HomeTeamPlay.cs new file mode 100644 index 00000000..f79d6388 --- /dev/null +++ b/07_Basketball/csharp/Plays/HomeTeamPlay.cs @@ -0,0 +1,106 @@ +using Basketball.Resources; +using Games.Common.IO; +using Games.Common.Randomness; + +namespace Basketball.Plays; + +internal class HomeTeamPlay : Play +{ + private readonly TextIO _io; + private readonly IRandom _random; + private readonly Clock _clock; + private readonly Defense _defense; + + public HomeTeamPlay(TextIO io, IRandom random, Clock clock, Defense defense) + : base(io, random) + { + _io = io; + _random = random; + _clock = clock; + _defense = defense; + } + + internal override bool Resolve(Scoreboard scoreboard) + { + var shot = _io.ReadShot("Your shot"); + + if (_random.NextFloat() >= 0.5f && _clock.IsFullTime) + { + _io.WriteLine(); + if (!scoreboard.ScoresAreEqual) + { + scoreboard.Display(Resource.Formats.EndOfGame); + return true; + } + + scoreboard.Display(Resource.Formats.EndOfSecondHalf); + _clock.StartOvertime(); + scoreboard.StartPeriod(); + return false; + } + + if (shot == 0) + { + _defense.Set(_io.ReadDefense("Your new defensive alignment is")); + _io.WriteLine(); + return false; + } + + var playContinues = shot >= 3; + + if (shot == 1 || shot == 2) + { + _clock.Increment(scoreboard); + if (_clock.IsHalfTime) { return false; } + + var ballContest = new BallContest(0.5f, "Shot is blocked. Ball controlled by {0}.", _io, _random); + + Resolve("Jump shot", _defense / 8) + .Do(0.341f, () => scoreboard.AddBasket("Shot is good")) + .Or(0.682f, () => ResolveShotOffTarget()) + .Or(0.782f, () => ballContest.Resolve(scoreboard)) + .Or(0.843f, () => ResolveFreeThrows(scoreboard, "Shooter is fouled. Two shots.")) + .Or(() => scoreboard.Turnover("Charging foul. Dartmouth loses ball.")); + } + + while (playContinues) + { + _clock.Increment(scoreboard); + if (_clock.IsHalfTime) { return false; } + + playContinues = false; + + 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."))); + } +} diff --git a/07_Basketball/csharp/Plays/Play.cs b/07_Basketball/csharp/Plays/Play.cs new file mode 100644 index 00000000..0edba943 --- /dev/null +++ b/07_Basketball/csharp/Plays/Play.cs @@ -0,0 +1,32 @@ +using Games.Common.IO; +using Games.Common.Randomness; + +namespace Basketball.Plays; + +internal abstract class Play +{ + private readonly IReadWrite _io; + private readonly IRandom _random; + + public Play(IReadWrite io, IRandom random) + { + _io = io; + _random = random; + } + + internal abstract bool Resolve(Scoreboard scoreboard); + + protected 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.")); + + protected Probably Resolve(string message) => Resolve(message, 1f); + + protected Probably Resolve(string message, float defenseFactor) + { + _io.WriteLine(message); + return new Probably(defenseFactor, _random); + } +} diff --git a/07_Basketball/csharp/Plays/VisitingTeamPlay.cs b/07_Basketball/csharp/Plays/VisitingTeamPlay.cs new file mode 100644 index 00000000..f9e8e460 --- /dev/null +++ b/07_Basketball/csharp/Plays/VisitingTeamPlay.cs @@ -0,0 +1,80 @@ +using Games.Common.IO; +using Games.Common.Randomness; + +namespace Basketball.Plays; + +internal class VisitingTeamPlay : Play +{ + private readonly TextIO _io; + private readonly IRandom _random; + private readonly Clock _clock; + private readonly Defense _defense; + + public VisitingTeamPlay(TextIO io, IRandom random, Clock clock, Defense defense) + : base(io, random) + { + _io = io; + _random = random; + _clock = clock; + _defense = defense; + } + + internal override bool Resolve(Scoreboard scoreboard) + { + _clock.Increment(scoreboard); + if (_clock.IsHalfTime) { return false; } + + _io.WriteLine(); + var shot = _random.NextFloat(1, 3.5f); + var playContinues = shot > 2; + + if (shot <= 2) + { + 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.")); + + _io.WriteLine(); + } + + while (playContinues) + { + playContinues = false; + + 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; + } + } +} diff --git a/07_Basketball/csharp/Team.cs b/07_Basketball/csharp/Team.cs index f6a58aaf..2cf262aa 100644 --- a/07_Basketball/csharp/Team.cs +++ b/07_Basketball/csharp/Team.cs @@ -1,8 +1,10 @@ +using Basketball.Plays; + namespace Basketball; -internal record Team(string Name, Func PlayResolution) +internal record Team(string Name, Play PlayResolver) { public override string ToString() => Name; - public bool ResolvePlay(Scoreboard scoreboard) => PlayResolution.Invoke(scoreboard); + public bool ResolvePlay(Scoreboard scoreboard) => PlayResolver.Resolve(scoreboard); }