From 1ba6dd48aa16faa835e7fdc0c4fbbfdd2aa57ea1 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Wed, 30 Mar 2022 16:50:48 +1100 Subject: [PATCH] Split out play resolution --- 07_Basketball/csharp/Clock.cs | 32 +++ 07_Basketball/csharp/Defense.cs | 12 + 07_Basketball/csharp/Game.cs | 403 ++++++++++++++--------------- 07_Basketball/csharp/Scoreboard.cs | 15 +- 07_Basketball/csharp/Team.cs | 4 +- 5 files changed, 252 insertions(+), 214 deletions(-) create mode 100644 07_Basketball/csharp/Clock.cs create mode 100644 07_Basketball/csharp/Defense.cs diff --git a/07_Basketball/csharp/Clock.cs b/07_Basketball/csharp/Clock.cs new file mode 100644 index 00000000..deb7c549 --- /dev/null +++ b/07_Basketball/csharp/Clock.cs @@ -0,0 +1,32 @@ +using Basketball.Resources; +using Games.Common.IO; + +namespace Basketball; + +internal class Clock +{ + private readonly IReadWrite _io; + private int time; + + public Clock(IReadWrite io) => _io = io; + + public bool IsHalfTime => time == 50; + public bool IsFullTime => time >= 100; + public bool TwoMinutesLeft => time == 92; + + public void Increment(Scoreboard scoreboard) + { + time += 1; + if (IsHalfTime) + { + scoreboard.Display(Resource.Formats.EndOfFirstHalf); + // Loop back to center jump; + } + if (TwoMinutesLeft) + { + _io.Write(Resource.Streams.TwoMinutesLeft); + } + } + + public void StartOvertime() => time = 93; +} \ No newline at end of file diff --git a/07_Basketball/csharp/Defense.cs b/07_Basketball/csharp/Defense.cs new file mode 100644 index 00000000..f447266f --- /dev/null +++ b/07_Basketball/csharp/Defense.cs @@ -0,0 +1,12 @@ +namespace Basketball; + +internal class Defense +{ + private float _value; + + public Defense(float value) => Set(value); + + public void Set(float value) => _value = value; + + public static implicit operator float(Defense defense) => defense._value; +} diff --git a/07_Basketball/csharp/Game.cs b/07_Basketball/csharp/Game.cs index 22e0ca48..1492cbcc 100644 --- a/07_Basketball/csharp/Game.cs +++ b/07_Basketball/csharp/Game.cs @@ -19,163 +19,109 @@ internal class Game { _io.Write(Resource.Streams.Introduction); - var defense = _io.ReadDefense("Your starting defense will be"); - - var homeTeam = new Team("Dartmouth"); - - _io.WriteLine(); - var visitingTeam = new Team(_io.ReadString("Choose your opponent")); - - var time = 0; - var scoreboard = new Scoreboard(homeTeam, visitingTeam, _io); - - _io.WriteLine("Center jump"); - scoreboard.Offense = ContestBall(0.6f, visitingTeam, homeTeam, "{0} controls the tap"); + var defense = new Defense(_io.ReadDefense("Your starting defense will be")); + var clock = new Clock(_io); _io.WriteLine(); - if (scoreboard.Offense == homeTeam) + var scoreboard = new Scoreboard( + new Team("Dartmouth", HomeTeamPlay(clock, defense)), + new Team(_io.ReadString("Choose your opponent"), VisitingTeamPlay(clock, defense)), + _io); + + while (true) { - var shot = _io.ReadShot("Your shot"); + _io.WriteLine("Center jump"); + scoreboard.Offense = ContestBall(0.4f, scoreboard, "{0} controls the tap"); - if (_random.NextFloat() >= 0.5f && time >= 100) + _io.WriteLine(); + + while (true) { - _io.WriteLine(); - if (scoreboard.ScoresAreEqual) - { - scoreboard.Display(Resource.Formats.EndOfSecondHalf); - time = 93; - // Loop back to center jump - } - else - { - scoreboard.Display(Resource.Formats.EndOfGame); - return; - } + scoreboard.Offense.ResolvePlay(scoreboard); + } + } + } + + private Action 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.EndOfSecondHalf); + clock.StartOvertime(); + // Loop back to center jump } else { - if (shot == 0) - { - defense = _io.ReadDefense("Your new defensive alignment is"); - // go to next shot - } + scoreboard.Display(Resource.Formats.EndOfGame); + return; + } + } + else + { + if (shot == 0) + { + defense.Set(_io.ReadDefense("Your new defensive alignment is")); + // go to next shot + } - if (shot == 1 || shot == 2) + if (shot == 1 || shot == 2) + { + clock.Increment(scoreboard); + if (clock.IsHalfTime) { - time++; - if (time == 50) - { - scoreboard.Display(Resource.Formats.EndOfFirstHalf); - // Loop back to center jump; - } - if (time == 92) - { - _io.Write(Resource.Streams.TwoMinutesLeft); - } - _io.WriteLine("Jump shot"); - if (_random.NextFloat() <= 0.341f * defense / 8) - { - scoreboard.AddBasket("Shot is good"); - // over to opponent - } - else if (_random.NextFloat() <= 0.682f * defense / 8) - { - _io.WriteLine("Shot is off target"); - if (defense / 6 * _random.NextFloat() > 0.45f) - { - scoreboard.Turnover($"Rebound to {visitingTeam}"); - // over to opponent - } - else - { - _io.WriteLine("Dartmouth controls the rebound."); - if (_random.NextFloat() <= 0.4f) - { - // fall through to 1300 - } - else - { - if (defense == 6) - { - if (_random.NextFloat() > 0.6f) - { - scoreboard.Turnover(); - scoreboard.AddBasket($"Pass stolen by {visitingTeam} easy layup."); - _io.WriteLine(); - } - } - _io.Write("Ball passed back to you. "); - // next shot without writeline - } - } - } - else if (_random.NextFloat() <= 0.782f * defense / 8) - { - scoreboard.Offense = - ContestBall(0.5f, homeTeam, visitingTeam, "Shot is blocked. Ball controlled by {0}."); - // go to next shot - } - else if (_random.NextFloat() <= 0.843f * defense / 8) - { - FreeThrows("Shooter is fouled. Two shots."); - // over to opponent - } - else - { - scoreboard.Turnover("Charging foul. Dartmouth loses ball."); - // over to opponent - } - } - // 1300 - time++; - if (time == 50) - { - scoreboard.Display(Resource.Formats.EndOfFirstHalf); // Loop back to center jump; } - if (time == 92) + _io.WriteLine("Jump shot"); + if (_random.NextFloat() <= 0.341f * defense / 8) { - _io.Write(Resource.Streams.TwoMinutesLeft); - } - - _io.WriteLine(shot == 3 ? "Lay up." : "Set shot."); - - if (_random.NextFloat() <= 0.4f * defense / 7) - { - scoreboard.AddBasket("Shot is good. Two points."); + scoreboard.AddBasket("Shot is good"); // over to opponent } - else if (_random.NextFloat() <= 0.7f * defense / 7) + else if (_random.NextFloat() <= 0.682f * defense / 8) { - _io.WriteLine("Shot is off the rim."); - if (_random.NextFloat() <= 2 / 3f) + _io.WriteLine("Shot is off target"); + if (defense / 6 * _random.NextFloat() > 0.45f) { - scoreboard.Turnover($"{visitingTeam} controls the rebound."); + scoreboard.Turnover($"Rebound to {scoreboard.Visitors}"); // over to opponent } else { - _io.WriteLine("Dartmouth controls the rebound"); + _io.WriteLine("Dartmouth controls the rebound."); if (_random.NextFloat() <= 0.4f) { - // goto 1300 + // fall through to 1300 } else { - _io.WriteLine("Ball passed back to you."); - // go to next shot + 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. "); + // next shot without writeline } } } - else if (_random.NextFloat() <= 0.875f * defense / 7) + else if (_random.NextFloat() <= 0.782f * defense / 8) { - FreeThrows("Shooter fouled. Two shots."); - // over to opponent + scoreboard.Offense = ContestBall(0.5f, scoreboard, "Shot is blocked. Ball controlled by {0}."); + // go to next shot } - else if (_random.NextFloat() <= 0.925f * defense / 7) + else if (_random.NextFloat() <= 0.843f * defense / 8) { - scoreboard.Turnover($"Shot blocked. {visitingTeam}'s ball."); + FreeThrows(scoreboard, "Shooter is fouled. Two shots."); // over to opponent } else @@ -184,91 +130,90 @@ internal class Game // over to opponent } } - } - else - { - time++; - if (time == 50) + // 1300 + clock.Increment(scoreboard); + if (clock.IsHalfTime) { - scoreboard.Display(Resource.Formats.EndOfFirstHalf); // Loop back to center jump; } - if (time == 92) + + _io.WriteLine(shot == 3 ? "Lay up." : "Set shot."); + + if (_random.NextFloat() <= 0.4f * defense / 7) { - _io.Write(Resource.Streams.TwoMinutesLeft); + scoreboard.AddBasket("Shot is good. Two points."); + // over to opponent } - - _io.WriteLine(); - var shot = _random.NextFloat(1, 3.5f); - if (shot <= 2) + else if (_random.NextFloat() <= 0.7f * defense / 7) { - _io.WriteLine("Jump shot."); - - if (_random.NextFloat() <= 0.35f * defense / 8) + _io.WriteLine("Shot is off the rim."); + if (_random.NextFloat() <= 2 / 3f) { - scoreboard.AddBasket("Shot is good."); - // over to Dartmouth - } - 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."); - // over to Dartmouth - } - else - { - _io.WriteLine($"{visitingTeam} controls the rebound."); - if (defense == 6) - { - if (_random.NextFloat() <= 0.25f) - { - scoreboard.Turnover(); - scoreboard.AddBasket("Ball stolen. Easy lay up for Dartmouth."); - _io.WriteLine(); - // next opponent shot - } - } - if (_random.NextFloat() <= 0.5f) - { - _io.WriteLine($"Pass back to {visitingTeam} guard."); - // next opponent shot - } - // goto 3500 - } - } - else if (_random.NextFloat() <= 0.9f * defense / 8) - { - FreeThrows("Player fouled. Two shots."); - // next Dartmouth shot + scoreboard.Turnover($"{scoreboard.Visitors} controls the rebound."); + // over to opponent } else { - _io.WriteLine("Offensive foul. Dartmouth's ball."); - // next Dartmouth shot + _io.WriteLine("Dartmouth controls the rebound"); + if (_random.NextFloat() <= 0.4f) + { + // goto 1300 + } + else + { + _io.WriteLine("Ball passed back to you."); + // go to next shot + } } } + else if (_random.NextFloat() <= 0.875f * defense / 7) + { + FreeThrows(scoreboard, "Shooter fouled. Two shots."); + // over to opponent + } + else if (_random.NextFloat() <= 0.925f * defense / 7) + { + scoreboard.Turnover($"Shot blocked. {scoreboard.Visitors}'s ball."); + // over to opponent + } + else + { + scoreboard.Turnover("Charging foul. Dartmouth loses ball."); + // over to opponent + } + } + }; - // 3500 - _io.WriteLine(shot > 3 ? "Set shot." : "Lay up."); + private Action VisitingTeamPlay(Clock clock, Defense defense) => scoreboard => + { + clock.Increment(scoreboard); + if (clock.IsHalfTime) + { + // Loop back to center jump; + } - if (_random.NextFloat() <= 0.413f * defense / 7) + _io.WriteLine(); + var shot = _random.NextFloat(1, 3.5f); + if (shot <= 2) + { + _io.WriteLine("Jump shot."); + + if (_random.NextFloat() <= 0.35f * defense / 8) { scoreboard.AddBasket("Shot is good."); // over to Dartmouth } - else + else if (_random.NextFloat() <= 0.75f * defense / 8) { - _io.WriteLine("Shot is missed."); - if (_random.NextFloat() <= 0.5f * 6 / defense) + _io.WriteLine("Shot is off the rim."); + if (_random.NextFloat() <= 0.5f / defense * 6) { scoreboard.Turnover("Dartmouth controls the rebound."); // over to Dartmouth } else { - _io.WriteLine($"{visitingTeam} controls the rebound."); + _io.WriteLine($"{scoreboard.Visitors} controls the rebound."); if (defense == 6) { if (_random.NextFloat() <= 0.25f) @@ -281,37 +226,85 @@ internal class Game } if (_random.NextFloat() <= 0.5f) { - _io.WriteLine($"Pass back to {visitingTeam} guard."); + _io.WriteLine($"Pass back to {scoreboard.Visitors} guard."); // next opponent shot } // goto 3500 } } - } - - Team ContestBall(float probability, Team a, Team b, string messageFormat) - { - var winner = _random.NextFloat() <= probability ? a : b; - _io.WriteLine(messageFormat, winner); - return winner; - } - - void FreeThrows(string message) - { - _io.WriteLine(message); - - if (_random.NextFloat() <= 0.49) + else if (_random.NextFloat() <= 0.9f * defense / 8) { - scoreboard.AddFreeThrows(2, "Shooter makes both shots."); - } - else if (_random.NextFloat() <= 0.75) - { - scoreboard.AddFreeThrows(1, "Shooter makes one shot and misses one."); + FreeThrows(scoreboard, "Player fouled. Two shots."); + // next Dartmouth shot } else { - scoreboard.AddFreeThrows(0, "Both shots missed."); + _io.WriteLine("Offensive foul. Dartmouth's ball."); + // next Dartmouth shot } } + + // 3500 + _io.WriteLine(shot > 3 ? "Set shot." : "Lay up."); + + if (_random.NextFloat() <= 0.413f * defense / 7) + { + scoreboard.AddBasket("Shot is good."); + // over to Dartmouth + } + else + { + _io.WriteLine("Shot is missed."); + if (_random.NextFloat() <= 0.5f * 6 / defense) + { + scoreboard.Turnover("Dartmouth controls the rebound."); + // over to Dartmouth + } + 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(); + // next opponent shot + } + } + if (_random.NextFloat() <= 0.5f) + { + _io.WriteLine($"Pass back to {scoreboard.Visitors} guard."); + // next opponent shot + } + // goto 3500 + } + } + }; + + Team ContestBall(float probability, Scoreboard scoreboard, string messageFormat) + { + var winner = _random.NextFloat() <= probability ? scoreboard.Home : scoreboard.Visitors; + _io.WriteLine(messageFormat, winner); + return winner; + } + + void FreeThrows(Scoreboard scoreboard, string message) + { + _io.WriteLine(message); + + 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."); + } } } diff --git a/07_Basketball/csharp/Scoreboard.cs b/07_Basketball/csharp/Scoreboard.cs index ae6f1b56..31bdf34d 100644 --- a/07_Basketball/csharp/Scoreboard.cs +++ b/07_Basketball/csharp/Scoreboard.cs @@ -8,20 +8,19 @@ internal class Scoreboard private readonly Dictionary _scores; private readonly IReadWrite _io; - private Team _home; - private Team _visitors; - public Scoreboard(Team home, Team visitors, IReadWrite io) { _scores = new() { [home] = 0, [visitors] = 0 }; - _home = home; - _visitors = visitors; + Home = home; + Visitors = visitors; Offense = home; _io = io; } - public bool ScoresAreEqual => _scores[_home] == _scores[_visitors]; + public bool ScoresAreEqual => _scores[Home] == _scores[Visitors]; public Team Offense { get; set; } + public Team Home { get; } + public Team Visitors { get; } public void AddBasket(string message) => AddScore(2, message); @@ -39,9 +38,9 @@ internal class Scoreboard { if (message is not null) { _io.WriteLine(message); } - Offense = Offense == _home ? _visitors : _home; + Offense = Offense == Home ? Visitors : Home; } public void Display(string? format = null) => - _io.WriteLine(format ?? Resource.Formats.Score, _home, _scores[_home], _visitors, _scores[_visitors]); + _io.WriteLine(format ?? Resource.Formats.Score, Home, _scores[Home], Visitors, _scores[Visitors]); } diff --git a/07_Basketball/csharp/Team.cs b/07_Basketball/csharp/Team.cs index 91181588..d7dccd4e 100644 --- a/07_Basketball/csharp/Team.cs +++ b/07_Basketball/csharp/Team.cs @@ -1,6 +1,8 @@ namespace Basketball; -internal record Team(string Name) +internal record Team(string Name, Action PlayResolution) { public override string ToString() => Name; + + public void ResolvePlay(Scoreboard scoreboard) => PlayResolution.Invoke(scoreboard); }