diff --git a/84 Super Star Trek/csharp/Command.cs b/84 Super Star Trek/csharp/Command.cs new file mode 100644 index 00000000..33d9fd01 --- /dev/null +++ b/84 Super Star Trek/csharp/Command.cs @@ -0,0 +1,34 @@ +using System.ComponentModel; + +namespace SuperStarTrek +{ + internal enum Command + { + [Description("To set course")] + NAV, + + [Description("For short range sensor scan")] + SRS, + + [Description("For long range sensor scan")] + LRS, + + [Description("To fire phasers")] + PHA, + + [Description("To fire photon torpedoes")] + TOR, + + [Description("To raise or lower shields")] + SHE, + + [Description("For damage control reports")] + DAM, + + [Description("To call on library-computer")] + COM, + + [Description("To resign your command")] + XXX + } +} diff --git a/84 Super Star Trek/csharp/CommandExtensions.cs b/84 Super Star Trek/csharp/CommandExtensions.cs new file mode 100644 index 00000000..2b69ea31 --- /dev/null +++ b/84 Super Star Trek/csharp/CommandExtensions.cs @@ -0,0 +1,14 @@ +using System.Reflection; +using System.ComponentModel; + +namespace SuperStarTrek +{ + internal static class CommandExtensions + { + internal static string GetDescription(this Command command) => + typeof(Command) + .GetField(command.ToString()) + .GetCustomAttribute() + .Description; + } +} diff --git a/84 Super Star Trek/csharp/Game.cs b/84 Super Star Trek/csharp/Game.cs new file mode 100644 index 00000000..d97b8198 --- /dev/null +++ b/84 Super Star Trek/csharp/Game.cs @@ -0,0 +1,97 @@ +using System; +using SuperStarTrek.Objects; +using SuperStarTrek.Resources; +using SuperStarTrek.Space; +using SuperStarTrek.Systems; +using static System.StringComparison; + +namespace SuperStarTrek +{ + internal class Game + { + private readonly Output _output; + private readonly Input _input; + + private int _initialStardate; + private int _finalStarDate; + private double _currentStardate; + private Coordinates _currentQuadrant; + private Coordinates _currentSector; + private Galaxy _galaxy; + private int _initialKlingonCount; + private Enterprise _enterprise; + + public Game() + { + _output = new Output(); + _input = new Input(_output); + } + + public double Stardate => _currentStardate; + + public void DoIntroduction() + { + _output.Write(Strings.Title); + + _output.Write("Do you need instructions (Y/N)? "); + var response = Console.ReadLine(); + _output.WriteLine(); + + if (!response.Equals("N", InvariantCultureIgnoreCase)) + { + _output.Write(Strings.Instructions); + + _input.WaitForAnyKeyButEnter("to continue"); + } + } + + public void Play() + { + var quadrant = Initialise(); + var gameOver = false; + + _output.Write(Strings.Enterprise); + _output.Write( + Strings.Orders, + _galaxy.KlingonCount, + _finalStarDate, + _finalStarDate - _initialStardate, + _galaxy.StarbaseCount > 1 ? "are" : "is", + _galaxy.StarbaseCount, + _galaxy.StarbaseCount > 1 ? "s" : ""); + + _input.WaitForAnyKeyButEnter("when ready to accept command"); + + _enterprise.Enter(quadrant, Strings.StartText); + + while (!gameOver) + { + var command = _input.GetCommand(); + + gameOver = command == Command.XXX || _enterprise.Execute(command); + } + } + + private Quadrant Initialise() + { + var random = new Random(); + + _currentStardate = _initialStardate = random.GetInt(20, 40) * 100; + _finalStarDate = _initialStardate + random.GetInt(25, 35); + + _currentQuadrant = random.GetCoordinate(); + _currentSector = random.GetCoordinate(); + + _galaxy = new Galaxy(); + _initialKlingonCount = _galaxy.KlingonCount; + + _enterprise = new Enterprise(3000, random.GetCoordinate()); + _enterprise.Add(new ShortRangeSensors(_enterprise, _galaxy, this, _output)) + .Add(new ShieldControl(_enterprise, _output, _input)); + + return new Quadrant(_galaxy[_currentQuadrant], _enterprise); + } + + public bool Replay() => _galaxy.StarbaseCount > 0 && _input.GetString(Strings.ReplayPrompt, "Aye"); + } +} diff --git a/84 Super Star Trek/csharp/Input.cs b/84 Super Star Trek/csharp/Input.cs new file mode 100644 index 00000000..98e5b297 --- /dev/null +++ b/84 Super Star Trek/csharp/Input.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using static System.StringComparison; + +namespace SuperStarTrek +{ + internal class Input + { + private readonly Output _output; + + public Input(Output output) + { + _output = output; + } + + public void WaitForAnyKeyButEnter(string prompt) + { + _output.Write($"Hit any key but Enter {prompt} "); + while (Console.ReadKey(intercept: true).Key == ConsoleKey.Enter); + } + + public string GetString(string prompt) + { + _output.Prompt(prompt); + return Console.ReadLine(); + } + + public double GetNumber(string prompt) + { + _output.Prompt(prompt); + + while (true) + { + var response = Console.ReadLine(); + if (double.TryParse(response, out var value)) + { + return value; + } + + _output.WriteLine("!Number expected - retry input line"); + _output.Prompt(); + } + } + + public bool TryGetNumber(string prompt, double minValue, double maxValue, out double value) + { + value = GetNumber($"{prompt} ({minValue}-{maxValue})"); + + return value >= minValue && value <= maxValue; + } + + internal bool GetString(string replayPrompt, string trueValue) + => GetString(replayPrompt).Equals(trueValue, InvariantCultureIgnoreCase); + + public Command GetCommand() + { + while(true) + { + var response = GetString("Command"); + + if (response != "" && + Enum.TryParse(response.Substring(0, 3), ignoreCase: true, out Command parsedCommand)) + { + return parsedCommand; + } + + _output.WriteLine("Enter one of the following:"); + foreach (var command in Enum.GetValues(typeof(Command)).OfType()) + { + _output.WriteLine($" {command} ({command.GetDescription()})"); + } + _output.WriteLine(); + } + } + } +} diff --git a/84 Super Star Trek/csharp/Objects/Enterprise.cs b/84 Super Star Trek/csharp/Objects/Enterprise.cs new file mode 100644 index 00000000..4f6055cc --- /dev/null +++ b/84 Super Star Trek/csharp/Objects/Enterprise.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Text; +using SuperStarTrek.Resources; +using SuperStarTrek.Space; +using SuperStarTrek.Systems; + +namespace SuperStarTrek.Objects +{ + internal class Enterprise + { + private readonly int _maxEnergy; + private readonly List _systems; + private readonly Dictionary _commandExecutors; + private Quadrant _quadrant; + private ShieldControl _shieldControl; + + public Enterprise(int maxEnergy, Coordinates sector) + { + Sector = sector; + TotalEnergy = _maxEnergy = maxEnergy; + + _systems = new List(); + _commandExecutors = new Dictionary(); + } + + public Coordinates Quadrant => _quadrant.Coordinates; + public Coordinates Sector { get; } + public string Condition => GetCondition(); + public double Shields => _shieldControl.Energy; + public double Energy => TotalEnergy - Shields; + public double TotalEnergy { get; private set; } + public int TorpedoCount { get; } + + public bool IsDocked { get; private set; } + + public Enterprise Add(Subsystem system) + { + _systems.Add(system); + _commandExecutors[system.Command] = system; + + if (system is ShieldControl shieldControl) { _shieldControl = shieldControl; } + + return this; + } + + public string GetDamageReport() + { + var report = new StringBuilder(); + report.AppendLine().AppendLine().AppendLine("Device State of Repair"); + foreach (var system in _systems) + { + report.Append(system.Name.PadRight(25)).AppendLine(system.Condition.ToString(" 0.00;-0.00")); + } + report.AppendLine(); + return report.ToString(); + } + + public void Enter(Quadrant quadrant, string entryTextFormat) + { + _quadrant = quadrant; + + var _output = new Output(); + _output.Write(entryTextFormat, quadrant); + + if (quadrant.HasKlingons) + { + _output.Write(Strings.CombatArea); + if (Shields <= 200) { _output.Write(Strings.LowShields); } + } + + IsDocked = quadrant.EnterpriseIsNextToStarbase; + + Execute(Command.SRS); + } + + private string GetCondition() => + (_quadrant.HasKlingons, Energy / _maxEnergy) switch + { + (true, _) => "*Red*", + (_, < 0.1) => "Yellow", + _ => "Green" + }; + + public bool Execute(Command command) + { + _commandExecutors[command].ExecuteCommand(_quadrant); + return false; + } + + internal bool Recognises(string command) + { + throw new NotImplementedException(); + } + + internal string GetCommandList() + { + throw new NotImplementedException(); + } + + public override string ToString() => "<*>"; + } +} diff --git a/84 Super Star Trek/csharp/Objects/Klingon.cs b/84 Super Star Trek/csharp/Objects/Klingon.cs new file mode 100644 index 00000000..fdf47031 --- /dev/null +++ b/84 Super Star Trek/csharp/Objects/Klingon.cs @@ -0,0 +1,14 @@ +namespace SuperStarTrek.Objects +{ + internal class Klingon + { + private double _energy; + + public Klingon() + { + _energy = new Random().GetDouble(100, 300); + } + + public override string ToString() => "+K+"; + } +} diff --git a/84 Super Star Trek/csharp/Objects/Star.cs b/84 Super Star Trek/csharp/Objects/Star.cs new file mode 100644 index 00000000..1d9eef6f --- /dev/null +++ b/84 Super Star Trek/csharp/Objects/Star.cs @@ -0,0 +1,7 @@ +namespace SuperStarTrek.Objects +{ + internal class Star + { + public override string ToString() => " * "; + } +} diff --git a/84 Super Star Trek/csharp/Objects/Starbase.cs b/84 Super Star Trek/csharp/Objects/Starbase.cs new file mode 100644 index 00000000..33110837 --- /dev/null +++ b/84 Super Star Trek/csharp/Objects/Starbase.cs @@ -0,0 +1,7 @@ +namespace SuperStarTrek.Objects +{ + internal class Starbase + { + public override string ToString() => ">!<"; + } +} diff --git a/84 Super Star Trek/csharp/Output.cs b/84 Super Star Trek/csharp/Output.cs new file mode 100644 index 00000000..77e5e919 --- /dev/null +++ b/84 Super Star Trek/csharp/Output.cs @@ -0,0 +1,15 @@ +using System; + +namespace SuperStarTrek +{ + internal class Output + { + public void Write(string text) => Console.Write(text); + public void Write(string format, params object[] args) => Console.Write(format, args); + public void WriteLine(string text = "") => Console.WriteLine(text); + + public void NextLine() => Console.WriteLine(); + + public void Prompt(string text = "") => Console.Write($"{text}? "); + } +} diff --git a/84 Super Star Trek/csharp/Program.cs b/84 Super Star Trek/csharp/Program.cs index 0a5b6268..30360ee0 100644 --- a/84 Super Star Trek/csharp/Program.cs +++ b/84 Super Star Trek/csharp/Program.cs @@ -23,31 +23,20 @@ // **** CONVERTED TO MICROSOFT C# 2/20/21 BY ANDREW COOPER // **** -using System; - -using static System.StringComparison; - namespace SuperStarTrek { - class Program + internal class Program { - static void Main(string[] args) + static void Main() { - Console.WriteLine(Strings.Title); + var game = new Game(); - Console.Write("Do you need instructions (Y/N)? "); - var response = Console.ReadLine(); - Console.WriteLine(); + game.DoIntroduction(); - if (!response.Equals("N", InvariantCultureIgnoreCase)) + do { - Console.WriteLine(Strings.Instructions); - - Console.WriteLine("Press to continue..."); - Console.ReadLine(); - } - - Console.WriteLine(Strings.Enterprise); + game.Play(); + } while (game.Replay()); } } } diff --git a/84 Super Star Trek/csharp/Random.cs b/84 Super Star Trek/csharp/Random.cs new file mode 100644 index 00000000..9b7e1bff --- /dev/null +++ b/84 Super Star Trek/csharp/Random.cs @@ -0,0 +1,25 @@ +using SuperStarTrek.Space; + +namespace SuperStarTrek +{ + internal class Random + { + private static readonly System.Random _random = new(); + + public Coordinates GetCoordinate() => new Coordinates(Get1To8Inclusive(), Get1To8Inclusive()); + + // Duplicates the algorithm used in the original code to get an integer value from 1 to 8, inclusive: + // 475 DEF FNR(R)=INT(RND(R)*7.98+1.01) + // Returns a value from 1 to 8, inclusive. + // Note there's a slight bias away from the extreme values, 1 and 8. + public int Get1To8Inclusive() => (int)(_random.NextDouble() * 7.98 + 1.01); + + public int GetInt(int inclusiveMinValue, int exclusiveMaxValue) => + _random.Next(inclusiveMinValue, exclusiveMaxValue); + + public double GetDouble() => _random.NextDouble(); + + public double GetDouble(double inclusiveMinValue, double exclusiveMaxValue) + => _random.NextDouble() * (exclusiveMaxValue - inclusiveMinValue) + inclusiveMinValue; + } +} diff --git a/84 Super Star Trek/csharp/Resources/AcceptCommand.txt b/84 Super Star Trek/csharp/Resources/AcceptCommand.txt new file mode 100644 index 00000000..e69de29b diff --git a/84 Super Star Trek/csharp/Resources/CombatArea.txt b/84 Super Star Trek/csharp/Resources/CombatArea.txt new file mode 100644 index 00000000..ea27a826 --- /dev/null +++ b/84 Super Star Trek/csharp/Resources/CombatArea.txt @@ -0,0 +1 @@ +COMBAT AREA CONDITION RED diff --git a/84 Super Star Trek/csharp/Resources/Instructions.txt b/84 Super Star Trek/csharp/Resources/Instructions.txt index 51f40cf6..fd74857e 100644 --- a/84 Super Star Trek/csharp/Resources/Instructions.txt +++ b/84 Super Star Trek/csharp/Resources/Instructions.txt @@ -53,7 +53,7 @@ LRS command = Long Range Sensor Scan The scan is coded in the form ###, where the units digit is the number of stars, the tens digit is the number of starbases, and the hundreds digit is the number of - Kilngons. + Klingons. Example - 207 = 2 Klingons, No starbases, & 7 stars. @@ -103,3 +103,4 @@ COM command = Library-Computer Option 5 = Galactic Region Name Map This option prints the names of the sixteen major galactic regions referred to in the game. + diff --git a/84 Super Star Trek/csharp/Resources/LowShields.txt b/84 Super Star Trek/csharp/Resources/LowShields.txt new file mode 100644 index 00000000..b449b6eb --- /dev/null +++ b/84 Super Star Trek/csharp/Resources/LowShields.txt @@ -0,0 +1 @@ + SHIELDS DANGEROUSLY LOW diff --git a/84 Super Star Trek/csharp/Resources/Orders.txt b/84 Super Star Trek/csharp/Resources/Orders.txt new file mode 100644 index 00000000..7dc14b24 --- /dev/null +++ b/84 Super Star Trek/csharp/Resources/Orders.txt @@ -0,0 +1,6 @@ +Your orders are as follows: + Destroy the {0} Klingon warships which have invaded + the galaxy before they can attack federation headquarters + on stardate {1}. This gives you {2} days. There {3} + {4} starbase{5} in the galaxy for resupplying your ship. + diff --git a/84 Super Star Trek/csharp/Resources/ReplayPrompt.txt b/84 Super Star Trek/csharp/Resources/ReplayPrompt.txt new file mode 100644 index 00000000..52aca0d1 --- /dev/null +++ b/84 Super Star Trek/csharp/Resources/ReplayPrompt.txt @@ -0,0 +1,3 @@ +The Federation is in need of a new starship commander +for a similar mission -- if there is a volunteer +let him step forward and enter 'Aye' \ No newline at end of file diff --git a/84 Super Star Trek/csharp/Resources/ShieldsDropped.txt b/84 Super Star Trek/csharp/Resources/ShieldsDropped.txt new file mode 100644 index 00000000..acc87f59 --- /dev/null +++ b/84 Super Star Trek/csharp/Resources/ShieldsDropped.txt @@ -0,0 +1 @@ +Shields dropped for docking purposes diff --git a/84 Super Star Trek/csharp/Resources/ShortRangeSensorsOut.txt b/84 Super Star Trek/csharp/Resources/ShortRangeSensorsOut.txt new file mode 100644 index 00000000..1c958cab --- /dev/null +++ b/84 Super Star Trek/csharp/Resources/ShortRangeSensorsOut.txt @@ -0,0 +1 @@ +*** Short Range Sensors are out *** diff --git a/84 Super Star Trek/csharp/Resources/StartText.txt b/84 Super Star Trek/csharp/Resources/StartText.txt new file mode 100644 index 00000000..3c6028a5 --- /dev/null +++ b/84 Super Star Trek/csharp/Resources/StartText.txt @@ -0,0 +1,5 @@ + + +Your mission begins with your starship located +in the galactic quadrant, '{0}'. + diff --git a/84 Super Star Trek/csharp/Strings.cs b/84 Super Star Trek/csharp/Resources/Strings.cs similarity index 61% rename from 84 Super Star Trek/csharp/Strings.cs rename to 84 Super Star Trek/csharp/Resources/Strings.cs index 45241225..930f1663 100644 --- a/84 Super Star Trek/csharp/Strings.cs +++ b/84 Super Star Trek/csharp/Resources/Strings.cs @@ -2,15 +2,20 @@ using System.Reflection; using System.Runtime.CompilerServices; -using static System.StringComparison; - -namespace SuperStarTrek +namespace SuperStarTrek.Resources { internal static class Strings { - public static string Title => GetResource(); - public static string Instructions => GetResource(); + public static string CombatArea => GetResource(); public static string Enterprise => GetResource(); + public static string Instructions => GetResource(); + public static string LowShields => GetResource(); + public static string Orders => GetResource(); + public static string ReplayPrompt => GetResource(); + public static string ShieldsDropped => GetResource(); + public static string ShortRangeSensorsOut => GetResource(); + public static string StartText => GetResource(); + public static string Title => GetResource(); private static string GetResource([CallerMemberName] string name = "") { diff --git a/84 Super Star Trek/csharp/Space/Coordinate.cs b/84 Super Star Trek/csharp/Space/Coordinate.cs new file mode 100644 index 00000000..b3328c24 --- /dev/null +++ b/84 Super Star Trek/csharp/Space/Coordinate.cs @@ -0,0 +1,27 @@ +using System; + +namespace SuperStarTrek.Space +{ + // Represents the corrdintate of a quadrant in the galaxy, or a sector in a quadrant. + // Note that the origin is top-left, x increase downwards, and y increases to the right. + internal record Coordinates + { + public Coordinates(int x, int y) + { + X = Validated(x, nameof(x)); + Y = Validated(y, nameof(y)); + } + + public int X { get; } + public int Y { get; } + + private int Validated(int value, string argumentName) + { + if (value >= 1 && value <= 8) { return value; } + + throw new ArgumentOutOfRangeException(argumentName, value, "Must be 1 to 8 inclusive"); + } + + public override string ToString() => $"{X} , {Y}"; + } +} diff --git a/84 Super Star Trek/csharp/Space/Course.cs b/84 Super Star Trek/csharp/Space/Course.cs new file mode 100644 index 00000000..dd9ecfba --- /dev/null +++ b/84 Super Star Trek/csharp/Space/Course.cs @@ -0,0 +1,50 @@ +using System; + +namespace SuperStarTrek.Space +{ + // Implements the course calculations from the original code: + // 530 FORI=1TO9:C(I,1)=0:C(I,2)=0:NEXTI + // 540 C(3,1)=-1:C(2,1)=-1:C(4,1)=-1:C(4,2)=-1:C(5,2)=-1:C(6,2)=-1 + // 600 C(1,2)=1:C(2,2)=1:C(6,1)=1:C(7,1)=1:C(8,1)=1:C(8,2)=1:C(9,2)=1 + // + // 3110 X1=C(C1,1)+(C(C1+1,1)-C(C1,1))*(C1-INT(C1)) + // 3140 X2=C(C1,2)+(C(C1+1,2)-C(C1,2))*(C1-INT(C1)) + internal class Course + { + private static readonly (int DeltaX, int DeltaY)[] cardinals = new[] + { + (0, 1), + (-1, 1), + (-1, 0), + (-1, -1), + (0, -1), + (1, -1), + (1, 0), + (1, 1), + (0, 1) + }; + + public Course(double direction) + { + if (direction < 1 || direction > 9) + { + throw new ArgumentOutOfRangeException( + nameof(direction), + direction, + "Must be between 1 and 9, inclusive."); + } + + var cardinalDirection = (int)(direction - 1) % 8; + var fractionalDirection = direction - (int)direction; + + var baseCardinal = cardinals[cardinalDirection]; + var nextCardinal = cardinals[cardinalDirection + 1]; + + DeltaX = baseCardinal.DeltaX + (nextCardinal.DeltaX - baseCardinal.DeltaX) * fractionalDirection; + DeltaY = baseCardinal.DeltaY + (nextCardinal.DeltaY - baseCardinal.DeltaY) * fractionalDirection; + } + + public double DeltaX { get; } + public double DeltaY { get; } + } +} diff --git a/84 Super Star Trek/csharp/Space/Galaxy.cs b/84 Super Star Trek/csharp/Space/Galaxy.cs new file mode 100644 index 00000000..171ecdea --- /dev/null +++ b/84 Super Star Trek/csharp/Space/Galaxy.cs @@ -0,0 +1,34 @@ +using System.Linq; + +namespace SuperStarTrek.Space +{ + internal class Galaxy + { + private readonly QuadrantInfo[][] _quadrants; + + public Galaxy() + { + var random = new Random(); + + _quadrants = Enumerable.Range(1, 8).Select(x => + Enumerable.Range(1, 8).Select(y => QuadrantInfo.Create(new Coordinates(x, y), "")).ToArray()) + .ToArray(); + + if (StarbaseCount == 0) + { + var randomQuadrant = this[random.GetCoordinate()]; + randomQuadrant.AddStarbase(); + + if (randomQuadrant.KlingonCount < 2) + { + randomQuadrant.AddKlingon(); + } + } + } + + public QuadrantInfo this[Coordinates coordinate] => _quadrants[coordinate.X - 1][coordinate.Y - 1]; + + public int KlingonCount => _quadrants.SelectMany(q => q).Sum(q => q.KlingonCount); + public int StarbaseCount => _quadrants.SelectMany(q => q).Count(q => q.HasStarbase); + } +} diff --git a/84 Super Star Trek/csharp/Space/Quadrant.cs b/84 Super Star Trek/csharp/Space/Quadrant.cs new file mode 100644 index 00000000..2aa45b6d --- /dev/null +++ b/84 Super Star Trek/csharp/Space/Quadrant.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SuperStarTrek.Objects; + +namespace SuperStarTrek.Space +{ + internal class Quadrant + { + private readonly QuadrantInfo _info; + private readonly Random _random; + private readonly Dictionary _sectors; + private readonly Coordinates _enterpriseSector; + private readonly Coordinates _starbaseSector; + + public Quadrant(QuadrantInfo info, Enterprise enterprise) + { + _info = info; + _random = new Random(); + + _enterpriseSector = enterprise.Sector; + _sectors = new Dictionary { [enterprise.Sector] = enterprise }; + PositionObject(() => new Klingon(), _info.KlingonCount); + if (_info.HasStarbase) + { + _starbaseSector = PositionObject(() => new Starbase()); + } + PositionObject(() => new Star(), _info.StarCount); + } + + public Coordinates Coordinates => _info.Coordinates; + public bool HasKlingons => _info.KlingonCount > 0; + public bool HasStarbase => _info.HasStarbase; + public bool EnterpriseIsNextToStarbase => + _info.HasStarbase && + Math.Abs(_enterpriseSector.X - _starbaseSector.X) <= 1 && + Math.Abs(_enterpriseSector.Y - _starbaseSector.Y) <= 1; + + public override string ToString() => _info.Name; + + private Coordinates PositionObject(Func objectFactory) + { + var sector = GetRandomEmptySector(); + _sectors[sector] = objectFactory.Invoke(); + return sector; + } + + private void PositionObject(Func objectFactory, int count) + { + for (int i = 0; i < count; i++) + { + PositionObject(objectFactory); + } + } + + private Coordinates GetRandomEmptySector() + { + while (true) + { + var sector = _random.GetCoordinate(); + if (!_sectors.ContainsKey(sector)) + { + return sector; + } + } + } + + public IEnumerable GetDisplayLines() => Enumerable.Range(1, 8).Select(x => GetDisplayLine(x)); + + private string GetDisplayLine(int x) + => string.Join( + " ", + Enumerable + .Range(1, 8) + .Select(y => new Coordinates(x, y)) + .Select(c => _sectors.GetValueOrDefault(c)) + .Select(o => o?.ToString() ?? " ")); + } +} diff --git a/84 Super Star Trek/csharp/Space/QuadrantInfo.cs b/84 Super Star Trek/csharp/Space/QuadrantInfo.cs new file mode 100644 index 00000000..ed9ed3fa --- /dev/null +++ b/84 Super Star Trek/csharp/Space/QuadrantInfo.cs @@ -0,0 +1,40 @@ +namespace SuperStarTrek.Space +{ + internal class QuadrantInfo + { + private QuadrantInfo(Coordinates coordinates, string name, int klingonCount, int starCount, bool hasStarbase) + { + Coordinates = coordinates; + Name = name; + KlingonCount = klingonCount; + StarCount = starCount; + HasStarbase = hasStarbase; + } + + public Coordinates Coordinates { get; } + public string Name { get; } + public int KlingonCount { get; private set; } + public bool HasStarbase { get; private set; } + public int StarCount { get; } + + public static QuadrantInfo Create(Coordinates coordinates, string name) + { + var random = new Random(); + var klingonCount = random.GetDouble() switch + { + > 0.98 => 3, + > 0.95 => 2, + > 0.80 => 1, + _ => 0 + }; + var hasStarbase = random.GetDouble() > 0.96; + var starCount = random.Get1To8Inclusive(); + + return new QuadrantInfo(coordinates, name, klingonCount, starCount, hasStarbase); + } + + internal void AddKlingon() => KlingonCount += 1; + + internal void AddStarbase() => HasStarbase = true; + } +} diff --git a/84 Super Star Trek/csharp/Systems/ShieldControl.cs b/84 Super Star Trek/csharp/Systems/ShieldControl.cs new file mode 100644 index 00000000..e7aca413 --- /dev/null +++ b/84 Super Star Trek/csharp/Systems/ShieldControl.cs @@ -0,0 +1,53 @@ +using SuperStarTrek.Objects; +using SuperStarTrek.Space; + +namespace SuperStarTrek.Systems +{ + internal class ShieldControl : Subsystem + { + private readonly Enterprise _enterprise; + private readonly Output _output; + private readonly Input _input; + + public ShieldControl(Enterprise enterprise, Output output, Input input) + : base("Shield Control", Command.SHE) + { + _enterprise = enterprise; + _output = output; + _input = input; + } + + public double Energy { get; private set; } + + public override void ExecuteCommand(Quadrant quadrant) + { + if (Condition < 0) + { + _output.WriteLine("Shield Control inoperable"); + return; + } + + _output.WriteLine($"Energy available = {_enterprise.TotalEnergy}"); + var requested = _input.GetNumber($"Number of units to shields"); + + if (Validate(requested)) + { + Energy = requested; + return; + } + + _output.WriteLine(""); + } + + private bool Validate(double requested) + { + if (requested > _enterprise.TotalEnergy) + { + _output.WriteLine("Shield Control reports, 'This is not the Federation Treasury.'"); + return false; + } + + return requested >= 0 && requested != Energy; + } + } +} diff --git a/84 Super Star Trek/csharp/Systems/ShortRangeSensors.cs b/84 Super Star Trek/csharp/Systems/ShortRangeSensors.cs new file mode 100644 index 00000000..d22dda70 --- /dev/null +++ b/84 Super Star Trek/csharp/Systems/ShortRangeSensors.cs @@ -0,0 +1,59 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using SuperStarTrek.Objects; +using SuperStarTrek.Resources; +using SuperStarTrek.Space; + +namespace SuperStarTrek.Systems +{ + internal class ShortRangeSensors : Subsystem + { + private readonly Enterprise _enterprise; + private readonly Galaxy _galaxy; + private readonly Game _game; + private readonly Output _output; + + public ShortRangeSensors(Enterprise enterprise, Galaxy galaxy, Game game, Output output) + : base("Short Range Sensors", Command.SRS) + { + _enterprise = enterprise; + _galaxy = galaxy; + _game = game; + _output = output; + } + + public override void ExecuteCommand(Quadrant quadrant) + { + if (_enterprise.IsDocked) + { + _output.WriteLine(Strings.ShieldsDropped); + } + + if (Condition < 0) + { + _output.WriteLine(Strings.ShortRangeSensorsOut); + } + + _output.WriteLine("---------------------------------"); + quadrant.GetDisplayLines() + .Zip(GetStatusLines(), (sectors, status) => $" {sectors} {status}") + .ToList() + .ForEach(l => _output.WriteLine(l)); + _output.WriteLine("---------------------------------"); + } + + public IEnumerable GetStatusLines() + { + yield return $"Stardate {_game.Stardate}"; + yield return $"Condition {_enterprise.Condition}"; + yield return $"Quadrant {_enterprise.Quadrant}"; + yield return $"Sector {_enterprise.Sector}"; + yield return $"Photon torpedoes {_enterprise.TorpedoCount}"; + yield return $"Total energy {Math.Ceiling(_enterprise.Energy)}"; + yield return $"Shields {(int)_enterprise.Shields}"; + yield return $"Klingons remaining {_galaxy.KlingonCount}"; + } + } +} diff --git a/84 Super Star Trek/csharp/Systems/Subsystem.cs b/84 Super Star Trek/csharp/Systems/Subsystem.cs new file mode 100644 index 00000000..ae2ef7d6 --- /dev/null +++ b/84 Super Star Trek/csharp/Systems/Subsystem.cs @@ -0,0 +1,20 @@ +using SuperStarTrek.Space; + +namespace SuperStarTrek.Systems +{ + internal abstract class Subsystem + { + protected Subsystem(string name, Command command) + { + Name = name; + Command = command; + Condition = 0; + } + + public string Name { get; } + public double Condition { get; } + public Command Command { get; } + + public abstract void ExecuteCommand(Quadrant quadrant); + } +}