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

@@ -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));
}
}