Use common library in SuperStarTrek

This commit is contained in:
Andrew Cooper
2022-03-22 17:55:44 +11:00
parent 4fb62ce30c
commit 42c21bbc62
57 changed files with 1595 additions and 1682 deletions

View File

@@ -12,4 +12,6 @@ public sealed class ConsoleIO : TextIO
: base(Console.In, Console.Out)
{
}
public override char ReadCharacter() => Console.ReadKey(intercept: true).KeyChar;
}

View File

@@ -8,6 +8,12 @@ namespace Games.Common.IO;
/// </summary>
public interface IReadWrite
{
/// <summary>
/// Reads a character from input.
/// </summary>
/// <returns>The character read.</returns>
char ReadCharacter();
/// <summary>
/// Reads a <see cref="float" /> value from input.
/// </summary>
@@ -99,6 +105,13 @@ public interface IReadWrite
/// </summary>
/// <param name="format">The format <see cref="string" /> to be written.</param>
/// <param name="value">The values to be inserted into the format.</param>
void Write(string format, params object[] values);
/// <summary>
/// Writes a formatted string to output followed by a new-line.
/// </summary>
/// <param name="format">The format <see cref="string" /> to be written.</param>
/// <param name="value">The values to be inserted into the format.</param>
void WriteLine(string format, params object[] values);
/// <summary>

View File

@@ -29,6 +29,15 @@ public class TextIO : IReadWrite
_numberTokenReader = TokenReader.ForNumbers(this);
}
public virtual char ReadCharacter()
{
while(true)
{
var ch = _input.Read();
if (ch != -1) { return (char)ch; }
}
}
public float ReadNumber(string prompt) => ReadNumbers(prompt, 1)[0];
public (float, float) Read2Numbers(string prompt)
@@ -99,6 +108,8 @@ public class TextIO : IReadWrite
public void WriteLine(object value) => _output.WriteLine(value.ToString());
public void Write(string format, params object[] values) => _output.Write(format, values);
public void WriteLine(string format, params object[] values) => _output.WriteLine(format, values);
public void Write(Stream stream, bool keepOpen = false)

View File

@@ -1,34 +1,33 @@
using System.ComponentModel;
namespace SuperStarTrek.Commands
namespace SuperStarTrek.Commands;
internal enum Command
{
internal enum Command
{
[Description("To set course")]
NAV,
[Description("To set course")]
NAV,
[Description("For short range sensor scan")]
SRS,
[Description("For short range sensor scan")]
SRS,
[Description("For long range sensor scan")]
LRS,
[Description("For long range sensor scan")]
LRS,
[Description("To fire phasers")]
PHA,
[Description("To fire phasers")]
PHA,
[Description("To fire photon torpedoes")]
TOR,
[Description("To fire photon torpedoes")]
TOR,
[Description("To raise or lower shields")]
SHE,
[Description("To raise or lower shields")]
SHE,
[Description("For damage control reports")]
DAM,
[Description("For damage control reports")]
DAM,
[Description("To call on library-computer")]
COM,
[Description("To call on library-computer")]
COM,
[Description("To resign your command")]
XXX
}
[Description("To resign your command")]
XXX
}

View File

@@ -1,14 +1,13 @@
using System.Reflection;
using System.ComponentModel;
namespace SuperStarTrek.Commands
namespace SuperStarTrek.Commands;
internal static class CommandExtensions
{
internal static class CommandExtensions
{
internal static string GetDescription(this Command command) =>
typeof(Command)
.GetField(command.ToString())
.GetCustomAttribute<DescriptionAttribute>()
.Description;
}
internal static string GetDescription(this Command command) =>
typeof(Command)
.GetField(command.ToString())
.GetCustomAttribute<DescriptionAttribute>()
.Description;
}

View File

@@ -1,23 +1,22 @@
namespace SuperStarTrek.Commands
namespace SuperStarTrek.Commands;
internal class CommandResult
{
internal class CommandResult
public static readonly CommandResult Ok = new(false);
public static readonly CommandResult GameOver = new(true);
private CommandResult(bool isGameOver)
{
public static readonly CommandResult Ok = new(false);
public static readonly CommandResult GameOver = new(true);
private CommandResult(bool isGameOver)
{
IsGameOver = isGameOver;
}
private CommandResult(float timeElapsed)
{
TimeElapsed = timeElapsed;
}
public bool IsGameOver { get; }
public float TimeElapsed { get; }
public static CommandResult Elapsed(float timeElapsed) => new(timeElapsed);
IsGameOver = isGameOver;
}
private CommandResult(float timeElapsed)
{
TimeElapsed = timeElapsed;
}
public bool IsGameOver { get; }
public float TimeElapsed { get; }
public static CommandResult Elapsed(float timeElapsed) => new(timeElapsed);
}

View File

@@ -1,131 +1,128 @@
using System;
using Games.Common.IO;
using Games.Common.Randomness;
using SuperStarTrek.Objects;
using SuperStarTrek.Resources;
using SuperStarTrek.Space;
using SuperStarTrek.Systems;
using SuperStarTrek.Systems.ComputerFunctions;
namespace SuperStarTrek
namespace SuperStarTrek;
internal class Game
{
internal class Game
private readonly TextIO _io;
private readonly IRandom _random;
private int _initialStardate;
private int _finalStarDate;
private float _currentStardate;
private Coordinates _currentQuadrant;
private Galaxy _galaxy;
private int _initialKlingonCount;
private Enterprise _enterprise;
internal Game(TextIO io, IRandom random)
{
private readonly Output _output;
private readonly Input _input;
private readonly Random _random;
private int _initialStardate;
private int _finalStarDate;
private float _currentStardate;
private Coordinates _currentQuadrant;
private Galaxy _galaxy;
private int _initialKlingonCount;
private Enterprise _enterprise;
internal Game(Output output, Input input, Random random)
{
_output = output;
_input = input;
_random = random;
}
internal float Stardate => _currentStardate;
internal float StardatesRemaining => _finalStarDate - _currentStardate;
internal void DoIntroduction()
{
_output.Write(Strings.Title);
if (_input.GetYesNo("Do you need instructions", Input.YesNoMode.FalseOnN))
{
_output.Write(Strings.Instructions);
_input.WaitForAnyKeyButEnter("to continue");
}
}
internal void Play()
{
Initialise();
var gameOver = false;
while (!gameOver)
{
var command = _input.GetCommand();
var result = _enterprise.Execute(command);
gameOver = result.IsGameOver || CheckIfStranded();
_currentStardate += result.TimeElapsed;
gameOver |= _currentStardate > _finalStarDate;
}
if (_galaxy.KlingonCount > 0)
{
_output.Write(Strings.EndOfMission, _currentStardate, _galaxy.KlingonCount);
}
else
{
_output.Write(Strings.Congratulations, GetEfficiency());
}
}
private void Initialise()
{
_currentStardate = _initialStardate = _random.GetInt(20, 40) * 100;
_finalStarDate = _initialStardate + _random.GetInt(25, 35);
_currentQuadrant = _random.GetCoordinate();
_galaxy = new Galaxy(_random);
_initialKlingonCount = _galaxy.KlingonCount;
_enterprise = new Enterprise(3000, _random.GetCoordinate(), _output, _random, _input);
_enterprise
.Add(new WarpEngines(_enterprise, _output, _input))
.Add(new ShortRangeSensors(_enterprise, _galaxy, this, _output))
.Add(new LongRangeSensors(_galaxy, _output))
.Add(new PhaserControl(_enterprise, _output, _input, _random))
.Add(new PhotonTubes(10, _enterprise, _output, _input))
.Add(new ShieldControl(_enterprise, _output, _input))
.Add(new DamageControl(_enterprise, _output))
.Add(new LibraryComputer(
_output,
_input,
new CumulativeGalacticRecord(_output, _galaxy),
new StatusReport(this, _galaxy, _enterprise, _output),
new TorpedoDataCalculator(_enterprise, _output),
new StarbaseDataCalculator(_enterprise, _output),
new DirectionDistanceCalculator(_enterprise, _output, _input),
new GalaxyRegionMap(_output, _galaxy)));
_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.StartIn(BuildCurrentQuadrant());
}
private Quadrant BuildCurrentQuadrant() =>
new Quadrant(_galaxy[_currentQuadrant], _enterprise, _random, _galaxy, _input, _output);
internal bool Replay() => _galaxy.StarbaseCount > 0 && _input.GetString(Strings.ReplayPrompt, "Aye");
private bool CheckIfStranded()
{
if (_enterprise.IsStranded) { _output.Write(Strings.Stranded); }
return _enterprise.IsStranded;
}
private float GetEfficiency() =>
1000 * (float)Math.Pow(_initialKlingonCount / (_currentStardate - _initialStardate), 2);
_io = io;
_random = random;
}
internal float Stardate => _currentStardate;
internal float StardatesRemaining => _finalStarDate - _currentStardate;
internal void DoIntroduction()
{
_io.Write(Strings.Title);
if (_io.GetYesNo("Do you need instructions", IReadWriteExtensions.YesNoMode.FalseOnN))
{
_io.Write(Strings.Instructions);
_io.WaitForAnyKeyButEnter("to continue");
}
}
internal void Play()
{
Initialise();
var gameOver = false;
while (!gameOver)
{
var command = _io.ReadCommand();
var result = _enterprise.Execute(command);
gameOver = result.IsGameOver || CheckIfStranded();
_currentStardate += result.TimeElapsed;
gameOver |= _currentStardate > _finalStarDate;
}
if (_galaxy.KlingonCount > 0)
{
_io.Write(Strings.EndOfMission, _currentStardate, _galaxy.KlingonCount);
}
else
{
_io.Write(Strings.Congratulations, CalculateEfficiency());
}
}
private void Initialise()
{
_currentStardate = _initialStardate = _random.Next(20, 40) * 100;
_finalStarDate = _initialStardate + _random.Next(25, 35);
_currentQuadrant = _random.NextCoordinate();
_galaxy = new Galaxy(_random);
_initialKlingonCount = _galaxy.KlingonCount;
_enterprise = new Enterprise(3000, _random.NextCoordinate(), _io, _random);
_enterprise
.Add(new WarpEngines(_enterprise, _io))
.Add(new ShortRangeSensors(_enterprise, _galaxy, this, _io))
.Add(new LongRangeSensors(_galaxy, _io))
.Add(new PhaserControl(_enterprise, _io, _random))
.Add(new PhotonTubes(10, _enterprise, _io))
.Add(new ShieldControl(_enterprise, _io))
.Add(new DamageControl(_enterprise, _io))
.Add(new LibraryComputer(
_io,
new CumulativeGalacticRecord(_io, _galaxy),
new StatusReport(this, _galaxy, _enterprise, _io),
new TorpedoDataCalculator(_enterprise, _io),
new StarbaseDataCalculator(_enterprise, _io),
new DirectionDistanceCalculator(_enterprise, _io),
new GalaxyRegionMap(_io, _galaxy)));
_io.Write(Strings.Enterprise);
_io.Write(
Strings.Orders,
_galaxy.KlingonCount,
_finalStarDate,
_finalStarDate - _initialStardate,
_galaxy.StarbaseCount > 1 ? "are" : "is",
_galaxy.StarbaseCount,
_galaxy.StarbaseCount > 1 ? "s" : "");
_io.WaitForAnyKeyButEnter("when ready to accept command");
_enterprise.StartIn(BuildCurrentQuadrant());
}
private Quadrant BuildCurrentQuadrant() => new(_galaxy[_currentQuadrant], _enterprise, _random, _galaxy, _io);
internal bool Replay() => _galaxy.StarbaseCount > 0 && _io.ReadExpectedString(Strings.ReplayPrompt, "Aye");
private bool CheckIfStranded()
{
if (_enterprise.IsStranded) { _io.Write(Strings.Stranded); }
return _enterprise.IsStranded;
}
private float CalculateEfficiency() =>
1000 * (float)Math.Pow(_initialKlingonCount / (_currentStardate - _initialStardate), 2);
}

View File

@@ -0,0 +1,16 @@
using Games.Common.Randomness;
using SuperStarTrek.Space;
namespace SuperStarTrek;
internal static class IRandomExtensions
{
internal static Coordinates NextCoordinate(this IRandom random) =>
new Coordinates(random.Next1To8Inclusive() - 1, random.Next1To8Inclusive() - 1);
// 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.
internal static int Next1To8Inclusive(this IRandom random) => (int)(random.NextFloat() * 7.98 + 1.01);
}

View File

