Encapsulate shot number and name

This commit is contained in:
Andrew Cooper
2022-04-10 13:10:46 +10:00
parent 1ce5f7b3a7
commit 11ee731832
7 changed files with 89 additions and 36 deletions

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

View File

@@ -13,16 +13,22 @@ internal static class IReadWriteExtensions
} }
} }
public static int ReadShot(this IReadWrite io, string prompt) private static bool TryReadInteger(this IReadWrite io, string prompt, out int value)
{
var floatValue = io.ReadNumber(prompt);
value = (int)floatValue;
return value == floatValue;
}
public static Shot? ReadShot(this IReadWrite io, string prompt)
{ {
while (true) while (true)
{ {
var shot = io.ReadNumber(prompt); if (io.TryReadInteger(prompt, out var value) && Shot.TryGet(value, out var shot))
if ((int)shot == shot && shot >= 0 && shot <= 4) { return (int)shot; } {
return shot;
}
io.Write("Incorrect answer. Retype it. "); io.Write("Incorrect answer. Retype it. ");
} }
} }
}
public static void WriteScore(this IReadWrite io, string format, string opponent, Dictionary<string ,int> score) =>
io.WriteLine(format, "Dartmouth", score["Dartmouth"], opponent, score[opponent]);
}

View File

@@ -0,0 +1,9 @@
namespace Basketball;
public class JumpShot : Shot
{
public JumpShot()
: base("Jump shot")
{
}
}

View File

