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) : 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> /// </summary>
public interface IReadWrite public interface IReadWrite
{ {
/// <summary>
/// Reads a character from input.
/// </summary>
/// <returns>The character read.</returns>
char ReadCharacter();
/// <summary> /// <summary>
/// Reads a <see cref="float" /> value from input. /// Reads a <see cref="float" /> value from input.
/// </summary> /// </summary>
@@ -99,6 +105,13 @@ public interface IReadWrite
/// </summary> /// </summary>
/// <param name="format">The format <see cref="string" /> to be written.</param> /// <param name="format">The format <see cref="string" /> to be written.</param>
/// <param name="value">The values to be inserted into the format.</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); void WriteLine(string format, params object[] values);
/// <summary> /// <summary>

View File

@@ -29,6 +29,15 @@ public class TextIO : IReadWrite
_numberTokenReader = TokenReader.ForNumbers(this); _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 ReadNumber(string prompt) => ReadNumbers(prompt, 1)[0];
public (float, float) Read2Numbers(string prompt) 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 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 WriteLine(string format, params object[] values) => _output.WriteLine(format, values);
public void Write(Stream stream, bool keepOpen = false) public void Write(Stream stream, bool keepOpen = false)

View File

@@ -1,34 +1,33 @@
using System.ComponentModel; 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")] [Description("For short range sensor scan")]
SRS, SRS,
[Description("For long range sensor scan")] [Description("For long range sensor scan")]
LRS, LRS,
[Description("To fire phasers")] [Description("To fire phasers")]
PHA, PHA,
[Description("To fire photon torpedoes")] [Description("To fire photon torpedoes")]
TOR, TOR,
[Description("To raise or lower shields")] [Description("To raise or lower shields")]
SHE, SHE,
[Description("For damage control reports")] [Description("For damage control reports")]
DAM, DAM,
[Description("To call on library-computer")] [Description("To call on library-computer")]
COM, COM,
[Description("To resign your command")] [Description("To resign your command")]
XXX XXX
}
} }

View File

@@ -1,14 +1,13 @@
using System.Reflection; using System.Reflection;
using System.ComponentModel; 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)
internal static string GetDescription(this Command command) => .GetField(command.ToString())
typeof(Command) .GetCustomAttribute<DescriptionAttribute>()
.GetField(command.ToString()) .Description;
.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); IsGameOver = isGameOver;
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);
} }
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 System;
using Games.Common.IO;
using Games.Common.Randomness;
using SuperStarTrek.Objects; using SuperStarTrek.Objects;
using SuperStarTrek.Resources; using SuperStarTrek.Resources;
using SuperStarTrek.Space; using SuperStarTrek.Space;
using SuperStarTrek.Systems; using SuperStarTrek.Systems;
using SuperStarTrek.Systems.ComputerFunctions; 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; _io = io;
private readonly Input _input; _random = random;
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);
} }
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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Games.Common.IO;
using Games.Common.Randomness;
using SuperStarTrek.Commands; using SuperStarTrek.Commands;
using SuperStarTrek.Resources; using SuperStarTrek.Resources;
using SuperStarTrek.Space; using SuperStarTrek.Space;
using SuperStarTrek.Systems; 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; SectorCoordinates = sector;
private readonly Output _output; TotalEnergy = _maxEnergy = maxEnergy;
private readonly List<Subsystem> _systems;
private readonly Dictionary<Command, Subsystem> _commandExecutors;
private readonly Random _random;
private readonly Input _input;
private Quadrant _quadrant;
public Enterprise(int maxEnergy, Coordinates sector, Output output, Random random, Input input) _systems = new List<Subsystem>();
{ _commandExecutors = new Dictionary<Command, Subsystem>();
SectorCoordinates = sector; _io = io;
TotalEnergy = _maxEnergy = maxEnergy; _random = random;
_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;
} }
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.Commands;
using SuperStarTrek.Space; 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; Sector = sector;
_random = random;
internal Klingon(Coordinates sector, Random random) Energy = _random.NextFloat(100, 300);
{
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;
} }
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.Resources;
using SuperStarTrek.Space; 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; Sector = sector;
private readonly Output _output; _repairDelay = random.NextFloat(0.5f);
private readonly float _repairDelay; _io = io;
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);
} }
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 // **** 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 game.Play();
{ } while (game.Replay());
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());
}
}
}

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