@@ -0,0 +1,89 @@
using System;
using System.Linq;
using Games.Common.IO;
using SuperStarTrek.Commands;
using SuperStarTrek.Space;
using static System.StringComparison;
namespace SuperStarTrek;
internal static class IReadWriteExtensions
{
internal static void WaitForAnyKeyButEnter(this IReadWrite io, string prompt)
{
io.Write($"Hit any key but Enter {prompt} ");
while (io.ReadCharacter() == '\r');
}
internal static (float X, float Y) GetCoordinates(this IReadWrite io, string prompt) =>
io.Read2Numbers($"{prompt} (X,Y)");
internal static bool TryReadNumberInRange(
this IReadWrite io,
string prompt,
float minValue,
float maxValue,
out float value)
{
value = io.ReadNumber($"{prompt} ({minValue}-{maxValue})");
return value >= minValue && value <= maxValue;
}
internal static bool ReadExpectedString(this IReadWrite io, string prompt, string trueValue) =>
io.ReadString(prompt).Equals(trueValue, InvariantCultureIgnoreCase);
internal static Command ReadCommand(this IReadWrite io)
{
while(true)
{
var response = io.ReadString("Command");
if (response.Length >= 3 &&
Enum.TryParse(response.Substring(0, 3), ignoreCase: true, out Command parsedCommand))
{
return parsedCommand;
}
io.WriteLine("Enter one of the following:");
foreach (var command in Enum.GetValues(typeof(Command)).OfType<Command>())
{
io.WriteLine($" {command} ({command.GetDescription()})");
}
io.WriteLine();
}
}
internal static bool TryReadCourse(this IReadWrite io, string prompt, string officer, out Course course)
{
if (!io.TryReadNumberInRange(prompt, 1, 9, out var direction))
{
io.WriteLine($"{officer} reports, 'Incorrect course data, sir!'");
course = default;
return false;
}
course = new Course(direction);
return true;
}
internal static bool GetYesNo(this IReadWrite io, string prompt, YesNoMode mode)
{
var response = io.ReadString($"{prompt} (Y/N)").ToUpperInvariant();
return (mode, response) switch
{
(YesNoMode.FalseOnN, "N") => false,
(YesNoMode.FalseOnN, _) => true,
(YesNoMode.TrueOnY, "Y") => true,
(YesNoMode.TrueOnY, _) => false,
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "Invalid value")
};
}
internal enum YesNoMode
{
TrueOnY,
FalseOnN
}
}

View File

@@ -1,159 +0,0 @@
using System;
using System.Linq;
using SuperStarTrek.Commands;
using SuperStarTrek.Space;
using static System.StringComparison;
namespace SuperStarTrek
{
internal class Input
{
private readonly Output _output;
internal Input(Output output)
{
_output = output;
}
internal void WaitForAnyKeyButEnter(string prompt)
{
_output.Write($"Hit any key but Enter {prompt} ");
while (Console.ReadKey(intercept: true).Key == ConsoleKey.Enter);
}
internal string GetString(string prompt)
{
_output.Prompt(prompt);
return Console.ReadLine();
}
internal float GetNumber(string prompt)
{
_output.Prompt(prompt);
while (true)
{
var response = Console.ReadLine();
if (float.TryParse(response, out var value))
{
return value;
}
_output.WriteLine("!Number expected - retry input line");
_output.Prompt();
}
}
internal (float X, float Y) GetCoordinates(string prompt)
{
_output.Prompt($"{prompt} (X,Y)");
var responses = ReadNumbers(2);
return (responses[0], responses[1]);
}
internal bool TryGetNumber(string prompt, float minValue, float maxValue, out float value)
{
value = GetNumber($"{prompt} ({minValue}-{maxValue})");
return value >= minValue && value <= maxValue;
}
internal bool GetString(string replayPrompt, string trueValue) =>
GetString(replayPrompt).Equals(trueValue, InvariantCultureIgnoreCase);
internal Command GetCommand()
{
while(true)
{
var response = GetString("Command");
if (response.Length >= 3 &&
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<Command>())
{
_output.WriteLine($" {command} ({command.GetDescription()})");
}
_output.WriteLine();
}
}
internal bool TryGetCourse(string prompt, string officer, out Course course)
{
if (!TryGetNumber(prompt, 1, 9, out var direction))
{
_output.WriteLine($"{officer} reports, 'Incorrect course data, sir!'");
course = default;
return false;
}
course = new Course(direction);
return true;
}
internal bool GetYesNo(string prompt, YesNoMode mode)
{
_output.Prompt($"{prompt} (Y/N)");
var response = Console.ReadLine().ToUpperInvariant();
return (mode, response) switch
{
(YesNoMode.FalseOnN, "N") => false,
(YesNoMode.FalseOnN, _) => true,
(YesNoMode.TrueOnY, "Y") => true,
(YesNoMode.TrueOnY, _) => false,
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "Invalid value")
};
}
private float[] ReadNumbers(int quantity)
{
var numbers = new float[quantity];
var index = 0;
bool tryAgain;
do
{
tryAgain = false;
var responses = Console.ReadLine().Split(',');
if (responses.Length > quantity)
{
_output.WriteLine("!Extra input ingored");
}
for (; index < responses.Length; index++)
{
if (!float.TryParse(responses[index], out numbers[index]))
{
_output.WriteLine("!Number expected - retry input line");
_output.Prompt();
tryAgain = true;
break;
}
}
} while (tryAgain);
if (index < quantity)
{
_output.Prompt("?");
var responses = ReadNumbers(quantity - index);
for (int i = 0; i < responses.Length; i++, index++)
{
numbers[index] = responses[i];
}
}
return numbers;
}
internal enum YesNoMode
{
TrueOnY,
FalseOnN
}
}
}

View File

@@ -1,231 +1,230 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Games.Common.IO;
using Games.Common.Randomness;
using SuperStarTrek.Commands;
using SuperStarTrek.Resources;
using SuperStarTrek.Space;
using SuperStarTrek.Systems;
namespace SuperStarTrek.Objects
namespace SuperStarTrek.Objects;
internal class Enterprise
{
internal class Enterprise
private readonly int _maxEnergy;
private readonly IReadWrite _io;
private readonly List<Subsystem> _systems;
private readonly Dictionary<Command, Subsystem> _commandExecutors;
private readonly IRandom _random;
private Quadrant _quadrant;
public Enterprise(int maxEnergy, Coordinates sector, IReadWrite io, IRandom random)
{
private readonly int _maxEnergy;
private readonly Output _output;
private readonly List<Subsystem> _systems;
private readonly Dictionary<Command, Subsystem> _commandExecutors;
private readonly Random _random;
private readonly Input _input;
private Quadrant _quadrant;
SectorCoordinates = sector;
TotalEnergy = _maxEnergy = maxEnergy;
public Enterprise(int maxEnergy, Coordinates sector, Output output, Random random, Input input)
{
SectorCoordinates = sector;
TotalEnergy = _maxEnergy = maxEnergy;
_systems = new List<Subsystem>();
_commandExecutors = new Dictionary<Command, Subsystem>();
_output = output;
_random = random;
_input = input;
}
internal Quadrant Quadrant => _quadrant;
internal Coordinates QuadrantCoordinates => _quadrant.Coordinates;
internal Coordinates SectorCoordinates { get; private set; }
internal string Condition => GetCondition();
internal LibraryComputer Computer => (LibraryComputer)_commandExecutors[Command.COM];
internal ShieldControl ShieldControl => (ShieldControl)_commandExecutors[Command.SHE];
internal float Energy => TotalEnergy - ShieldControl.ShieldEnergy;
internal float TotalEnergy { get; private set; }
internal int DamagedSystemCount => _systems.Count(s => s.IsDamaged);
internal IEnumerable<Subsystem> Systems => _systems;
internal PhotonTubes PhotonTubes => (PhotonTubes)_commandExecutors[Command.TOR];
internal bool IsDocked => _quadrant.EnterpriseIsNextToStarbase;
internal bool IsStranded => TotalEnergy < 10 || Energy < 10 && ShieldControl.IsDamaged;
internal Enterprise Add(Subsystem system)
{
_systems.Add(system);
_commandExecutors[system.Command] = system;
return this;
}
internal void StartIn(Quadrant quadrant)
{
_quadrant = quadrant;
quadrant.Display(Strings.StartText);
}
private string GetCondition() =>
IsDocked switch
{
true => "Docked",
false when _quadrant.HasKlingons => "*Red*",
false when Energy / _maxEnergy < 0.1f => "Yellow",
false => "Green"
};
internal CommandResult Execute(Command command)
{
if (command == Command.XXX) { return CommandResult.GameOver; }
return _commandExecutors[command].ExecuteCommand(_quadrant);
}
internal void Refuel() => TotalEnergy = _maxEnergy;
public override string ToString() => "<*>";
internal void UseEnergy(float amountUsed)
{
TotalEnergy -= amountUsed;
}
internal CommandResult TakeHit(Coordinates sector, int hitStrength)
{
_output.WriteLine($"{hitStrength} unit hit on Enterprise from sector {sector}");
ShieldControl.AbsorbHit(hitStrength);
if (ShieldControl.ShieldEnergy <= 0)
{
_output.WriteLine(Strings.Destroyed);
return CommandResult.GameOver;
}
_output.WriteLine($" <Shields down to {ShieldControl.ShieldEnergy} units>");
if (hitStrength >= 20)
{
TakeDamage(hitStrength);
}
return CommandResult.Ok;
}
private void TakeDamage(float hitStrength)
{
var hitShieldRatio = hitStrength / ShieldControl.ShieldEnergy;
if (_random.GetFloat() > 0.6 || hitShieldRatio <= 0.02f)
{
return;
}
var system = _systems[_random.Get1To8Inclusive() - 1];
system.TakeDamage(hitShieldRatio + 0.5f * _random.GetFloat());
_output.WriteLine($"Damage Control reports, '{system.Name} damaged by the hit.'");
}
internal void RepairSystems(float repairWorkDone)
{
var repairedSystems = new List<string>();
foreach (var system in _systems.Where(s => s.IsDamaged))
{
if (system.Repair(repairWorkDone))
{
repairedSystems.Add(system.Name);
}
}
if (repairedSystems.Any())
{
_output.WriteLine("Damage Control report:");
foreach (var systemName in repairedSystems)
{
_output.WriteLine($" {systemName} repair completed.");
}
}
}
internal void VaryConditionOfRandomSystem()
{
if (_random.GetFloat() > 0.2f) { return; }
var system = _systems[_random.Get1To8Inclusive() - 1];
_output.Write($"Damage Control report: {system.Name} ");
if (_random.GetFloat() >= 0.6)
{
system.Repair(_random.GetFloat() * 3 + 1);
_output.WriteLine("state of repair improved");
}
else
{
system.TakeDamage(_random.GetFloat() * 5 + 1);
_output.WriteLine("damaged");
}
}
internal float Move(Course course, float warpFactor, int distance)
{
var (quadrant, sector) = MoveWithinQuadrant(course, distance) ?? MoveBeyondQuadrant(course, distance);
if (quadrant != _quadrant.Coordinates)
{
_quadrant = new Quadrant(_quadrant.Galaxy[quadrant], this, _random, _quadrant.Galaxy, _input, _output);
}
_quadrant.SetEnterpriseSector(sector);
SectorCoordinates = sector;
TotalEnergy -= distance + 10;
if (Energy < 0)
{
_output.WriteLine("Shield Control supplies energy to complete the maneuver.");
ShieldControl.ShieldEnergy = Math.Max(0, TotalEnergy);
}
return GetTimeElapsed(quadrant, warpFactor);
}
private (Coordinates, Coordinates)? MoveWithinQuadrant(Course course, int distance)
{
var currentSector = SectorCoordinates;
foreach (var (sector, index) in course.GetSectorsFrom(SectorCoordinates).Select((s, i) => (s, i)))
{
if (distance == 0) { break; }
if (_quadrant.HasObjectAt(sector))
{
_output.WriteLine($"Warp engines shut down at sector {currentSector} dues to bad navigation");
distance = 0;
break;
}
currentSector = sector;
distance -= 1;
}
return distance == 0 ? (_quadrant.Coordinates, currentSector) : null;
}
private (Coordinates, Coordinates) MoveBeyondQuadrant(Course course, int distance)
{
var (complete, quadrant, sector) = course.GetDestination(QuadrantCoordinates, SectorCoordinates, distance);
if (!complete)
{
_output.Write(Strings.PermissionDenied, sector, quadrant);
}
return (quadrant, sector);
}
private float GetTimeElapsed(Coordinates finalQuadrant, float warpFactor) =>
finalQuadrant == _quadrant.Coordinates
? Math.Min(1, (float)Math.Round(warpFactor, 1, MidpointRounding.ToZero))
: 1;
_systems = new List<Subsystem>();
_commandExecutors = new Dictionary<Command, Subsystem>();
_io = io;
_random = random;
}
internal Quadrant Quadrant => _quadrant;
internal Coordinates QuadrantCoordinates => _quadrant.Coordinates;
internal Coordinates SectorCoordinates { get; private set; }
internal string Condition => GetCondition();
internal LibraryComputer Computer => (LibraryComputer)_commandExecutors[Command.COM];
internal ShieldControl ShieldControl => (ShieldControl)_commandExecutors[Command.SHE];
internal float Energy => TotalEnergy - ShieldControl.ShieldEnergy;
internal float TotalEnergy { get; private set; }
internal int DamagedSystemCount => _systems.Count(s => s.IsDamaged);
internal IEnumerable<Subsystem> Systems => _systems;
internal PhotonTubes PhotonTubes => (PhotonTubes)_commandExecutors[Command.TOR];
internal bool IsDocked => _quadrant.EnterpriseIsNextToStarbase;
internal bool IsStranded => TotalEnergy < 10 || Energy < 10 && ShieldControl.IsDamaged;
internal Enterprise Add(Subsystem system)
{
_systems.Add(system);
_commandExecutors[system.Command] = system;
return this;
}
internal void StartIn(Quadrant quadrant)
{
_quadrant = quadrant;
quadrant.Display(Strings.StartText);
}
private string GetCondition() =>
IsDocked switch
{
true => "Docked",
false when _quadrant.HasKlingons => "*Red*",
false when Energy / _maxEnergy < 0.1f => "Yellow",
false => "Green"
};
internal CommandResult Execute(Command command)
{
if (command == Command.XXX) { return CommandResult.GameOver; }
return _commandExecutors[command].ExecuteCommand(_quadrant);
}
internal void Refuel() => TotalEnergy = _maxEnergy;
public override string ToString() => "<*>";
internal void UseEnergy(float amountUsed)
{
TotalEnergy -= amountUsed;
}
internal CommandResult TakeHit(Coordinates sector, int hitStrength)
{
_io.WriteLine($"{hitStrength} unit hit on Enterprise from sector {sector}");
ShieldControl.AbsorbHit(hitStrength);
if (ShieldControl.ShieldEnergy <= 0)
{
_io.WriteLine(Strings.Destroyed);
return CommandResult.GameOver;
}
_io.WriteLine($" <Shields down to {ShieldControl.ShieldEnergy} units>");
if (hitStrength >= 20)
{
TakeDamage(hitStrength);
}
return CommandResult.Ok;
}
private void TakeDamage(float hitStrength)
{
var hitShieldRatio = hitStrength / ShieldControl.ShieldEnergy;
if (_random.NextFloat() > 0.6 || hitShieldRatio <= 0.02f)
{
return;
}
var system = _systems[_random.Next1To8Inclusive() - 1];
system.TakeDamage(hitShieldRatio + 0.5f * _random.NextFloat());
_io.WriteLine($"Damage Control reports, '{system.Name} damaged by the hit.'");
}
internal void RepairSystems(float repairWorkDone)
{
var repairedSystems = new List<string>();
foreach (var system in _systems.Where(s => s.IsDamaged))
{
if (system.Repair(repairWorkDone))
{
repairedSystems.Add(system.Name);
}
}
if (repairedSystems.Any())
{
_io.WriteLine("Damage Control report:");
foreach (var systemName in repairedSystems)
{
_io.WriteLine($" {systemName} repair completed.");
}
}
}
internal void VaryConditionOfRandomSystem()
{
if (_random.NextFloat() > 0.2f) { return; }
var system = _systems[_random.Next1To8Inclusive() - 1];
_io.Write($"Damage Control report: {system.Name} ");
if (_random.NextFloat() >= 0.6)
{
system.Repair(_random.NextFloat() * 3 + 1);
_io.WriteLine("state of repair improved");
}
else
{
system.TakeDamage(_random.NextFloat() * 5 + 1);
_io.WriteLine("damaged");
}
}
internal float Move(Course course, float warpFactor, int distance)
{
var (quadrant, sector) = MoveWithinQuadrant(course, distance) ?? MoveBeyondQuadrant(course, distance);
if (quadrant != _quadrant.Coordinates)
{
_quadrant = new Quadrant(_quadrant.Galaxy[quadrant], this, _random, _quadrant.Galaxy, _io);
}
_quadrant.SetEnterpriseSector(sector);
SectorCoordinates = sector;
TotalEnergy -= distance + 10;
if (Energy < 0)
{
_io.WriteLine("Shield Control supplies energy to complete the maneuver.");
ShieldControl.ShieldEnergy = Math.Max(0, TotalEnergy);
}
return GetTimeElapsed(quadrant, warpFactor);
}
private (Coordinates, Coordinates)? MoveWithinQuadrant(Course course, int distance)
{
var currentSector = SectorCoordinates;
foreach (var (sector, index) in course.GetSectorsFrom(SectorCoordinates).Select((s, i) => (s, i)))
{
if (distance == 0) { break; }
if (_quadrant.HasObjectAt(sector))
{
_io.WriteLine($"Warp engines shut down at sector {currentSector} dues to bad navigation");
distance = 0;
break;
}
currentSector = sector;
distance -= 1;
}
return distance == 0 ? (_quadrant.Coordinates, currentSector) : null;
}
private (Coordinates, Coordinates) MoveBeyondQuadrant(Course course, int distance)
{
var (complete, quadrant, sector) = course.GetDestination(QuadrantCoordinates, SectorCoordinates, distance);
if (!complete)
{
_io.Write(Strings.PermissionDenied, sector, quadrant);
}
return (quadrant, sector);
}
private float GetTimeElapsed(Coordinates finalQuadrant, float warpFactor) =>
finalQuadrant == _quadrant.Coordinates
? Math.Min(1, (float)Math.Round(warpFactor, 1, MidpointRounding.ToZero))
: 1;
}

