mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2026-02-05 03:17:03 -08:00
Merge pull request #722 from drewjcooper/csharp-07-basketball
C# 07 basketball
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -6,4 +6,13 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources/*.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\00_Common\dotnet\Games.Common\Games.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
25
07_Basketball/csharp/Clock.cs
Normal file
25
07_Basketball/csharp/Clock.cs
Normal file
@@ -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;
|
||||
}
|
||||
12
07_Basketball/csharp/Defense.cs
Normal file
12
07_Basketball/csharp/Defense.cs
Normal file
@@ -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;
|
||||
}
|
||||
73
07_Basketball/csharp/Game.cs
Normal file
73
07_Basketball/csharp/Game.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
8
07_Basketball/csharp/IRandomExtensions.cs
Normal file
8
07_Basketball/csharp/IRandomExtensions.cs
Normal file
@@ -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));
|
||||
}
|
||||
34
07_Basketball/csharp/IReadWriteExtensions.cs
Normal file
34
07_Basketball/csharp/IReadWriteExtensions.cs
Normal file
@@ -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. ");
|
||||
}
|
||||
}
|
||||
}
|
||||
9
07_Basketball/csharp/JumpShot.cs
Normal file
9
07_Basketball/csharp/JumpShot.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Basketball;
|
||||
|
||||
public class JumpShot : Shot
|
||||
{
|
||||
public JumpShot()
|
||||
: base("Jump shot")
|
||||
{
|
||||
}
|
||||
}
|
||||
28
07_Basketball/csharp/Plays/BallContest.cs
Normal file
28
07_Basketball/csharp/Plays/BallContest.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
93
07_Basketball/csharp/Plays/HomeTeamPlay.cs
Normal file
93
07_Basketball/csharp/Plays/HomeTeamPlay.cs
Normal file
@@ -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<Scoreboard> 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.")));
|
||||
}
|
||||
40
07_Basketball/csharp/Plays/Play.cs
Normal file
40
07_Basketball/csharp/Plays/Play.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
81
07_Basketball/csharp/Plays/VisitingTeamPlay.cs
Normal file
81
07_Basketball/csharp/Plays/VisitingTeamPlay.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
50
07_Basketball/csharp/Probably.cs
Normal file
50
07_Basketball/csharp/Probably.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using Games.Common.Randomness;
|
||||
|
||||
namespace Basketball;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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<bool> 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<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) =>
|
||||
_result is null && _random.NextFloat() <= probability * _defenseFactor;
|
||||
}
|
||||
7
07_Basketball/csharp/Program.cs
Normal file
7
07_Basketball/csharp/Program.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using Basketball;
|
||||
using Games.Common.IO;
|
||||
using Games.Common.Randomness;
|
||||
|
||||
var game = Game.Create(new ConsoleIO(), new RandomNumberGenerator());
|
||||
|
||||
game.Play();
|
||||
5
07_Basketball/csharp/Resources/EndOfFirstHalf.txt
Normal file
5
07_Basketball/csharp/Resources/EndOfFirstHalf.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
***** End of first half *****
|
||||
|
||||
Score: {0}: {1} {2}: {3}
|
||||
|
||||
2
07_Basketball/csharp/Resources/EndOfGame.txt
Normal file
2
07_Basketball/csharp/Resources/EndOfGame.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
***** End of game *****
|
||||
Final score: {0}: {1} {2}: {3}
|
||||
7
07_Basketball/csharp/Resources/EndOfSecondHalf.txt
Normal file
7
07_Basketball/csharp/Resources/EndOfSecondHalf.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
***** End of second half *****
|
||||
|
||||
Score at end of regulation time:
|
||||
{0}: {1} {2}: {3}
|
||||
|
||||
Begin two minute overtime period
|
||||
12
07_Basketball/csharp/Resources/Introduction.txt
Normal file
12
07_Basketball/csharp/Resources/Introduction.txt
Normal file
@@ -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.
|
||||
32
07_Basketball/csharp/Resources/Resource.cs
Normal file
32
07_Basketball/csharp/Resources/Resource.cs
Normal file
@@ -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}'.");
|
||||
}
|
||||
1
07_Basketball/csharp/Resources/Score.txt
Normal file
1
07_Basketball/csharp/Resources/Score.txt
Normal file
@@ -0,0 +1 @@
|
||||
Score: {1} to {3}
|
||||
3
07_Basketball/csharp/Resources/TwoMinutesLeft.txt
Normal file
3
07_Basketball/csharp/Resources/TwoMinutesLeft.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
*** Two minutes left in the game ***
|
||||
|
||||
48
07_Basketball/csharp/Scoreboard.cs
Normal file
48
07_Basketball/csharp/Scoreboard.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using Basketball.Resources;
|
||||
using Games.Common.IO;
|
||||
|
||||
namespace Basketball;
|
||||
|
||||
internal class Scoreboard
|
||||
{
|
||||
private readonly Dictionary<Team, uint> _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]);
|
||||
}
|
||||
37
07_Basketball/csharp/Shot.cs
Normal file
37
07_Basketball/csharp/Shot.cs
Normal file
@@ -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;
|
||||
}
|
||||
10
07_Basketball/csharp/Team.cs
Normal file
10
07_Basketball/csharp/Team.cs
Normal file
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user