@@ -1,3 +1,3 @@
Starfleet Command reviewing your record to consider Starfleet Command reviewing your record to consider
court martial! court martial!

View File

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

View File

@@ -104,3 +104,4 @@ COM command = Library-Computer
Option 5 = Galactic Region Name Map Option 5 = Galactic Region Name Map
This option prints the names of the sixteen major This option prints the names of the sixteen major
galactic regions referred to in the game. galactic regions referred to in the game.

View File

@@ -1,2 +1,2 @@
Science Officer Spock reports, 'Sensors show no enemy ships Science Officer Spock reports, 'Sensors show no enemy ships
in this quadrant' in this quadrant'

View File

@@ -1 +1 @@
Mr. Spock reports, 'Sensors show no starbases in this quadrant.' Mr. Spock reports, 'Sensors show no starbases in this quadrant.'

View File

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

View File

@@ -3,3 +3,4 @@ Your orders are as follows:
the galaxy before they can attack federation headquarters the galaxy before they can attack federation headquarters
on stardate {1}. This gives you {2} days. There {3} on stardate {1}. This gives you {2} days. There {3}
{4} starbase{5} in the galaxy for resupplying your ship. {4} starbase{5} in the galaxy for resupplying your ship.

View File

@@ -1 +1 @@
Starbase shields protect the Enterprise Starbase shields protect the Enterprise

View File

@@ -5,4 +5,4 @@
Canopus Aldebaran Canopus Aldebaran
Altair Regulus Altair Regulus
Sagittarius Arcturus Sagittarius Arcturus
Pollux Spica Pollux Spica

View File

@@ -1,3 +1,3 @@
That does it, Captain!! You are hereby relieved of command That does it, Captain!! You are hereby relieved of command
and sentenced to 99 stardates at hard labor on Cygnus 12!! and sentenced to 99 stardates at hard labor on Cygnus 12!!

View File

@@ -1 +1 @@
Will you authorize the repair order (Y/N) Will you authorize the repair order (Y/N)

View File

@@ -2,4 +2,4 @@
The Federation is in need of a new starship commander The Federation is in need of a new starship commander
for a similar mission -- if there is a volunteer for a similar mission -- if there is a volunteer
let him step forward and enter 'Aye' let him step forward and enter 'Aye'

View File

@@ -1 +1 @@
Shields dropped for docking purposes Shields dropped for docking purposes

View File

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

View File

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

View File

@@ -1,70 +1,69 @@
using System; using System;
using SuperStarTrek.Utils; 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. internal Coordinates(int x, int y)
// 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) 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); RegionIndex = (X << 1) + (Y >> 2);
SubRegionIndex = Y % 4; 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; } internal float GetDistanceTo(Coordinates destination)
{
private static int Validated(int value, string argumentName) var (_, distance) = GetDirectionAndDistanceTo(destination);
{ return distance;
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;
}
} }
} }

View File