View File

@@ -1,43 +1,43 @@
using Games.Common.Randomness;
using SuperStarTrek.Commands;
using SuperStarTrek.Space;
namespace SuperStarTrek.Objects
namespace SuperStarTrek.Objects;
internal class Klingon
{
internal class Klingon
private readonly IRandom _random;
internal Klingon(Coordinates sector, IRandom random)
{
private readonly Random _random;
internal Klingon(Coordinates sector, Random random)
{
Sector = sector;
_random = random;
Energy = _random.GetFloat(100, 300);
}
internal float Energy { get; private set; }
internal Coordinates Sector { get; private set; }
public override string ToString() => "+K+";
internal CommandResult FireOn(Enterprise enterprise)
{
var attackStrength = _random.GetFloat();
var distanceToEnterprise = Sector.GetDistanceTo(enterprise.SectorCoordinates);
var hitStrength = (int)(Energy * (2 + attackStrength) / distanceToEnterprise);
Energy /= 3 + attackStrength;
return enterprise.TakeHit(Sector, hitStrength);
}
internal bool TakeHit(int hitStrength)
{
if (hitStrength < 0.15 * Energy) { return false; }
Energy -= hitStrength;
return true;
}
internal void MoveTo(Coordinates newSector) => Sector = newSector;
Sector = sector;
_random = random;
Energy = _random.NextFloat(100, 300);
}
internal float Energy { get; private set; }
internal Coordinates Sector { get; private set; }
public override string ToString() => "+K+";
internal CommandResult FireOn(Enterprise enterprise)
{
var attackStrength = _random.NextFloat();
var distanceToEnterprise = Sector.GetDistanceTo(enterprise.SectorCoordinates);
var hitStrength = (int)(Energy * (2 + attackStrength) / distanceToEnterprise);
Energy /= 3 + attackStrength;
return enterprise.TakeHit(Sector, hitStrength);
}
internal bool TakeHit(int hitStrength)
{
if (hitStrength < 0.15 * Energy) { return false; }
Energy -= hitStrength;
return true;
}
internal void MoveTo(Coordinates newSector) => Sector = newSector;
}

View File

@@ -1,7 +1,6 @@
namespace SuperStarTrek.Objects
namespace SuperStarTrek.Objects;
internal class Star
{
internal class Star
{
public override string ToString() => " * ";
}
public override string ToString() => " * ";
}

View File

@@ -1,45 +1,44 @@
using Games.Common.IO;
using Games.Common.Randomness;
using SuperStarTrek.Resources;
using SuperStarTrek.Space;
namespace SuperStarTrek.Objects
namespace SuperStarTrek.Objects;
internal class Starbase
{
internal class Starbase
private readonly IReadWrite _io;
private readonly float _repairDelay;
internal Starbase(Coordinates sector, IRandom random, IReadWrite io)
{
private readonly Input _input;
private readonly Output _output;
private readonly float _repairDelay;
internal Starbase(Coordinates sector, Random random, Input input, Output output)
{
Sector = sector;
_repairDelay = random.GetFloat() * 0.5f;
_input = input;
_output = output;
}
internal Coordinates Sector { get; }
public override string ToString() => ">!<";
internal bool TryRepair(Enterprise enterprise, out float repairTime)
{
repairTime = enterprise.DamagedSystemCount * 0.1f + _repairDelay;
if (repairTime >= 1) { repairTime = 0.9f; }
_output.Write(Strings.RepairEstimate, repairTime);
if (_input.GetYesNo(Strings.RepairPrompt, Input.YesNoMode.TrueOnY))
{
foreach (var system in enterprise.Systems)
{
system.Repair();
}
return true;
}
repairTime = 0;
return false;
}
internal void ProtectEnterprise() => _output.WriteLine(Strings.Protected);
Sector = sector;
_repairDelay = random.NextFloat(0.5f);
_io = io;
}
internal Coordinates Sector { get; }
public override string ToString() => ">!<";
internal bool TryRepair(Enterprise enterprise, out float repairTime)
{
repairTime = enterprise.DamagedSystemCount * 0.1f + _repairDelay;
if (repairTime >= 1) { repairTime = 0.9f; }
_io.Write(Strings.RepairEstimate, repairTime);
if (_io.GetYesNo(Strings.RepairPrompt, IReadWriteExtensions.YesNoMode.TrueOnY))
{
foreach (var system in enterprise.Systems)
{
system.Repair();
}
return true;
}
repairTime = 0;
return false;
}
internal void ProtectEnterprise() => _io.WriteLine(Strings.Protected);
}

View File

@@ -1,40 +0,0 @@
using System;
namespace SuperStarTrek
{
internal class Output
{
internal Output Write(string text)
{
Console.Write(text);
return this;
}
internal Output Write(string format, params object[] args)
{
Console.Write(format, args);
return this;
}
internal Output WriteLine(string text = "")
{
Console.WriteLine(text);
return this;
}
internal Output NextLine()
{
Console.WriteLine();
return this;
}
internal Output Prompt(string text = "")
{
Console.Write($"{text}? ");
return this;
}
}
}

View File

@@ -23,24 +23,18 @@
// **** CONVERTED TO MICROSOFT C# 2/20/21 BY ANDREW COOPER
// ****
namespace SuperStarTrek
using Games.Common.IO;
using Games.Common.Randomness;
using SuperStarTrek;
var io = new ConsoleIO();
var random = new RandomNumberGenerator();
var game = new Game(io, random);
game.DoIntroduction();
do
{
internal class Program
{
static void Main()
{
var output = new Output();
var input = new Input(output);
var random = new Random();
var game = new Game(output, input, random);
game.DoIntroduction();
do
{
game.Play();
} while (game.Replay());
}
}
}
game.Play();
} while (game.Replay());

View File

@@ -1,25 +0,0 @@
using SuperStarTrek.Space;
namespace SuperStarTrek
{
internal class Random
{
private readonly System.Random _random = new();
internal Coordinates GetCoordinate() => new Coordinates(Get1To8Inclusive() - 1, Get1To8Inclusive() - 1);
// 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.
internal int Get1To8Inclusive() => (int)(GetFloat() * 7.98 + 1.01);
internal int GetInt(int inclusiveMinValue, int exclusiveMaxValue) =>
_random.Next(inclusiveMinValue, exclusiveMaxValue);
internal float GetFloat() => (float)_random.NextDouble();
internal float GetFloat(float inclusiveMinValue, float exclusiveMaxValue)
=> GetFloat() * (exclusiveMaxValue - inclusiveMinValue) + inclusiveMinValue;
}
}

View File

@@ -16,3 +16,9 @@
'----------------'
THE USS ENTERPRISE --- NCC-1701

View File

@@ -104,3 +104,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.

View File

@@ -1,2 +1,3 @@
Now entering {0} quadrant . . .

View File

@@ -3,3 +3,4 @@ Your orders are as follows:
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.

View File

@@ -2,3 +2,4 @@
Your mission begins with your starship located
in the galactic quadrant, '{0}'.

View File

@@ -17,3 +17,11 @@
* *
* *
*************************************

