Add Warp Engines

This commit is contained in:
Andrew Cooper
2021-03-21 12:39:19 +11:00
parent 86cab1da58
commit 835ee35464
20 changed files with 330 additions and 61 deletions

View File

@@ -12,6 +12,7 @@ namespace SuperStarTrek
{
private readonly Output _output;
private readonly Input _input;
private readonly Random _random;
private int _initialStardate;
private int _finalStarDate;
@@ -25,6 +26,7 @@ namespace SuperStarTrek
{
_output = new Output();
_input = new Input(_output);
_random = new Random();
}
public float Stardate => _currentStardate;
@@ -46,15 +48,19 @@ namespace SuperStarTrek
{
Initialise();
var gameOver = false;
var newQuadrantText = Strings.StartText;
while (!gameOver)
{
_enterprise.Quadrant.Display(Strings.NowEntering);
var command = _input.GetCommand();
var result = _enterprise.Execute(command);
gameOver = result.IsGameOver || CheckIfStranded();
_currentStardate += result.TimeElapsed;
gameOver |= _currentStardate > _finalStarDate;
}
if (_galaxy.KlingonCount > 0)
@@ -69,21 +75,20 @@ namespace SuperStarTrek
private void Initialise()
{
var random = new Random();
_currentStardate = _initialStardate = _random.GetInt(20, 40) * 100;
_finalStarDate = _initialStardate + _random.GetInt(25, 35);
_currentStardate = _initialStardate = random.GetInt(20, 40) * 100;
_finalStarDate = _initialStardate + random.GetInt(25, 35);
_currentQuadrant = _random.GetCoordinate();
_currentQuadrant = random.GetCoordinate();
_galaxy = new Galaxy();
_galaxy = new Galaxy(_random);
_initialKlingonCount = _galaxy.KlingonCount;
_enterprise = new Enterprise(3000, random.GetCoordinate(), _output, random);
_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 PhaserControl(_enterprise, _output, _input, _random))
.Add(new PhotonTubes(10, _enterprise, _output, _input))
.Add(new ShieldControl(_enterprise, _output, _input))
.Add(new DamageControl(_enterprise, _output))
@@ -109,10 +114,12 @@ namespace SuperStarTrek
_input.WaitForAnyKeyButEnter("when ready to accept command");
var quadrant = _galaxy[_currentQuadrant].BuildQuadrant(_enterprise, random, _galaxy, _input, _output);
_enterprise.Enter(quadrant, Strings.StartText);
_enterprise.StartIn(BuildCurrentQuadrant());
}
private Quadrant BuildCurrentQuadrant() =>
new Quadrant(_galaxy[_currentQuadrant], _enterprise, _random, _galaxy, _input, _output);
public bool Replay() => _galaxy.StarbaseCount > 0 && _input.GetString(Strings.ReplayPrompt, "Aye");
private bool CheckIfStranded()

View File

@@ -1,7 +1,7 @@
using System;
using System.Linq;
using SuperStarTrek.Commands;
using SuperStarTrek.Space;
using static System.StringComparison;
namespace SuperStarTrek
@@ -82,6 +82,19 @@ namespace SuperStarTrek
}
}
public 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;
}
public bool GetYesNo(string prompt, YesNoMode mode)
{
_output.Prompt($"{prompt} (Y/N)");

View File

@@ -16,21 +16,25 @@ namespace SuperStarTrek.Objects
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)
public Enterprise(int maxEnergy, Coordinates sector, Output output, Random random, Input input)
{
Sector = sector;
SectorCoordinates = sector;
TotalEnergy = _maxEnergy = maxEnergy;
_systems = new List<Subsystem>();
_commandExecutors = new Dictionary<Command, Subsystem>();
_output = output;
_random = random;
_input = input;
}
public Coordinates Quadrant => _quadrant.Coordinates;
public Coordinates Sector { get; }
public Quadrant Quadrant => _quadrant;
public Coordinates QuadrantCoordinates => _quadrant.Coordinates;
public Coordinates SectorCoordinates { get; private set; }
public string Condition => GetCondition();
public LibraryComputer Computer => (LibraryComputer)_commandExecutors[Command.COM];
public ShieldControl ShieldControl => (ShieldControl)_commandExecutors[Command.SHE];
@@ -50,19 +54,10 @@ namespace SuperStarTrek.Objects
return this;
}
public void Enter(Quadrant quadrant, string entryTextFormat)
public void StartIn(Quadrant quadrant)
{
_quadrant = quadrant;
_output.Write(entryTextFormat, quadrant);
if (quadrant.HasKlingons)
{
_output.Write(Strings.CombatArea);
if (ShieldControl.ShieldEnergy <= 200) { _output.Write(Strings.LowShields); }
}
Execute(Command.SRS);
quadrant.Display(Strings.StartText);
}
private string GetCondition() =>
@@ -121,7 +116,100 @@ namespace SuperStarTrek.Objects
return;
}
_systems[_random.Get1To8Inclusive() - 1].TakeDamage(hitShieldRatio + 0.5f * _random.GetFloat());
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);
}
SectorCoordinates = sector;
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;
}
}

