mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-23 07:29:02 -08:00
Break out classes for resolving plays
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
using Basketball.Plays;
|
||||||
using Basketball.Resources;
|
using Basketball.Resources;
|
||||||
using Games.Common.IO;
|
using Games.Common.IO;
|
||||||
using Games.Common.Randomness;
|
using Games.Common.Randomness;
|
||||||
@@ -25,14 +26,16 @@ internal class Game
|
|||||||
_io.WriteLine();
|
_io.WriteLine();
|
||||||
|
|
||||||
var scoreboard = new Scoreboard(
|
var scoreboard = new Scoreboard(
|
||||||
new Team("Dartmouth", HomeTeamPlay(clock, defense)),
|
new Team("Dartmouth", new HomeTeamPlay(_io, _random, clock, defense)),
|
||||||
new Team(_io.ReadString("Choose your opponent"), VisitingTeamPlay(clock, defense)),
|
new Team(_io.ReadString("Choose your opponent"), new VisitingTeamPlay(_io, _random, clock, defense)),
|
||||||
_io);
|
_io);
|
||||||
|
|
||||||
|
var ballContest = new BallContest(0.4f, "{0} controls the tap", _io, _random);
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
_io.WriteLine("Center jump");
|
_io.WriteLine("Center jump");
|
||||||
ContestBall(0.4f, scoreboard, "{0} controls the tap");
|
ballContest.Resolve(scoreboard);
|
||||||
|
|
||||||
_io.WriteLine();
|
_io.WriteLine();
|
||||||
|
|
||||||
@@ -44,167 +47,4 @@ internal class Game
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Func<Scoreboard, bool> 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<Scoreboard, bool> 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
29
07_Basketball/csharp/Plays/BallContest.cs
Normal file
29
07_Basketball/csharp/Plays/BallContest.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
106
07_Basketball/csharp/Plays/HomeTeamPlay.cs
Normal file
106
07_Basketball/csharp/Plays/HomeTeamPlay.cs
Normal file
@@ -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.")));
|
||||||
|
}
|
||||||
|
}
|
||||||
32
07_Basketball/csharp/Plays/Play.cs
Normal file
32
07_Basketball/csharp/Plays/Play.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
80
07_Basketball/csharp/Plays/VisitingTeamPlay.cs
Normal file
80
07_Basketball/csharp/Plays/VisitingTeamPlay.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
|
using Basketball.Plays;
|
||||||
|
|
||||||
namespace Basketball;
|
namespace Basketball;
|
||||||
|
|
||||||
internal record Team(string Name, Func<Scoreboard, bool> PlayResolution)
|
internal record Team(string Name, Play PlayResolver)
|
||||||
{
|
{
|
||||||
public override string ToString() => Name;
|
public override string ToString() => Name;
|
||||||
|
|
||||||
public bool ResolvePlay(Scoreboard scoreboard) => PlayResolution.Invoke(scoreboard);
|
public bool ResolvePlay(Scoreboard scoreboard) => PlayResolver.Resolve(scoreboard);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user