View File

@@ -1,70 +1,69 @@
using System;
using SuperStarTrek.Utils;
namespace SuperStarTrek.Space
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
{
// 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
internal Coordinates(int x, int y)
{
internal Coordinates(int x, int y)
{
X = Validated(x, nameof(x));
Y = Validated(y, nameof(y));
X = Validated(x, nameof(x));
Y = Validated(y, nameof(y));
RegionIndex = (X << 1) + (Y >> 2);
SubRegionIndex = Y % 4;
RegionIndex = (X << 1) + (Y >> 2);
SubRegionIndex = Y % 4;
}
internal int X { get; }
internal int Y { get; }
internal int RegionIndex { get; }
internal int SubRegionIndex { get; }
private static int Validated(int value, string argumentName)
{
if (value >= 0 && value <= 7) { return value; }
throw new ArgumentOutOfRangeException(argumentName, value, "Must be 0 to 7 inclusive");
}
private static bool IsValid(int value) => value >= 0 && value <= 7;
public override string ToString() => $"{X+1} , {Y+1}";
internal void Deconstruct(out int x, out int y)
{
x = X;
y = Y;
}
internal static bool TryCreate(float x, float y, out Coordinates coordinates)
{
var roundedX = Round(x);
var roundedY = Round(y);
if (IsValid(roundedX) && IsValid(roundedY))
{
coordinates = new Coordinates(roundedX, roundedY);
return true;
}
internal int X { get; }
coordinates = default;
return false;
internal int Y { get; }
static int Round(float value) => (int)Math.Round(value, MidpointRounding.AwayFromZero);
}
internal int RegionIndex { get; }
internal (float Direction, float Distance) GetDirectionAndDistanceTo(Coordinates destination) =>
DirectionAndDistance.From(this).To(destination);
internal int SubRegionIndex { get; }
private static int Validated(int value, string argumentName)
{
if (value >= 0 && value <= 7) { return value; }
throw new ArgumentOutOfRangeException(argumentName, value, "Must be 0 to 7 inclusive");
}
private static bool IsValid(int value) => value >= 0 && value <= 7;
public override string ToString() => $"{X+1} , {Y+1}";
internal void Deconstruct(out int x, out int y)
{
x = X;
y = Y;
}
internal static bool TryCreate(float x, float y, out Coordinates coordinates)
{
var roundedX = Round(x);
var roundedY = Round(y);
if (IsValid(roundedX) && IsValid(roundedY))
{
coordinates = new Coordinates(roundedX, roundedY);
return true;
}
coordinates = default;
return false;
static int Round(float value) => (int)Math.Round(value, MidpointRounding.AwayFromZero);
}
internal (float Direction, float Distance) GetDirectionAndDistanceTo(Coordinates destination) =>
DirectionAndDistance.From(this).To(destination);
internal float GetDistanceTo(Coordinates destination)
{
var (_, distance) = GetDirectionAndDistanceTo(destination);
return distance;
}
internal float GetDistanceTo(Coordinates destination)
{
var (_, distance) = GetDirectionAndDistanceTo(destination);
return distance;
}
}

View File

@@ -1,98 +1,97 @@
using System;
using System.Collections.Generic;
namespace SuperStarTrek.Space
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
{
// 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[]
{
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)
};
internal Course(float direction)
{
if (direction < 1 || direction > 9)
{
(0, 1),
(-1, 1),
(-1, 0),
(-1, -1),
(0, -1),
(1, -1),
(1, 0),
(1, 1),
(0, 1)
};
internal Course(float 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;
throw new ArgumentOutOfRangeException(
nameof(direction),
direction,
"Must be between 1 and 9, inclusive.");
}
internal float DeltaX { get; }
var cardinalDirection = (int)(direction - 1) % 8;
var fractionalDirection = direction - (int)direction;
internal float DeltaY { get; }
var baseCardinal = cardinals[cardinalDirection];
var nextCardinal = cardinals[cardinalDirection + 1];
internal IEnumerable<Coordinates> GetSectorsFrom(Coordinates start)
DeltaX = baseCardinal.DeltaX + (nextCardinal.DeltaX - baseCardinal.DeltaX) * fractionalDirection;
DeltaY = baseCardinal.DeltaY + (nextCardinal.DeltaY - baseCardinal.DeltaY) * fractionalDirection;
}
internal float DeltaX { get; }
internal float DeltaY { get; }
internal IEnumerable<Coordinates> GetSectorsFrom(Coordinates start)
{
(float x, float y) = start;
while(true)
{
(float x, float y) = start;
x += DeltaX;
y += DeltaY;
while(true)
if (!Coordinates.TryCreate(x, y, out var coordinates))
{
x += DeltaX;
y += DeltaY;
if (!Coordinates.TryCreate(x, y, out var coordinates))
{
yield break;
}
yield return coordinates;
}
}
internal (bool, Coordinates, Coordinates) GetDestination(Coordinates quadrant, Coordinates sector, int distance)
{
var (xComplete, quadrantX, sectorX) = GetNewCoordinate(quadrant.X, sector.X, DeltaX * distance);
var (yComplete, quadrantY, sectorY) = GetNewCoordinate(quadrant.Y, sector.Y, DeltaY * distance);
return (xComplete && yComplete, new Coordinates(quadrantX, quadrantY), new Coordinates(sectorX, sectorY));
}
private static (bool, int, int) GetNewCoordinate(int quadrant, int sector, float sectorsTravelled)
{
var galacticCoordinate = quadrant * 8 + sector + sectorsTravelled;
var newQuadrant = (int)(galacticCoordinate / 8);
var newSector = (int)(galacticCoordinate - newQuadrant * 8);
if (newSector < 0)
{
newQuadrant -= 1;
newSector += 8;
yield break;
}
return newQuadrant switch
{
< 0 => (false, 0, 0),
> 7 => (false, 7, 7),
_ => (true, newQuadrant, newSector)
};
yield return coordinates;
}
}
internal (bool, Coordinates, Coordinates) GetDestination(Coordinates quadrant, Coordinates sector, int distance)
{
var (xComplete, quadrantX, sectorX) = GetNewCoordinate(quadrant.X, sector.X, DeltaX * distance);
var (yComplete, quadrantY, sectorY) = GetNewCoordinate(quadrant.Y, sector.Y, DeltaY * distance);
return (xComplete && yComplete, new Coordinates(quadrantX, quadrantY), new Coordinates(sectorX, sectorY));
}
private static (bool, int, int) GetNewCoordinate(int quadrant, int sector, float sectorsTravelled)
{
var galacticCoordinate = quadrant * 8 + sector + sectorsTravelled;
var newQuadrant = (int)(galacticCoordinate / 8);
var newSector = (int)(galacticCoordinate - newQuadrant * 8);
if (newSector < 0)
{
newQuadrant -= 1;
newSector += 8;
}
return newQuadrant switch
{
< 0 => (false, 0, 0),
> 7 => (false, 7, 7),
_ => (true, newQuadrant, newSector)
};
}
}

View File

