diff --git a/12_Bombs_Away/csharp/BombsAway.sln b/12_Bombs_Away/csharp/BombsAway.sln new file mode 100644 index 00000000..8ae70157 --- /dev/null +++ b/12_Bombs_Away/csharp/BombsAway.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32014.148 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BombsAwayConsole", "BombsAwayConsole\BombsAwayConsole.csproj", "{D80015FA-423C-4A16-AA2B-16669245AD59}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BombsAwayGame", "BombsAwayGame\BombsAwayGame.csproj", "{F57AEC18-FEE9-4F08-9F20-DFC56EFE6BFC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D80015FA-423C-4A16-AA2B-16669245AD59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D80015FA-423C-4A16-AA2B-16669245AD59}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D80015FA-423C-4A16-AA2B-16669245AD59}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D80015FA-423C-4A16-AA2B-16669245AD59}.Release|Any CPU.Build.0 = Release|Any CPU + {F57AEC18-FEE9-4F08-9F20-DFC56EFE6BFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F57AEC18-FEE9-4F08-9F20-DFC56EFE6BFC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F57AEC18-FEE9-4F08-9F20-DFC56EFE6BFC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F57AEC18-FEE9-4F08-9F20-DFC56EFE6BFC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {39B2ECFB-037D-4335-BBD2-64892E953DD4} + EndGlobalSection +EndGlobal diff --git a/12_Bombs_Away/csharp/BombsAwayConsole/BombsAwayConsole.csproj b/12_Bombs_Away/csharp/BombsAwayConsole/BombsAwayConsole.csproj new file mode 100644 index 00000000..aae99def --- /dev/null +++ b/12_Bombs_Away/csharp/BombsAwayConsole/BombsAwayConsole.csproj @@ -0,0 +1,14 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + diff --git a/12_Bombs_Away/csharp/BombsAwayConsole/ConsoleUserInterface.cs b/12_Bombs_Away/csharp/BombsAwayConsole/ConsoleUserInterface.cs new file mode 100644 index 00000000..66d6d4d3 --- /dev/null +++ b/12_Bombs_Away/csharp/BombsAwayConsole/ConsoleUserInterface.cs @@ -0,0 +1,134 @@ +namespace BombsAwayConsole; + +/// +/// Implements by writing to and reading from . +/// +internal class ConsoleUserInterface : BombsAwayGame.IUserInterface +{ + /// + /// Write message to console. + /// + /// Message to display. + public void Output(string message) + { + Console.WriteLine(message); + } + + /// + /// Write choices with affixed indexes, allowing the user to choose by index. + /// + /// Message to display. + /// Choices to display. + /// Choice that user picked. + public int Choose(string message, IList choices) + { + IEnumerable choicesWithIndexes = choices.Select((choice, index) => $"{choice}({index + 1})"); + string choiceText = string.Join(", ", choicesWithIndexes); + Output($"{message} -- {choiceText}"); + + ISet allowedKeys = ConsoleKeysFromList(choices); + ConsoleKey? choice; + do + { + choice = ReadChoice(allowedKeys); + if (choice is null) + { + Output("TRY AGAIN..."); + } + } + while (choice is null); + + return ListIndexFromConsoleKey(choice.Value); + } + + /// + /// Convert the given list to its equivalents. This generates keys that map + /// the first element to , the second element to , + /// and so on, up to the last element of the list. + /// + /// List whose elements will be converted to equivalents. + /// equivalents from . + private ISet ConsoleKeysFromList(IList list) + { + IEnumerable indexes = Enumerable.Range((int)ConsoleKey.D1, list.Count); + return new HashSet(indexes.Cast()); + } + + /// + /// Convert the given console key to its list index equivalent. This assumes the key was generated from + /// + /// + /// Key to convert to its list index equivalent. + /// List index equivalent of key. + private int ListIndexFromConsoleKey(ConsoleKey key) + { + return key - ConsoleKey.D1; + } + + /// + /// Read a key from the console and return it if it is in the given allowed keys. + /// + /// Allowed keys. + /// Key read from , if it is in ; null otherwise./> + private ConsoleKey? ReadChoice(ISet allowedKeys) + { + ConsoleKeyInfo keyInfo = ReadKey(); + return allowedKeys.Contains(keyInfo.Key) ? keyInfo.Key : null; + } + + /// + /// Read key from . + /// + /// Key read from . + private ConsoleKeyInfo ReadKey() + { + ConsoleKeyInfo result = Console.ReadKey(intercept: false); + // Write a blank line to the console so the displayed key is on its own line. + Console.WriteLine(); + return result; + } + + /// + /// Allow user to choose 'Y' or 'N' from . + /// + /// Message to display. + /// True if user chose 'Y', false if user chose 'N'. + public bool ChooseYesOrNo(string message) + { + Output(message); + ConsoleKey? choice; + do + { + choice = ReadChoice(new HashSet(new[] { ConsoleKey.Y, ConsoleKey.N })); + if (choice is null) + { + Output("ENTER Y OR N"); + } + } + while (choice is null); + + return choice.Value == ConsoleKey.Y; + } + + /// + /// Get integer by reading a line from . + /// + /// Integer read from . + public int InputInteger() + { + bool resultIsValid; + int result; + do + { + string? integerText = Console.ReadLine(); + resultIsValid = int.TryParse(integerText, out result); + if (!resultIsValid) + { + Output("PLEASE ENTER A NUMBER"); + } + } + while (!resultIsValid); + + return result; + } +} diff --git a/12_Bombs_Away/csharp/BombsAwayConsole/Program.cs b/12_Bombs_Away/csharp/BombsAwayConsole/Program.cs new file mode 100644 index 00000000..35728cfc --- /dev/null +++ b/12_Bombs_Away/csharp/BombsAwayConsole/Program.cs @@ -0,0 +1,26 @@ +using BombsAwayConsole; +using BombsAwayGame; + +/// Create and play s using a . +PlayGameWhileUserWantsTo(new ConsoleUserInterface()); + +void PlayGameWhileUserWantsTo(ConsoleUserInterface ui) +{ + do + { + new Game(ui).Play(); + } + while (UserWantsToPlayAgain(ui)); +} + +bool UserWantsToPlayAgain(IUserInterface ui) +{ + bool result = ui.ChooseYesOrNo("ANOTHER MISSION (Y OR N)?"); + if (!result) + { + Console.WriteLine("CHICKEN !!!"); + } + + return result; +} + diff --git a/12_Bombs_Away/csharp/BombsAwayGame/AlliesSide.cs b/12_Bombs_Away/csharp/BombsAwayGame/AlliesSide.cs new file mode 100644 index 00000000..c6c7105b --- /dev/null +++ b/12_Bombs_Away/csharp/BombsAwayGame/AlliesSide.cs @@ -0,0 +1,22 @@ +namespace BombsAwayGame; + +/// +/// Allies protagonist. Can fly missions in a Liberator, B-29, B-17, or Lancaster. +/// +internal class AlliesSide : MissionSide +{ + public AlliesSide(IUserInterface ui) + : base(ui) + { + } + + protected override string ChooseMissionMessage => "AIRCRAFT"; + + protected override IList AllMissions => new Mission[] + { + new("LIBERATOR", "YOU'VE GOT 2 TONS OF BOMBS FLYING FOR PLOESTI."), + new("B-29", "YOU'RE DUMPING THE A-BOMB ON HIROSHIMA."), + new("B-17", "YOU'RE CHASING THE BISMARK IN THE NORTH SEA."), + new("LANCASTER", "YOU'RE BUSTING A GERMAN HEAVY WATER PLANT IN THE RUHR.") + }; +} diff --git a/12_Bombs_Away/csharp/BombsAwayGame/BombsAwayGame.csproj b/12_Bombs_Away/csharp/BombsAwayGame/BombsAwayGame.csproj new file mode 100644 index 00000000..132c02c5 --- /dev/null +++ b/12_Bombs_Away/csharp/BombsAwayGame/BombsAwayGame.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/12_Bombs_Away/csharp/BombsAwayGame/EnemyArtillery.cs b/12_Bombs_Away/csharp/BombsAwayGame/EnemyArtillery.cs new file mode 100644 index 00000000..a810c8c0 --- /dev/null +++ b/12_Bombs_Away/csharp/BombsAwayGame/EnemyArtillery.cs @@ -0,0 +1,8 @@ +namespace BombsAwayGame; + +/// +/// Represents enemy artillery. +/// +/// Name of artillery type. +/// Accuracy of artillery. This is the `T` variable in the original BASIC. +internal record class EnemyArtillery(string Name, int Accuracy); diff --git a/12_Bombs_Away/csharp/BombsAwayGame/Game.cs b/12_Bombs_Away/csharp/BombsAwayGame/Game.cs new file mode 100644 index 00000000..d6b5c3e9 --- /dev/null +++ b/12_Bombs_Away/csharp/BombsAwayGame/Game.cs @@ -0,0 +1,58 @@ +namespace BombsAwayGame; + +/// +/// Plays the Bombs Away game using a supplied . +/// +public class Game +{ + private readonly IUserInterface _ui; + + /// + /// Create game instance using the given UI. + /// + /// UI to use for game. + public Game(IUserInterface ui) + { + _ui = ui; + } + + /// + /// Play game. Choose a side and play the side's logic. + /// + public void Play() + { + _ui.Output("YOU ARE A PILOT IN A WORLD WAR II BOMBER."); + Side side = ChooseSide(); + side.Play(); + } + + /// + /// Represents a . + /// + /// Name of side. + /// Create instance of side that this descriptor represents. + private record class SideDescriptor(string Name, Func CreateSide); + + /// + /// Choose side and return a new instance of that side. + /// + /// New instance of side that was chosen. + private Side ChooseSide() + { + SideDescriptor[] sides = AllSideDescriptors; + string[] sideNames = sides.Select(a => a.Name).ToArray(); + int index = _ui.Choose("WHAT SIDE", sideNames); + return sides[index].CreateSide(); + } + + /// + /// All side descriptors. + /// + private SideDescriptor[] AllSideDescriptors => new SideDescriptor[] + { + new("ITALY", () => new ItalySide(_ui)), + new("ALLIES", () => new AlliesSide(_ui)), + new("JAPAN", () => new JapanSide(_ui)), + new("GERMANY", () => new GermanySide(_ui)), + }; +} diff --git a/12_Bombs_Away/csharp/BombsAwayGame/GermanySide.cs b/12_Bombs_Away/csharp/BombsAwayGame/GermanySide.cs new file mode 100644 index 00000000..99843fce --- /dev/null +++ b/12_Bombs_Away/csharp/BombsAwayGame/GermanySide.cs @@ -0,0 +1,21 @@ +namespace BombsAwayGame; + +/// +/// Germany protagonist. Can fly missions to Russia, England, and France. +/// +internal class GermanySide : MissionSide +{ + public GermanySide(IUserInterface ui) + : base(ui) + { + } + + protected override string ChooseMissionMessage => "A NAZI, EH? OH WELL. ARE YOU GOING FOR"; + + protected override IList AllMissions => new Mission[] + { + new("RUSSIA", "YOU'RE NEARING STALINGRAD."), + new("ENGLAND", "NEARING LONDON. BE CAREFUL, THEY'VE GOT RADAR."), + new("FRANCE", "NEARING VERSAILLES. DUCK SOUP. THEY'RE NEARLY DEFENSELESS.") + }; +} diff --git a/12_Bombs_Away/csharp/BombsAwayGame/IUserInterface.cs b/12_Bombs_Away/csharp/BombsAwayGame/IUserInterface.cs new file mode 100644 index 00000000..50b7828c --- /dev/null +++ b/12_Bombs_Away/csharp/BombsAwayGame/IUserInterface.cs @@ -0,0 +1,38 @@ +namespace BombsAwayGame; + +/// +/// Represents an interface for supplying data to the game. +/// +/// +/// Abstracting the UI allows us to concentrate its concerns in one part of our code and to change UI behavior +/// without creating any risk of changing the game logic. It also allows us to supply an automated UI for tests. +/// +public interface IUserInterface +{ + /// + /// Display the given message. + /// + /// Message to display. + void Output(string message); + + /// + /// Choose an item from the given choices. + /// + /// Message to display. + /// Choices to choose from. + /// Index of choice in that user chose. + int Choose(string message, IList choices); + + /// + /// Allow user to choose Yes or No. + /// + /// Message to display. + /// True if user chose Yes, false if user chose No. + bool ChooseYesOrNo(string message); + + /// + /// Get integer from user. + /// + /// Integer supplied by user. + int InputInteger(); +} diff --git a/12_Bombs_Away/csharp/BombsAwayGame/ItalySide.cs b/12_Bombs_Away/csharp/BombsAwayGame/ItalySide.cs new file mode 100644 index 00000000..9f8bcd83 --- /dev/null +++ b/12_Bombs_Away/csharp/BombsAwayGame/ItalySide.cs @@ -0,0 +1,21 @@ +namespace BombsAwayGame; + +/// +/// Italy protagonist. Can fly missions to Albania, Greece, and North Africa. +/// +internal class ItalySide : MissionSide +{ + public ItalySide(IUserInterface ui) + : base(ui) + { + } + + protected override string ChooseMissionMessage => "YOUR TARGET"; + + protected override IList AllMissions => new Mission[] + { + new("ALBANIA", "SHOULD BE EASY -- YOU'RE FLYING A NAZI-MADE PLANE."), + new("GREECE", "BE CAREFUL!!!"), + new("NORTH AFRICA", "YOU'RE GOING FOR THE OIL, EH?") + }; +} diff --git a/12_Bombs_Away/csharp/BombsAwayGame/JapanSide.cs b/12_Bombs_Away/csharp/BombsAwayGame/JapanSide.cs new file mode 100644 index 00000000..33abc83b --- /dev/null +++ b/12_Bombs_Away/csharp/BombsAwayGame/JapanSide.cs @@ -0,0 +1,38 @@ +namespace BombsAwayGame; + +/// +/// Japan protagonist. Flies a kamikaze mission, which has a different logic from s. +/// +internal class JapanSide : Side +{ + public JapanSide(IUserInterface ui) + : base(ui) + { + } + + /// + /// Perform a kamikaze mission. If first kamikaze mission, it will succeed 65% of the time. If it's not + /// first kamikaze mission, perform an enemy counterattack. + /// + public override void Play() + { + UI.Output("YOU'RE FLYING A KAMIKAZE MISSION OVER THE USS LEXINGTON."); + + bool isFirstMission = UI.ChooseYesOrNo("YOUR FIRST KAMIKAZE MISSION(Y OR N)?"); + if (!isFirstMission) + { + // LINE 207 of original BASIC: hitRatePercent is initialized to 0, + // but R, the type of artillery, is not initialized at all. Setting + // R = 1, which is to say EnemyArtillery = Guns, gives the same result. + EnemyCounterattack(Guns, hitRatePercent: 0); + } + else if (RandomFrac() > 0.65) + { + MissionSucceeded(); + } + else + { + MissionFailed(); + } + } +} diff --git a/12_Bombs_Away/csharp/BombsAwayGame/Mission.cs b/12_Bombs_Away/csharp/BombsAwayGame/Mission.cs new file mode 100644 index 00000000..c93892fd --- /dev/null +++ b/12_Bombs_Away/csharp/BombsAwayGame/Mission.cs @@ -0,0 +1,8 @@ +namespace BombsAwayGame; + +/// +/// Represents a mission that can be flown by a . +/// +/// Name of mission. +/// Description of mission. +internal record class Mission(string Name, string Description); diff --git a/12_Bombs_Away/csharp/BombsAwayGame/MissionSide.cs b/12_Bombs_Away/csharp/BombsAwayGame/MissionSide.cs new file mode 100644 index 00000000..b3a54275 --- /dev/null +++ b/12_Bombs_Away/csharp/BombsAwayGame/MissionSide.cs @@ -0,0 +1,208 @@ +namespace BombsAwayGame; + +/// +/// Represents a protagonist that chooses a standard (non-kamikaze) mission. +/// +internal abstract class MissionSide : Side +{ + /// + /// Create instance using the given UI. + /// + /// UI to use. + public MissionSide(IUserInterface ui) + : base(ui) + { + } + + /// + /// Reasonable upper bound for missions flown previously. + /// + private const int MaxMissionCount = 160; + + /// + /// Choose a mission and attempt it. If attempt fails, perform an enemy counterattack. + /// + public override void Play() + { + Mission mission = ChooseMission(); + UI.Output(mission.Description); + + int missionCount = MissionCountFromUI(); + CommentOnMissionCount(missionCount); + + AttemptMission(missionCount); + } + + /// + /// Choose a mission. + /// + /// Mission chosen. + private Mission ChooseMission() + { + IList missions = AllMissions; + string[] missionNames = missions.Select(a => a.Name).ToArray(); + int index = UI.Choose(ChooseMissionMessage, missionNames); + return missions[index]; + } + + /// + /// Message to display when choosing a mission. + /// + protected abstract string ChooseMissionMessage { get; } + + /// + /// All aviailable missions to choose from. + /// + protected abstract IList AllMissions { get; } + + /// + /// Get mission count from UI. If mission count exceeds a reasonable maximum, ask UI again. + /// + /// Mission count from UI. + private int MissionCountFromUI() + { + const string HowManyMissions = "HOW MANY MISSIONS HAVE YOU FLOWN?"; + string inputMessage = HowManyMissions; + + bool resultIsValid; + int result; + do + { + UI.Output(inputMessage); + result = UI.InputInteger(); + if (result < 0) + { + UI.Output($"NUMBER OF MISSIONS CAN'T BE NEGATIVE."); + resultIsValid = false; + } + else if (result > MaxMissionCount) + { + resultIsValid = false; + UI.Output($"MISSIONS, NOT MILES...{MaxMissionCount} MISSIONS IS HIGH EVEN FOR OLD-TIMERS."); + inputMessage = "NOW THEN, " + HowManyMissions; + } + else + { + resultIsValid = true; + } + } + while (!resultIsValid); + + return result; + } + + /// + /// Display a message about the given mission count, if it is unusually high or low. + /// + /// Mission count to comment on. + private void CommentOnMissionCount(int missionCount) + { + if (missionCount >= 100) + { + UI.Output("THAT'S PUSHING THE ODDS!"); + } + else if (missionCount < 25) + { + UI.Output("FRESH OUT OF TRAINING, EH?"); + } + } + + /// + /// Attempt mission. + /// + /// Number of missions previously flown. Higher mission counts will yield a higher probability of success. + private void AttemptMission(int missionCount) + { + if (missionCount < RandomInteger(0, MaxMissionCount)) + { + MissedTarget(); + } + else + { + MissionSucceeded(); + } + } + + /// + /// Display message indicating that target was missed. Choose enemy artillery and perform a counterattack. + /// + private void MissedTarget() + { + UI.Output("MISSED TARGET BY " + (2 + RandomInteger(0, 30)) + " MILES!"); + UI.Output("NOW YOU'RE REALLY IN FOR IT !!"); + + // Choose enemy and counterattack. + EnemyArtillery enemyArtillery = ChooseEnemyArtillery(); + + if (enemyArtillery == Missiles) + { + EnemyCounterattack(enemyArtillery, hitRatePercent: 0); + } + else + { + int hitRatePercent = EnemyHitRatePercentFromUI(); + if (hitRatePercent < MinEnemyHitRatePercent) + { + UI.Output("YOU LIE, BUT YOU'LL PAY..."); + MissionFailed(); + } + else + { + EnemyCounterattack(enemyArtillery, hitRatePercent); + } + } + } + + /// + /// Choose enemy artillery from UI. + /// + /// Artillery chosen. + private EnemyArtillery ChooseEnemyArtillery() + { + EnemyArtillery[] artilleries = new EnemyArtillery[] { Guns, Missiles, Both }; + string[] artilleryNames = artilleries.Select(a => a.Name).ToArray(); + int index = UI.Choose("DOES THE ENEMY HAVE", artilleryNames); + return artilleries[index]; + } + + /// + /// Minimum allowed hit rate percent. + /// + private const int MinEnemyHitRatePercent = 10; + + /// + /// Maximum allowed hit rate percent. + /// + private const int MaxEnemyHitRatePercent = 50; + + /// + /// Get the enemy hit rate percent from UI. Value must be between zero and . + /// If value is less than , mission fails automatically because the user is + /// assumed to be untruthful. + /// + /// Enemy hit rate percent from UI. + private int EnemyHitRatePercentFromUI() + { + UI.Output($"WHAT'S THE PERCENT HIT RATE OF ENEMY GUNNERS ({MinEnemyHitRatePercent} TO {MaxEnemyHitRatePercent})"); + + bool resultIsValid; + int result; + do + { + result = UI.InputInteger(); + // Let them enter a number below the stated minimum, as they will be caught and punished. + if (0 <= result && result <= MaxEnemyHitRatePercent) + { + resultIsValid = true; + } + else + { + resultIsValid = false; + UI.Output($"NUMBER MUST BE FROM {MinEnemyHitRatePercent} TO {MaxEnemyHitRatePercent}"); + } + } + while (!resultIsValid); + + return result; + } +} diff --git a/12_Bombs_Away/csharp/BombsAwayGame/Side.cs b/12_Bombs_Away/csharp/BombsAwayGame/Side.cs new file mode 100644 index 00000000..7e643971 --- /dev/null +++ b/12_Bombs_Away/csharp/BombsAwayGame/Side.cs @@ -0,0 +1,97 @@ +namespace BombsAwayGame; + +/// +/// Represents a protagonist in the game. +/// +internal abstract class Side +{ + /// + /// Create instance using the given UI. + /// + /// UI to use. + public Side(IUserInterface ui) + { + UI = ui; + } + + /// + /// Play this side. + /// + public abstract void Play(); + + /// + /// User interface supplied to ctor. + /// + protected IUserInterface UI { get; } + + /// + /// Random-number generator for this play-through. + /// + private readonly Random _random = new(); + + /// + /// Gets a random floating-point number greater than or equal to zero, and less than one. + /// + /// Random floating-point number greater than or equal to zero, and less than one. + protected double RandomFrac() => _random.NextDouble(); + + /// + /// Gets a random integer in a range. + /// + /// The inclusive lower bound of the number returned. + /// The exclusive upper bound of the number returned. + /// Random integer in a range. + protected int RandomInteger(int minValue, int maxValue) => _random.Next(minValue: minValue, maxValue: maxValue); + + /// + /// Display messages indicating the mission succeeded. + /// + protected void MissionSucceeded() + { + UI.Output("DIRECT HIT!!!! " + RandomInteger(0, 100) + " KILLED."); + UI.Output("MISSION SUCCESSFUL."); + } + + /// + /// Gets the Guns type of enemy artillery. + /// + protected EnemyArtillery Guns { get; } = new("GUNS", 0); + + /// + /// Gets the Missiles type of enemy artillery. + /// + protected EnemyArtillery Missiles { get; } = new("MISSILES", 35); + + /// + /// Gets the Both Guns and Missiles type of enemy artillery. + /// + protected EnemyArtillery Both { get; } = new("BOTH", 35); + + /// + /// Perform enemy counterattack using the given artillery and hit rate percent. + /// + /// Enemy artillery to use. + /// Hit rate percent for enemy. + protected void EnemyCounterattack(EnemyArtillery artillery, int hitRatePercent) + { + if (hitRatePercent + artillery.Accuracy > RandomInteger(0, 100)) + { + MissionFailed(); + } + else + { + UI.Output("YOU MADE IT THROUGH TREMENDOUS FLAK!!"); + } + } + + /// + /// Display messages indicating the mission failed. + /// + protected void MissionFailed() + { + UI.Output("* * * * BOOM * * * *"); + UI.Output("YOU HAVE BEEN SHOT DOWN....."); + UI.Output("DEARLY BELOVED, WE ARE GATHERED HERE TODAY TO PAY OUR"); + UI.Output("LAST TRIBUTE..."); + } +}