From bc628e71daf1f807730fc9843ca4edde1a7ee76a Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 22 Jul 2021 16:55:23 -0400 Subject: [PATCH 1/2] Initial port of Bullfight to C# --- 17 Bullfight/csharp/Bullfight.sln | 25 ++ 17 Bullfight/csharp/Game.csproj | 7 + 17 Bullfight/csharp/src/Action.cs | 24 ++ 17 Bullfight/csharp/src/ActionResult.cs | 44 ++++ 17 Bullfight/csharp/src/Controller.cs | 129 +++++++++++ 17 Bullfight/csharp/src/MatchConditions.cs | 41 ++++ 17 Bullfight/csharp/src/MatchState.cs | 28 +++ 17 Bullfight/csharp/src/Program.cs | 56 +++++ 17 Bullfight/csharp/src/Quality.cs | 19 ++ 17 Bullfight/csharp/src/Reward.cs | 13 ++ 17 Bullfight/csharp/src/RiskLevel.cs | 12 + 17 Bullfight/csharp/src/Rules.cs | 258 +++++++++++++++++++++ 17 Bullfight/csharp/src/View.cs | 240 +++++++++++++++++++ 13 files changed, 896 insertions(+) create mode 100644 17 Bullfight/csharp/Bullfight.sln create mode 100644 17 Bullfight/csharp/Game.csproj create mode 100644 17 Bullfight/csharp/src/Action.cs create mode 100644 17 Bullfight/csharp/src/ActionResult.cs create mode 100644 17 Bullfight/csharp/src/Controller.cs create mode 100644 17 Bullfight/csharp/src/MatchConditions.cs create mode 100644 17 Bullfight/csharp/src/MatchState.cs create mode 100644 17 Bullfight/csharp/src/Program.cs create mode 100644 17 Bullfight/csharp/src/Quality.cs create mode 100644 17 Bullfight/csharp/src/Reward.cs create mode 100644 17 Bullfight/csharp/src/RiskLevel.cs create mode 100644 17 Bullfight/csharp/src/Rules.cs create mode 100644 17 Bullfight/csharp/src/View.cs diff --git a/17 Bullfight/csharp/Bullfight.sln b/17 Bullfight/csharp/Bullfight.sln new file mode 100644 index 00000000..29f5276f --- /dev/null +++ b/17 Bullfight/csharp/Bullfight.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31321.278 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Game", "Game.csproj", "{8F7C450E-5F3A-45BA-9DB9-329744214931}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8F7C450E-5F3A-45BA-9DB9-329744214931}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F7C450E-5F3A-45BA-9DB9-329744214931}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F7C450E-5F3A-45BA-9DB9-329744214931}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F7C450E-5F3A-45BA-9DB9-329744214931}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C5BFC749-C7D8-4981-A7D4-1D401901A890} + EndGlobalSection +EndGlobal diff --git a/17 Bullfight/csharp/Game.csproj b/17 Bullfight/csharp/Game.csproj new file mode 100644 index 00000000..849a99d4 --- /dev/null +++ b/17 Bullfight/csharp/Game.csproj @@ -0,0 +1,7 @@ + + + Exe + net5.0 + enable + + diff --git a/17 Bullfight/csharp/src/Action.cs b/17 Bullfight/csharp/src/Action.cs new file mode 100644 index 00000000..2a148683 --- /dev/null +++ b/17 Bullfight/csharp/src/Action.cs @@ -0,0 +1,24 @@ +namespace Game +{ + /// + /// Enumerates the different actions that the player can take on each round + /// of the fight. + /// + public enum Action + { + /// + /// Dodge the bull. + /// + Dodge, + + /// + /// Kill the bull. + /// + Kill, + + /// + /// Freeze in place and don't do anything. + /// + Panic + } +} diff --git a/17 Bullfight/csharp/src/ActionResult.cs b/17 Bullfight/csharp/src/ActionResult.cs new file mode 100644 index 00000000..baa74f2e --- /dev/null +++ b/17 Bullfight/csharp/src/ActionResult.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Game +{ + /// + /// Enumerates the different possible outcomes of the player's action. + /// + public enum ActionResult + { + /// + /// The fight continues. + /// + FightContinues, + + /// + /// The player fled from the ring. + /// + PlayerFlees, + + /// + /// The bull has gored the player. + /// + BullGoresPlayer, + + /// + /// The bull killed the player. + /// + BullKillsPlayer, + + /// + /// The player killed the bull. + /// + PlayerKillsBull, + + /// + /// The player attempted to kill the bull and both survived. + /// + Draw + } +} diff --git a/17 Bullfight/csharp/src/Controller.cs b/17 Bullfight/csharp/src/Controller.cs new file mode 100644 index 00000000..2355e247 --- /dev/null +++ b/17 Bullfight/csharp/src/Controller.cs @@ -0,0 +1,129 @@ +using System; + +namespace Game +{ + /// + /// Contains functions for getting input from the user. + /// + public static class Controller + { + /// + /// Handles the initial interaction with the player. + /// + public static void StartGame() + { + View.ShowBanner(); + View.PromptShowInstructions(); + + var input = Console.ReadLine(); + if (input is null) + Environment.Exit(0); + + if (input.ToUpperInvariant() != "NO") + View.ShowInstructions(); + + View.ShowSeparator(); + } + + /// + /// Gets the player's action for the current round. + /// + /// + /// The current pass number. + /// + public static (Action action, RiskLevel riskLevel) GetPlayerIntention(int passNumber) + { + if (passNumber < 3) + View.PromptKillBull(); + else + View.PromptKillBullBrief(); + + var attemptToKill = GetYesOrNo(); + + if (attemptToKill) + { + View.PromptKillMethod(); + + var input = Console.ReadLine(); + if (input is null) + Environment.Exit(0); + + return input switch + { + "4" => (Action.Kill, RiskLevel.High), + "5" => (Action.Kill, RiskLevel.Low), + _ => (Action.Panic, default(RiskLevel)) + }; + } + else + { + if (passNumber < 2) + View.PromptCapeMove(); + else + View.PromptCapeMoveBrief(); + + var action = Action.Panic; + var riskLevel = default(RiskLevel); + + while (action == Action.Panic) + { + var input = Console.ReadLine(); + if (input is null) + Environment.Exit(0); + + (action, riskLevel) = input switch + { + "0" => (Action.Dodge, RiskLevel.High), + "1" => (Action.Dodge, RiskLevel.Medium), + "2" => (Action.Dodge, RiskLevel.Low), + _ => (Action.Panic, default(RiskLevel)) + }; + + if (action == Action.Panic) + View.PromptDontPanic(); + } + + return (action, riskLevel); + } + } + + /// + /// Gets the player's intention to flee (or not). + /// + /// + /// True if the player flees; otherwise, false. + /// + public static bool PlayerRunsFromRing() + { + View.PromptRunFromRing(); + return GetYesOrNo(); + } + + /// + /// Gets a yes or no response from the player. + /// + /// + /// True if the user answered yes; otherwise, false. + /// + public static bool GetYesOrNo() + { + while (true) + { + var input = Console.ReadLine(); + if (input is null) + Environment.Exit(0); + + switch (input.ToUpperInvariant()) + { + case "YES": + return true; + case "NO": + return false; + default: + Console.WriteLine("INCORRECT ANSWER - - PLEASE TYPE 'YES' OR 'NO'."); + break; + } + } + } + } +} diff --git a/17 Bullfight/csharp/src/MatchConditions.cs b/17 Bullfight/csharp/src/MatchConditions.cs new file mode 100644 index 00000000..5d5d979c --- /dev/null +++ b/17 Bullfight/csharp/src/MatchConditions.cs @@ -0,0 +1,41 @@ +namespace Game +{ + /// + /// Stores the initial conditions of a match. + /// + public record MatchConditions + { + /// + /// Gets the quality of the bull. + /// + public Quality BullQuality { get; init; } + + /// + /// Gets the quality of help received from the toreadores. + /// + public Quality ToreadorePerformance { get; init; } + + /// + /// Gets the quality of help received from the picadores. + /// + public Quality PicadorePerformance { get; init; } + + /// + /// Gets the number of toreadores killed while preparing for the + /// final round. + /// + public int ToreadoresKilled { get; init; } + + /// + /// Gets the number of picadores killed while preparing for the + /// final round. + /// + public int PicadoresKilled { get; init; } + + /// + /// Gets the number of horses killed while preparing for the final + /// round. + /// + public int HorsesKilled { get; init; } + } +} diff --git a/17 Bullfight/csharp/src/MatchState.cs b/17 Bullfight/csharp/src/MatchState.cs new file mode 100644 index 00000000..3eae6d5d --- /dev/null +++ b/17 Bullfight/csharp/src/MatchState.cs @@ -0,0 +1,28 @@ +namespace Game +{ + /// + /// Stores the current state of the match. + /// + public record MatchState(MatchConditions Conditions) + { + /// + /// Gets the number of times the bull has charged. + /// + public int PassNumber { get; init; } + + /// + /// Measures the player's bravery during the match. + /// + public double Bravery { get; init; } + + /// + /// Measures how much style the player showed during the match. + /// + public double Style { get; init; } + + /// + /// Gets the result of the player's last action. + /// + public ActionResult Result { get; init; } + } +} diff --git a/17 Bullfight/csharp/src/Program.cs b/17 Bullfight/csharp/src/Program.cs new file mode 100644 index 00000000..17da736f --- /dev/null +++ b/17 Bullfight/csharp/src/Program.cs @@ -0,0 +1,56 @@ +using System; + +namespace Game +{ + class Program + { + static void Main() + { + Controller.StartGame(); + + var random = new Random(); + var match = Rules.StartMatch(random); + View.ShowStartingConditions(match.Conditions); + + while (match.Result == ActionResult.FightContinues) + { + match = match with { PassNumber = match.PassNumber + 1 }; + + View.StartOfPass(match.PassNumber); + + var (action, riskLevel) = Controller.GetPlayerIntention(match.PassNumber); + match = action switch + { + Action.Dodge => Rules.TryDodge(random, riskLevel, match), + Action.Kill => Rules.TryKill(random, riskLevel, match), + _ => Rules.Panic(match) + }; + + var first = true; + while (match.Result == ActionResult.BullGoresPlayer) + { + View.ShowPlayerGored(action == Action.Panic, first); + first = false; + + match = Rules.TrySurvive(random, match); + if (match.Result == ActionResult.FightContinues) + { + View.ShowPlayerSurvives(); + + if (Controller.PlayerRunsFromRing()) + { + match = Rules.Flee(match); + } + else + { + View.ShowPlayerFoolhardy(); + match = Rules.IgnoreInjury(random, action, match); + } + } + } + } + + View.ShowFinalResult(match.Result, match.Bravery, Rules.GetReward(random, match)); + } + } +} diff --git a/17 Bullfight/csharp/src/Quality.cs b/17 Bullfight/csharp/src/Quality.cs new file mode 100644 index 00000000..ee7bf6d9 --- /dev/null +++ b/17 Bullfight/csharp/src/Quality.cs @@ -0,0 +1,19 @@ +namespace Game +{ + /// + /// Enumerates the different levels of quality in the game. + /// + /// + /// Quality applies both to the bull and to the help received from the + /// toreadores and picadores. Note that the ordinal values are significant + /// (these are used in various calculations). + /// + public enum Quality + { + Superb = 1, + Good = 2, + Fair = 3, + Poor = 4, + Awful = 5 + } +} diff --git a/17 Bullfight/csharp/src/Reward.cs b/17 Bullfight/csharp/src/Reward.cs new file mode 100644 index 00000000..295789be --- /dev/null +++ b/17 Bullfight/csharp/src/Reward.cs @@ -0,0 +1,13 @@ +namespace Game +{ + /// + /// Enumerates the different things the player can be awarded. + /// + public enum Reward + { + Nothing, + OneEar, + TwoEars, + CarriedFromRing + } +} diff --git a/17 Bullfight/csharp/src/RiskLevel.cs b/17 Bullfight/csharp/src/RiskLevel.cs new file mode 100644 index 00000000..8048f663 --- /dev/null +++ b/17 Bullfight/csharp/src/RiskLevel.cs @@ -0,0 +1,12 @@ +namespace Game +{ + /// + /// Enumerates the different levels of risk for manoeuvres in the game. + /// + public enum RiskLevel + { + Low, + Medium, + High + } +} diff --git a/17 Bullfight/csharp/src/Rules.cs b/17 Bullfight/csharp/src/Rules.cs new file mode 100644 index 00000000..463af1ed --- /dev/null +++ b/17 Bullfight/csharp/src/Rules.cs @@ -0,0 +1,258 @@ +using System; + +namespace Game +{ + /// + /// Provides functions implementing the rules of the game. + /// + public static class Rules + { + /// + /// Gets the state of a new match. + /// + /// + /// The random number generator. + /// + public static MatchState StartMatch(Random random) + { + var bullQuality = GetBullQuality(); + var toreadorePerformance = GetHelpQuality(); + var picadorePerformance = GetHelpQuality(); + + var conditions = new MatchConditions + { + BullQuality = bullQuality, + ToreadorePerformance = toreadorePerformance, + PicadorePerformance = picadorePerformance, + ToreadoresKilled = GetHumanCasualties(toreadorePerformance), + PicadoresKilled = GetHumanCasualties(picadorePerformance), + HorsesKilled = GetHorseCasualties(picadorePerformance) + }; + + return new MatchState(conditions) + { + Bravery = 1.0, + Style = 1.0 + }; + + Quality GetBullQuality() => + (Quality)random.Next(1, 6); + + Quality GetHelpQuality() => + ((3.0 / (int)bullQuality) * random.NextDouble()) switch + { + < 0.37 => Quality.Superb, + < 0.50 => Quality.Good, + < 0.63 => Quality.Fair, + < 0.87 => Quality.Poor, + _ => Quality.Awful + }; + + int GetHumanCasualties(Quality performance) => + performance switch + { + Quality.Poor => random.Next(0, 2), + Quality.Awful => random.Next(1, 3), + _ => 0 + }; + + int GetHorseCasualties(Quality performance) => + performance switch + { + // NOTE: The code for displaying a single horse casuality + // following a poor picadore peformance was unreachable + // in the original BASIC version. I've assumed this was + // a bug. + Quality.Poor => 1, + Quality.Awful => random.Next(1, 3), + _ => 0 + }; + } + + /// + /// Determines the result when the player attempts to dodge the bull. + /// + /// + /// The random number generator. + /// + /// + /// The level of risk in the dodge manoeuvre chosen. + /// + /// + /// The current match state. + /// + /// + /// The updated match state. + /// + public static MatchState TryDodge(Random random, RiskLevel riskLevel, MatchState match) + { + var difficultyModifier = riskLevel switch + { + RiskLevel.High => 3.0, + RiskLevel.Medium => 2.0, + _ => 0.5 + }; + + var outcome = (GetBullStrength(match) + (difficultyModifier / 10)) * random.NextDouble() / + ((GetAssisstance(match) + (match.PassNumber / 10.0)) * 5); + + return outcome < 0.51 ? + match with { Result = ActionResult.FightContinues, Style = match.Style + difficultyModifier } : + match with { Result = ActionResult.BullGoresPlayer }; + } + + /// + /// Determines the result when the player attempts to kill the bull. + /// + /// + /// The random number generator. + /// + /// + /// The level of risk in the manoeuvre chosen. + /// + /// + /// The current match state. + /// + /// + /// The updated match state. + /// + public static MatchState TryKill(Random random, RiskLevel riskLevel, MatchState match) + { + var K = GetBullStrength(match) * 10 * random.NextDouble() / (GetAssisstance(match) * 5 * match.PassNumber); + + return ((riskLevel == RiskLevel.High && K > 0.2) || K > 0.8) ? + match with { Result = ActionResult.BullGoresPlayer } : + match with { Result = ActionResult.PlayerKillsBull }; + } + + /// + /// Determines if the player survives being gored by the bull. + /// + /// + /// The random number generator. + /// + /// + /// The current match state. + /// + /// + /// The updated match state. + /// + public static MatchState TrySurvive(Random random, MatchState match) => + (random.Next(2) == 0) ? + match with { Result = ActionResult.BullKillsPlayer, Bravery = 1.5 } : + match with { Result = ActionResult.FightContinues }; + + /// + /// Determines the result when the player panics and fails to do anything. + /// + /// + /// The match state. + /// + public static MatchState Panic(MatchState match) => + match with { Result = ActionResult.BullGoresPlayer }; + + /// + /// Determines the result when the player flees the ring. + /// + /// + /// The current match state. + /// + /// + /// The updated match state. + /// + public static MatchState Flee(MatchState match) => + match with { Result = ActionResult.PlayerFlees, Bravery = 0.0 }; + + /// + /// Determines the result when the player decides to continue fighting + /// following an injury. + /// + /// + /// The random number generator. + /// + /// + /// The action the player took that lead to the injury. + /// + /// + /// The current match state. + /// + /// + /// The updated match state. + /// + public static MatchState IgnoreInjury(Random random, Action action, MatchState match) => + (random.Next(2) == 0) ? + match with { Result = action == Action.Dodge ? ActionResult.FightContinues : ActionResult.Draw, Bravery = 2.0 } : + match with { Result = ActionResult.BullGoresPlayer }; + + /// + /// Gets the player's reward for completing a match. + /// + /// + /// The random number generator. + /// + /// + /// The final match state. + /// + public static Reward GetReward(Random random, MatchState match) + { + var score = CalculateScore(); + + if (score * random.NextDouble() < 2.4) + return Reward.Nothing; + else + if (score * random.NextDouble() < 4.9) + return Reward.OneEar; + else + if (score * random.NextDouble() < 7.4) + return Reward.TwoEars; + else + return Reward.CarriedFromRing; + + double CalculateScore() + { + var score = 4.5; + + // Style + score += match.Style / 6; + + // Assisstance + score -= GetAssisstance(match) * 2.5; + + // Courage + score += 4 * match.Bravery; + + // Kill bonus + score += (match.Result == ActionResult.PlayerKillsBull) ? 4 : 2; + + // Match length + score -= Math.Pow(match.PassNumber, 2) / 120; + + // Difficulty + score -= (int)match.Conditions.BullQuality; + + return score; + } + } + + /// + /// Calculates the strength of the bull in a match. + /// + private static double GetBullStrength(MatchState match) => + 6 - (int)match.Conditions.BullQuality; + + /// + /// Gets the amount of assistance received from the toreadores and + /// picadores in a match. + /// + private static double GetAssisstance(MatchState match) => + GetPerformanceBonus(match.Conditions.ToreadorePerformance) + + GetPerformanceBonus(match.Conditions.PicadorePerformance); + + /// + /// Gets the amount of assistance rendered by a performance of the + /// given quality. + /// + private static double GetPerformanceBonus(Quality performance) => + (6 - (int)performance) * 0.1; + } +} diff --git a/17 Bullfight/csharp/src/View.cs b/17 Bullfight/csharp/src/View.cs new file mode 100644 index 00000000..2bff39c8 --- /dev/null +++ b/17 Bullfight/csharp/src/View.cs @@ -0,0 +1,240 @@ +using System; + +namespace Game +{ + /// + /// Contains functions for displaying information to the user. + /// + public static class View + { + private static readonly string[] QualityString = { "SUPERB", "GOOD", "FAIR", "POOR", "AWFUL" }; + + public static void ShowBanner() + { + Console.WriteLine(" BULL"); + Console.WriteLine(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + } + + public static void ShowInstructions() + { + Console.WriteLine("HELLO, ALL YOU BLOODLOVERS AND AFICIONADOS."); + Console.WriteLine("HERE IS YOUR BIG CHANCE TO KILL A BULL."); + Console.WriteLine(); + Console.WriteLine("ON EACH PASS OF THE BULL, YOU MAY TRY"); + Console.WriteLine("0 - VERONICA (DANGEROUS INSIDE MOVE OF THE CAPE)"); + Console.WriteLine("1 - LESS DANGEROUS OUTSIDE MOVE OF THE CAPE"); + Console.WriteLine("2 - ORDINARY SWIRL OF THE CAPE."); + Console.WriteLine(); + Console.WriteLine("INSTEAD OF THE ABOVE, YOU MAY TRY TO KILL THE BULL"); + Console.WriteLine("ON ANY TURN: 4 (OVER THE HORNS), 5 (IN THE CHEST)."); + Console.WriteLine("BUT IF I WERE YOU,"); + Console.WriteLine("I WOULDN'T TRY IT BEFORE THE SEVENTH PASS."); + Console.WriteLine(); + Console.WriteLine("THE CROWD WILL DETERMINE WHAT AWARD YOU DESERVE"); + Console.WriteLine("(POSTHUMOUSLY IF NECESSARY)."); + Console.WriteLine("THE BRAVER YOU ARE, THE BETTER THE AWARD YOU RECEIVE."); + Console.WriteLine(); + Console.WriteLine("THE BETTER THE JOB THE PICADORES AND TOREADORES DO,"); + Console.WriteLine("THE BETTER YOUR CHANCES ARE."); + } + + public static void ShowSeparator() + { + Console.WriteLine(); + Console.WriteLine(); + } + + public static void ShowStartingConditions(MatchConditions conditions) + { + ShowBullQuality(); + ShowHelpQuality("TOREADORES", conditions.ToreadorePerformance, conditions.ToreadoresKilled, 0); + ShowHelpQuality("PICADORES", conditions.PicadorePerformance, conditions.PicadoresKilled, conditions.HorsesKilled); + + void ShowBullQuality() + { + Console.WriteLine($"YOU HAVE DRAWN A {QualityString[(int)conditions.BullQuality - 1]} BULL."); + + if (conditions.BullQuality > Quality.Poor) + { + Console.WriteLine("YOU'RE LUCKY"); + } + else + if (conditions.BullQuality < Quality.Good) + { + Console.WriteLine("GOOD LUCK. YOU'LL NEED IT."); + Console.WriteLine(); + } + + Console.WriteLine(); + } + + static void ShowHelpQuality(string helperName, Quality helpQuality, int helpersKilled, int horsesKilled) + { + Console.WriteLine($"THE {helperName} DID A {QualityString[(int)helpQuality - 1]} JOB."); + + // NOTE: The code below makes some *strong* assumptions about + // how the casualty numbers were generated. It is written + // this way to preserve the behaviour of the original BASIC + // version, but it would make more sense ignore the helpQuality + // parameter and just use the provided numbers to decide what + // to display. + switch (helpQuality) + { + case Quality.Poor: + if (horsesKilled > 0) + Console.WriteLine($"ONE OF THE HORSES OF THE {helperName} WAS KILLED."); + + if (helpersKilled > 0) + Console.WriteLine($"ONE OF THE {helperName} WAS KILLED."); + break; + + case Quality.Awful: + if (horsesKilled > 0) + Console.WriteLine($" {horsesKilled} OF THE HORSES OF THE {helperName} KILLED."); + + Console.WriteLine($" {helpersKilled} OF THE {helperName} KILLED."); + break; + } + } + } + + public static void StartOfPass(int passNumber) + { + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine($"PASS NUMBER {passNumber}"); + } + + public static void ShowPlayerGored(bool playerPanicked, bool firstGoring) + { + Console.WriteLine((playerPanicked, firstGoring) switch + { + (true, true) => "YOU PANICKED. THE BULL GORED YOU.", + (false, true) => "THE BULL HAS GORED YOU!", + (_, false) => "YOU ARE GORED AGAIN!" + }); + } + + public static void ShowPlayerSurvives() + { + Console.WriteLine("YOU ARE STILL ALIVE."); + Console.WriteLine(); + } + + public static void ShowPlayerFoolhardy() + { + Console.WriteLine("YOU ARE BRAVE. STUPID, BUT BRAVE."); + } + + public static void ShowFinalResult(ActionResult result, double bravery, Reward reward) + { + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + + switch (result) + { + case ActionResult.PlayerFlees: + Console.WriteLine("COWARD"); + break; + case ActionResult.BullKillsPlayer: + Console.WriteLine("YOU ARE DEAD."); + break; + case ActionResult.PlayerKillsBull: + Console.WriteLine("YOU KILLED THE BULL!"); + break; + } + + if (result == ActionResult.PlayerFlees) + { + Console.WriteLine("THE CROWD BOOS FOR TEN MINUTES. IF YOU EVER DARE TO SHOW"); + Console.WriteLine("YOUR FACE IN A RING AGAIN, THEY SWEAR THEY WILL KILL YOU--"); + Console.WriteLine("UNLESS THE BULL DOES FIRST."); + } + else + { + if (bravery == 2) // You were gored by the bull but survived (and did not later die or flee) + Console.WriteLine("THE CROWD CHEERS WILDLY!"); + else + if (result == ActionResult.PlayerKillsBull) + { + Console.WriteLine("THE CROWD CHEERS!"); + Console.WriteLine(); + } + + Console.WriteLine("THE CROWD AWARDS YOU"); + switch (reward) + { + case Reward.Nothing: + Console.WriteLine("NOTHING AT ALL."); + break; + case Reward.OneEar: + Console.WriteLine("ONE EAR OF THE BULL."); + break; + case Reward.TwoEars: + Console.WriteLine("BOTH EARS OF THE BULL!"); + Console.WriteLine("OLE!"); + break; + default: + Console.WriteLine("OLE! YOU ARE 'MUY HOMBRE'!! OLE! OLE!"); + break; + } + } + + Console.WriteLine(); + Console.WriteLine("ADIOS"); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + } + + public static void PromptShowInstructions() + { + Console.Write("DO YOU WANT INSTRUCTIONS? "); + } + + public static void PromptKillBull() + { + Console.WriteLine("THE BULL IS CHARGING AT YOU! YOU ARE THE MATADOR--"); + Console.Write("DO YOU WANT TO KILL THE BULL? "); + } + + public static void PromptKillBullBrief() + { + Console.Write("HERE COMES THE BULL. TRY FOR A KILL? "); + } + + public static void PromptKillMethod() + { + Console.WriteLine(); + Console.WriteLine("IT IS THE MOMENT OF TRUTH."); + Console.WriteLine(); + + Console.Write("HOW DO YOU TRY TO KILL THE BULL? "); + } + + public static void PromptCapeMove() + { + Console.Write("WHAT MOVE DO YOU MAKE WITH THE CAPE? "); + } + + public static void PromptCapeMoveBrief() + { + Console.Write("CAPE MOVE? "); + } + + public static void PromptDontPanic() + { + Console.WriteLine("DON'T PANIC, YOU IDIOT! PUT DOWN A CORRECT NUMBER"); + Console.Write("? "); + } + + public static void PromptRunFromRing() + { + Console.Write("DO YOU RUN FROM THE RING? "); + } + } +} From 6c4f019d40cf893c68b5adc2b36e28feaf38e9a4 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 23 Jul 2021 20:02:54 -0400 Subject: [PATCH 2/2] Reworked Bull Fight in a reactive style --- 17 Bullfight/csharp/Game.csproj | 2 +- 17 Bullfight/csharp/src/BullFight.cs | 220 +++++++++++++++ 17 Bullfight/csharp/src/Controller.cs | 9 +- .../csharp/src/Events/BullCharging.cs | 7 + 17 Bullfight/csharp/src/Events/Event.cs | 7 + .../csharp/src/Events/MatchCompleted.cs | 7 + .../csharp/src/Events/MatchStarted.cs | 13 + 17 Bullfight/csharp/src/Events/PlayerGored.cs | 7 + .../csharp/src/Events/PlayerSurvived.cs | 7 + 17 Bullfight/csharp/src/MatchConditions.cs | 41 --- 17 Bullfight/csharp/src/MatchState.cs | 28 -- 17 Bullfight/csharp/src/Mediator.cs | 48 ++++ 17 Bullfight/csharp/src/Program.cs | 74 +++-- 17 Bullfight/csharp/src/Rules.cs | 258 ------------------ 17 Bullfight/csharp/src/View.cs | 22 +- 15 files changed, 372 insertions(+), 378 deletions(-) create mode 100644 17 Bullfight/csharp/src/BullFight.cs create mode 100644 17 Bullfight/csharp/src/Events/BullCharging.cs create mode 100644 17 Bullfight/csharp/src/Events/Event.cs create mode 100644 17 Bullfight/csharp/src/Events/MatchCompleted.cs create mode 100644 17 Bullfight/csharp/src/Events/MatchStarted.cs create mode 100644 17 Bullfight/csharp/src/Events/PlayerGored.cs create mode 100644 17 Bullfight/csharp/src/Events/PlayerSurvived.cs delete mode 100644 17 Bullfight/csharp/src/MatchConditions.cs delete mode 100644 17 Bullfight/csharp/src/MatchState.cs create mode 100644 17 Bullfight/csharp/src/Mediator.cs delete mode 100644 17 Bullfight/csharp/src/Rules.cs diff --git a/17 Bullfight/csharp/Game.csproj b/17 Bullfight/csharp/Game.csproj index 849a99d4..36c64642 100644 --- a/17 Bullfight/csharp/Game.csproj +++ b/17 Bullfight/csharp/Game.csproj @@ -1,4 +1,4 @@ - + Exe net5.0 diff --git a/17 Bullfight/csharp/src/BullFight.cs b/17 Bullfight/csharp/src/BullFight.cs new file mode 100644 index 00000000..8df6ff8c --- /dev/null +++ b/17 Bullfight/csharp/src/BullFight.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; + +namespace Game +{ + /// + /// Provides a method for simulating a bull fight. + /// + public static class BullFight + { + /// + /// Begins a new fight. + /// + /// + /// Object used to communicate with the player. + /// + /// + /// The sequence of events that take place during the fight. + /// + /// + /// After receiving each event, the caller must invoke the appropriate + /// mediator method to inform this coroutine what to do next. Failure + /// to do so will result in an exception. + /// + public static IEnumerable Begin(Mediator mediator) + { + var random = new Random(); + var result = ActionResult.FightContinues; + + var bullQuality = GetBullQuality(); + var toreadorePerformance = GetHelpQuality(bullQuality); + var picadorePerformance = GetHelpQuality(bullQuality); + + var bullStrength = 6 - (int)bullQuality; + var assistanceLevel = (12 - (int)toreadorePerformance - (int)picadorePerformance) * 0.1; + var bravery = 1.0; + var style = 1.0; + var passNumber = 0; + + yield return new Events.MatchStarted( + bullQuality, + toreadorePerformance, + picadorePerformance, + GetHumanCasualties(toreadorePerformance), + GetHumanCasualties(picadorePerformance), + GetHorseCasualties(picadorePerformance)); + + while (result == ActionResult.FightContinues) + { + yield return new Events.BullCharging(++passNumber); + + var (action, riskLevel) = mediator.GetInput<(Action, RiskLevel)>(); + result = action switch + { + Action.Dodge => TryDodge(riskLevel), + Action.Kill => TryKill(riskLevel), + _ => Panic() + }; + + var first = true; + while (result == ActionResult.BullGoresPlayer) + { + yield return new Events.PlayerGored(action == Action.Panic, first); + first = false; + + result = TrySurvive(); + if (result == ActionResult.FightContinues) + { + yield return new Events.PlayerSurvived(); + + var runFromRing = mediator.GetInput(); + if (runFromRing) + result = Flee(); + else + result = IgnoreInjury(action); + } + } + } + + yield return new Events.MatchCompleted( + result, + bravery == 2, + GetReward()); + + Quality GetBullQuality() => + (Quality)random.Next(1, 6); + + Quality GetHelpQuality(Quality bullQuality) => + ((3.0 / (int)bullQuality) * random.NextDouble()) switch + { + < 0.37 => Quality.Superb, + < 0.50 => Quality.Good, + < 0.63 => Quality.Fair, + < 0.87 => Quality.Poor, + _ => Quality.Awful + }; + + int GetHumanCasualties(Quality performance) => + performance switch + { + Quality.Poor => random.Next(0, 2), + Quality.Awful => random.Next(1, 3), + _ => 0 + }; + + int GetHorseCasualties(Quality performance) => + performance switch + { + // NOTE: The code for displaying a single horse casuality + // following a poor picadore peformance was unreachable + // in the original BASIC version. I've assumed this was + // a bug. + Quality.Poor => 1, + Quality.Awful => random.Next(1, 3), + _ => 0 + }; + + ActionResult TryDodge(RiskLevel riskLevel) + { + var difficultyModifier = riskLevel switch + { + RiskLevel.High => 3.0, + RiskLevel.Medium => 2.0, + _ => 0.5 + }; + + var outcome = (bullStrength + (difficultyModifier / 10)) * random.NextDouble() / + ((assistanceLevel + (passNumber / 10.0)) * 5); + + if (outcome < 0.51) + { + style += difficultyModifier; + return ActionResult.FightContinues; + } + else + return ActionResult.BullGoresPlayer; + } + + ActionResult TryKill(RiskLevel riskLevel) + { + var luck = bullStrength * 10 * random.NextDouble() / (assistanceLevel * 5 * passNumber); + + return ((riskLevel == RiskLevel.High && luck > 0.2) || luck > 0.8) ? + ActionResult.BullGoresPlayer : ActionResult.PlayerKillsBull; + } + + ActionResult Panic() => + ActionResult.BullGoresPlayer; + + ActionResult TrySurvive() + { + if (random.Next(2) == 0) + { + bravery = 1.5; + return ActionResult.BullKillsPlayer; + } + else + return ActionResult.FightContinues; + } + + ActionResult Flee() + { + bravery = 0.0; + return ActionResult.PlayerFlees; + } + + ActionResult IgnoreInjury(Action action) + { + if (random.Next(2) == 0) + { + bravery = 2.0; + return action == Action.Dodge ? ActionResult.FightContinues : ActionResult.Draw; + } + else + return ActionResult.BullGoresPlayer; + } + + Reward GetReward() + { + var score = CalculateScore(); + + if (score * random.NextDouble() < 2.4) + return Reward.Nothing; + else + if (score * random.NextDouble() < 4.9) + return Reward.OneEar; + else + if (score * random.NextDouble() < 7.4) + return Reward.TwoEars; + else + return Reward.CarriedFromRing; + } + + double CalculateScore() + { + var score = 4.5; + + // Style + score += style / 6; + + // Assisstance + score -= assistanceLevel * 2.5; + + // Courage + score += 4 * bravery; + + // Kill bonus + score += (result == ActionResult.PlayerKillsBull) ? 4 : 2; + + // Match length + score -= Math.Pow(passNumber, 2) / 120; + + // Difficulty + score -= (int)bullQuality; + + return score; + } + } + } +} diff --git a/17 Bullfight/csharp/src/Controller.cs b/17 Bullfight/csharp/src/Controller.cs index 2355e247..c1441f9a 100644 --- a/17 Bullfight/csharp/src/Controller.cs +++ b/17 Bullfight/csharp/src/Controller.cs @@ -93,10 +93,15 @@ namespace Game /// /// True if the player flees; otherwise, false. /// - public static bool PlayerRunsFromRing() + public static bool GetPlayerRunsFromRing() { View.PromptRunFromRing(); - return GetYesOrNo(); + + var playerFlees = GetYesOrNo(); + if (!playerFlees) + View.ShowPlayerFoolhardy(); + + return playerFlees; } /// diff --git a/17 Bullfight/csharp/src/Events/BullCharging.cs b/17 Bullfight/csharp/src/Events/BullCharging.cs new file mode 100644 index 00000000..bc041c64 --- /dev/null +++ b/17 Bullfight/csharp/src/Events/BullCharging.cs @@ -0,0 +1,7 @@ +namespace Game.Events +{ + /// + /// Indicates that the bull is charing the player. + /// + public sealed record BullCharging(int PassNumber) : Event; +} diff --git a/17 Bullfight/csharp/src/Events/Event.cs b/17 Bullfight/csharp/src/Events/Event.cs new file mode 100644 index 00000000..e9a8a646 --- /dev/null +++ b/17 Bullfight/csharp/src/Events/Event.cs @@ -0,0 +1,7 @@ +namespace Game.Events +{ + /// + /// Common base class for all events in the game. + /// + public abstract record Event(); +} diff --git a/17 Bullfight/csharp/src/Events/MatchCompleted.cs b/17 Bullfight/csharp/src/Events/MatchCompleted.cs new file mode 100644 index 00000000..94c67d06 --- /dev/null +++ b/17 Bullfight/csharp/src/Events/MatchCompleted.cs @@ -0,0 +1,7 @@ +namespace Game.Events +{ + /// + /// Indicates that the fight has completed. + /// + public sealed record MatchCompleted(ActionResult Result, bool ExtremeBravery, Reward Reward) : Event; +} diff --git a/17 Bullfight/csharp/src/Events/MatchStarted.cs b/17 Bullfight/csharp/src/Events/MatchStarted.cs new file mode 100644 index 00000000..8141b617 --- /dev/null +++ b/17 Bullfight/csharp/src/Events/MatchStarted.cs @@ -0,0 +1,13 @@ +namespace Game.Events +{ + /// + /// Indicates that a new match has started. + /// + public sealed record MatchStarted( + Quality BullQuality, + Quality ToreadorePerformance, + Quality PicadorePerformance, + int ToreadoresKilled, + int PicadoresKilled, + int HorsesKilled) : Event; +} diff --git a/17 Bullfight/csharp/src/Events/PlayerGored.cs b/17 Bullfight/csharp/src/Events/PlayerGored.cs new file mode 100644 index 00000000..f55ae20d --- /dev/null +++ b/17 Bullfight/csharp/src/Events/PlayerGored.cs @@ -0,0 +1,7 @@ +namespace Game.Events +{ + /// + /// Indicates that the player has been gored by the bull. + /// + public sealed record PlayerGored(bool Panicked, bool FirstGoring) : Event; +} diff --git a/17 Bullfight/csharp/src/Events/PlayerSurvived.cs b/17 Bullfight/csharp/src/Events/PlayerSurvived.cs new file mode 100644 index 00000000..135a9a09 --- /dev/null +++ b/17 Bullfight/csharp/src/Events/PlayerSurvived.cs @@ -0,0 +1,7 @@ +namespace Game.Events +{ + /// + /// Indicates that the player has survived being gored by the bull. + /// + public sealed record PlayerSurvived() : Event; +} diff --git a/17 Bullfight/csharp/src/MatchConditions.cs b/17 Bullfight/csharp/src/MatchConditions.cs deleted file mode 100644 index 5d5d979c..00000000 --- a/17 Bullfight/csharp/src/MatchConditions.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace Game -{ - /// - /// Stores the initial conditions of a match. - /// - public record MatchConditions - { - /// - /// Gets the quality of the bull. - /// - public Quality BullQuality { get; init; } - - /// - /// Gets the quality of help received from the toreadores. - /// - public Quality ToreadorePerformance { get; init; } - - /// - /// Gets the quality of help received from the picadores. - /// - public Quality PicadorePerformance { get; init; } - - /// - /// Gets the number of toreadores killed while preparing for the - /// final round. - /// - public int ToreadoresKilled { get; init; } - - /// - /// Gets the number of picadores killed while preparing for the - /// final round. - /// - public int PicadoresKilled { get; init; } - - /// - /// Gets the number of horses killed while preparing for the final - /// round. - /// - public int HorsesKilled { get; init; } - } -} diff --git a/17 Bullfight/csharp/src/MatchState.cs b/17 Bullfight/csharp/src/MatchState.cs deleted file mode 100644 index 3eae6d5d..00000000 --- a/17 Bullfight/csharp/src/MatchState.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Game -{ - /// - /// Stores the current state of the match. - /// - public record MatchState(MatchConditions Conditions) - { - /// - /// Gets the number of times the bull has charged. - /// - public int PassNumber { get; init; } - - /// - /// Measures the player's bravery during the match. - /// - public double Bravery { get; init; } - - /// - /// Measures how much style the player showed during the match. - /// - public double Style { get; init; } - - /// - /// Gets the result of the player's last action. - /// - public ActionResult Result { get; init; } - } -} diff --git a/17 Bullfight/csharp/src/Mediator.cs b/17 Bullfight/csharp/src/Mediator.cs new file mode 100644 index 00000000..c9d4e298 --- /dev/null +++ b/17 Bullfight/csharp/src/Mediator.cs @@ -0,0 +1,48 @@ +using System.Diagnostics; + +namespace Game +{ + /// + /// Facilitates sending messages between the two game loops. + /// + /// + /// This class serves as a little piece of glue in between the main program + /// loop and the bull fight coroutine. When the main program calls one of + /// its methods, the mediator creates the appropriate input data that the + /// bull fight coroutine later retrieves with . + /// + public class Mediator + { + private object? m_input; + + public void Dodge(RiskLevel riskLevel) => + m_input = (Action.Dodge, riskLevel); + + public void Kill(RiskLevel riskLevel) => + m_input = (Action.Kill, riskLevel); + + public void Panic() => + m_input = (Action.Panic, default(RiskLevel)); + + public void RunFromRing() => + m_input = true; + + public void ContinueFighting() => + m_input = false; + + /// + /// Gets the next input from the user. + /// + /// + /// The type of input to receive. + /// + public T GetInput() + { + Debug.Assert(m_input is not null, "No input received"); + Debug.Assert(m_input.GetType() == typeof(T), "Invalid input received"); + var result = (T)m_input; + m_input = null; + return result; + } + } +} diff --git a/17 Bullfight/csharp/src/Program.cs b/17 Bullfight/csharp/src/Program.cs index 17da736f..d3f3760b 100644 --- a/17 Bullfight/csharp/src/Program.cs +++ b/17 Bullfight/csharp/src/Program.cs @@ -1,6 +1,4 @@ -using System; - -namespace Game +namespace Game { class Program { @@ -8,49 +6,49 @@ namespace Game { Controller.StartGame(); - var random = new Random(); - var match = Rules.StartMatch(random); - View.ShowStartingConditions(match.Conditions); - - while (match.Result == ActionResult.FightContinues) + var mediator = new Mediator(); + foreach (var evt in BullFight.Begin(mediator)) { - match = match with { PassNumber = match.PassNumber + 1 }; - - View.StartOfPass(match.PassNumber); - - var (action, riskLevel) = Controller.GetPlayerIntention(match.PassNumber); - match = action switch + switch (evt) { - Action.Dodge => Rules.TryDodge(random, riskLevel, match), - Action.Kill => Rules.TryKill(random, riskLevel, match), - _ => Rules.Panic(match) - }; + case Events.MatchStarted matchStarted: + View.ShowStartingConditions(matchStarted); + break; - var first = true; - while (match.Result == ActionResult.BullGoresPlayer) - { - View.ShowPlayerGored(action == Action.Panic, first); - first = false; + case Events.BullCharging bullCharging: + View.ShowStartOfPass(bullCharging.PassNumber); + var (action, riskLevel) = Controller.GetPlayerIntention(bullCharging.PassNumber); + switch (action) + { + case Action.Dodge: + mediator.Dodge(riskLevel); + break; + case Action.Kill: + mediator.Kill(riskLevel); + break; + case Action.Panic: + mediator.Panic(); + break; + } + break; - match = Rules.TrySurvive(random, match); - if (match.Result == ActionResult.FightContinues) - { + case Events.PlayerGored playerGored: + View.ShowPlayerGored(playerGored.Panicked, playerGored.FirstGoring); + break; + + case Events.PlayerSurvived: View.ShowPlayerSurvives(); - - if (Controller.PlayerRunsFromRing()) - { - match = Rules.Flee(match); - } + if (Controller.GetPlayerRunsFromRing()) + mediator.RunFromRing(); else - { - View.ShowPlayerFoolhardy(); - match = Rules.IgnoreInjury(random, action, match); - } - } + mediator.ContinueFighting(); + break; + + case Events.MatchCompleted matchCompleted: + View.ShowFinalResult(matchCompleted.Result, matchCompleted.ExtremeBravery, matchCompleted.Reward); + break; } } - - View.ShowFinalResult(match.Result, match.Bravery, Rules.GetReward(random, match)); } } } diff --git a/17 Bullfight/csharp/src/Rules.cs b/17 Bullfight/csharp/src/Rules.cs deleted file mode 100644 index 463af1ed..00000000 --- a/17 Bullfight/csharp/src/Rules.cs +++ /dev/null @@ -1,258 +0,0 @@ -using System; - -namespace Game -{ - /// - /// Provides functions implementing the rules of the game. - /// - public static class Rules - { - /// - /// Gets the state of a new match. - /// - /// - /// The random number generator. - /// - public static MatchState StartMatch(Random random) - { - var bullQuality = GetBullQuality(); - var toreadorePerformance = GetHelpQuality(); - var picadorePerformance = GetHelpQuality(); - - var conditions = new MatchConditions - { - BullQuality = bullQuality, - ToreadorePerformance = toreadorePerformance, - PicadorePerformance = picadorePerformance, - ToreadoresKilled = GetHumanCasualties(toreadorePerformance), - PicadoresKilled = GetHumanCasualties(picadorePerformance), - HorsesKilled = GetHorseCasualties(picadorePerformance) - }; - - return new MatchState(conditions) - { - Bravery = 1.0, - Style = 1.0 - }; - - Quality GetBullQuality() => - (Quality)random.Next(1, 6); - - Quality GetHelpQuality() => - ((3.0 / (int)bullQuality) * random.NextDouble()) switch - { - < 0.37 => Quality.Superb, - < 0.50 => Quality.Good, - < 0.63 => Quality.Fair, - < 0.87 => Quality.Poor, - _ => Quality.Awful - }; - - int GetHumanCasualties(Quality performance) => - performance switch - { - Quality.Poor => random.Next(0, 2), - Quality.Awful => random.Next(1, 3), - _ => 0 - }; - - int GetHorseCasualties(Quality performance) => - performance switch - { - // NOTE: The code for displaying a single horse casuality - // following a poor picadore peformance was unreachable - // in the original BASIC version. I've assumed this was - // a bug. - Quality.Poor => 1, - Quality.Awful => random.Next(1, 3), - _ => 0 - }; - } - - /// - /// Determines the result when the player attempts to dodge the bull. - /// - /// - /// The random number generator. - /// - /// - /// The level of risk in the dodge manoeuvre chosen. - /// - /// - /// The current match state. - /// - /// - /// The updated match state. - /// - public static MatchState TryDodge(Random random, RiskLevel riskLevel, MatchState match) - { - var difficultyModifier = riskLevel switch - { - RiskLevel.High => 3.0, - RiskLevel.Medium => 2.0, - _ => 0.5 - }; - - var outcome = (GetBullStrength(match) + (difficultyModifier / 10)) * random.NextDouble() / - ((GetAssisstance(match) + (match.PassNumber / 10.0)) * 5); - - return outcome < 0.51 ? - match with { Result = ActionResult.FightContinues, Style = match.Style + difficultyModifier } : - match with { Result = ActionResult.BullGoresPlayer }; - } - - /// - /// Determines the result when the player attempts to kill the bull. - /// - /// - /// The random number generator. - /// - /// - /// The level of risk in the manoeuvre chosen. - /// - /// - /// The current match state. - /// - /// - /// The updated match state. - /// - public static MatchState TryKill(Random random, RiskLevel riskLevel, MatchState match) - { - var K = GetBullStrength(match) * 10 * random.NextDouble() / (GetAssisstance(match) * 5 * match.PassNumber); - - return ((riskLevel == RiskLevel.High && K > 0.2) || K > 0.8) ? - match with { Result = ActionResult.BullGoresPlayer } : - match with { Result = ActionResult.PlayerKillsBull }; - } - - /// - /// Determines if the player survives being gored by the bull. - /// - /// - /// The random number generator. - /// - /// - /// The current match state. - /// - /// - /// The updated match state. - /// - public static MatchState TrySurvive(Random random, MatchState match) => - (random.Next(2) == 0) ? - match with { Result = ActionResult.BullKillsPlayer, Bravery = 1.5 } : - match with { Result = ActionResult.FightContinues }; - - /// - /// Determines the result when the player panics and fails to do anything. - /// - /// - /// The match state. - /// - public static MatchState Panic(MatchState match) => - match with { Result = ActionResult.BullGoresPlayer }; - - /// - /// Determines the result when the player flees the ring. - /// - /// - /// The current match state. - /// - /// - /// The updated match state. - /// - public static MatchState Flee(MatchState match) => - match with { Result = ActionResult.PlayerFlees, Bravery = 0.0 }; - - /// - /// Determines the result when the player decides to continue fighting - /// following an injury. - /// - /// - /// The random number generator. - /// - /// - /// The action the player took that lead to the injury. - /// - /// - /// The current match state. - /// - /// - /// The updated match state. - /// - public static MatchState IgnoreInjury(Random random, Action action, MatchState match) => - (random.Next(2) == 0) ? - match with { Result = action == Action.Dodge ? ActionResult.FightContinues : ActionResult.Draw, Bravery = 2.0 } : - match with { Result = ActionResult.BullGoresPlayer }; - - /// - /// Gets the player's reward for completing a match. - /// - /// - /// The random number generator. - /// - /// - /// The final match state. - /// - public static Reward GetReward(Random random, MatchState match) - { - var score = CalculateScore(); - - if (score * random.NextDouble() < 2.4) - return Reward.Nothing; - else - if (score * random.NextDouble() < 4.9) - return Reward.OneEar; - else - if (score * random.NextDouble() < 7.4) - return Reward.TwoEars; - else - return Reward.CarriedFromRing; - - double CalculateScore() - { - var score = 4.5; - - // Style - score += match.Style / 6; - - // Assisstance - score -= GetAssisstance(match) * 2.5; - - // Courage - score += 4 * match.Bravery; - - // Kill bonus - score += (match.Result == ActionResult.PlayerKillsBull) ? 4 : 2; - - // Match length - score -= Math.Pow(match.PassNumber, 2) / 120; - - // Difficulty - score -= (int)match.Conditions.BullQuality; - - return score; - } - } - - /// - /// Calculates the strength of the bull in a match. - /// - private static double GetBullStrength(MatchState match) => - 6 - (int)match.Conditions.BullQuality; - - /// - /// Gets the amount of assistance received from the toreadores and - /// picadores in a match. - /// - private static double GetAssisstance(MatchState match) => - GetPerformanceBonus(match.Conditions.ToreadorePerformance) + - GetPerformanceBonus(match.Conditions.PicadorePerformance); - - /// - /// Gets the amount of assistance rendered by a performance of the - /// given quality. - /// - private static double GetPerformanceBonus(Quality performance) => - (6 - (int)performance) * 0.1; - } -} diff --git a/17 Bullfight/csharp/src/View.cs b/17 Bullfight/csharp/src/View.cs index 2bff39c8..0cc627b8 100644 --- a/17 Bullfight/csharp/src/View.cs +++ b/17 Bullfight/csharp/src/View.cs @@ -47,22 +47,22 @@ namespace Game Console.WriteLine(); } - public static void ShowStartingConditions(MatchConditions conditions) + public static void ShowStartingConditions(Events.MatchStarted matchStarted) { ShowBullQuality(); - ShowHelpQuality("TOREADORES", conditions.ToreadorePerformance, conditions.ToreadoresKilled, 0); - ShowHelpQuality("PICADORES", conditions.PicadorePerformance, conditions.PicadoresKilled, conditions.HorsesKilled); + ShowHelpQuality("TOREADORES", matchStarted.ToreadorePerformance, matchStarted.ToreadoresKilled, 0); + ShowHelpQuality("PICADORES", matchStarted.PicadorePerformance, matchStarted.PicadoresKilled, matchStarted.HorsesKilled); void ShowBullQuality() { - Console.WriteLine($"YOU HAVE DRAWN A {QualityString[(int)conditions.BullQuality - 1]} BULL."); + Console.WriteLine($"YOU HAVE DRAWN A {QualityString[(int)matchStarted.BullQuality - 1]} BULL."); - if (conditions.BullQuality > Quality.Poor) + if (matchStarted.BullQuality > Quality.Poor) { Console.WriteLine("YOU'RE LUCKY"); } else - if (conditions.BullQuality < Quality.Good) + if (matchStarted.BullQuality < Quality.Good) { Console.WriteLine("GOOD LUCK. YOU'LL NEED IT."); Console.WriteLine(); @@ -86,9 +86,11 @@ namespace Game case Quality.Poor: if (horsesKilled > 0) Console.WriteLine($"ONE OF THE HORSES OF THE {helperName} WAS KILLED."); - + if (helpersKilled > 0) Console.WriteLine($"ONE OF THE {helperName} WAS KILLED."); + else + Console.WriteLine($"NO {helperName} WERE KILLED."); break; case Quality.Awful: @@ -101,7 +103,7 @@ namespace Game } } - public static void StartOfPass(int passNumber) + public static void ShowStartOfPass(int passNumber) { Console.WriteLine(); Console.WriteLine(); @@ -129,7 +131,7 @@ namespace Game Console.WriteLine("YOU ARE BRAVE. STUPID, BUT BRAVE."); } - public static void ShowFinalResult(ActionResult result, double bravery, Reward reward) + public static void ShowFinalResult(ActionResult result, bool extremeBravery, Reward reward) { Console.WriteLine(); Console.WriteLine(); @@ -156,7 +158,7 @@ namespace Game } else { - if (bravery == 2) // You were gored by the bull but survived (and did not later die or flee) + if (extremeBravery) Console.WriteLine("THE CROWD CHEERS WILDLY!"); else if (result == ActionResult.PlayerKillsBull)