@@ -1,98 +1,97 @@
using System; using System;
using System.Collections.Generic; 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: private static readonly (int DeltaX, int DeltaY)[] cardinals = new[]
// 530 FORI=1TO9:C(I,1)=0:C(I,2)=0:NEXTI
// 540 C(3,1)=-1:C(2,1)=-1:C(4,1)=-1:C(4,2)=-1:C(5,2)=-1:C(6,2)=-1
// 600 C(1,2)=1:C(2,2)=1:C(6,1)=1:C(7,1)=1:C(8,1)=1:C(8,2)=1:C(9,2)=1
//
// 3110 X1=C(C1,1)+(C(C1+1,1)-C(C1,1))*(C1-INT(C1))
// 3140 X2=C(C1,2)+(C(C1+1,2)-C(C1,2))*(C1-INT(C1))
internal class Course
{ {
private static readonly (int DeltaX, int DeltaY)[] cardinals = new[] (0, 1),
(-1, 1),
(-1, 0),
(-1, -1),
(0, -1),
(1, -1),
(1, 0),
(1, 1),
(0, 1)
};
internal Course(float direction)
{
if (direction < 1 || direction > 9)
{ {
(0, 1), throw new ArgumentOutOfRangeException(
(-1, 1), nameof(direction),
(-1, 0), direction,
(-1, -1), "Must be between 1 and 9, inclusive.");
(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;
} }
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; yield break;
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;
} }
return newQuadrant switch yield return coordinates;
{
< 0 => (false, 0, 0),
> 7 => (false, 7, 7),
_ => (true, newQuadrant, newSector)
};
} }
} }
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.Collections.Generic;
using System.Linq; using System.Linq;
using Games.Common.Randomness;
using SuperStarTrek.Resources; using SuperStarTrek.Resources;
using static System.StringSplitOptions; 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; _regionNames = Strings.RegionNames.Split(new[] { ' ', '\n' }, RemoveEmptyEntries | TrimEntries);
private static readonly string[] _subRegionIdentifiers; _subRegionIdentifiers = new[] { "I", "II", "III", "IV" };
private readonly QuadrantInfo[][] _quadrants; }
static Galaxy() internal Galaxy(IRandom random)
{ {
_regionNames = Strings.RegionNames.Split(new[] { ' ', '\n' }, RemoveEmptyEntries | TrimEntries); _quadrants = Enumerable
_subRegionIdentifiers = new[] { "I", "II", "III", "IV" }; .Range(0, 8)
} .Select(x => Enumerable
internal Galaxy(Random random)
{
_quadrants = Enumerable
.Range(0, 8) .Range(0, 8)
.Select(x => Enumerable .Select(y => new Coordinates(x, y))
.Range(0, 8) .Select(c => QuadrantInfo.Create(c, GetQuadrantName(c), random))
.Select(y => new Coordinates(x, y)) .ToArray())
.Select(c => QuadrantInfo.Create(c, GetQuadrantName(c), random)) .ToArray();
.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.AddKlingon();
randomQuadrant.AddStarbase();
if (randomQuadrant.KlingonCount < 2)
{
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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Games.Common.IO;
using Games.Common.Randomness;
using SuperStarTrek.Commands; using SuperStarTrek.Commands;
using SuperStarTrek.Objects; using SuperStarTrek.Objects;
using SuperStarTrek.Resources; 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; _info = info;
private readonly Random _random; _random = random;
private readonly Dictionary<Coordinates, object> _sectors; _io = io;
private readonly Enterprise _enterprise; Galaxy = galaxy;
private readonly Output _output;
private bool _entered = false;
internal Quadrant( info.MarkAsKnown();
QuadrantInfo info, _sectors = new() { [enterprise.SectorCoordinates] = _enterprise = enterprise };
Enterprise enterprise, PositionObject(sector => new Klingon(sector, _random), _info.KlingonCount);
Random random, if (_info.HasStarbase)
Galaxy galaxy,
Input input,
Output output)
{ {
_info = info; Starbase = PositionObject(sector => new Starbase(sector, _random, io));
_random = random; }
_output = output; PositionObject(_ => new Star(), _info.StarCount);
Galaxy = galaxy; }
info.MarkAsKnown(); internal Coordinates Coordinates => _info.Coordinates;
_sectors = new() { [enterprise.SectorCoordinates] = _enterprise = enterprise };
PositionObject(sector => new Klingon(sector, _random), _info.KlingonCount); internal bool HasKlingons => _info.KlingonCount > 0;
if (_info.HasStarbase)
{ internal int KlingonCount => _info.KlingonCount;
Starbase = PositionObject(sector => new Starbase(sector, _random, input, output));
} internal bool HasStarbase => _info.HasStarbase;
PositionObject(_ => new Star(), _info.StarCount);
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; if (_info.KlingonCount > 0)
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(); _io.Write(Strings.CombatArea);
_sectors[sector] = objectFactory.Invoke(sector); if (_enterprise.ShieldControl.ShieldEnergy <= 200) { _io.Write(Strings.LowShields); }
return (T)_sectors[sector];
} }
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++) case Klingon klingon:
{ message = Remove(klingon);
PositionObject(objectFactory); 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) internal string Remove(Klingon klingon)
{ {
if (!_entered) _sectors.Remove(klingon.Sector);
{ _info.RemoveKlingon();
_output.Write(textFormat, this); return "*** Klingon destroyed ***";
_entered = true; }
}
internal CommandResult KlingonsMoveAndFire()
if (_info.KlingonCount > 0) {
{ foreach (var klingon in Klingons.ToList())
_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)
{ {
var newSector = GetRandomEmptySector();
_sectors.Remove(klingon.Sector); _sectors.Remove(klingon.Sector);
_info.RemoveKlingon(); _sectors[newSector] = klingon;
return "*** Klingon destroyed ***"; klingon.MoveTo(newSector);
} }
internal CommandResult KlingonsMoveAndFire() return KlingonsFireOnEnterprise();
}
internal CommandResult KlingonsFireOnEnterprise()
{
if (EnterpriseIsNextToStarbase && Klingons.Any())
{ {
foreach (var klingon in Klingons.ToList()) Starbase.ProtectEnterprise();
{
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; }
}
return CommandResult.Ok; 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(); return sector;
if (!_sectors.ContainsKey(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) => private string GetDisplayLine(int x) =>
string.Join( string.Join(
" ", " ",
Enumerable Enumerable
.Range(0, 8) .Range(0, 8)
.Select(y => new Coordinates(x, y)) .Select(y => new Coordinates(x, y))
.Select(c => _sectors.GetValueOrDefault(c)) .Select(c => _sectors.GetValueOrDefault(c))
.Select(o => o?.ToString() ?? " ")); .Select(o => o?.ToString() ?? " "));
internal void SetEnterpriseSector(Coordinates sector) internal void SetEnterpriseSector(Coordinates sector)
{ {
_sectors.Remove(_enterprise.SectorCoordinates); _sectors.Remove(_enterprise.SectorCoordinates);
_sectors[sector] = _enterprise; _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; Coordinates = coordinates;
Name = name;
private QuadrantInfo(Coordinates coordinates, string name, int klingonCount, int starCount, bool hasStarbase) KlingonCount = klingonCount;
{ StarCount = starCount;
Coordinates = coordinates; HasStarbase = hasStarbase;
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;
} }
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> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Resources\*.txt" /> <EmbeddedResource Include="Resources\*.txt" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\00_Common\dotnet\Games.Common\Games.Common.csproj" />
</ItemGroup>
</Project> </Project>

View File

@@ -1,19 +1,19 @@
using Games.Common.IO;
using SuperStarTrek.Space; 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;
{ IO = io;
Description = description;
Output = output;
}
internal string Description { get; }
protected Output Output { get; }
internal abstract void Execute(Quadrant quadrant);
} }
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.Collections.Generic;
using System.Linq; using System.Linq;
using Games.Common.IO;
using SuperStarTrek.Space; 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.Objects;
using SuperStarTrek.Space; 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; _enterprise = enterprise;
private readonly Input _input; _io = io;
}
internal DirectionDistanceCalculator(Enterprise enterprise, Output output, Input input) internal override void Execute(Quadrant quadrant)
: base("Direction/distance calculator", output) {
{ IO.WriteLine("Direction/distance calculator:");
_enterprise = enterprise; IO.Write($"You are at quadrant {_enterprise.QuadrantCoordinates}");
_input = input; IO.WriteLine($" sector {_enterprise.SectorCoordinates}");
} IO.WriteLine("Please enter");
internal override void Execute(Quadrant quadrant) WriteDirectionAndDistance(
{ _io.GetCoordinates(" Initial coordinates"),
Output.WriteLine("Direction/distance calculator:") _io.GetCoordinates(" Final coordinates"));
.Write($"You are at quadrant {_enterprise.QuadrantCoordinates}")
.WriteLine($" sector {_enterprise.SectorCoordinates}")
.WriteLine("Please enter");
WriteDirectionAndDistance(
_input.GetCoordinates(" Initial coordinates"),
_input.GetCoordinates(" Final coordinates"));
}
} }
} }

