mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-12 07:40:50 -08:00
Implementing https://github.com/coding-horror/basic-computer-games/blob/main/15_Boxing/boxing.bas in C#
This commit is contained in:
48
15_Boxing/csharp/AttackStrategy.cs
Normal file
48
15_Boxing/csharp/AttackStrategy.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
namespace Boxing;
|
||||
|
||||
public abstract class AttackStrategy
|
||||
{
|
||||
protected const int KnockoutDamageThreshold = 35;
|
||||
protected readonly Boxer Other;
|
||||
protected readonly Stack<Action> Work;
|
||||
private readonly Action _notifyGameEnded;
|
||||
|
||||
public AttackStrategy(Boxer other, Stack<Action> work, Action notifyGameEnded)
|
||||
{
|
||||
Other = other;
|
||||
Work = work;
|
||||
_notifyGameEnded = notifyGameEnded;
|
||||
}
|
||||
|
||||
public void Attack()
|
||||
{
|
||||
var punch = GetPunch();
|
||||
if (punch.IsBestPunch)
|
||||
{
|
||||
Other.DamageTaken += 2;
|
||||
}
|
||||
|
||||
Work.Push(punch.Punch switch
|
||||
{
|
||||
Punch.FullSwing => FullSwing,
|
||||
Punch.Hook => Hook,
|
||||
Punch.Uppercut => Uppercut,
|
||||
_ => Jab
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract AttackPunch GetPunch();
|
||||
protected abstract void FullSwing();
|
||||
protected abstract void Hook();
|
||||
protected abstract void Uppercut();
|
||||
protected abstract void Jab();
|
||||
|
||||
protected void RegisterKnockout(string knockoutMessage)
|
||||
{
|
||||
Work.Clear();
|
||||
_notifyGameEnded();
|
||||
Console.WriteLine(knockoutMessage);
|
||||
}
|
||||
|
||||
protected record AttackPunch(Punch Punch, bool IsBestPunch);
|
||||
}
|
||||
45
15_Boxing/csharp/Boxer.cs
Normal file
45
15_Boxing/csharp/Boxer.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
namespace Boxing;
|
||||
|
||||
public class Boxer
|
||||
{
|
||||
private int _wins;
|
||||
|
||||
private string Name { get; set; } = string.Empty;
|
||||
|
||||
public Punch BestPunch { get; set; }
|
||||
|
||||
public Punch Vulnerability { get; set; }
|
||||
|
||||
public void SetName(string prompt)
|
||||
{
|
||||
Console.WriteLine(prompt);
|
||||
string? name;
|
||||
do
|
||||
{
|
||||
name = Console.ReadLine();
|
||||
} while (string.IsNullOrWhiteSpace(name));
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public int DamageTaken { get; set; }
|
||||
|
||||
public void ResetForNewRound() => DamageTaken = 0;
|
||||
|
||||
public void RecordWin() => _wins += 1;
|
||||
|
||||
public bool IsWinner => _wins >= 2;
|
||||
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
|
||||
public class Opponent : Boxer
|
||||
{
|
||||
public void SetRandomPunches()
|
||||
{
|
||||
do
|
||||
{
|
||||
BestPunch = (Punch) GameUtils.Roll(4); // B1
|
||||
Vulnerability = (Punch) GameUtils.Roll(4); // D1
|
||||
} while (BestPunch == Vulnerability);
|
||||
}
|
||||
}
|
||||
10
15_Boxing/csharp/Boxing.csproj
Normal file
10
15_Boxing/csharp/Boxing.csproj
Normal file
@@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
22
15_Boxing/csharp/Boxing.sln
Normal file
22
15_Boxing/csharp/Boxing.sln
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30114.105
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Boxing", "Boxing.csproj", "{52A7BDE5-3085-4F58-AC57-2BA4E65212D8}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{52A7BDE5-3085-4F58-AC57-2BA4E65212D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{52A7BDE5-3085-4F58-AC57-2BA4E65212D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{52A7BDE5-3085-4F58-AC57-2BA4E65212D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{52A7BDE5-3085-4F58-AC57-2BA4E65212D8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
115
15_Boxing/csharp/OpponentAttackStrategy.cs
Normal file
115
15_Boxing/csharp/OpponentAttackStrategy.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using static Boxing.GameUtils;
|
||||
using static System.Console;
|
||||
|
||||
namespace Boxing;
|
||||
|
||||
public class OpponentAttackStrategy : AttackStrategy
|
||||
{
|
||||
private readonly Opponent _opponent;
|
||||
|
||||
public OpponentAttackStrategy(Opponent opponent, Boxer player, Action notifyGameEnded, Stack<Action> work) : base(player, work, notifyGameEnded)
|
||||
{
|
||||
_opponent = opponent;
|
||||
}
|
||||
|
||||
protected override AttackPunch GetPunch()
|
||||
{
|
||||
var punch = (Punch)Roll(4);
|
||||
return new AttackPunch(punch, punch == _opponent.BestPunch);
|
||||
}
|
||||
|
||||
protected override void FullSwing() // 720
|
||||
{
|
||||
Write($"{_opponent} TAKES A FULL SWING AND");
|
||||
if (Other.Vulnerability == Punch.FullSwing)
|
||||
{
|
||||
ScoreFullSwing();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RollSatisfies(60, x => x < 30))
|
||||
{
|
||||
WriteLine(" IT'S BLOCKED!");
|
||||
}
|
||||
else
|
||||
{
|
||||
ScoreFullSwing();
|
||||
}
|
||||
}
|
||||
|
||||
void ScoreFullSwing()
|
||||
{
|
||||
WriteLine(" POW!!!!! HE HITS HIM RIGHT IN THE FACE!");
|
||||
if (Other.DamageTaken > KnockoutDamageThreshold)
|
||||
{
|
||||
Work.Push(RegisterOtherKnockedOut);
|
||||
}
|
||||
Other.DamageTaken += 15;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Hook() // 810
|
||||
{
|
||||
Write($"{_opponent} GETS {Other} IN THE JAW (OUCH!)");
|
||||
Other.DamageTaken += 7;
|
||||
WriteLine("....AND AGAIN!");
|
||||
Other.DamageTaken += 5;
|
||||
if (Other.DamageTaken > KnockoutDamageThreshold)
|
||||
{
|
||||
Work.Push(RegisterOtherKnockedOut);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Uppercut() // 860
|
||||
{
|
||||
Write($"{Other} IS ATTACKED BY AN UPPERCUT (OH,OH)...");
|
||||
if (Other.Vulnerability == Punch.Uppercut)
|
||||
{
|
||||
ScoreUppercut();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RollSatisfies(200, x => x > 75))
|
||||
{
|
||||
WriteLine($" BLOCKS AND HITS {_opponent} WITH A HOOK.");
|
||||
_opponent.DamageTaken += 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
ScoreUppercut();
|
||||
}
|
||||
}
|
||||
|
||||
void ScoreUppercut()
|
||||
{
|
||||
WriteLine($"AND {_opponent} CONNECTS...");
|
||||
Other.DamageTaken += 8;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Jab() // 640
|
||||
{
|
||||
Write($"{_opponent} JABS AND ");
|
||||
if (Other.Vulnerability == Punch.Jab)
|
||||
{
|
||||
ScoreJab();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RollSatisfies(7, x => x > 4))
|
||||
{
|
||||
WriteLine("BLOOD SPILLS !!!");
|
||||
ScoreJab();
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLine("IT'S BLOCKED!");
|
||||
}
|
||||
}
|
||||
|
||||
void ScoreJab() => Other.DamageTaken += 5;
|
||||
}
|
||||
|
||||
private void RegisterOtherKnockedOut()
|
||||
=> RegisterKnockout($"{Other} IS KNOCKED COLD AND {_opponent} IS THE WINNER AND CHAMP!");
|
||||
}
|
||||
121
15_Boxing/csharp/PlayerAttackStrategy.cs
Normal file
121
15_Boxing/csharp/PlayerAttackStrategy.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using static Boxing.GameUtils;
|
||||
using static System.Console;
|
||||
namespace Boxing;
|
||||
|
||||
public class PlayerAttackStrategy : AttackStrategy
|
||||
{
|
||||
private readonly Boxer _player;
|
||||
|
||||
public PlayerAttackStrategy(Boxer player, Opponent opponent, Action notifyGameEnded, Stack<Action> work)
|
||||
: base(opponent, work, notifyGameEnded) => _player = player;
|
||||
|
||||
protected override AttackPunch GetPunch()
|
||||
{
|
||||
var punch = GameUtils.GetPunch($"{_player}'S PUNCH");
|
||||
return new AttackPunch(punch, punch == _player.BestPunch);
|
||||
}
|
||||
|
||||
protected override void FullSwing() // 340
|
||||
{
|
||||
Write($"{_player} SWINGS AND ");
|
||||
if (Other.Vulnerability == Punch.FullSwing)
|
||||
{
|
||||
ScoreFullSwing();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RollSatisfies(30, x => x < 10))
|
||||
{
|
||||
ScoreFullSwing();
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLine("HE MISSES");
|
||||
}
|
||||
}
|
||||
|
||||
void ScoreFullSwing()
|
||||
{
|
||||
WriteLine("HE CONNECTS!");
|
||||
if (Other.DamageTaken > KnockoutDamageThreshold)
|
||||
{
|
||||
Work.Push(() => RegisterKnockout($"{Other} IS KNOCKED COLD AND {_player} IS THE WINNER AND CHAMP!"));
|
||||
}
|
||||
Other.DamageTaken += 15;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Uppercut() // 520
|
||||
{
|
||||
Write($"{_player} TRIES AN UPPERCUT ");
|
||||
if (Other.Vulnerability == Punch.Uppercut)
|
||||
{
|
||||
ScoreUpperCut();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RollSatisfies(100, x => x < 51))
|
||||
{
|
||||
ScoreUpperCut();
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLine("AND IT'S BLOCKED (LUCKY BLOCK!)");
|
||||
}
|
||||
}
|
||||
|
||||
void ScoreUpperCut()
|
||||
{
|
||||
WriteLine("AND HE CONNECTS!");
|
||||
Other.DamageTaken += 4;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Hook() // 450
|
||||
{
|
||||
Write($"{_player} GIVES THE HOOK... ");
|
||||
if (Other.Vulnerability == Punch.Hook)
|
||||
{
|
||||
ScoreHookOnOpponent();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RollSatisfies(2, x => x == 1))
|
||||
{
|
||||
WriteLine("BUT IT'S BLOCKED!!!!!!!!!!!!!");
|
||||
}
|
||||
else
|
||||
{
|
||||
ScoreHookOnOpponent();
|
||||
}
|
||||
}
|
||||
|
||||
void ScoreHookOnOpponent()
|
||||
{
|
||||
WriteLine("CONNECTS...");
|
||||
Other.DamageTaken += 7;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Jab()
|
||||
{
|
||||
WriteLine($"{_player} JABS AT {Other}'S HEAD");
|
||||
if (Other.Vulnerability == Punch.Jab)
|
||||
{
|
||||
ScoreJabOnOpponent();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RollSatisfies(8, x => x < 4))
|
||||
{
|
||||
WriteLine("IT'S BLOCKED.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ScoreJabOnOpponent();
|
||||
}
|
||||
}
|
||||
|
||||
void ScoreJabOnOpponent() => Other.DamageTaken += 3;
|
||||
}
|
||||
}
|
||||
29
15_Boxing/csharp/Program.cs
Normal file
29
15_Boxing/csharp/Program.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Boxing;
|
||||
using static Boxing.GameUtils;
|
||||
using static System.Console;
|
||||
|
||||
WriteLine(new string('\t', 33) + "BOXING");
|
||||
WriteLine(new string('\t', 15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
|
||||
WriteLine("{0}{0}{0}BOXING OLYMPIC STYLE (3 ROUNDS -- 2 OUT OF 3 WINS){0}", Environment.NewLine);
|
||||
|
||||
var opponent = new Opponent();
|
||||
opponent.SetName("WHAT IS YOUR OPPONENT'S NAME"); // J$
|
||||
var player = new Boxer();
|
||||
player.SetName("INPUT YOUR MAN'S NAME"); // L$
|
||||
|
||||
PrintPunchDescription();
|
||||
player.BestPunch = GetPunch("WHAT IS YOUR MANS BEST"); // B
|
||||
player.Vulnerability = GetPunch("WHAT IS HIS VULNERABILITY"); // D
|
||||
opponent.SetRandomPunches();
|
||||
WriteLine($"{opponent}'S ADVANTAGE IS {opponent.BestPunch.ToFriendlyString()} AND VULNERABILITY IS SECRET.");
|
||||
|
||||
|
||||
for (var i = 1; i <= 3; i ++) // R
|
||||
{
|
||||
var round = new Round(player, opponent, i);
|
||||
round.Start();
|
||||
round.CheckOpponentWin();
|
||||
round.CheckPlayerWin();
|
||||
if (round.GameEnded) break;
|
||||
}
|
||||
WriteLine("{0}{0}AND NOW GOODBYE FROM THE OLYMPIC ARENA.{0}", Environment.NewLine);
|
||||
9
15_Boxing/csharp/Punch.cs
Normal file
9
15_Boxing/csharp/Punch.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Boxing;
|
||||
|
||||
public enum Punch
|
||||
{
|
||||
FullSwing = 1,
|
||||
Hook = 2,
|
||||
Uppercut = 3,
|
||||
Jab = 4
|
||||
}
|
||||
96
15_Boxing/csharp/Round.cs
Normal file
96
15_Boxing/csharp/Round.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
namespace Boxing;
|
||||
|
||||
class Round
|
||||
{
|
||||
|
||||
private readonly Boxer _player;
|
||||
private readonly Boxer _opponent;
|
||||
private readonly int _round;
|
||||
private Stack<Action> _work = new();
|
||||
private readonly PlayerAttackStrategy _playerAttackStrategy;
|
||||
private readonly OpponentAttackStrategy _opponentAttackStrategy;
|
||||
|
||||
public bool GameEnded { get; private set; }
|
||||
|
||||
public Round(Boxer player, Opponent opponent, int round)
|
||||
{
|
||||
_player = player;
|
||||
_opponent = opponent;
|
||||
_round = round;
|
||||
_work.Push(ResetPlayers);
|
||||
_work.Push(CheckOpponentWin);
|
||||
_work.Push(CheckPlayerWin);
|
||||
|
||||
void NotifyGameEnded() => GameEnded = true;
|
||||
_playerAttackStrategy = new PlayerAttackStrategy(player, opponent, NotifyGameEnded, _work);
|
||||
_opponentAttackStrategy = new OpponentAttackStrategy(opponent, player, NotifyGameEnded, _work);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
while (_work.Count > 0)
|
||||
{
|
||||
var action = _work.Pop();
|
||||
// This delay does not exist in the VB code but it makes a bit easier to follow the game.
|
||||
// I assume the computers at the time were slow enough
|
||||
// so that they did not need this delay...
|
||||
Thread.Sleep(300);
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckOpponentWin()
|
||||
{
|
||||
if (_opponent.IsWinner)
|
||||
{
|
||||
Console.WriteLine($"{_opponent} WINS (NICE GOING, {_opponent}).");
|
||||
GameEnded = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckPlayerWin()
|
||||
{
|
||||
if (_player.IsWinner)
|
||||
{
|
||||
Console.WriteLine($"{_player} AMAZINGLY WINS!!");
|
||||
GameEnded = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetPlayers()
|
||||
{
|
||||
_player.ResetForNewRound();
|
||||
_opponent.ResetForNewRound();
|
||||
_work.Push(RoundBegins);
|
||||
}
|
||||
|
||||
private void RoundBegins()
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"ROUND {_round} BEGINS...");
|
||||
_work.Push(CheckRoundWinner);
|
||||
for (var i = 0; i < 7; i++)
|
||||
{
|
||||
_work.Push(DecideWhoAttacks);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckRoundWinner()
|
||||
{
|
||||
if (_opponent.DamageTaken > _player.DamageTaken)
|
||||
{
|
||||
Console.WriteLine($"{_player} WINS ROUND {_round}");
|
||||
_player.RecordWin();
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"{_opponent} WINS ROUND {_round}");
|
||||
_opponent.RecordWin();
|
||||
}
|
||||
}
|
||||
|
||||
private void DecideWhoAttacks()
|
||||
{
|
||||
_work.Push( GameUtils.RollSatisfies(10, x => x > 5) ? _opponentAttackStrategy.Attack : _playerAttackStrategy.Attack );
|
||||
}
|
||||
}
|
||||
35
15_Boxing/csharp/Utils.cs
Normal file
35
15_Boxing/csharp/Utils.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
namespace Boxing;
|
||||
public static class GameUtils
|
||||
{
|
||||
private static readonly Random Rnd = new((int) DateTime.UtcNow.Ticks);
|
||||
public static void PrintPunchDescription() =>
|
||||
Console.WriteLine($"DIFFERENT PUNCHES ARE: {PunchDesc(Punch.FullSwing)}; {PunchDesc(Punch.Hook)}; {PunchDesc(Punch.Uppercut)}; {PunchDesc(Punch.Jab)}.");
|
||||
|
||||
private static string PunchDesc(Punch punch) => $"({(int)punch}) {punch.ToFriendlyString()}";
|
||||
|
||||
public static Punch GetPunch(string prompt)
|
||||
{
|
||||
Console.WriteLine(prompt);
|
||||
Punch result;
|
||||
while (!Enum.TryParse(Console.ReadLine(), out result) || !Enum.IsDefined(typeof(Punch), result))
|
||||
{
|
||||
PrintPunchDescription();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Func<int, int> Roll { get; } = upperLimit => (int) (upperLimit * Rnd.NextSingle()) + 1;
|
||||
|
||||
public static bool RollSatisfies(int upperLimit, Predicate<int> predicate) => predicate(Roll(upperLimit));
|
||||
|
||||
public static string ToFriendlyString(this Punch punch)
|
||||
=> punch switch
|
||||
{
|
||||
Punch.FullSwing => "FULL SWING",
|
||||
Punch.Hook => "HOOK",
|
||||
Punch.Uppercut => "UPPERCUT",
|
||||
Punch.Jab => "JAB",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(punch), punch, null)
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user