@@ -1,64 +1,64 @@
using System.Collections.Generic;
using System.Linq;
using Games.Common.Randomness;
using SuperStarTrek.Resources;
using static System.StringSplitOptions;
namespace SuperStarTrek.Space
namespace SuperStarTrek.Space;
internal class Galaxy
{
internal class Galaxy
private static readonly string[] _regionNames;
private static readonly string[] _subRegionIdentifiers;
private readonly QuadrantInfo[][] _quadrants;
static Galaxy()
{
private static readonly string[] _regionNames;
private static readonly string[] _subRegionIdentifiers;
private readonly QuadrantInfo[][] _quadrants;
_regionNames = Strings.RegionNames.Split(new[] { ' ', '\n' }, RemoveEmptyEntries | TrimEntries);
_subRegionIdentifiers = new[] { "I", "II", "III", "IV" };
}
static Galaxy()
{
_regionNames = Strings.RegionNames.Split(new[] { ' ', '\n' }, RemoveEmptyEntries | TrimEntries);
_subRegionIdentifiers = new[] { "I", "II", "III", "IV" };
}
internal Galaxy(Random random)
{
_quadrants = Enumerable
internal Galaxy(IRandom random)
{
_quadrants = Enumerable
.Range(0, 8)
.Select(x => Enumerable
.Range(0, 8)
.Select(x => Enumerable
.Range(0, 8)
.Select(y => new Coordinates(x, y))
.Select(c => QuadrantInfo.Create(c, GetQuadrantName(c), random))
.ToArray())
.ToArray();
.Select(y => new Coordinates(x, y))
.Select(c => QuadrantInfo.Create(c, GetQuadrantName(c), random))
.ToArray())
.ToArray();
if (StarbaseCount == 0)
if (StarbaseCount == 0)
{
var randomQuadrant = this[random.NextCoordinate()];
randomQuadrant.AddStarbase();
if (randomQuadrant.KlingonCount < 2)
{
var randomQuadrant = this[random.GetCoordinate()];
randomQuadrant.AddStarbase();
if (randomQuadrant.KlingonCount < 2)
{
randomQuadrant.AddKlingon();
}
randomQuadrant.AddKlingon();
}
}
internal QuadrantInfo this[Coordinates coordinate] => _quadrants[coordinate.X][coordinate.Y];
internal int KlingonCount => _quadrants.SelectMany(q => q).Sum(q => q.KlingonCount);
internal int StarbaseCount => _quadrants.SelectMany(q => q).Count(q => q.HasStarbase);
internal IEnumerable<IEnumerable<QuadrantInfo>> Quadrants => _quadrants;
private static string GetQuadrantName(Coordinates coordinates) =>
$"{_regionNames[coordinates.RegionIndex]} {_subRegionIdentifiers[coordinates.SubRegionIndex]}";
internal IEnumerable<IEnumerable<QuadrantInfo>> GetNeighborhood(Quadrant quadrant) =>
Enumerable.Range(-1, 3)
.Select(dx => dx + quadrant.Coordinates.X)
.Select(x => GetNeighborhoodRow(quadrant, x));
private IEnumerable<QuadrantInfo> GetNeighborhoodRow(Quadrant quadrant, int x) =>
Enumerable.Range(-1, 3)
.Select(dy => dy + quadrant.Coordinates.Y)
.Select(y => y < 0 || y > 7 || x < 0 || x > 7 ? null : _quadrants[x][y]);
}
internal QuadrantInfo this[Coordinates coordinate] => _quadrants[coordinate.X][coordinate.Y];
internal int KlingonCount => _quadrants.SelectMany(q => q).Sum(q => q.KlingonCount);
internal int StarbaseCount => _quadrants.SelectMany(q => q).Count(q => q.HasStarbase);
internal IEnumerable<IEnumerable<QuadrantInfo>> Quadrants => _quadrants;
private static string GetQuadrantName(Coordinates coordinates) =>
$"{_regionNames[coordinates.RegionIndex]} {_subRegionIdentifiers[coordinates.SubRegionIndex]}";
internal IEnumerable<IEnumerable<QuadrantInfo>> GetNeighborhood(Quadrant quadrant) =>
Enumerable.Range(-1, 3)
.Select(dx => dx + quadrant.Coordinates.X)
.Select(x => GetNeighborhoodRow(quadrant, x));
private IEnumerable<QuadrantInfo> GetNeighborhoodRow(Quadrant quadrant, int x) =>
Enumerable.Range(-1, 3)
.Select(dy => dy + quadrant.Coordinates.Y)
.Select(y => y < 0 || y > 7 || x < 0 || x > 7 ? null : _quadrants[x][y]);
}

View File

@@ -1,192 +1,192 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Games.Common.IO;
using Games.Common.Randomness;
using SuperStarTrek.Commands;
using SuperStarTrek.Objects;
using SuperStarTrek.Resources;
namespace SuperStarTrek.Space
namespace SuperStarTrek.Space;
internal class Quadrant
{
internal class Quadrant
private readonly QuadrantInfo _info;
private readonly IRandom _random;
private readonly Dictionary<Coordinates, object> _sectors;
private readonly Enterprise _enterprise;
private readonly IReadWrite _io;
private bool _entered = false;
internal Quadrant(
QuadrantInfo info,
Enterprise enterprise,
IRandom random,
Galaxy galaxy,
IReadWrite io)
{
private readonly QuadrantInfo _info;
private readonly Random _random;
private readonly Dictionary<Coordinates, object> _sectors;
private readonly Enterprise _enterprise;
private readonly Output _output;
private bool _entered = false;
_info = info;
_random = random;
_io = io;
Galaxy = galaxy;
internal Quadrant(
QuadrantInfo info,
Enterprise enterprise,
Random random,
Galaxy galaxy,
Input input,
Output output)
info.MarkAsKnown();
_sectors = new() { [enterprise.SectorCoordinates] = _enterprise = enterprise };
PositionObject(sector => new Klingon(sector, _random), _info.KlingonCount);
if (_info.HasStarbase)
{
_info = info;
_random = random;
_output = output;
Galaxy = galaxy;
Starbase = PositionObject(sector => new Starbase(sector, _random, io));
}
PositionObject(_ => new Star(), _info.StarCount);
}
info.MarkAsKnown();
_sectors = new() { [enterprise.SectorCoordinates] = _enterprise = enterprise };
PositionObject(sector => new Klingon(sector, _random), _info.KlingonCount);
if (_info.HasStarbase)
{
Starbase = PositionObject(sector => new Starbase(sector, _random, input, output));
}
PositionObject(_ => new Star(), _info.StarCount);
internal Coordinates Coordinates => _info.Coordinates;
internal bool HasKlingons => _info.KlingonCount > 0;
internal int KlingonCount => _info.KlingonCount;
internal bool HasStarbase => _info.HasStarbase;
internal Starbase Starbase { get; }
internal Galaxy Galaxy { get; }
internal bool EnterpriseIsNextToStarbase =>
_info.HasStarbase &&
Math.Abs(_enterprise.SectorCoordinates.X - Starbase.Sector.X) <= 1 &&
Math.Abs(_enterprise.SectorCoordinates.Y - Starbase.Sector.Y) <= 1;
internal IEnumerable<Klingon> Klingons => _sectors.Values.OfType<Klingon>();
public override string ToString() => _info.Name;
private T PositionObject<T>(Func<Coordinates, T> objectFactory)
{
var sector = GetRandomEmptySector();
_sectors[sector] = objectFactory.Invoke(sector);
return (T)_sectors[sector];
}
private void PositionObject(Func<Coordinates, object> objectFactory, int count)
{
for (int i = 0; i < count; i++)
{
PositionObject(objectFactory);
}
}
internal void Display(string textFormat)
{
if (!_entered)
{
_io.Write(textFormat, this);
_entered = true;
}
internal Coordinates Coordinates => _info.Coordinates;
internal bool HasKlingons => _info.KlingonCount > 0;
internal int KlingonCount => _info.KlingonCount;
internal bool HasStarbase => _info.HasStarbase;
internal Starbase Starbase { get; }
internal Galaxy Galaxy { get; }
internal bool EnterpriseIsNextToStarbase =>
_info.HasStarbase &&
Math.Abs(_enterprise.SectorCoordinates.X - Starbase.Sector.X) <= 1 &&
Math.Abs(_enterprise.SectorCoordinates.Y - Starbase.Sector.Y) <= 1;
internal IEnumerable<Klingon> Klingons => _sectors.Values.OfType<Klingon>();
public override string ToString() => _info.Name;
private T PositionObject<T>(Func<Coordinates, T> objectFactory)
if (_info.KlingonCount > 0)
{
var sector = GetRandomEmptySector();
_sectors[sector] = objectFactory.Invoke(sector);
return (T)_sectors[sector];
_io.Write(Strings.CombatArea);
if (_enterprise.ShieldControl.ShieldEnergy <= 200) { _io.Write(Strings.LowShields); }
}
private void PositionObject(Func<Coordinates, object> objectFactory, int count)
_enterprise.Execute(Command.SRS);
}
internal bool HasObjectAt(Coordinates coordinates) => _sectors.ContainsKey(coordinates);
internal bool TorpedoCollisionAt(Coordinates coordinates, out string message, out bool gameOver)
{
gameOver = false;
message = default;
switch (_sectors.GetValueOrDefault(coordinates))
{
for (int i = 0; i < count; i++)
{
PositionObject(objectFactory);
}
case Klingon klingon:
message = Remove(klingon);
gameOver = Galaxy.KlingonCount == 0;
return true;
case Star _:
message = $"Star at {coordinates} absorbed torpedo energy.";
return true;
case Starbase _:
_sectors.Remove(coordinates);
_info.RemoveStarbase();
message = "*** Starbase destroyed ***" +
(Galaxy.StarbaseCount > 0 ? Strings.CourtMartial : Strings.RelievedOfCommand);
gameOver = Galaxy.StarbaseCount == 0;
return true;
default:
return false;
}
}
internal void Display(string textFormat)
{
if (!_entered)
{
_output.Write(textFormat, this);
_entered = true;
}
if (_info.KlingonCount > 0)
{
_output.Write(Strings.CombatArea);
if (_enterprise.ShieldControl.ShieldEnergy <= 200) { _output.Write(Strings.LowShields); }
}
_enterprise.Execute(Command.SRS);
}
internal bool HasObjectAt(Coordinates coordinates) => _sectors.ContainsKey(coordinates);
internal bool TorpedoCollisionAt(Coordinates coordinates, out string message, out bool gameOver)
{
gameOver = false;
message = default;
switch (_sectors.GetValueOrDefault(coordinates))
{
case Klingon klingon:
message = Remove(klingon);
gameOver = Galaxy.KlingonCount == 0;
return true;
case Star _:
message = $"Star at {coordinates} absorbed torpedo energy.";
return true;
case Starbase _:
_sectors.Remove(coordinates);
_info.RemoveStarbase();
message = "*** Starbase destroyed ***" +
(Galaxy.StarbaseCount > 0 ? Strings.CourtMartial : Strings.RelievedOfCommand);
gameOver = Galaxy.StarbaseCount == 0;
return true;
default:
return false;
}
}
internal string Remove(Klingon klingon)
internal string Remove(Klingon klingon)
{
_sectors.Remove(klingon.Sector);
_info.RemoveKlingon();
return "*** Klingon destroyed ***";
}
internal CommandResult KlingonsMoveAndFire()
{
foreach (var klingon in Klingons.ToList())
{
var newSector = GetRandomEmptySector();
_sectors.Remove(klingon.Sector);
_info.RemoveKlingon();
return "*** Klingon destroyed ***";
_sectors[newSector] = klingon;
klingon.MoveTo(newSector);
}
internal CommandResult KlingonsMoveAndFire()
return KlingonsFireOnEnterprise();
}
internal CommandResult KlingonsFireOnEnterprise()
{
if (EnterpriseIsNextToStarbase && Klingons.Any())
{
foreach (var klingon in Klingons.ToList())
{
var newSector = GetRandomEmptySector();
_sectors.Remove(klingon.Sector);
_sectors[newSector] = klingon;
klingon.MoveTo(newSector);
}
return KlingonsFireOnEnterprise();
}
internal CommandResult KlingonsFireOnEnterprise()
{
if (EnterpriseIsNextToStarbase && Klingons.Any())
{
Starbase.ProtectEnterprise();
return CommandResult.Ok;
}
foreach (var klingon in Klingons)
{
var result = klingon.FireOn(_enterprise);
if (result.IsGameOver) { return result; }
}
Starbase.ProtectEnterprise();
return CommandResult.Ok;
}
private Coordinates GetRandomEmptySector()
foreach (var klingon in Klingons)
{
while (true)
var result = klingon.FireOn(_enterprise);
if (result.IsGameOver) { return result; }
}
return CommandResult.Ok;
}
private Coordinates GetRandomEmptySector()
{
while (true)
{
var sector = _random.NextCoordinate();
if (!_sectors.ContainsKey(sector))
{
var sector = _random.GetCoordinate();
if (!_sectors.ContainsKey(sector))
{
return sector;
}
return sector;
}
}
}
internal IEnumerable<string> GetDisplayLines() => Enumerable.Range(0, 8).Select(x => GetDisplayLine(x));
internal IEnumerable<string> GetDisplayLines() => Enumerable.Range(0, 8).Select(x => GetDisplayLine(x));
private string GetDisplayLine(int x) =>
string.Join(
" ",
Enumerable
.Range(0, 8)
.Select(y => new Coordinates(x, y))
.Select(c => _sectors.GetValueOrDefault(c))
.Select(o => o?.ToString() ?? " "));
private string GetDisplayLine(int x) =>
string.Join(
" ",
Enumerable
.Range(0, 8)
.Select(y => new Coordinates(x, y))
.Select(c => _sectors.GetValueOrDefault(c))
.Select(o => o?.ToString() ?? " "));
internal void SetEnterpriseSector(Coordinates sector)
{
_sectors.Remove(_enterprise.SectorCoordinates);
_sectors[sector] = _enterprise;
}
internal void SetEnterpriseSector(Coordinates sector)
{
_sectors.Remove(_enterprise.SectorCoordinates);
_sectors[sector] = _enterprise;
}
}

View File

@@ -1,65 +1,66 @@
namespace SuperStarTrek.Space
using Games.Common.Randomness;
namespace SuperStarTrek.Space;
internal class QuadrantInfo
{
internal class QuadrantInfo
private bool _isKnown;
private QuadrantInfo(Coordinates coordinates, string name, int klingonCount, int starCount, bool hasStarbase)
{
private bool _isKnown;
private QuadrantInfo(Coordinates coordinates, string name, int klingonCount, int starCount, bool hasStarbase)
{
Coordinates = coordinates;
Name = name;
KlingonCount = klingonCount;
StarCount = starCount;
HasStarbase = hasStarbase;
}
internal Coordinates Coordinates { get; }
internal string Name { get; }
internal int KlingonCount { get; private set; }
internal bool HasStarbase { get; private set; }
internal int StarCount { get; }
internal static QuadrantInfo Create(Coordinates coordinates, string name, Random random)
{
var klingonCount = random.GetFloat() switch
{
> 0.98f => 3,
> 0.95f => 2,
> 0.80f => 1,
_ => 0
};
var hasStarbase = random.GetFloat() > 0.96f;
var starCount = random.Get1To8Inclusive();
return new QuadrantInfo(coordinates, name, klingonCount, starCount, hasStarbase);
}
internal void AddKlingon() => KlingonCount += 1;
internal void AddStarbase() => HasStarbase = true;
internal void MarkAsKnown() => _isKnown = true;
internal string Scan()
{
_isKnown = true;
return ToString();
}
public override string ToString() => _isKnown ? $"{KlingonCount}{(HasStarbase ? 1 : 0)}{StarCount}" : "***";
internal void RemoveKlingon()
{
if (KlingonCount > 0)
{
KlingonCount -= 1;
}
}
internal void RemoveStarbase() => HasStarbase = false;
Coordinates = coordinates;
Name = name;
KlingonCount = klingonCount;
StarCount = starCount;
HasStarbase = hasStarbase;
}
internal Coordinates Coordinates { get; }
internal string Name { get; }
internal int KlingonCount { get; private set; }
internal bool HasStarbase { get; private set; }
internal int StarCount { get; }
internal static QuadrantInfo Create(Coordinates coordinates, string name, IRandom random)
{
var klingonCount = random.NextFloat() switch
{
> 0.98f => 3,
> 0.95f => 2,
> 0.80f => 1,
_ => 0
};
var hasStarbase = random.NextFloat() > 0.96f;
var starCount = random.Next1To8Inclusive();
return new QuadrantInfo(coordinates, name, klingonCount, starCount, hasStarbase);
}
internal void AddKlingon() => KlingonCount += 1;
internal void AddStarbase() => HasStarbase = true;
internal void MarkAsKnown() => _isKnown = true;
internal string Scan()
{
_isKnown = true;
return ToString();
}
public override string ToString() => _isKnown ? $"{KlingonCount}{(HasStarbase ? 1 : 0)}{StarCount}" : "***";
internal void RemoveKlingon()
{
if (KlingonCount > 0)
{
KlingonCount -= 1;
}
}
internal void RemoveStarbase() => HasStarbase = false;
}

View File

@@ -1,7 +1,6 @@
namespace SuperStarTrek
namespace SuperStarTrek;
internal static class StringExtensions
{
internal static class StringExtensions
{
internal static string Pluralize(this string singular, int quantity) => singular + (quantity > 1 ? "s" : "");
}
internal static string Pluralize(this string singular, int quantity) => singular + (quantity > 1 ? "s" : "");
}

View File

@@ -2,11 +2,15 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\*.txt" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\00_Common\dotnet\Games.Common\Games.Common.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,19 +1,19 @@
using Games.Common.IO;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems.ComputerFunctions
namespace SuperStarTrek.Systems.ComputerFunctions;
internal abstract class ComputerFunction
{
internal abstract class ComputerFunction
protected ComputerFunction(string description, IReadWrite io)
{
protected ComputerFunction(string description, Output output)
{
Description = description;
Output = output;
}
internal string Description { get; }
protected Output Output { get; }
internal abstract void Execute(Quadrant quadrant);
Description = description;
IO = io;
}
internal string Description { get; }
protected IReadWrite IO { get; }
internal abstract void Execute(Quadrant quadrant);
}

View File

@@ -1,21 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Games.Common.IO;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems.ComputerFunctions
namespace SuperStarTrek.Systems.ComputerFunctions;
internal class CumulativeGalacticRecord : GalacticReport
{
internal class CumulativeGalacticRecord : GalacticReport
internal CumulativeGalacticRecord(IReadWrite io, Galaxy galaxy)
: base("Cumulative galactic record", io, galaxy)
{
internal CumulativeGalacticRecord(Output output, Galaxy galaxy)
: base("Cumulative galactic record", output, galaxy)
{
}
protected override void WriteHeader(Quadrant quadrant) =>
Output.NextLine().WriteLine($"Computer record of galaxy for quadrant {quadrant.Coordinates}").NextLine();
protected override IEnumerable<string> GetRowData() =>
Galaxy.Quadrants.Select(row => " " + string.Join(" ", row));
}
protected override void WriteHeader(Quadrant quadrant)
{
IO.WriteLine();
IO.WriteLine($"Computer record of galaxy for quadrant {quadrant.Coordinates}");
IO.WriteLine();
}
protected override IEnumerable<string> GetRowData() =>
Galaxy.Quadrants.Select(row => " " + string.Join(" ", row));
}

View File

@@ -1,30 +1,30 @@
using Games.Common.IO;
using SuperStarTrek.Objects;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems.ComputerFunctions
namespace SuperStarTrek.Systems.ComputerFunctions;
internal class DirectionDistanceCalculator : NavigationCalculator
{
internal class DirectionDistanceCalculator : NavigationCalculator
private readonly Enterprise _enterprise;
private readonly IReadWrite _io;
internal DirectionDistanceCalculator(Enterprise enterprise, IReadWrite io)
: base("Direction/distance calculator", io)
{
private readonly Enterprise _enterprise;
private readonly Input _input;
_enterprise = enterprise;
_io = io;
}
internal DirectionDistanceCalculator(Enterprise enterprise, Output output, Input input)
: base("Direction/distance calculator", output)
{
_enterprise = enterprise;
_input = input;
}
internal override void Execute(Quadrant quadrant)
{
IO.WriteLine("Direction/distance calculator:");
IO.Write($"You are at quadrant {_enterprise.QuadrantCoordinates}");
IO.WriteLine($" sector {_enterprise.SectorCoordinates}");
IO.WriteLine("Please enter");
internal override void Execute(Quadrant quadrant)
{
Output.WriteLine("Direction/distance calculator:")
.Write($"You are at quadrant {_enterprise.QuadrantCoordinates}")
.WriteLine($" sector {_enterprise.SectorCoordinates}")
.WriteLine("Please enter");
WriteDirectionAndDistance(
_input.GetCoordinates(" Initial coordinates"),
_input.GetCoordinates(" Final coordinates"));
}
WriteDirectionAndDistance(
_io.GetCoordinates(" Initial coordinates"),
_io.GetCoordinates(" Final coordinates"));
}
}

View File

@@ -1,34 +1,34 @@
using System.Collections.Generic;
using System.Linq;
using Games.Common.IO;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems.ComputerFunctions
namespace SuperStarTrek.Systems.ComputerFunctions;
internal abstract class GalacticReport : ComputerFunction
{
internal abstract class GalacticReport : ComputerFunction
internal GalacticReport(string description, IReadWrite io, Galaxy galaxy)
: base(description, io)
{
internal GalacticReport(string description, Output output, Galaxy galaxy)
: base(description, output)
Galaxy = galaxy;
}
protected Galaxy Galaxy { get; }
protected abstract void WriteHeader(Quadrant quadrant);
protected abstract IEnumerable<string> GetRowData();
internal sealed override void Execute(Quadrant quadrant)
{
WriteHeader(quadrant);
IO.WriteLine(" 1 2 3 4 5 6 7 8");
IO.WriteLine(" ----- ----- ----- ----- ----- ----- ----- -----");
foreach (var (row, index) in GetRowData().Select((r, i) => (r, i)))
{
Galaxy = galaxy;
}
protected Galaxy Galaxy { get; }
protected abstract void WriteHeader(Quadrant quadrant);
protected abstract IEnumerable<string> GetRowData();
internal sealed override void Execute(Quadrant quadrant)
{
WriteHeader(quadrant);
Output.WriteLine(" 1 2 3 4 5 6 7 8")
.WriteLine(" ----- ----- ----- ----- ----- ----- ----- -----");
foreach (var (row, index) in GetRowData().Select((r, i) => (r, i)))
{
Output.WriteLine($" {index+1} {row}")
.WriteLine(" ----- ----- ----- ----- ----- ----- ----- -----");
}
IO.WriteLine($" {index+1} {row}");
IO.WriteLine(" ----- ----- ----- ----- ----- ----- ----- -----");
}
}
}

View File

@@ -1,21 +1,21 @@
using System.Collections.Generic;
using System.Linq;
using Games.Common.IO;
using SuperStarTrek.Resources;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems.ComputerFunctions
namespace SuperStarTrek.Systems.ComputerFunctions;
internal class GalaxyRegionMap : GalacticReport
{
internal class GalaxyRegionMap : GalacticReport
internal GalaxyRegionMap(IReadWrite io, Galaxy galaxy)
: base("Galaxy 'region name' map", io, galaxy)
{
internal GalaxyRegionMap(Output output, Galaxy galaxy)
: base("Galaxy 'region name' map", output, galaxy)
{
}
protected override void WriteHeader(Quadrant quadrant) =>
Output.WriteLine(" The Galaxy");
protected override IEnumerable<string> GetRowData() =>
Strings.RegionNames.Split('\n').Select(n => n.TrimEnd('\r'));
}
protected override void WriteHeader(Quadrant quadrant) =>
IO.WriteLine(" The Galaxy");
protected override IEnumerable<string> GetRowData() =>
Strings.RegionNames.Split('\n').Select(n => n.TrimEnd('\r'));
}

View File

@@ -1,29 +1,31 @@
using Games.Common.IO;
using SuperStarTrek.Space;
using SuperStarTrek.Utils;
namespace SuperStarTrek.Systems.ComputerFunctions
namespace SuperStarTrek.Systems.ComputerFunctions;
internal abstract class NavigationCalculator : ComputerFunction
{
internal abstract class NavigationCalculator : ComputerFunction
protected NavigationCalculator(string description, IReadWrite io)
: base(description, io)
{
protected NavigationCalculator(string description, Output output)
: base(description, output)
{
}
}
protected void WriteDirectionAndDistance(Coordinates from, Coordinates to)
{
var (direction, distance) = from.GetDirectionAndDistanceTo(to);
Write(direction, distance);
}
protected void WriteDirectionAndDistance(Coordinates from, Coordinates to)
{
var (direction, distance) = from.GetDirectionAndDistanceTo(to);
Write(direction, distance);
}
protected void WriteDirectionAndDistance((float X, float Y) from, (float X, float Y) to)
{
var (direction, distance) = DirectionAndDistance.From(from.X, from.Y).To(to.X, to.Y);
Write(direction, distance);
}
protected void WriteDirectionAndDistance((float X, float Y) from, (float X, float Y) to)
{
var (direction, distance) = DirectionAndDistance.From(from.X, from.Y).To(to.X, to.Y);
Write(direction, distance);
}
private void Write(float direction, float distance) =>
Output.WriteLine($"Direction = {direction}")
.WriteLine($"Distance = {distance}");
private void Write(float direction, float distance)
{
IO.WriteLine($"Direction = {direction}");
IO.WriteLine($"Distance = {distance}");
}
}

View File

@@ -1,30 +1,30 @@
using Games.Common.IO;
using SuperStarTrek.Objects;
using SuperStarTrek.Resources;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems.ComputerFunctions
namespace SuperStarTrek.Systems.ComputerFunctions;
internal class StarbaseDataCalculator : NavigationCalculator
{
internal class StarbaseDataCalculator : NavigationCalculator
private readonly Enterprise _enterprise;
internal StarbaseDataCalculator(Enterprise enterprise, IReadWrite io)
: base("Starbase nav data", io)
{
private readonly Enterprise _enterprise;
_enterprise = enterprise;
}
internal StarbaseDataCalculator(Enterprise enterprise, Output output)
: base("Starbase nav data", output)
internal override void Execute(Quadrant quadrant)
{
if (!quadrant.HasStarbase)
{
_enterprise = enterprise;
IO.WriteLine(Strings.NoStarbase);
return;
}
internal override void Execute(Quadrant quadrant)
{
if (!quadrant.HasStarbase)
{
Output.WriteLine(Strings.NoStarbase);
return;
}
IO.WriteLine("From Enterprise to Starbase:");
Output.WriteLine("From Enterprise to Starbase:");
WriteDirectionAndDistance(_enterprise.SectorCoordinates, quadrant.Starbase.Sector);
}
WriteDirectionAndDistance(_enterprise.SectorCoordinates, quadrant.Starbase.Sector);
}
}

View File

@@ -1,41 +1,43 @@
using Games.Common.IO;
using SuperStarTrek.Commands;
using SuperStarTrek.Objects;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems.ComputerFunctions
namespace SuperStarTrek.Systems.ComputerFunctions;
internal class StatusReport : ComputerFunction
{
internal class StatusReport : ComputerFunction
private readonly Game _game;
private readonly Galaxy _galaxy;
private readonly Enterprise _enterprise;
internal StatusReport(Game game, Galaxy galaxy, Enterprise enterprise, IReadWrite io)
: base("Status report", io)
{
private readonly Game _game;
private readonly Galaxy _galaxy;
private readonly Enterprise _enterprise;
_game = game;
_galaxy = galaxy;
_enterprise = enterprise;
}
internal StatusReport(Game game, Galaxy galaxy, Enterprise enterprise, Output output)
: base("Status report", output)
internal override void Execute(Quadrant quadrant)
{
IO.WriteLine(" Status report:");
IO.Write("Klingon".Pluralize(_galaxy.KlingonCount));
IO.WriteLine($" left: {_galaxy.KlingonCount}");
IO.WriteLine($"Mission must be completed in {_game.StardatesRemaining:0.#} stardates.");
if (_galaxy.StarbaseCount > 0)
{
_game = game;
_galaxy = galaxy;
_enterprise = enterprise;
IO.Write($"The Federation is maintaining {_galaxy.StarbaseCount} ");
IO.Write("starbase".Pluralize(_galaxy.StarbaseCount));
IO.WriteLine(" in the galaxy.");
}
else
{
IO.WriteLine("Your stupidity has left you on your own in");
IO.WriteLine(" the galaxy -- you have no starbases left!");
}
internal override void Execute(Quadrant quadrant)
{
Output.WriteLine(" Status report:")
.Write("Klingon".Pluralize(_galaxy.KlingonCount)).WriteLine($" left: {_galaxy.KlingonCount}")
.WriteLine($"Mission must be completed in {_game.StardatesRemaining:0.#} stardates.");
if (_galaxy.StarbaseCount > 0)
{
Output.Write($"The Federation is maintaining {_galaxy.StarbaseCount} ")
.Write("starbase".Pluralize(_galaxy.StarbaseCount)).WriteLine(" in the galaxy.");
}
else
{
Output.WriteLine("Your stupidity has left you on your own in")
.WriteLine(" the galaxy -- you have no starbases left!");
}
_enterprise.Execute(Command.DAM);
}
_enterprise.Execute(Command.DAM);
}
}

View File

@@ -1,33 +1,33 @@
using Games.Common.IO;
using SuperStarTrek.Objects;
using SuperStarTrek.Resources;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems.ComputerFunctions
{
internal class TorpedoDataCalculator : NavigationCalculator
{
private readonly Enterprise _enterprise;
namespace SuperStarTrek.Systems.ComputerFunctions;
internal TorpedoDataCalculator(Enterprise enterprise, Output output)
: base("Photon torpedo data", output)
internal class TorpedoDataCalculator : NavigationCalculator
{
private readonly Enterprise _enterprise;
internal TorpedoDataCalculator(Enterprise enterprise, IReadWrite io)
: base("Photon torpedo data", io)
{
_enterprise = enterprise;
}
internal override void Execute(Quadrant quadrant)
{
if (!quadrant.HasKlingons)
{
_enterprise = enterprise;
IO.WriteLine(Strings.NoEnemyShips);
return;
}
internal override void Execute(Quadrant quadrant)
IO.WriteLine("From Enterprise to Klingon battle cruiser".Pluralize(quadrant.KlingonCount));
foreach (var klingon in quadrant.Klingons)
{
if (!quadrant.HasKlingons)
{
Output.WriteLine(Strings.NoEnemyShips);
return;
}
Output.WriteLine("From Enterprise to Klingon battle cruiser".Pluralize(quadrant.KlingonCount));
foreach (var klingon in quadrant.Klingons)
{
WriteDirectionAndDistance(_enterprise.SectorCoordinates, klingon.Sector);
}
WriteDirectionAndDistance(_enterprise.SectorCoordinates, klingon.Sector);
}
}
}

View File

@@ -1,54 +1,55 @@
using Games.Common.IO;
using SuperStarTrek.Commands;
using SuperStarTrek.Objects;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems
namespace SuperStarTrek.Systems;
internal class DamageControl : Subsystem
{
internal class DamageControl : Subsystem
private readonly Enterprise _enterprise;
private readonly IReadWrite _io;
internal DamageControl(Enterprise enterprise, IReadWrite io)
: base("Damage Control", Command.DAM, io)
{
private readonly Enterprise _enterprise;
private readonly Output _output;
_enterprise = enterprise;
_io = io;
}
internal DamageControl(Enterprise enterprise, Output output)
: base("Damage Control", Command.DAM, output)
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
{
if (IsDamaged)
{
_enterprise = enterprise;
_output = output;
_io.WriteLine("Damage Control report not available");
}
else
{
_io.WriteLine();
WriteDamageReport();
}
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
if (_enterprise.DamagedSystemCount > 0 && _enterprise.IsDocked)
{
if (IsDamaged)
if (quadrant.Starbase.TryRepair(_enterprise, out var repairTime))
{
_output.WriteLine("Damage Control report not available");
}
else
{
_output.NextLine();
WriteDamageReport();
return CommandResult.Elapsed(repairTime);
}
if (_enterprise.DamagedSystemCount > 0 && _enterprise.IsDocked)
{
if (quadrant.Starbase.TryRepair(_enterprise, out var repairTime))
{
WriteDamageReport();
return CommandResult.Elapsed(repairTime);
}
}
return CommandResult.Ok;
}
internal void WriteDamageReport()
return CommandResult.Ok;
}
internal void WriteDamageReport()
{
_io.WriteLine();
_io.WriteLine("Device State of Repair");
foreach (var system in _enterprise.Systems)
{
_output.NextLine().WriteLine("Device State of Repair");
foreach (var system in _enterprise.Systems)
{
_output.Write(system.Name.PadRight(25))
.WriteLine(((int)(system.Condition * 100) * 0.01).ToString(" 0.##;-0.##"));
}
_output.NextLine();
_io.Write(system.Name.PadRight(25));
_io.WriteLine((int)(system.Condition * 100) * 0.01F);
}
_io.WriteLine();
}
}

View File

@@ -1,46 +1,44 @@
using Games.Common.IO;
using SuperStarTrek.Commands;
using SuperStarTrek.Space;
using SuperStarTrek.Systems.ComputerFunctions;
namespace SuperStarTrek.Systems
namespace SuperStarTrek.Systems;
internal class LibraryComputer : Subsystem
{
internal class LibraryComputer : Subsystem
private readonly IReadWrite _io;
private readonly ComputerFunction[] _functions;
internal LibraryComputer(IReadWrite io, params ComputerFunction[] functions)
: base("Library-Computer", Command.COM, io)
{
private readonly Output _output;
private readonly Input _input;
private readonly ComputerFunction[] _functions;
_io = io;
_functions = functions;
}
internal LibraryComputer(Output output, Input input, params ComputerFunction[] functions)
: base("Library-Computer", Command.COM, output)
protected override bool CanExecuteCommand() => IsOperational("Computer disabled");
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
{
var index = GetFunctionIndex();
_io.WriteLine();
_functions[index].Execute(quadrant);
return CommandResult.Ok;
}
private int GetFunctionIndex()
{
while (true)
{
_output = output;
_input = input;
_functions = functions;
}
var index = (int)_io.ReadNumber("Computer active and waiting command");
if (index >= 0 && index <= 5) { return index; }
protected override bool CanExecuteCommand() => IsOperational("Computer disabled");
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
{
var index = GetFunctionIndex();
_output.NextLine();
_functions[index].Execute(quadrant);
return CommandResult.Ok;
}
private int GetFunctionIndex()
{
while (true)
for (int i = 0; i < _functions.Length; i++)
{
var index = (int)_input.GetNumber("Computer active and waiting command");
if (index >= 0 && index <= 5) { return index; }
for (int i = 0; i < _functions.Length; i++)
{
_output.WriteLine($" {i} = {_functions[i].Description}");
}
_io.WriteLine($" {i} = {_functions[i].Description}");
}
}
}

View File

@@ -1,34 +1,35 @@
using System.Linq;
using Games.Common.IO;
using SuperStarTrek.Commands;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems
namespace SuperStarTrek.Systems;
internal class LongRangeSensors : Subsystem
{
internal class LongRangeSensors : Subsystem
private readonly Galaxy _galaxy;
private readonly IReadWrite _io;
internal LongRangeSensors(Galaxy galaxy, IReadWrite io)
: base("Long Range Sensors", Command.LRS, io)
{
private readonly Galaxy _galaxy;
private readonly Output _output;
_galaxy = galaxy;
_io = io;
}
internal LongRangeSensors(Galaxy galaxy, Output output)
: base("Long Range Sensors", Command.LRS, output)
protected override bool CanExecuteCommand() => IsOperational("{name} are inoperable");
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
{
_io.WriteLine($"Long range scan for quadrant {quadrant.Coordinates}");
_io.WriteLine("-------------------");
foreach (var quadrants in _galaxy.GetNeighborhood(quadrant))
{
_galaxy = galaxy;
_output = output;
_io.WriteLine(": " + string.Join(" : ", quadrants.Select(q => q?.Scan() ?? "***")) + " :");
_io.WriteLine("-------------------");
}
protected override bool CanExecuteCommand() => IsOperational("{name} are inoperable");
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
{
_output.WriteLine($"Long range scan for quadrant {quadrant.Coordinates}");
_output.WriteLine("-------------------");
foreach (var quadrants in _galaxy.GetNeighborhood(quadrant))
{
_output.WriteLine(": " + string.Join(" : ", quadrants.Select(q => q?.Scan() ?? "***")) + " :");
_output.WriteLine("-------------------");
}
return CommandResult.Ok;
}
return CommandResult.Ok;
}
}

View File

@@ -1,97 +1,96 @@
using System.Linq;
using Games.Common.IO;
using Games.Common.Randomness;
using SuperStarTrek.Commands;
using SuperStarTrek.Objects;
using SuperStarTrek.Resources;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems
namespace SuperStarTrek.Systems;
internal class PhaserControl : Subsystem
{
internal class PhaserControl : Subsystem
private readonly Enterprise _enterprise;
private readonly IReadWrite _io;
private readonly IRandom _random;
internal PhaserControl(Enterprise enterprise, IReadWrite io, IRandom random)
: base("Phaser Control", Command.PHA, io)
{
private readonly Enterprise _enterprise;
private readonly Output _output;
private readonly Input _input;
private readonly Random _random;
_enterprise = enterprise;
_io = io;
_random = random;
}
internal PhaserControl(Enterprise enterprise, Output output, Input input, Random random)
: base("Phaser Control", Command.PHA, output)
protected override bool CanExecuteCommand() => IsOperational("Phasers inoperative");
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
{
if (!quadrant.HasKlingons)
{
_enterprise = enterprise;
_output = output;
_input = input;
_random = random;
_io.WriteLine(Strings.NoEnemyShips);
return CommandResult.Ok;
}
protected override bool CanExecuteCommand() => IsOperational("Phasers inoperative");
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
if (_enterprise.Computer.IsDamaged)
{
if (!quadrant.HasKlingons)
{
_output.WriteLine(Strings.NoEnemyShips);
return CommandResult.Ok;
}
if (_enterprise.Computer.IsDamaged)
{
_output.WriteLine("Computer failure hampers accuracy");
}
_output.Write($"Phasers locked on target; ");
var phaserStrength = GetPhaserStrength();
if (phaserStrength < 0) { return CommandResult.Ok; }
_enterprise.UseEnergy(phaserStrength);
var perEnemyStrength = GetPerTargetPhaserStrength(phaserStrength, quadrant.KlingonCount);
foreach (var klingon in quadrant.Klingons.ToList())
{
ResolveHitOn(klingon, perEnemyStrength, quadrant);
}
return quadrant.KlingonsFireOnEnterprise();
_io.WriteLine("Computer failure hampers accuracy");
}
private float GetPhaserStrength()
{
while (true)
{
_output.WriteLine($"Energy available = {_enterprise.Energy} units");
var phaserStrength = _input.GetNumber("Number of units to fire");
_io.Write($"Phasers locked on target; ");
if (phaserStrength <= _enterprise.Energy) { return phaserStrength; }
}
var phaserStrength = GetPhaserStrength();
if (phaserStrength < 0) { return CommandResult.Ok; }
_enterprise.UseEnergy(phaserStrength);
var perEnemyStrength = GetPerTargetPhaserStrength(phaserStrength, quadrant.KlingonCount);
foreach (var klingon in quadrant.Klingons.ToList())
{
ResolveHitOn(klingon, perEnemyStrength, quadrant);
}
private float GetPerTargetPhaserStrength(float phaserStrength, int targetCount)
{
if (_enterprise.Computer.IsDamaged)
{
phaserStrength *= _random.GetFloat();
}
return quadrant.KlingonsFireOnEnterprise();
}
return phaserStrength / targetCount;
private float GetPhaserStrength()
{
while (true)
{
_io.WriteLine($"Energy available = {_enterprise.Energy} units");
var phaserStrength = _io.ReadNumber("Number of units to fire");
if (phaserStrength <= _enterprise.Energy) { return phaserStrength; }
}
}
private float GetPerTargetPhaserStrength(float phaserStrength, int targetCount)
{
if (_enterprise.Computer.IsDamaged)
{
phaserStrength *= _random.NextFloat();
}
private void ResolveHitOn(Klingon klingon, float perEnemyStrength, Quadrant quadrant)
{
var distance = _enterprise.SectorCoordinates.GetDistanceTo(klingon.Sector);
var hitStrength = (int)(perEnemyStrength / distance * (2 + _random.GetFloat()));
return phaserStrength / targetCount;
}
if (klingon.TakeHit(hitStrength))
{
_output.WriteLine($"{hitStrength} unit hit on Klingon at sector {klingon.Sector}");
_output.WriteLine(
klingon.Energy <= 0
? quadrant.Remove(klingon)
: $" (sensors show {klingon.Energy} units remaining)");
}
else
{
_output.WriteLine($"Sensors show no damage to enemy at {klingon.Sector}");
}
private void ResolveHitOn(Klingon klingon, float perEnemyStrength, Quadrant quadrant)
{
var distance = _enterprise.SectorCoordinates.GetDistanceTo(klingon.Sector);
var hitStrength = (int)(perEnemyStrength / distance * (2 + _random.NextFloat()));
if (klingon.TakeHit(hitStrength))
{
_io.WriteLine($"{hitStrength} unit hit on Klingon at sector {klingon.Sector}");
_io.WriteLine(
klingon.Energy <= 0
? quadrant.Remove(klingon)
: $" (sensors show {klingon.Energy} units remaining)");
}
else
{
_io.WriteLine($"Sensors show no damage to enemy at {klingon.Sector}");
}
}
}

View File

@@ -1,66 +1,64 @@
using Games.Common.IO;
using SuperStarTrek.Commands;
using SuperStarTrek.Objects;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems
namespace SuperStarTrek.Systems;
internal class PhotonTubes : Subsystem
{
internal class PhotonTubes : Subsystem
private readonly int _tubeCount;
private readonly Enterprise _enterprise;
private readonly IReadWrite _io;
internal PhotonTubes(int tubeCount, Enterprise enterprise, IReadWrite io)
: base("Photon Tubes", Command.TOR, io)
{
private readonly int _tubeCount;
private readonly Enterprise _enterprise;
private readonly Output _output;
private readonly Input _input;
internal PhotonTubes(int tubeCount, Enterprise enterprise, Output output, Input input)
: base("Photon Tubes", Command.TOR, output)
{
TorpedoCount = _tubeCount = tubeCount;
_enterprise = enterprise;
_output = output;
_input = input;
}
internal int TorpedoCount { get; private set; }
protected override bool CanExecuteCommand() => HasTorpedoes() && IsOperational("{name} are not operational");
private bool HasTorpedoes()
{
if (TorpedoCount > 0) { return true; }
_output.WriteLine("All photon torpedoes expended");
return false;
}
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
{
if (!_input.TryGetCourse("Photon torpedo course", "Ensign Chekov", out var course))
{
return CommandResult.Ok;
}
TorpedoCount -= 1;
var isHit = false;
_output.WriteLine("Torpedo track:");
foreach (var sector in course.GetSectorsFrom(_enterprise.SectorCoordinates))
{
_output.WriteLine($" {sector}");
if (quadrant.TorpedoCollisionAt(sector, out var message, out var gameOver))
{
_output.WriteLine(message);
isHit = true;
if (gameOver) { return CommandResult.GameOver; }
break;
}
}
if (!isHit) { _output.WriteLine("Torpedo missed!"); }
return quadrant.KlingonsFireOnEnterprise();
}
internal void ReplenishTorpedoes() => TorpedoCount = _tubeCount;
TorpedoCount = _tubeCount = tubeCount;
_enterprise = enterprise;
_io = io;
}
internal int TorpedoCount { get; private set; }
protected override bool CanExecuteCommand() => HasTorpedoes() && IsOperational("{name} are not operational");
private bool HasTorpedoes()
{
if (TorpedoCount > 0) { return true; }
_io.WriteLine("All photon torpedoes expended");
return false;
}
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
{
if (!_io.TryReadCourse("Photon torpedo course", "Ensign Chekov", out var course))
{
return CommandResult.Ok;
}
TorpedoCount -= 1;
var isHit = false;
_io.WriteLine("Torpedo track:");
foreach (var sector in course.GetSectorsFrom(_enterprise.SectorCoordinates))
{
_io.WriteLine($" {sector}");
if (quadrant.TorpedoCollisionAt(sector, out var message, out var gameOver))
{
_io.WriteLine(message);
isHit = true;
if (gameOver) { return CommandResult.GameOver; }
break;
}
}
if (!isHit) { _io.WriteLine("Torpedo missed!"); }
return quadrant.KlingonsFireOnEnterprise();
}
internal void ReplenishTorpedoes() => TorpedoCount = _tubeCount;
}

View File

@@ -1,59 +1,57 @@
using Games.Common.IO;
using SuperStarTrek.Commands;
using SuperStarTrek.Objects;
using SuperStarTrek.Resources;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems
namespace SuperStarTrek.Systems;
internal class ShieldControl : Subsystem
{
internal class ShieldControl : Subsystem
private readonly Enterprise _enterprise;
private readonly IReadWrite _io;
internal ShieldControl(Enterprise enterprise, IReadWrite io)
: base("Shield Control", Command.SHE, io)
{
private readonly Enterprise _enterprise;
private readonly Output _output;
private readonly Input _input;
internal ShieldControl(Enterprise enterprise, Output output, Input input)
: base("Shield Control", Command.SHE, output)
{
_enterprise = enterprise;
_output = output;
_input = input;
}
internal float ShieldEnergy { get; set; }
protected override bool CanExecuteCommand() => IsOperational("{name} inoperable");
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
{
_output.WriteLine($"Energy available = {_enterprise.TotalEnergy}");
var requested = _input.GetNumber($"Number of units to shields");
if (Validate(requested))
{
ShieldEnergy = requested;
_output.Write(Strings.ShieldsSet, requested);
}
else
{
_output.WriteLine("<SHIELDS UNCHANGED>");
}
return CommandResult.Ok;
}
private bool Validate(float requested)
{
if (requested > _enterprise.TotalEnergy)
{
_output.WriteLine("Shield Control reports, 'This is not the Federation Treasury.'");
return false;
}
return requested >= 0 && requested != ShieldEnergy;
}
internal void AbsorbHit(int hitStrength) => ShieldEnergy -= hitStrength;
internal void DropShields() => ShieldEnergy = 0;
_enterprise = enterprise;
_io = io;
}
internal float ShieldEnergy { get; set; }
protected override bool CanExecuteCommand() => IsOperational("{name} inoperable");
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
{
_io.WriteLine($"Energy available = {_enterprise.TotalEnergy}");
var requested = _io.ReadNumber($"Number of units to shields");
if (Validate(requested))
{
ShieldEnergy = requested;
_io.Write(Strings.ShieldsSet, requested);
}
else
{
_io.WriteLine("<SHIELDS UNCHANGED>");
}
return CommandResult.Ok;
}
private bool Validate(float requested)
{
if (requested > _enterprise.TotalEnergy)
{
_io.WriteLine("Shield Control reports, 'This is not the Federation Treasury.'");
return false;
}
return requested >= 0 && requested != ShieldEnergy;
}
internal void AbsorbHit(int hitStrength) => ShieldEnergy -= hitStrength;
internal void DropShields() => ShieldEnergy = 0;
}

View File

@@ -2,61 +2,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Games.Common.IO;
using SuperStarTrek.Commands;
using SuperStarTrek.Objects;
using SuperStarTrek.Resources;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems
namespace SuperStarTrek.Systems;
internal class ShortRangeSensors : Subsystem
{
internal class ShortRangeSensors : Subsystem
private readonly Enterprise _enterprise;
private readonly Galaxy _galaxy;
private readonly Game _game;
private readonly IReadWrite _io;
internal ShortRangeSensors(Enterprise enterprise, Galaxy galaxy, Game game, IReadWrite io)
: base("Short Range Sensors", Command.SRS, io)
{
private readonly Enterprise _enterprise;
private readonly Galaxy _galaxy;
private readonly Game _game;
private readonly Output _output;
_enterprise = enterprise;
_galaxy = galaxy;
_game = game;
_io = io;
}
internal ShortRangeSensors(Enterprise enterprise, Galaxy galaxy, Game game, Output output)
: base("Short Range Sensors", Command.SRS, output)
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
{
if (_enterprise.IsDocked)
{
_enterprise = enterprise;
_galaxy = galaxy;
_game = game;
_output = output;
_io.WriteLine(Strings.ShieldsDropped);
}
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
if (Condition < 0)
{
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("---------------------------------");
return CommandResult.Ok;
_io.WriteLine(Strings.ShortRangeSensorsOut);
}
internal IEnumerable<string> GetStatusLines()
{
yield return $"Stardate {_game.Stardate}";
yield return $"Condition {_enterprise.Condition}";
yield return $"Quadrant {_enterprise.QuadrantCoordinates}";
yield return $"Sector {_enterprise.SectorCoordinates}";
yield return $"Photon torpedoes {_enterprise.PhotonTubes.TorpedoCount}";
yield return $"Total energy {Math.Ceiling(_enterprise.TotalEnergy)}";
yield return $"Shields {(int)_enterprise.ShieldControl.ShieldEnergy}";
yield return $"Klingons remaining {_galaxy.KlingonCount}";
}
_io.WriteLine("---------------------------------");
quadrant.GetDisplayLines()
.Zip(GetStatusLines(), (sectors, status) => $" {sectors} {status}")
.ToList()
.ForEach(l => _io.WriteLine(l));
_io.WriteLine("---------------------------------");
return CommandResult.Ok;
}
internal IEnumerable<string> GetStatusLines()
{
yield return $"Stardate {_game.Stardate}";
yield return $"Condition {_enterprise.Condition}";
yield return $"Quadrant {_enterprise.QuadrantCoordinates}";
yield return $"Sector {_enterprise.SectorCoordinates}";
yield return $"Photon torpedoes {_enterprise.PhotonTubes.TorpedoCount}";
yield return $"Total energy {Math.Ceiling(_enterprise.TotalEnergy)}";
yield return $"Shields {(int)_enterprise.ShieldControl.ShieldEnergy}";
yield return $"Klingons remaining {_galaxy.KlingonCount}";
}
}

View File

@@ -1,68 +1,68 @@
using Games.Common.IO;
using SuperStarTrek.Commands;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems
namespace SuperStarTrek.Systems;
internal abstract class Subsystem
{
internal abstract class Subsystem
private readonly IReadWrite _io;
protected Subsystem(string name, Command command, IReadWrite io)
{
private readonly Output _output;
protected Subsystem(string name, Command command, Output output)
{
Name = name;
Command = command;
Condition = 0;
_output = output;
}
internal string Name { get; }
internal float Condition { get; private set; }
internal bool IsDamaged => Condition < 0;
internal Command Command { get; }
protected virtual bool CanExecuteCommand() => true;
protected bool IsOperational(string notOperationalMessage)
{
if (IsDamaged)
{
_output.WriteLine(notOperationalMessage.Replace("{name}", Name));
return false;
}
return true;
}
internal CommandResult ExecuteCommand(Quadrant quadrant)
=> CanExecuteCommand() ? ExecuteCommandCore(quadrant) : CommandResult.Ok;
protected abstract CommandResult ExecuteCommandCore(Quadrant quadrant);
internal virtual void Repair()
{
if (IsDamaged)
{
Condition = 0;
}
}
internal virtual bool Repair(float repairWorkDone)
{
if (IsDamaged)
{
Condition += repairWorkDone;
if (Condition > -0.1f && Condition < 0)
{
Condition = -0.1f;
}
}
return !IsDamaged;
}
internal void TakeDamage(float damage) => Condition -= damage;
Name = name;
Command = command;
Condition = 0;
_io = io;
}
internal string Name { get; }
internal float Condition { get; private set; }
internal bool IsDamaged => Condition < 0;
internal Command Command { get; }
protected virtual bool CanExecuteCommand() => true;
protected bool IsOperational(string notOperationalMessage)
{
if (IsDamaged)
{
_io.WriteLine(notOperationalMessage.Replace("{name}", Name));
return false;
}
return true;
}
internal CommandResult ExecuteCommand(Quadrant quadrant)
=> CanExecuteCommand() ? ExecuteCommandCore(quadrant) : CommandResult.Ok;
protected abstract CommandResult ExecuteCommandCore(Quadrant quadrant);
internal virtual void Repair()
{
if (IsDamaged)
{
Condition = 0;
}
}
internal virtual bool Repair(float repairWorkDone)
{
if (IsDamaged)
{
Condition += repairWorkDone;
if (Condition > -0.1f && Condition < 0)
{
Condition = -0.1f;
}
}
return !IsDamaged;
}
internal void TakeDamage(float damage) => Condition -= damage;
}

View File

@@ -1,4 +1,5 @@
using System;
using Games.Common.IO;
using SuperStarTrek.Commands;
using SuperStarTrek.Objects;
using SuperStarTrek.Resources;
@@ -9,20 +10,18 @@ namespace SuperStarTrek.Systems
internal class WarpEngines : Subsystem
{
private readonly Enterprise _enterprise;
private readonly Output _output;
private readonly Input _input;
private readonly IReadWrite _io;
internal WarpEngines(Enterprise enterprise, Output output, Input input)
: base("Warp Engines", Command.NAV, output)
internal WarpEngines(Enterprise enterprise, IReadWrite io)
: base("Warp Engines", Command.NAV, io)
{
_enterprise = enterprise;
_output = output;
_input = input;
_io = io;
}
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
{
if (_input.TryGetCourse("Course", " Lt. Sulu", out var course) &&
if (_io.TryReadCourse("Course", " Lt. Sulu", out var course) &&
TryGetWarpFactor(out var warpFactor) &&
TryGetDistanceToMove(warpFactor, out var distanceToMove))
{
@@ -51,12 +50,12 @@ namespace SuperStarTrek.Systems
private bool TryGetWarpFactor(out float warpFactor)
{
var maximumWarp = IsDamaged ? 0.2f : 8;
if (_input.TryGetNumber("Warp Factor", 0, maximumWarp, out warpFactor))
if (_io.TryReadNumberInRange("Warp Factor", 0, maximumWarp, out warpFactor))
{
return warpFactor > 0;
}
_output.WriteLine(
_io.WriteLine(
IsDamaged && warpFactor > maximumWarp
? "Warp engines are damaged. Maximum speed = warp 0.2"
: $" Chief Engineer Scott reports, 'The engines won't take warp {warpFactor} !'");
@@ -69,14 +68,14 @@ namespace SuperStarTrek.Systems
distanceToTravel = (int)Math.Round(warpFactor * 8, MidpointRounding.AwayFromZero);
if (distanceToTravel <= _enterprise.Energy) { return true; }
_output.WriteLine("Engineering reports, 'Insufficient energy available")
.WriteLine($" for maneuvering at warp {warpFactor} !'");
_io.WriteLine("Engineering reports, 'Insufficient energy available");
_io.WriteLine($" for maneuvering at warp {warpFactor} !'");
if (distanceToTravel <= _enterprise.TotalEnergy && !_enterprise.ShieldControl.IsDamaged)
{
_output.Write($"Deflector control room acknowledges {_enterprise.ShieldControl.ShieldEnergy} ")
.WriteLine("units of energy")
.WriteLine(" presently deployed to shields.");
_io.Write($"Deflector control room acknowledges {_enterprise.ShieldControl.ShieldEnergy} ");
_io.WriteLine("units of energy");
_io.WriteLine(" presently deployed to shields.");
}
return false;