View File

@@ -22,7 +22,7 @@ namespace SuperStarTrek.Objects
public CommandResult FireOn(Enterprise enterprise)
{
var attackStrength = _random.GetFloat();
var distanceToEnterprise = Sector.GetDistanceTo(enterprise.Sector);
var distanceToEnterprise = Sector.GetDistanceTo(enterprise.SectorCoordinates);
var hitStrength = (int)(Energy * (2 + attackStrength) / distanceToEnterprise);
Energy /= 3 + attackStrength;
@@ -36,5 +36,7 @@ namespace SuperStarTrek.Objects
Energy -= hitStrength;
return true;
}
internal void MoveTo(Coordinates newSector) => Sector = newSector;
}
}

View File

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

View File

@@ -0,0 +1,5 @@
Lt. Uhura report message from Starfleet Command:
'Permission to attempt crossing of galactic perimeter
is hereby *Denied*. Shut down your engines.'
Chief Engineer Scott reports, 'Warp engines shut down
at sector {0} of quadrant {1}.'

View File

@@ -17,7 +17,9 @@ namespace SuperStarTrek.Resources
public static string LowShields => GetResource();
public static string NoEnemyShips => GetResource();
public static string NoStarbase => GetResource();
public static string NowEntering => GetResource();
public static string Orders => GetResource();
public static string PermissionDenied => GetResource();
public static string Protected => GetResource();
public static string RegionNames => GetResource();
public static string RelievedOfCommand => GetResource();

View File

@@ -52,7 +52,7 @@ namespace SuperStarTrek.Space
coordinates = default;
return false;
int Round(float value) => (int)Math.Round(value, MidpointRounding.AwayFromZero);
static int Round(float value) => (int)Math.Round(value, MidpointRounding.AwayFromZero);
}
internal (float Direction, float Distance) GetDirectionAndDistanceTo(Coordinates destination) =>

View File

@@ -25,7 +25,7 @@ namespace SuperStarTrek.Space
(0, 1)
};
public Course(float direction)
internal Course(float direction)
{
if (direction < 1 || direction > 9)
{
@@ -45,10 +45,10 @@ namespace SuperStarTrek.Space
DeltaY = baseCardinal.DeltaY + (nextCardinal.DeltaY - baseCardinal.DeltaY) * fractionalDirection;
}
public float DeltaX { get; }
public float DeltaY { get; }
internal float DeltaX { get; }
internal float DeltaY { get; }
public IEnumerable<Coordinates> GetSectorsFrom(Coordinates start)
internal IEnumerable<Coordinates> GetSectorsFrom(Coordinates start)
{
(float x, float y) = start;
@@ -65,5 +65,33 @@ namespace SuperStarTrek.Space
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 (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 == -1)
{
newQuadrant -= 1;
newSector = 7;
}
return newQuadrant switch
{
< 0 => (false, 0, 0),
> 7 => (false, 7, 7),
_ => (true, newQuadrant, newSector)
};
}
}
}

View File

