diff --git a/07_Basketball/README.md b/07_Basketball/README.md index 5200748d..ac0662dd 100644 --- a/07_Basketball/README.md +++ b/07_Basketball/README.md @@ -14,7 +14,7 @@ Both teams use the same defense, but you may call it: - Enter (7): Zone - Enter (7.5): None -To change defense, type “0” as your next shot. +To change defense, type "0" as your next shot. Note: The game is biased slightly in favor of Dartmouth. The average probability of a Dartmouth shot being good is 62.95% compared to a probability of 61.85% for their opponent. (This makes the sample run slightly remarkable in that Cornell won by a score of 45 to 42 Hooray for the Big Red!) @@ -33,3 +33,19 @@ http://www.vintage-basic.net/games.html (please note any difficulties or challenges in porting here) +##### Original bugs + +###### Initial defense selection + +If a number <6 is entered for the starting defense then the original code prompts again until a value >=6 is entered, +but then skips the opponent selection center jump. + +The C# port does not reproduce this behavior. It does prompt for a correct value, but will then go to opponent selection +followed by the center jump. + +###### Unvalidated defense selection + +The original code does not validate the value entered for the defense beyond checking that it is >=6. A large enough +defense value will guarantee that all shots are good, and the game gets rather predictable. + +This bug is preserved in the C# port. diff --git a/07_Basketball/csharp/Basketball.csproj b/07_Basketball/csharp/Basketball.csproj index d3fe4757..91e759c0 100644 --- a/07_Basketball/csharp/Basketball.csproj +++ b/07_Basketball/csharp/Basketball.csproj @@ -6,4 +6,13 @@ enable enable + + + + + + + + + diff --git a/07_Basketball/csharp/Clock.cs b/07_Basketball/csharp/Clock.cs new file mode 100644 index 00000000..25033052 --- /dev/null +++ b/07_Basketball/csharp/Clock.cs @@ -0,0 +1,25 @@ +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); } + 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 new file mode 100644 index 00000000..5a7d6ffa --- /dev/null +++ b/07_Basketball/csharp/Game.cs @@ -0,0 +1,73 @@ +using Basketball.Plays; +using Basketball.Resources; +using Games.Common.IO; +using Games.Common.Randomness; + +namespace Basketball; + +internal class Game +{ + private readonly Clock _clock; + private readonly Scoreboard _scoreboard; + private readonly TextIO _io; + private readonly IRandom _random; + + private Game(Clock clock, Scoreboard scoreboard, TextIO io, IRandom random) + { + _clock = clock; + _scoreboard = scoreboard; + _io = io; + _random = random; + } + + public static Game Create(TextIO io, IRandom random) + { + io.Write(Resource.Streams.Introduction); + + var defense = new Defense(io.ReadDefense("Your starting defense will be")); + var clock = new Clock(io); + + io.WriteLine(); + + var scoreboard = new Scoreboard( + new Team("Dartmouth", new HomeTeamPlay(io, random, clock, defense)), + new Team(io.ReadString("Choose your opponent"), new VisitingTeamPlay(io, random, clock, defense)), + io); + + return new Game(clock, scoreboard, io, random); + } + + public void Play() + { + var ballContest = new BallContest(0.4f, "{0} controls the tap", _io, _random); + + while (true) + { + _io.WriteLine("Center jump"); + ballContest.Resolve(_scoreboard); + + _io.WriteLine(); + + while (true) + { + var isFullTime = _scoreboard.Offense.ResolvePlay(_scoreboard); + if (isFullTime && IsGameOver()) { return; } + if (_clock.IsHalfTime) { break; } + } + } + } + + private bool IsGameOver() + { + _io.WriteLine(); + if (_scoreboard.ScoresAreEqual) + { + _scoreboard.Display(Resource.Formats.EndOfSecondHalf); + _clock.StartOvertime(); + return false; + } + + _scoreboard.Display(Resource.Formats.EndOfGame); + return true; + } +} diff --git a/07_Basketball/csharp/IRandomExtensions.cs b/07_Basketball/csharp/IRandomExtensions.cs new file mode 100644 index 00000000..876e05a2 --- /dev/null +++ b/07_Basketball/csharp/IRandomExtensions.cs @@ -0,0 +1,8 @@ +using Games.Common.Randomness; + +namespace Basketball; + +internal static class IRandomExtensions +{ + internal static Shot NextShot(this IRandom random) => Shot.Get(random.NextFloat(1, 3.5f)); +} diff --git a/07_Basketball/csharp/IReadWriteExtensions.cs b/07_Basketball/csharp/IReadWriteExtensions.cs new file mode 100644 index 00000000..6197ff97 --- /dev/null +++ b/07_Basketball/csharp/IReadWriteExtensions.cs @@ -0,0 +1,34 @@ +using Games.Common.IO; + +namespace Basketball; + +internal static class IReadWriteExtensions +{ + public static float ReadDefense(this IReadWrite io, string prompt) + { + while (true) + { + var defense = io.ReadNumber(prompt); + if (defense >= 6) { return defense; } + } + } + + private static bool TryReadInteger(this IReadWrite io, string prompt, out int intValue) + { + var floatValue = io.ReadNumber(prompt); + intValue = (int)floatValue; + return intValue == floatValue; + } + + public static Shot? ReadShot(this IReadWrite io, string prompt) + { + while (true) + { + if (io.TryReadInteger(prompt, out var value) && Shot.TryGet(value, out var shot)) + { + return shot; + } + io.Write("Incorrect answer. Retype it. "); + } + } +} diff --git a/07_Basketball/csharp/JumpShot.cs b/07_Basketball/csharp/JumpShot.cs new file mode 100644 index 00000000..46ffc6d8 --- /dev/null +++ b/07_Basketball/csharp/JumpShot.cs @@ -0,0 +1,9 @@ +namespace Basketball; + +public class JumpShot : Shot +{ + public JumpShot() + : base("Jump shot") + { + } +} \ No newline at end of file diff --git a/07_Basketball/csharp/Plays/BallContest.cs b/07_Basketball/csharp/Plays/BallContest.cs new file mode 100644 index 00000000..a12ddc36 --- /dev/null +++ b/07_Basketball/csharp/Plays/BallContest.cs @@ -0,0 +1,28 @@ +using Games.Common.IO; +using Games.Common.Randomness; + +namespace Basketball.Plays; + +internal class BallContest +{ + 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) + { + _io = io; + _probability = probability; + _messageFormat = messageFormat; + _random = random; + } + + internal 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..48ef249e --- /dev/null +++ b/07_Basketball/csharp/Plays/HomeTeamPlay.cs @@ -0,0 +1,93 @@ +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; + private readonly BallContest _ballContest; + + public HomeTeamPlay(TextIO io, IRandom random, Clock clock, Defense defense) + : base(io, random, clock) + { + _io = io; + _random = random; + _clock = clock; + _defense = defense; + _ballContest = new BallContest(0.5f, "Shot is blocked. Ball controlled by {0}.", _io, _random); + } + + internal override bool Resolve(Scoreboard scoreboard) + { + var shot = _io.ReadShot("Your shot"); + + if (_random.NextFloat() >= 0.5f && _clock.IsFullTime) { return true; } + + if (shot is null) + { + _defense.Set(_io.ReadDefense("Your new defensive alignment is")); + _io.WriteLine(); + return false; + } + + if (shot is JumpShot jumpShot) + { + if (ClockIncrementsToHalfTime(scoreboard)) { return false; } + if (!Resolve(jumpShot, scoreboard)) { return false; } + } + + do + { + if (ClockIncrementsToHalfTime(scoreboard)) { return false; } + } while (Resolve(shot, scoreboard)); + + return false; + } + + // 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) + .Do(0.341f, () => scoreboard.AddBasket("Shot is good")) + .Or(0.682f, () => ResolveShotOffTarget(scoreboard)) + .Or(0.782f, () => _ballContest.Resolve(scoreboard)) + .Or(0.843f, () => ResolveFreeThrows(scoreboard, "Shooter is fouled. Two shots.")) + .Or(() => scoreboard.Turnover($"Charging foul. {scoreboard.Home} loses ball.")); + + private bool Resolve(Shot shot, Scoreboard scoreboard) => + Resolve(shot.ToString(), _defense / 7) + .Do(0.4f, () => scoreboard.AddBasket("Shot is good. Two points.")) + .Or(0.7f, () => ResolveShotOffTheRim(scoreboard)) + .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. {scoreboard.Home} loses ball.")); + + private bool ResolveShotOffTarget(Scoreboard scoreboard) => + Resolve("Shot is off target", 6 / _defense) + .Do(0.45f, () => ResolveHomeRebound(scoreboard, ResolvePossibleSteal)) + .Or(() => scoreboard.Turnover($"Rebound to {scoreboard.Visitors}")); + + private bool ResolveHomeRebound(Scoreboard scoreboard, Action endOfPlayAction) => + Resolve($"{scoreboard.Home} controls the rebound.") + .Do(0.4f, () => true) + .Or(() => endOfPlayAction.Invoke(scoreboard)); + private void ResolvePossibleSteal(Scoreboard scoreboard) + { + 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. "); + } + + private void ResolveShotOffTheRim(Scoreboard scoreboard) => + Resolve("Shot is off the rim.") + .Do(2 / 3f, () => scoreboard.Turnover($"{scoreboard.Visitors} controls the rebound.")) + .Or(() => ResolveHomeRebound(scoreboard, _ => _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..9b82a74e --- /dev/null +++ b/07_Basketball/csharp/Plays/Play.cs @@ -0,0 +1,40 @@ +using Games.Common.IO; +using Games.Common.Randomness; + +namespace Basketball.Plays; + +internal abstract class Play +{ + private readonly IReadWrite _io; + private readonly IRandom _random; + private readonly Clock _clock; + + public Play(IReadWrite io, IRandom random, Clock clock) + { + _io = io; + _random = random; + _clock = clock; + } + + protected bool ClockIncrementsToHalfTime(Scoreboard scoreboard) + { + _clock.Increment(scoreboard); + return _clock.IsHalfTime; + } + + 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..3975a6ba --- /dev/null +++ b/07_Basketball/csharp/Plays/VisitingTeamPlay.cs @@ -0,0 +1,81 @@ +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 Defense _defense; + + public VisitingTeamPlay(TextIO io, IRandom random, Clock clock, Defense defense) + : base(io, random, clock) + { + _io = io; + _random = random; + _defense = defense; + } + + internal override bool Resolve(Scoreboard scoreboard) + { + if (ClockIncrementsToHalfTime(scoreboard)) { return false; } + + _io.WriteLine(); + var shot = _random.NextShot(); + + if (shot is JumpShot jumpShot) + { + var continuePlay = Resolve(jumpShot, scoreboard); + _io.WriteLine(); + if (!continuePlay) { return false; } + } + + while (true) + { + var continuePlay = Resolve(shot, scoreboard); + _io.WriteLine(); + if (!continuePlay) { 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) => + Resolve(shot.ToString(), _defense / 8) + .Do(0.35f, () => scoreboard.AddBasket("Shot is good.")) + .Or(0.75f, () => ResolveBadShot(scoreboard, "Shot is off the rim.", _defense * 6)) + .Or(0.9f, () => ResolveFreeThrows(scoreboard, "Player fouled. Two shots.")) + .Or(() => _io.WriteLine($"Offensive foul. {scoreboard.Home}'s ball.")); + + private bool Resolve(Shot shot, Scoreboard scoreboard) => + Resolve(shot.ToString(), _defense / 7) + .Do(0.413f, () => scoreboard.AddBasket("Shot is good.")) + .Or(() => ResolveBadShot(scoreboard, "Shot is missed.", 6 / _defense)); + + private bool ResolveBadShot(Scoreboard scoreboard, string message, float defenseFactor) => + Resolve(message, defenseFactor) + .Do(0.5f, () => scoreboard.Turnover($"{scoreboard.Home} controls the rebound.")) + .Or(() => ResolveVisitorsRebound(scoreboard)); + + private bool ResolveVisitorsRebound(Scoreboard scoreboard) + { + _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 {scoreboard.Home}."); + return false; + } + + if (_random.NextFloat() <= 0.5f) + { + _io.WriteLine(); + _io.Write($"Pass back to {scoreboard.Visitors} guard."); + return false; + } + + return true; + } +} diff --git a/07_Basketball/csharp/Probably.cs b/07_Basketball/csharp/Probably.cs new file mode 100644 index 00000000..0ba5864a --- /dev/null +++ b/07_Basketball/csharp/Probably.cs @@ -0,0 +1,50 @@ +using Games.Common.Randomness; + +namespace Basketball; + +/// +/// Supports a chain of actions to be performed based on various probabilities. The original game code gets a new +/// random number for each probability check. Evaluating a set of probabilities against a single random number is +/// much simpler, but yield a very different outcome distribution. The purpose of this class is to simplify the code +/// to for the original probabilistic branch decisions. +/// +internal struct Probably +{ + private readonly float _defenseFactor; + private readonly IRandom _random; + private readonly bool? _result; + + internal Probably(float defenseFactor, IRandom random, bool? result = null) + { + _defenseFactor = defenseFactor; + _random = random; + _result = result; + } + + public Probably Do(float probability, Action action) => + ShouldResolveAction(probability) + ? new Probably(_defenseFactor, _random, Resolve(action) ?? false) + : this; + + public Probably Do(float probability, Func action) => + ShouldResolveAction(probability) + ? new Probably(_defenseFactor, _random, Resolve(action) ?? false) + : this; + + public Probably Or(float probability, Action action) => Do(probability, action); + + public Probably Or(float probability, Func 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 action) => action.Invoke(); + + private readonly bool ShouldResolveAction(float probability) => + _result is null && _random.NextFloat() <= probability * _defenseFactor; +} diff --git a/07_Basketball/csharp/Program.cs b/07_Basketball/csharp/Program.cs new file mode 100644 index 00000000..7a610336 --- /dev/null +++ b/07_Basketball/csharp/Program.cs @@ -0,0 +1,7 @@ +using Basketball; +using Games.Common.IO; +using Games.Common.Randomness; + +var game = Game.Create(new ConsoleIO(), new RandomNumberGenerator()); + +game.Play(); \ No newline at end of file diff --git a/07_Basketball/csharp/Resources/EndOfFirstHalf.txt b/07_Basketball/csharp/Resources/EndOfFirstHalf.txt new file mode 100644 index 00000000..6a132a0e --- /dev/null +++ b/07_Basketball/csharp/Resources/EndOfFirstHalf.txt @@ -0,0 +1,5 @@ + + ***** End of first half ***** + +Score: {0}: {1} {2}: {3} + diff --git a/07_Basketball/csharp/Resources/EndOfGame.txt b/07_Basketball/csharp/Resources/EndOfGame.txt new file mode 100644 index 00000000..fc65416c --- /dev/null +++ b/07_Basketball/csharp/Resources/EndOfGame.txt @@ -0,0 +1,2 @@ + ***** End of game ***** +Final score: {0}: {1} {2}: {3} \ No newline at end of file diff --git a/07_Basketball/csharp/Resources/EndOfSecondHalf.txt b/07_Basketball/csharp/Resources/EndOfSecondHalf.txt new file mode 100644 index 00000000..c5f73218 --- /dev/null +++ b/07_Basketball/csharp/Resources/EndOfSecondHalf.txt @@ -0,0 +1,7 @@ + + ***** End of second half ***** + +Score at end of regulation time: + {0}: {1} {2}: {3} + +Begin two minute overtime period \ No newline at end of file diff --git a/07_Basketball/csharp/Resources/Introduction.txt b/07_Basketball/csharp/Resources/Introduction.txt new file mode 100644 index 00000000..b5ad510b --- /dev/null +++ b/07_Basketball/csharp/Resources/Introduction.txt @@ -0,0 +1,12 @@ + Basketball + Creative Computing Morristown, New Jersey + + + +This is Dartmouth College basketball. You will be Dartmouth + captain and playmaker. Call shots as follows: 1. Long + (30 ft.) jump shot; 2. Short (15 ft.) jump shot; 3. Lay + up; 4. Set shot. +Both teams will use the same defense. Call defense as +follows: 6. Press; 6.5 Man-to-man; 7. Zone; 7.5 None. +To change defense, just type 0 as your next shot. \ No newline at end of file diff --git a/07_Basketball/csharp/Resources/Resource.cs b/07_Basketball/csharp/Resources/Resource.cs new file mode 100644 index 00000000..c442a3ff --- /dev/null +++ b/07_Basketball/csharp/Resources/Resource.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Basketball.Resources; + +internal static class Resource +{ + internal static class Streams + { + public static Stream Introduction => GetStream(); + public static Stream TwoMinutesLeft => GetStream(); + } + + internal static class Formats + { + public static string EndOfFirstHalf => GetString(); + public static string EndOfGame => GetString(); + public static string EndOfSecondHalf => GetString(); + public static string Score => GetString(); + } + + private static string GetString([CallerMemberName] string? name = null) + { + using var stream = GetStream(name); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + private static Stream GetStream([CallerMemberName] string? name = null) => + Assembly.GetExecutingAssembly().GetManifestResourceStream($"Basketball.Resources.{name}.txt") + ?? throw new Exception($"Could not find embedded resource stream '{name}'."); +} \ No newline at end of file diff --git a/07_Basketball/csharp/Resources/Score.txt b/07_Basketball/csharp/Resources/Score.txt new file mode 100644 index 00000000..7b317cb3 --- /dev/null +++ b/07_Basketball/csharp/Resources/Score.txt @@ -0,0 +1 @@ +Score: {1} to {3} \ No newline at end of file diff --git a/07_Basketball/csharp/Resources/TwoMinutesLeft.txt b/07_Basketball/csharp/Resources/TwoMinutesLeft.txt new file mode 100644 index 00000000..0ad16993 --- /dev/null +++ b/07_Basketball/csharp/Resources/TwoMinutesLeft.txt @@ -0,0 +1,3 @@ + + *** Two minutes left in the game *** + diff --git a/07_Basketball/csharp/Scoreboard.cs b/07_Basketball/csharp/Scoreboard.cs new file mode 100644 index 00000000..79068392 --- /dev/null +++ b/07_Basketball/csharp/Scoreboard.cs @@ -0,0 +1,48 @@ +using Basketball.Resources; +using Games.Common.IO; + +namespace Basketball; + +internal class Scoreboard +{ + private readonly Dictionary _scores; + private readonly IReadWrite _io; + + public Scoreboard(Team home, Team visitors, IReadWrite io) + { + _scores = new() { [home] = 0, [visitors] = 0 }; + Home = home; + Visitors = visitors; + Offense = home; // temporary value till first center jump + _io = io; + } + + 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); + + public void AddFreeThrows(uint count, string message) => AddScore(count, message); + + private void AddScore(uint score, string message) + { + if (Offense is null) { throw new InvalidOperationException("Offense must be set before adding to score."); } + + _io.WriteLine(message); + _scores[Offense] += score; + Turnover(); + Display(); + } + + public void Turnover(string? message = null) + { + if (message is not null) { _io.WriteLine(message); } + + Offense = Offense == Home ? Visitors : Home; + } + + public void Display(string? format = null) => + _io.WriteLine(format ?? Resource.Formats.Score, Home, _scores[Home], Visitors, _scores[Visitors]); +} diff --git a/07_Basketball/csharp/Shot.cs b/07_Basketball/csharp/Shot.cs new file mode 100644 index 00000000..fb7c2f3e --- /dev/null +++ b/07_Basketball/csharp/Shot.cs @@ -0,0 +1,37 @@ +namespace Basketball; + +public class Shot +{ + private readonly string _name; + + public Shot(string name) + { + _name = name; + } + + public static bool TryGet(int shotNumber, out Shot? shot) + { + shot = shotNumber switch + { + // Although the game instructions reference two different jump shots, + // the original game code treats them both the same and just prints "Jump shot" + 0 => null, + <= 2 => new JumpShot(), + 3 => new Shot("Lay up"), + 4 => new Shot("Set shot"), + _ => null + }; + return shotNumber == 0 || shot is not null; + } + + public static Shot Get(float shotNumber) => + shotNumber switch + { + <= 2 => new JumpShot(), + > 3 => new Shot("Set shot"), + > 2 => new Shot("Lay up"), + _ => throw new Exception("Unexpected value") + }; + + public override string ToString() => _name; +} diff --git a/07_Basketball/csharp/Team.cs b/07_Basketball/csharp/Team.cs new file mode 100644 index 00000000..2cf262aa --- /dev/null +++ b/07_Basketball/csharp/Team.cs @@ -0,0 +1,10 @@ +using Basketball.Plays; + +namespace Basketball; + +internal record Team(string Name, Play PlayResolver) +{ + public override string ToString() => Name; + + public bool ResolvePlay(Scoreboard scoreboard) => PlayResolver.Resolve(scoreboard); +}