View File

@@ -1,34 +1,34 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Games.Common.IO;
using SuperStarTrek.Space; 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) Galaxy = galaxy;
: base(description, output) }
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; IO.WriteLine($" {index+1} {row}");
} IO.WriteLine(" ----- ----- ----- ----- ----- ----- ----- -----");
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(" ----- ----- ----- ----- ----- ----- ----- -----");
}
} }
} }
} }

View File

@@ -1,21 +1,21 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Games.Common.IO;
using SuperStarTrek.Resources; using SuperStarTrek.Resources;
using SuperStarTrek.Space; 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.Space;
using SuperStarTrek.Utils; 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) protected void WriteDirectionAndDistance(Coordinates from, Coordinates to)
{ {
var (direction, distance) = from.GetDirectionAndDistanceTo(to); var (direction, distance) = from.GetDirectionAndDistanceTo(to);
Write(direction, distance); Write(direction, distance);
} }
protected void WriteDirectionAndDistance((float X, float Y) from, (float X, float Y) to) 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); var (direction, distance) = DirectionAndDistance.From(from.X, from.Y).To(to.X, to.Y);
Write(direction, distance); Write(direction, distance);
} }
private void Write(float direction, float distance) => private void Write(float direction, float distance)
Output.WriteLine($"Direction = {direction}") {
.WriteLine($"Distance = {distance}"); IO.WriteLine($"Direction = {direction}");
IO.WriteLine($"Distance = {distance}");
} }
} }