@@ -1,6 +1,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using SuperStarTrek.Objects;
using SuperStarTrek.Resources;
using static System.StringSplitOptions;
@@ -12,6 +13,7 @@ namespace SuperStarTrek.Space
private static readonly string[] _regionNames;
private static readonly string[] _subRegionIdentifiers;
private readonly QuadrantInfo[][] _quadrants;
private readonly Random _random;
static Galaxy()
{
@@ -19,9 +21,9 @@ namespace SuperStarTrek.Space
_subRegionIdentifiers = new[] { "I", "II", "III", "IV" };
}
public Galaxy()
public Galaxy(Random random)
{
var random = new Random();
_random = random;
_quadrants = Enumerable
.Range(0, 8)

View File

@@ -13,7 +13,8 @@ namespace SuperStarTrek.Space
private readonly Random _random;
private readonly Dictionary<Coordinates, object> _sectors;
private readonly Enterprise _enterprise;
private readonly Galaxy _galaxy;
private readonly Output _output;
private bool _displayed = false;
public Quadrant(
QuadrantInfo info,
@@ -25,9 +26,11 @@ namespace SuperStarTrek.Space
{
_info = info;
_random = random;
_galaxy = galaxy;
_output = output;
Galaxy = galaxy;
_sectors = new() { [enterprise.Sector] = _enterprise = enterprise };
info.MarkAsKnown();
_sectors = new() { [enterprise.SectorCoordinates] = _enterprise = enterprise };
PositionObject(sector => new Klingon(sector, _random), _info.KlingonCount);
if (_info.HasStarbase)
{
@@ -41,10 +44,11 @@ namespace SuperStarTrek.Space
public int KlingonCount => _info.KlingonCount;
public bool HasStarbase => _info.HasStarbase;
public Starbase Starbase { get; }
internal Galaxy Galaxy { get; }
public bool EnterpriseIsNextToStarbase =>
_info.HasStarbase &&
Math.Abs(_enterprise.Sector.X - Starbase.Sector.X) <= 1 &&
Math.Abs(_enterprise.Sector.Y - Starbase.Sector.Y) <= 1;
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>();
@@ -65,6 +69,25 @@ namespace SuperStarTrek.Space
}
}
internal void Display(string textFormat)
{
if (_displayed) { return; }
_output.Write(textFormat, this);
if (_info.KlingonCount > 0)
{
_output.Write(Strings.CombatArea);
if (_enterprise.ShieldControl.ShieldEnergy <= 200) { _output.Write(Strings.LowShields); }
}
_enterprise.Execute(Command.SRS);
_displayed = true;
}
internal bool HasObjectAt(Coordinates coordinates) => _sectors.ContainsKey(coordinates);
internal bool TorpedoCollisionAt(Coordinates coordinates, out string message, out bool gameOver)
{
gameOver = false;
@@ -74,7 +97,7 @@ namespace SuperStarTrek.Space
{
case Klingon klingon:
message = Remove(klingon);
gameOver = _galaxy.KlingonCount == 0;
gameOver = Galaxy.KlingonCount == 0;
return true;
case Star _:
@@ -85,8 +108,8 @@ namespace SuperStarTrek.Space
_sectors.Remove(coordinates);
_info.RemoveStarbase();
message = "*** Starbase destroyed ***" +
(_galaxy.StarbaseCount > 0 ? Strings.CourtMartial : Strings.RelievedOfCommand);
gameOver = _galaxy.StarbaseCount == 0;
(Galaxy.StarbaseCount > 0 ? Strings.CourtMartial : Strings.RelievedOfCommand);
gameOver = Galaxy.StarbaseCount == 0;
return true;
default:
@@ -101,6 +124,19 @@ namespace SuperStarTrek.Space
return "*** Klingon destroyed ***";
}
internal CommandResult KlingonsMoveAndFire()
{
foreach (var klingon in Klingons.ToList())
{
var newSector = GetRandomEmptySector();
_sectors.Remove(klingon.Sector);
_sectors[newSector] = klingon;
klingon.MoveTo(newSector);
}
return KlingonsFireOnEnterprise();
}
internal CommandResult KlingonsFireOnEnterprise()
{
if (EnterpriseIsNextToStarbase && Klingons.Any())

View File

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

View File

@@ -21,7 +21,8 @@ namespace SuperStarTrek.Systems.ComputerFunctions
internal override void Execute(Quadrant quadrant)
{
Output.WriteLine("Direction/distance calculator:")
.WriteLine($"You are at quadrant {_enterprise.Quadrant} sector {_enterprise.Sector}")
.Write($"You are at quadrant {_enterprise.QuadrantCoordinates}")
.WriteLine($" sector {_enterprise.SectorCoordinates}")
.WriteLine("Please enter");
WriteDirectionAndDistance(

View File

@@ -24,7 +24,7 @@ namespace SuperStarTrek.Systems.ComputerFunctions
Output.WriteLine("From Enterprise to Starbase:");
WriteDirectionAndDistance(_enterprise.Sector, quadrant.Starbase.Sector);
WriteDirectionAndDistance(_enterprise.SectorCoordinates, quadrant.Starbase.Sector);
}
}
}

View File

@@ -26,7 +26,7 @@ namespace SuperStarTrek.Systems.ComputerFunctions
foreach (var klingon in quadrant.Klingons)
{
WriteDirectionAndDistance(_enterprise.Sector, klingon.Sector);
WriteDirectionAndDistance(_enterprise.SectorCoordinates, klingon.Sector);
}
}
}

View File

@@ -75,7 +75,7 @@ namespace SuperStarTrek.Systems
private void ResolveHitOn(Klingon klingon, float perEnemyStrength, Quadrant quadrant)
{
var distance = _enterprise.Sector.GetDistanceTo(klingon.Sector);
var distance = _enterprise.SectorCoordinates.GetDistanceTo(klingon.Sector);
var hitStrength = (int)(perEnemyStrength / distance * (2 + _random.GetFloat()));
if (klingon.TakeHit(hitStrength))

View File

@@ -34,15 +34,14 @@ namespace SuperStarTrek.Systems
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
{
if (!_input.TryGetNumber("Photon torpedo course", 1, 9, out var direction))
if (!_input.TryGetCourse("Photon torpedo course", "Ensign Chekov", out var course))
{
_output.WriteLine("Ensign Chekov reports, 'Incorrect course data, sir!'");
return CommandResult.Ok;
}
var isHit = false;
_output.WriteLine("Torpedo track:");
foreach (var sector in new Course(direction).GetSectorsFrom(_enterprise.Sector))
foreach (var sector in course.GetSectorsFrom(_enterprise.SectorCoordinates))
{
_output.WriteLine($" {sector}");

View File

@@ -51,8 +51,8 @@ namespace SuperStarTrek.Systems
{
yield return $"Stardate {_game.Stardate}";
yield return $"Condition {_enterprise.Condition}";
yield return $"Quadrant {_enterprise.Quadrant}";
yield return $"Sector {_enterprise.Sector}";
yield return $"Quadrant {_enterprise.QuadrantCoordinates}";
yield return $"Sector {_enterprise.SectorCoordinates}";
yield return $"Photon torpedoes {_enterprise.TorpedoCount}";
yield return $"Total energy {Math.Ceiling(_enterprise.TotalEnergy)}";
yield return $"Shields {(int)_enterprise.ShieldControl.ShieldEnergy}";

View File

@@ -41,13 +41,26 @@ namespace SuperStarTrek.Systems
public virtual void Repair()
{
if (IsDamaged) { Condition = 0; }
if (IsDamaged)
{
Condition = 0;
}
}
internal void TakeDamage(float damage)
public virtual bool Repair(float repairWorkDone)
{
Condition -= damage;
_output.WriteLine($"Damage Control reports, '{Name} damaged by the hit.'");
if (IsDamaged)
{
Condition += repairWorkDone;
if (Condition > -0.1f && Condition < 0)
{
Condition = -0.1f;
}
}
return !IsDamaged;
}
internal void TakeDamage(float damage) => Condition -= damage;
}
}

View File

@@ -0,0 +1,75 @@
using System;
using SuperStarTrek.Commands;
using SuperStarTrek.Objects;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems
{
internal class WarpEngines : Subsystem
{
private readonly Enterprise _enterprise;
private readonly Output _output;
private readonly Input _input;
public WarpEngines(Enterprise enterprise, Output output, Input input)
: base("Warp Engines", Command.NAV, output)
{
_enterprise = enterprise;
_output = output;
_input = input;
}
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
{
if (_input.TryGetCourse("Course", " Lt. Sulu", out var course) &&
TryGetWarpFactor(out var warpFactor) &&
TryGetDistanceToMove(warpFactor, out var distanceToMove))
{
var result = quadrant.KlingonsMoveAndFire();
if (result.IsGameOver) { return result; }
_enterprise.RepairSystems(warpFactor);
_enterprise.VaryConditionOfRandomSystem();
var timeElapsed = _enterprise.Move(course, warpFactor, distanceToMove);
return CommandResult.Elapsed(timeElapsed);
}
return CommandResult.Ok;
}
private bool TryGetWarpFactor(out float warpFactor)
{
var maximumWarp = IsDamaged ? 0.2f : 8;
if (_input.TryGetNumber("Warp Factor", 0, maximumWarp, out warpFactor))
{
return warpFactor > 0;
}
_output.WriteLine(
IsDamaged && warpFactor > maximumWarp
? "Warp engines are damaged. Maximum speed = warp 0.2"
: $" Chief Engineer Scott reports, 'The engines won't take warp {warpFactor} !'");
return false;
}
private bool TryGetDistanceToMove(float warpFactor, out int distanceToTravel)
{
distanceToTravel = (int)Math.Round(warpFactor * 8, MidpointRounding.AwayFromZero);
if (distanceToTravel <= _enterprise.Energy) { return true; }
_output.WriteLine("Engineering reports, 'Insufficient energy available")
.WriteLine($" for maneuvering at warp {warpFactor} !'");
if (distanceToTravel <= _enterprise.TotalEnergy && !_enterprise.ShieldControl.IsDamaged)
{
_output.Write($"Deflector control room acknowledges {_enterprise.ShieldControl.ShieldEnergy} ")
.WriteLine("units of energy")
.WriteLine(" presently deployed to shields.");
}
return false;
}
}
}