Add Library-Computer

This commit is contained in:
Andrew Cooper
2021-03-11 07:20:28 +11:00
parent d0ed8d2f35
commit b422db3f4a
27 changed files with 503 additions and 34 deletions

View File

@@ -3,6 +3,7 @@ using SuperStarTrek.Objects;
using SuperStarTrek.Resources;
using SuperStarTrek.Space;
using SuperStarTrek.Systems;
using SuperStarTrek.Systems.ComputerFunctions;
using static System.StringComparison;
namespace SuperStarTrek
@@ -16,7 +17,6 @@ namespace SuperStarTrek
private int _finalStarDate;
private float _currentStardate;
private Coordinates _currentQuadrant;
private Coordinates _currentSector;
private Galaxy _galaxy;
private int _initialKlingonCount;
private Enterprise _enterprise;
@@ -28,6 +28,7 @@ namespace SuperStarTrek
}
public float Stardate => _currentStardate;
public float StardatesRemaining => _finalStarDate - _currentStardate;
public void DoIntroduction()
{
@@ -74,7 +75,6 @@ namespace SuperStarTrek
_finalStarDate = _initialStardate + random.GetInt(25, 35);
_currentQuadrant = random.GetCoordinate();
_currentSector = random.GetCoordinate();
_galaxy = new Galaxy();
_initialKlingonCount = _galaxy.KlingonCount;
@@ -85,7 +85,16 @@ namespace SuperStarTrek
.Add(new LongRangeSensors(_galaxy, _output))
.Add(new PhotonTubes(10, _enterprise, _output, _input))
.Add(new ShieldControl(_enterprise, _output, _input))
.Add(new DamageControl(_enterprise, _output));
.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(
@@ -99,7 +108,7 @@ namespace SuperStarTrek
_input.WaitForAnyKeyButEnter("when ready to accept command");
var quadrant = _galaxy[_currentQuadrant].BuildQuadrant(_enterprise, random, _galaxy);
var quadrant = _galaxy[_currentQuadrant].BuildQuadrant(_enterprise, random, _galaxy, _input, _output);
_enterprise.Enter(quadrant, Strings.StartText);
}

View File

@@ -44,6 +44,13 @@ namespace SuperStarTrek
}
}
public (float X, float Y) GetCoordinates(string prompt)
{
_output.Prompt($"{prompt} (X,Y)");
var responses = ReadNumbers(2);
return (responses[0], responses[1]);
}
public bool TryGetNumber(string prompt, float minValue, float maxValue, out float value)
{
value = GetNumber($"{prompt} ({minValue}-{maxValue})");
@@ -90,6 +97,46 @@ namespace SuperStarTrek
};
}
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;
}
public enum YesNoMode
{
TrueOnY,

View File

@@ -6,25 +6,27 @@ namespace SuperStarTrek.Objects
internal class Klingon
{
private float _energy;
private Coordinates _sector;
private readonly Random _random;
public Klingon(Coordinates sector, Random random)
{
_sector = sector;
Sector = sector;
_random = random;
_energy = _random.GetFloat(100, 300);
}
public Coordinates Sector { get; private set; }
public override string ToString() => "+K+";
public CommandResult FireOn(Enterprise enterprise)
{
var attackStrength = _random.GetFloat();
var hitStrength = (int)(_energy * (2 + attackStrength) / _sector.GetDistanceTo(enterprise.Sector));
var (_, distanceToEnterprise) = Sector.GetDirectionAndDistanceTo(enterprise.Sector);
var hitStrength = (int)(_energy * (2 + attackStrength) / distanceToEnterprise);
_energy /= 3 + attackStrength;
return enterprise.TakeHit(_sector, hitStrength);
return enterprise.TakeHit(Sector, hitStrength);
}
}
}

View File

@@ -1,4 +1,5 @@
using SuperStarTrek.Resources;
using SuperStarTrek.Space;
namespace SuperStarTrek.Objects
{
@@ -8,13 +9,15 @@ namespace SuperStarTrek.Objects
private readonly Output _output;
private readonly float _repairDelay;
public Starbase(Random random, Input input, Output output)
public 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)

View File