@@ -3,7 +3,7 @@ using Games.Common.Randomness;
namespace Basketball.Plays; namespace Basketball.Plays;
internal class BallContest : Play internal class BallContest
{ {
private readonly float _probability; private readonly float _probability;
private readonly string _messageFormat; private readonly string _messageFormat;
@@ -11,7 +11,6 @@ internal class BallContest : Play
private readonly IRandom _random; private readonly IRandom _random;
internal BallContest(float probability, string messageFormat, IReadWrite io, IRandom random) internal BallContest(float probability, string messageFormat, IReadWrite io, IRandom random)
: base(io, random)
{ {
_io = io; _io = io;
_probability = probability; _probability = probability;
@@ -19,7 +18,7 @@ internal class BallContest : Play
_random = random; _random = random;
} }
internal override bool Resolve(Scoreboard scoreboard) internal bool Resolve(Scoreboard scoreboard)
{ {
var winner = _random.NextFloat() <= _probability ? scoreboard.Home : scoreboard.Visitors; var winner = _random.NextFloat() <= _probability ? scoreboard.Home : scoreboard.Visitors;
scoreboard.Offense = winner; scoreboard.Offense = winner;

View File

@@ -26,35 +26,35 @@ internal class HomeTeamPlay : Play
if (_random.NextFloat() >= 0.5f && _clock.IsFullTime) { return true; } if (_random.NextFloat() >= 0.5f && _clock.IsFullTime) { return true; }
if (shot == 0) if (shot is null)
{ {
_defense.Set(_io.ReadDefense("Your new defensive alignment is")); _defense.Set(_io.ReadDefense("Your new defensive alignment is"));
_io.WriteLine(); _io.WriteLine();
return false; return false;
} }
if (shot == 1 || shot == 2) if (shot is JumpShot jumpShot)
{ {
if (ClockIncrementsToHalfTime(scoreboard)) { return false; } if (ClockIncrementsToHalfTime(scoreboard)) { return false; }
ResolveJumpShot(scoreboard); Resolve(jumpShot, scoreboard);
} }
_playContinues |= shot >= 3; _playContinues |= shot is not JumpShot;
while (_playContinues) while (_playContinues)
{ {
if (ClockIncrementsToHalfTime(scoreboard)) { return false; } if (ClockIncrementsToHalfTime(scoreboard)) { return false; }
ResolveLayupOrSetShot(scoreboard, shot); Resolve(shot, scoreboard);
} }
return false; return false;
} }
private void ResolveJumpShot(Scoreboard scoreboard) private void Resolve(JumpShot shot, Scoreboard scoreboard)
{ {
var ballContest = new BallContest(0.5f, "Shot is blocked. Ball controlled by {0}.", _io, _random); var ballContest = new BallContest(0.5f, "Shot is blocked. Ball controlled by {0}.", _io, _random);
Resolve("Jump shot", _defense / 8) Resolve(shot.ToString(), _defense / 8)
.Do(0.341f, () => scoreboard.AddBasket("Shot is good")) .Do(0.341f, () => scoreboard.AddBasket("Shot is good"))
.Or(0.682f, () => ResolveShotOffTarget(scoreboard)) .Or(0.682f, () => ResolveShotOffTarget(scoreboard))
.Or(0.782f, () => ballContest.Resolve(scoreboard)) .Or(0.782f, () => ballContest.Resolve(scoreboard))
@@ -62,11 +62,11 @@ internal class HomeTeamPlay : Play
.Or(() => scoreboard.Turnover($"Charging foul. {scoreboard.Home} loses ball.")); .Or(() => scoreboard.Turnover($"Charging foul. {scoreboard.Home} loses ball."));
} }
private void ResolveLayupOrSetShot(Scoreboard scoreboard, int shot) private void Resolve(Shot shot, Scoreboard scoreboard)
{ {
_playContinues = false; _playContinues = false;
Resolve(shot == 3 ? "Lay up." : "Set shot.", _defense / 7) Resolve(shot.ToString(), _defense / 7)
.Do(0.4f, () => scoreboard.AddBasket("Shot is good. Two points.")) .Do(0.4f, () => scoreboard.AddBasket("Shot is good. Two points."))
.Or(0.7f, () => ResolveShotOffTheRim(scoreboard)) .Or(0.7f, () => ResolveShotOffTheRim(scoreboard))
.Or(0.875f, () => ResolveFreeThrows(scoreboard, "Shooter fouled. Two shots.")) .Or(0.875f, () => ResolveFreeThrows(scoreboard, "Shooter fouled. Two shots."))

View File

@@ -23,42 +23,37 @@ internal class VisitingTeamPlay : Play
if (ClockIncrementsToHalfTime(scoreboard)) { return false; } if (ClockIncrementsToHalfTime(scoreboard)) { return false; }
_io.WriteLine(); _io.WriteLine();
var shot = _random.NextFloat(1, 3.5f); var shot = _random.NextShot();
if (shot <= 2) if (shot is JumpShot jumpShot)
{ {
ResolveJumpShot(scoreboard); Resolve(jumpShot, scoreboard);
_io.WriteLine();
} }
_playContinues |= shot > 2; _playContinues |= shot is not JumpShot;
while (_playContinues) while (_playContinues)
{ {
ResolveLayupOrSetShot(scoreboard, shot); _playContinues = false;
Resolve(shot, scoreboard);
_io.WriteLine();
} }
return false; return false;
} }
private void ResolveJumpShot(Scoreboard scoreboard) private void Resolve(JumpShot shot, Scoreboard scoreboard) =>
{ Resolve(shot.ToString(), _defense / 8)
Resolve("Jump shot.", _defense / 8)
.Do(0.35f, () => scoreboard.AddBasket("Shot is good.")) .Do(0.35f, () => scoreboard.AddBasket("Shot is good."))
.Or(0.75f, () => ResolveBadShot(scoreboard, "Shot is off the rim.", _defense * 6)) .Or(0.75f, () => ResolveBadShot(scoreboard, "Shot is off the rim.", _defense * 6))
.Or(0.9f, () => ResolveFreeThrows(scoreboard, "Player fouled. Two shots.")) .Or(0.9f, () => ResolveFreeThrows(scoreboard, "Player fouled. Two shots."))
.Or(() => _io.WriteLine("Offensive foul. Dartmouth's ball.")); .Or(() => _io.WriteLine("Offensive foul. Dartmouth's ball."));
_io.WriteLine();
}
private void ResolveLayupOrSetShot(Scoreboard scoreboard, float shot) private void Resolve(Shot shot, Scoreboard scoreboard) =>
{ Resolve(shot.ToString(), _defense / 7)
_playContinues = false;
Resolve(shot > 3 ? "Set shot." : "Lay up.", _defense / 7)
.Do(0.413f, () => scoreboard.AddBasket("Shot is good.")) .Do(0.413f, () => scoreboard.AddBasket("Shot is good."))
.Or(() => ResolveBadShot(scoreboard, "Shot is missed.", 6 / _defense)); .Or(() => ResolveBadShot(scoreboard, "Shot is missed.", 6 / _defense));
_io.WriteLine();
}
void ResolveBadShot(Scoreboard scoreboard, string message, float defenseFactor) => void ResolveBadShot(Scoreboard scoreboard, string message, float defenseFactor) =>
Resolve(message, defenseFactor) Resolve(message, defenseFactor)

View File

@@ -0,0 +1,36 @@
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"
<= 2 => new JumpShot(),
3 => new Shot("Lay up"),
4 => new Shot("Set shot"),
_ => null
};
return 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;
}