View File

@@ -1,30 +1,30 @@
using Games.Common.IO;
using SuperStarTrek.Objects; using SuperStarTrek.Objects;
using SuperStarTrek.Resources; using SuperStarTrek.Resources;
using SuperStarTrek.Space; 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) internal override void Execute(Quadrant quadrant)
: base("Starbase nav data", output) {
if (!quadrant.HasStarbase)
{ {
_enterprise = enterprise; IO.WriteLine(Strings.NoStarbase);
return;
} }
internal override void Execute(Quadrant quadrant) IO.WriteLine("From Enterprise to Starbase:");
{
if (!quadrant.HasStarbase)
{
Output.WriteLine(Strings.NoStarbase);
return;
}
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.Commands;
using SuperStarTrek.Objects; using SuperStarTrek.Objects;
using SuperStarTrek.Space; 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; _game = game;
private readonly Galaxy _galaxy; _galaxy = galaxy;
private readonly Enterprise _enterprise; _enterprise = enterprise;
}
internal StatusReport(Game game, Galaxy galaxy, Enterprise enterprise, Output output) internal override void Execute(Quadrant quadrant)
: base("Status report", output) {
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; IO.Write($"The Federation is maintaining {_galaxy.StarbaseCount} ");
_galaxy = galaxy; IO.Write("starbase".Pluralize(_galaxy.StarbaseCount));
_enterprise = enterprise; 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) _enterprise.Execute(Command.DAM);
{
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);
}
} }
} }

View File

@@ -1,33 +1,33 @@
using Games.Common.IO;
using SuperStarTrek.Objects; using SuperStarTrek.Objects;
using SuperStarTrek.Resources; using SuperStarTrek.Resources;
using SuperStarTrek.Space; using SuperStarTrek.Space;
namespace SuperStarTrek.Systems.ComputerFunctions namespace SuperStarTrek.Systems.ComputerFunctions;
{
internal class TorpedoDataCalculator : NavigationCalculator
{
private readonly Enterprise _enterprise;
internal TorpedoDataCalculator(Enterprise enterprise, Output output) internal class TorpedoDataCalculator : NavigationCalculator
: base("Photon torpedo data", output) {
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) WriteDirectionAndDistance(_enterprise.SectorCoordinates, klingon.Sector);
{
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);
}
} }
} }
} }

View File

@@ -1,54 +1,55 @@
using Games.Common.IO;
using SuperStarTrek.Commands; using SuperStarTrek.Commands;
using SuperStarTrek.Objects; using SuperStarTrek.Objects;
using SuperStarTrek.Space; 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; _enterprise = enterprise;
private readonly Output _output; _io = io;
}
internal DamageControl(Enterprise enterprise, Output output) protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
: base("Damage Control", Command.DAM, output) {
if (IsDamaged)
{ {
_enterprise = enterprise; _io.WriteLine("Damage Control report not available");
_output = output; }
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(); 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"); _io.Write(system.Name.PadRight(25));
foreach (var system in _enterprise.Systems) _io.WriteLine((int)(system.Condition * 100) * 0.01F);
{
_output.Write(system.Name.PadRight(25))
.WriteLine(((int)(system.Condition * 100) * 0.01).ToString(" 0.##;-0.##"));
}
_output.NextLine();
} }
_io.WriteLine();
} }
} }

View File

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

View File

@@ -1,34 +1,35 @@
using System.Linq; using System.Linq;
using Games.Common.IO;
using SuperStarTrek.Commands; using SuperStarTrek.Commands;
using SuperStarTrek.Space; 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; _galaxy = galaxy;
private readonly Output _output; _io = io;
}
internal LongRangeSensors(Galaxy galaxy, Output output) protected override bool CanExecuteCommand() => IsOperational("{name} are inoperable");
: base("Long Range Sensors", Command.LRS, output)
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; _io.WriteLine(": " + string.Join(" : ", quadrants.Select(q => q?.Scan() ?? "***")) + " :");
_output = output; _io.WriteLine("-------------------");
} }
protected override bool CanExecuteCommand() => IsOperational("{name} are inoperable"); return CommandResult.Ok;
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;
}
} }
} }