@@ -29,6 +29,7 @@ namespace SuperStarTrek
{
static void Main()
{
var foo = Utils.DirectionAndDistance.From(1,1).To(4,5);
var game = new Game();
game.DoIntroduction();

View File

@@ -0,0 +1,9 @@
Functions available from Library-Computer:
0 = Cumulative galactic record
1 = Status report
2 = Photon torpedo data
3 = Starbase nav data
4 = Direction/distance calculator
5 = Galaxy 'region name' map

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
Antares Sirius
Rigel Deneb
Procyon Capella
Vega Betelgeuse
Canopus Aldebaran
Altair Regulus
Sagittarius Arcturus
Pollux Spica
Antares Sirius
Rigel Deneb
Procyon Capella
Vega Betelgeuse
Canopus Aldebaran
Altair Regulus
Sagittarius Arcturus
Pollux Spica

View File

@@ -7,6 +7,7 @@ namespace SuperStarTrek.Resources
internal static class Strings
{
public static string CombatArea => GetResource();
public static string ComputerFunctions => GetResource();
public static string Congratulations => GetResource();
public static string CourtMartial => GetResource();
public static string Destroyed => GetResource();
@@ -14,6 +15,8 @@ namespace SuperStarTrek.Resources
public static string Enterprise => GetResource();
public static string Instructions => GetResource();
public static string LowShields => GetResource();
public static string NoEnemyShips => GetResource();
public static string NoStarbase => GetResource();
public static string Orders => GetResource();
public static string Protected => GetResource();
public static string RegionNames => GetResource();

View File

@@ -1,4 +1,5 @@
using System;
using SuperStarTrek.Utils;
namespace SuperStarTrek.Space
{
@@ -10,12 +11,15 @@ namespace SuperStarTrek.Space
{
X = Validated(x, nameof(x));
Y = Validated(y, nameof(y));
RegionIndex = (X << 1) + (Y >> 2);
SubRegionIndex = Y % 4;
}
public int X { get; }
public int Y { get; }
public int RegionIndex => (X << 1) + (Y >> 2);
public int SubRegionIndex => Y % 4;
public int RegionIndex { get; }
public int SubRegionIndex { get; }
private int Validated(int value, string argumentName)
{
@@ -51,7 +55,7 @@ namespace SuperStarTrek.Space
int Round(float value) => (int)Math.Round(value, MidpointRounding.AwayFromZero);
}
internal float GetDistanceTo(Coordinates destination) =>
(float)Math.Sqrt(Math.Pow(X - destination.X, 2) + Math.Pow(Y - destination.Y, 2));
internal (float Direction, float Distance) GetDirectionAndDistanceTo(Coordinates destination) =>
DirectionAndDistance.From(this).To(destination);
}
}

View File

@@ -48,6 +48,7 @@ namespace SuperStarTrek.Space
public int KlingonCount => _quadrants.SelectMany(q => q).Sum(q => q.KlingonCount);
public int StarbaseCount => _quadrants.SelectMany(q => q).Count(q => q.HasStarbase);
public IEnumerable<IEnumerable<QuadrantInfo>> Quadrants => _quadrants;
private static string GetQuadrantName(Coordinates coordinates) =>
$"{_regionNames[coordinates.RegionIndex]} {_subRegionIdentifiers[coordinates.SubRegionIndex]}";

View File

@@ -13,10 +13,15 @@ namespace SuperStarTrek.Space
private readonly Random _random;
private readonly Dictionary<Coordinates, object> _sectors;
private readonly Enterprise _enterprise;
private readonly Coordinates _starbaseSector;
private readonly Galaxy _galaxy;
public Quadrant(QuadrantInfo info, Enterprise enterprise, Random random, Galaxy galaxy)
public Quadrant(
QuadrantInfo info,
Enterprise enterprise,
Random random,
Galaxy galaxy,
Input input,
Output output)
{
_info = info;
_random = random;
@@ -26,30 +31,30 @@ namespace SuperStarTrek.Space
PositionObject(sector => new Klingon(sector, _random), _info.KlingonCount);
if (_info.HasStarbase)
{
_starbaseSector = PositionObject(_ => new Starbase(_random, new Input(new Output()), new Output()));
Starbase = PositionObject(sector => new Starbase(sector, _random, input, output));
}
PositionObject(_ => new Star(), _info.StarCount);
}
public object this[Coordinates coordinates] => _sectors.GetValueOrDefault(coordinates);
public Coordinates Coordinates => _info.Coordinates;
public bool HasKlingons => _info.KlingonCount > 0;
public int KlingonCount => _info.KlingonCount;
public bool HasStarbase => _info.HasStarbase;
public Starbase Starbase => HasStarbase ? (Starbase)_sectors[_starbaseSector] : null;
public Starbase Starbase { get; }
public bool EnterpriseIsNextToStarbase =>
_info.HasStarbase &&
Math.Abs(_enterprise.Sector.X - _starbaseSector.X) <= 1 &&
Math.Abs(_enterprise.Sector.Y - _starbaseSector.Y) <= 1;
Math.Abs(_enterprise.Sector.X - Starbase.Sector.X) <= 1 &&
Math.Abs(_enterprise.Sector.Y - Starbase.Sector.Y) <= 1;
private IEnumerable<Klingon> Klingons => _sectors.Values.OfType<Klingon>();
internal IEnumerable<Klingon> Klingons => _sectors.Values.OfType<Klingon>();
public override string ToString() => _info.Name;
private Coordinates PositionObject(Func<Coordinates, object> objectFactory)
private T PositionObject<T>(Func<Coordinates, T> objectFactory)
{
var sector = GetRandomEmptySector();
_sectors[sector] = objectFactory.Invoke(sector);
return sector;
return (T)_sectors[sector];
}
private void PositionObject(Func<Coordinates, object> objectFactory, int count)

View File

@@ -41,8 +41,11 @@ namespace SuperStarTrek.Space
internal void AddStarbase() => HasStarbase = true;
internal Quadrant BuildQuadrant(Enterprise enterprise, Random random, Galaxy galaxy) =>
new(this, enterprise, random, galaxy);
internal Quadrant BuildQuadrant(Enterprise enterprise, Random random, Galaxy galaxy, Input input, Output output)
{
_isKnown = true;
return new(this, enterprise, random, galaxy, input, output);
}
internal string Scan()
{

View File

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

View File

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

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems.ComputerFunctions
{
internal class CumulativeGalacticRecord : GalacticReport
{
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));
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SuperStarTrek.Objects;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems.ComputerFunctions
{
internal class DirectionDistanceCalculator : NavigationCalculator
{
private readonly Enterprise _enterprise;
private readonly Input _input;
public DirectionDistanceCalculator(Enterprise enterprise, Output output, Input input)
: base("Starbase nav data", output)
{
_enterprise = enterprise;
_input = input;
}
internal override void Execute(Quadrant quadrant)
{
Output.WriteLine("Direction/distance calculator:")
.WriteLine($"You are at quadrant {_enterprise.Quadrant} sector {_enterprise.Sector}")
.WriteLine("Please enter");
WriteDirectionAndDistance(
_input.GetCoordinates(" Initial coordinates"),
_input.GetCoordinates(" Final coordinates"));
}
}
}

View File

@@ -0,0 +1,34 @@
using System.Collections.Generic;
using System.Linq;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems.ComputerFunctions
{
internal abstract class GalacticReport : ComputerFunction
{
public GalacticReport(string description, Output output, Galaxy galaxy)
: base(description, output)
{
Galaxy = galaxy;
}
protected Galaxy Galaxy { get; }
protected abstract void WriteHeader(Quadrant quadrant);
protected abstract IEnumerable<string> GetRowData();
internal sealed override void Execute(Quadrant quadrant)
{
WriteHeader(quadrant);
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

@@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.Linq;
using SuperStarTrek.Resources;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems.ComputerFunctions
{
internal class GalaxyRegionMap : GalacticReport
{
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'));
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,6 +24,7 @@ namespace SuperStarTrek.Systems
}
else
{
_output.NextLine();
WriteDamageReport();
}
@@ -41,7 +42,7 @@ namespace SuperStarTrek.Systems
public void WriteDamageReport()
{
_output.NextLine().NextLine().WriteLine("Device State of Repair");
_output.NextLine().WriteLine("Device State of Repair");
foreach (var system in _enterprise.Systems)
{
_output.Write(system.Name.PadRight(25))

View File

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

View File

@@ -0,0 +1,65 @@
using System;
using SuperStarTrek.Space;
namespace SuperStarTrek.Utils
{
internal class DirectionAndDistance
{
private readonly float _fromX;
private readonly float _fromY;
private DirectionAndDistance(float fromX, float fromY)
{
_fromX = fromX;
_fromY = fromY;
}
public static DirectionAndDistance From(Coordinates coordinates) => From(coordinates.X, coordinates.Y);
public static DirectionAndDistance From(float x, float y) => new DirectionAndDistance(x, y);
public (float Direction, float Distance) To(Coordinates coordinates) => To(coordinates.X, coordinates.Y);
public (float Direction, float Distance) To(float x, float y)
{
var deltaX = x - _fromX;
var deltaY = y - _fromY;
return (GetDirection(deltaX, deltaY), GetDistance(deltaX, deltaY));
}
// The algorithm here is mathematically equivalent to the following code in the original,
// where X is deltaY and A is deltaX
// 8220 X=X-A:A=C1-W1:IFX<0THEN8350
// 8250 IFA<0THEN8410
// 8260 IFX>0THEN8280
// 8270 IFA=0THENC1=5:GOTO8290
// 8280 C1=1
// 8290 IFABS(A)<=ABS(X)THEN8330
// 8310 PRINT"DIRECTION =";C1+(((ABS(A)-ABS(X))+ABS(A))/ABS(A)):GOTO8460
// 8330 PRINT"DIRECTION =";C1+(ABS(A)/ABS(X)):GOTO8460
// 8350 IFA>0THENC1=3:GOTO8420
// 8360 IFX<>0THENC1=5:GOTO8290
// 8410 C1=7
// 8420 IFABS(A)>=ABS(X)THEN8450
// 8430 PRINT"DIRECTION =";C1+(((ABS(X)-ABS(A))+ABS(X))/ABS(X)):GOTO8460
// 8450 PRINT"DIRECTION =";C1+(ABS(X)/ABS(A))
// 8460 PRINT"DISTANCE =";SQR(X^2+A^2):IFH8=1THEN1990
private float GetDirection(float deltaX, float deltaY)
{
var deltaXDominant = Math.Abs(deltaX) > Math.Abs(deltaY);
var fractionalPart = deltaXDominant ? deltaY / deltaX : -deltaX / deltaY;
var nearestCardinal = deltaXDominant switch
{
true => deltaX > 0 ? 7 : 3,
false => deltaY > 0 ? 1 : 5
};
var direction = nearestCardinal + fractionalPart;
return direction < 1 ? direction + 8 : direction;
}
private float GetDistance(float deltaX, float deltaY) =>
(float)Math.Sqrt(Math.Pow(deltaX, 2) + Math.Pow(deltaY, 2));
}
}