View File

@@ -1,97 +1,96 @@
using System.Linq; using System.Linq;
using Games.Common.IO;
using Games.Common.Randomness;
using SuperStarTrek.Commands; using SuperStarTrek.Commands;
using SuperStarTrek.Objects; using SuperStarTrek.Objects;
using SuperStarTrek.Resources; using SuperStarTrek.Resources;
using SuperStarTrek.Space; 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; _enterprise = enterprise;
private readonly Output _output; _io = io;
private readonly Input _input; _random = random;
private readonly Random _random; }
internal PhaserControl(Enterprise enterprise, Output output, Input input, Random random) protected override bool CanExecuteCommand() => IsOperational("Phasers inoperative");
: base("Phaser Control", Command.PHA, output)
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
{
if (!quadrant.HasKlingons)
{ {
_enterprise = enterprise; _io.WriteLine(Strings.NoEnemyShips);
_output = output; return CommandResult.Ok;
_input = input;
_random = random;
} }
protected override bool CanExecuteCommand() => IsOperational("Phasers inoperative"); if (_enterprise.Computer.IsDamaged)
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
{ {
if (!quadrant.HasKlingons) _io.WriteLine("Computer failure hampers accuracy");
{
_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();
} }
private float GetPhaserStrength() _io.Write($"Phasers locked on target; ");
{
while (true)
{
_output.WriteLine($"Energy available = {_enterprise.Energy} units");
var phaserStrength = _input.GetNumber("Number of units to fire");
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) return quadrant.KlingonsFireOnEnterprise();
{ }
if (_enterprise.Computer.IsDamaged)
{
phaserStrength *= _random.GetFloat();
}
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) return phaserStrength / targetCount;
{ }
var distance = _enterprise.SectorCoordinates.GetDistanceTo(klingon.Sector);
var hitStrength = (int)(perEnemyStrength / distance * (2 + _random.GetFloat()));
if (klingon.TakeHit(hitStrength)) private void ResolveHitOn(Klingon klingon, float perEnemyStrength, Quadrant quadrant)
{ {
_output.WriteLine($"{hitStrength} unit hit on Klingon at sector {klingon.Sector}"); var distance = _enterprise.SectorCoordinates.GetDistanceTo(klingon.Sector);
_output.WriteLine( var hitStrength = (int)(perEnemyStrength / distance * (2 + _random.NextFloat()));
klingon.Energy <= 0
? quadrant.Remove(klingon) if (klingon.TakeHit(hitStrength))
: $" (sensors show {klingon.Energy} units remaining)"); {
} _io.WriteLine($"{hitStrength} unit hit on Klingon at sector {klingon.Sector}");
else _io.WriteLine(
{ klingon.Energy <= 0
_output.WriteLine($"Sensors show no damage to enemy at {klingon.Sector}"); ? 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.Commands;
using SuperStarTrek.Objects; using SuperStarTrek.Objects;
using SuperStarTrek.Space; 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; TorpedoCount = _tubeCount = tubeCount;
private readonly Enterprise _enterprise; _enterprise = enterprise;
private readonly Output _output; _io = io;
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;
} }
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.Commands;
using SuperStarTrek.Objects; using SuperStarTrek.Objects;
using SuperStarTrek.Resources; using SuperStarTrek.Resources;
using SuperStarTrek.Space; 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; _enterprise = enterprise;
private readonly Output _output; _io = io;
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;
} }
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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Games.Common.IO;
using SuperStarTrek.Commands; using SuperStarTrek.Commands;
using SuperStarTrek.Objects; using SuperStarTrek.Objects;
using SuperStarTrek.Resources; using SuperStarTrek.Resources;
using SuperStarTrek.Space; 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; _enterprise = enterprise;
private readonly Galaxy _galaxy; _galaxy = galaxy;
private readonly Game _game; _game = game;
private readonly Output _output; _io = io;
}
internal ShortRangeSensors(Enterprise enterprise, Galaxy galaxy, Game game, Output output) protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
: base("Short Range Sensors", Command.SRS, output) {
if (_enterprise.IsDocked)
{ {
_enterprise = enterprise; _io.WriteLine(Strings.ShieldsDropped);
_galaxy = galaxy;
_game = game;
_output = output;
} }
protected override CommandResult ExecuteCommandCore(Quadrant quadrant) if (Condition < 0)
{ {
if (_enterprise.IsDocked) _io.WriteLine(Strings.ShortRangeSensorsOut);
{
_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;
} }
internal IEnumerable<string> GetStatusLines() _io.WriteLine("---------------------------------");
{ quadrant.GetDisplayLines()
yield return $"Stardate {_game.Stardate}"; .Zip(GetStatusLines(), (sectors, status) => $" {sectors} {status}")
yield return $"Condition {_enterprise.Condition}"; .ToList()
yield return $"Quadrant {_enterprise.QuadrantCoordinates}"; .ForEach(l => _io.WriteLine(l));
yield return $"Sector {_enterprise.SectorCoordinates}"; _io.WriteLine("---------------------------------");
yield return $"Photon torpedoes {_enterprise.PhotonTubes.TorpedoCount}";
yield return $"Total energy {Math.Ceiling(_enterprise.TotalEnergy)}"; return CommandResult.Ok;
yield return $"Shields {(int)_enterprise.ShieldControl.ShieldEnergy}"; }
yield return $"Klingons remaining {_galaxy.KlingonCount}";
} 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.Commands;
using SuperStarTrek.Space; 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; Name = name;
Command = command;
protected Subsystem(string name, Command command, Output output) Condition = 0;
{ _io = io;
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;
} }
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 System;
using Games.Common.IO;
using SuperStarTrek.Commands; using SuperStarTrek.Commands;
using SuperStarTrek.Objects; using SuperStarTrek.Objects;
using SuperStarTrek.Resources; using SuperStarTrek.Resources;
@@ -9,20 +10,18 @@ namespace SuperStarTrek.Systems
internal class WarpEngines : Subsystem internal class WarpEngines : Subsystem
{ {
private readonly Enterprise _enterprise; private readonly Enterprise _enterprise;
private readonly Output _output; private readonly IReadWrite _io;
private readonly Input _input;
internal WarpEngines(Enterprise enterprise, Output output, Input input) internal WarpEngines(Enterprise enterprise, IReadWrite io)
: base("Warp Engines", Command.NAV, output) : base("Warp Engines", Command.NAV, io)
{ {
_enterprise = enterprise; _enterprise = enterprise;
_output = output; _io = io;
_input = input;
} }
protected override CommandResult ExecuteCommandCore(Quadrant quadrant) 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) && TryGetWarpFactor(out var warpFactor) &&
TryGetDistanceToMove(warpFactor, out var distanceToMove)) TryGetDistanceToMove(warpFactor, out var distanceToMove))
{ {
@@ -51,12 +50,12 @@ namespace SuperStarTrek.Systems
private bool TryGetWarpFactor(out float warpFactor) private bool TryGetWarpFactor(out float warpFactor)
{ {
var maximumWarp = IsDamaged ? 0.2f : 8; 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; return warpFactor > 0;
} }
_output.WriteLine( _io.WriteLine(
IsDamaged && warpFactor > maximumWarp IsDamaged && warpFactor > maximumWarp
? "Warp engines are damaged. Maximum speed = warp 0.2" ? "Warp engines are damaged. Maximum speed = warp 0.2"
: $" Chief Engineer Scott reports, 'The engines won't take warp {warpFactor} !'"); : $" 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); distanceToTravel = (int)Math.Round(warpFactor * 8, MidpointRounding.AwayFromZero);
if (distanceToTravel <= _enterprise.Energy) { return true; } if (distanceToTravel <= _enterprise.Energy) { return true; }
_output.WriteLine("Engineering reports, 'Insufficient energy available") _io.WriteLine("Engineering reports, 'Insufficient energy available");
.WriteLine($" for maneuvering at warp {warpFactor} !'"); _io.WriteLine($" for maneuvering at warp {warpFactor} !'");
if (distanceToTravel <= _enterprise.TotalEnergy && !_enterprise.ShieldControl.IsDamaged) if (distanceToTravel <= _enterprise.TotalEnergy && !_enterprise.ShieldControl.IsDamaged)
{ {
_output.Write($"Deflector control room acknowledges {_enterprise.ShieldControl.ShieldEnergy} ") _io.Write($"Deflector control room acknowledges {_enterprise.ShieldControl.ShieldEnergy} ");
.WriteLine("units of energy") _io.WriteLine("units of energy");
.WriteLine(" presently deployed to shields."); _io.WriteLine(" presently deployed to shields.");
} }
return false; return false;