Add Photon Tubes

This commit is contained in:
Andrew Cooper
2021-03-07 14:36:11 +11:00
parent a1b257d5ae
commit 345545a27d
19 changed files with 319 additions and 63 deletions

View File

@@ -79,15 +79,14 @@ namespace SuperStarTrek
_galaxy = new Galaxy();
_initialKlingonCount = _galaxy.KlingonCount;
_enterprise = new Enterprise(3000, random.GetCoordinate(), _output);
_enterprise = new Enterprise(3000, random.GetCoordinate(), _output, random);
_enterprise
.Add(new ShortRangeSensors(_enterprise, _galaxy, this, _output))
.Add(new LongRangeSensors(_galaxy, _output))
.Add(new PhotonTubes(10, _enterprise, _output, _input))
.Add(new ShieldControl(_enterprise, _output, _input))
.Add(new DamageControl(_enterprise, _output));
var quadrant = new Quadrant(_galaxy[_currentQuadrant], _enterprise);
_output.Write(Strings.Enterprise);
_output.Write(
Strings.Orders,
@@ -100,6 +99,7 @@ namespace SuperStarTrek
_input.WaitForAnyKeyButEnter("when ready to accept command");
var quadrant = _galaxy[_currentQuadrant].BuildQuadrant(_enterprise, random, _galaxy);
_enterprise.Enter(quadrant, Strings.StartText);
}

View File

@@ -27,14 +27,14 @@ namespace SuperStarTrek
return Console.ReadLine();
}
public double GetNumber(string prompt)
public float GetNumber(string prompt)
{
_output.Prompt(prompt);
while (true)
{
var response = Console.ReadLine();
if (double.TryParse(response, out var value))
if (float.TryParse(response, out var value))
{
return value;
}
@@ -44,7 +44,7 @@ namespace SuperStarTrek
}
}
public bool TryGetNumber(string prompt, double minValue, double maxValue, out double value)
public bool TryGetNumber(string prompt, float minValue, float maxValue, out float value)
{
value = GetNumber($"{prompt} ({minValue}-{maxValue})");

View File

@@ -15,9 +15,10 @@ namespace SuperStarTrek.Objects
private readonly Output _output;
private readonly List<Subsystem> _systems;
private readonly Dictionary<Command, Subsystem> _commandExecutors;
private readonly Random _random;
private Quadrant _quadrant;
public Enterprise(int maxEnergy, Coordinates sector, Output output)
public Enterprise(int maxEnergy, Coordinates sector, Output output, Random random)
{
Sector = sector;
TotalEnergy = _maxEnergy = maxEnergy;
@@ -25,6 +26,7 @@ namespace SuperStarTrek.Objects
_systems = new List<Subsystem>();
_commandExecutors = new Dictionary<Command, Subsystem>();
_output = output;
_random = random;
}
public Coordinates Quadrant => _quadrant.Coordinates;
@@ -88,5 +90,37 @@ namespace SuperStarTrek.Objects
}
public override string ToString() => "<*>";
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(double hitStrength)
{
var hitShieldRatio = hitStrength / ShieldControl.ShieldEnergy;
if (_random.GetDouble() > 0.6 || hitShieldRatio <= 0.02)
{
return;
}
_systems[_random.Get1To8Inclusive() - 1].TakeDamage(hitShieldRatio + 0.5 * _random.GetDouble());
}
}
}

View File

@@ -1,14 +1,30 @@
using SuperStarTrek.Commands;
using SuperStarTrek.Space;
namespace SuperStarTrek.Objects
{
internal class Klingon
{
private double _energy;
private Coordinates _sector;
private readonly Random _random;
public Klingon()
public Klingon(Coordinates sector, Random random)
{
_energy = new Random().GetDouble(100, 300);
_sector = sector;
_random = random;
_energy = _random.GetDouble(100, 300);
}
public override string ToString() => "+K+";
public CommandResult FireOn(Enterprise enterprise)
{
var attackStrength = _random.GetDouble();
var hitStrength = (int)(_energy * (2 + attackStrength) / _sector.GetDistanceTo(enterprise.Sector));
_energy /= 3 + attackStrength;
return enterprise.TakeHit(_sector, hitStrength);
}
}
}

View File

@@ -8,10 +8,11 @@ namespace SuperStarTrek.Objects
private readonly Output _output;
private readonly double _repairDelay;
public Starbase(Random random, Input input)
public Starbase(Random random, Input input, Output output)
{
_repairDelay = random.GetDouble() * 0.5;
_input = input;
_output = output;
}
public override string ToString() => ">!<";
@@ -34,5 +35,7 @@ namespace SuperStarTrek.Objects
repairTime = 0;
return false;
}
internal void ProtectEnterprise() => _output.WriteLine(Strings.Protected);
}
}

View File

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

View File

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

View File

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

View File

@@ -8,13 +8,16 @@ namespace SuperStarTrek.Resources
{
public static string CombatArea => GetResource();
public static string Congratulations => GetResource();
public static string CourtMartial => GetResource();
public static string Destroyed => GetResource();
public static string EndOfMission => GetResource();
public static string Enterprise => GetResource();
public static string Instructions => GetResource();
public static string LowShields => GetResource();
public static string Orders => GetResource();
public static string Protected => GetResource();
public static string RegionNames => GetResource();
public static string RelievedOfCommand => GetResource();
public static string RepairEstimate => GetResource();
public static string RepairPrompt => GetResource();
public static string ReplayPrompt => GetResource();

View File

@@ -24,6 +24,34 @@ namespace SuperStarTrek.Space
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;
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));
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
namespace SuperStarTrek.Space
{
@@ -24,7 +25,7 @@ namespace SuperStarTrek.Space
(0, 1)
};
public Course(double direction)
public Course(float direction)
{
if (direction < 1 || direction > 9)
{
@@ -44,7 +45,25 @@ namespace SuperStarTrek.Space
DeltaY = baseCardinal.DeltaY + (nextCardinal.DeltaY - baseCardinal.DeltaY) * fractionalDirection;
}
public double DeltaX { get; }
public double DeltaY { get; }
public float DeltaX { get; }
public float DeltaY { get; }
public IEnumerable<Coordinates> GetSectorsFrom(Coordinates start)
{
(double x, double y) = start;
while(true)
{
x += DeltaX;
y += DeltaY;
if (!Coordinates.TryCreate(x, y, out var coordinates))
{
yield break;
}
yield return coordinates;
}
}
}
}

View File

@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SuperStarTrek.Commands;
using SuperStarTrek.Objects;
using SuperStarTrek.Resources;
namespace SuperStarTrek.Space
{
@@ -10,43 +12,47 @@ namespace SuperStarTrek.Space
private readonly QuadrantInfo _info;
private readonly Random _random;
private readonly Dictionary<Coordinates, object> _sectors;
private readonly Coordinates _enterpriseSector;
private readonly Enterprise _enterprise;
private readonly Coordinates _starbaseSector;
private readonly Galaxy _galaxy;
public Quadrant(QuadrantInfo info, Enterprise enterprise)
public Quadrant(QuadrantInfo info, Enterprise enterprise, Random random, Galaxy galaxy)
{
_info = info;
_random = new Random();
_random = random;
_galaxy = galaxy;
_enterpriseSector = enterprise.Sector;
_sectors = new Dictionary<Coordinates, object> { [enterprise.Sector] = enterprise };
PositionObject(() => new Klingon(), _info.KlingonCount);
_sectors = new() { [enterprise.Sector] = _enterprise = enterprise };
PositionObject(sector => new Klingon(sector, _random), _info.KlingonCount);
if (_info.HasStarbase)
{
_starbaseSector = PositionObject(() => new Starbase(_random, new Input(new Output())));
_starbaseSector = PositionObject(_ => new Starbase(_random, new Input(new Output()), new Output()));
}
PositionObject(() => new Star(), _info.StarCount);
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 bool HasStarbase => _info.HasStarbase;
public Starbase Starbase => HasStarbase ? (Starbase)_sectors[_starbaseSector] : null;
public bool EnterpriseIsNextToStarbase =>
_info.HasStarbase &&
Math.Abs(_enterpriseSector.X - _starbaseSector.X) <= 1 &&
Math.Abs(_enterpriseSector.Y - _starbaseSector.Y) <= 1;
Math.Abs(_enterprise.Sector.X - _starbaseSector.X) <= 1 &&
Math.Abs(_enterprise.Sector.Y - _starbaseSector.Y) <= 1;
private IEnumerable<Klingon> Klingons => _sectors.Values.OfType<Klingon>();
public override string ToString() => _info.Name;
private Coordinates PositionObject(Func<object> objectFactory)
private Coordinates PositionObject(Func<Coordinates, object> objectFactory)
{
var sector = GetRandomEmptySector();
_sectors[sector] = objectFactory.Invoke();
_sectors[sector] = objectFactory.Invoke(sector);
return sector;
}
private void PositionObject(Func<object> objectFactory, int count)
private void PositionObject(Func<Coordinates, object> objectFactory, int count)
{
for (int i = 0; i < count; i++)
{
@@ -54,6 +60,54 @@ namespace SuperStarTrek.Space
}
}
internal bool TorpedoCollisionAt(Coordinates coordinates, out string message, out bool gameOver)
{
gameOver = false;
message = default;
switch (_sectors.GetValueOrDefault(coordinates))
{
case Klingon _:
_sectors.Remove(coordinates);
_info.RemoveKlingon();
message = "*** Klingon destroyed ***";
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 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;
}
private Coordinates GetRandomEmptySector()
{
while (true)

View File

@@ -1,3 +1,5 @@
using SuperStarTrek.Objects;
namespace SuperStarTrek.Space
{
internal class QuadrantInfo
@@ -39,12 +41,25 @@ namespace SuperStarTrek.Space
internal void AddStarbase() => HasStarbase = true;
public string Scan()
internal Quadrant BuildQuadrant(Enterprise enterprise, Random random, Galaxy galaxy) =>
new(this, enterprise, random, galaxy);
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

@@ -10,13 +10,13 @@ namespace SuperStarTrek.Systems
private readonly Output _output;
public DamageControl(Enterprise enterprise, Output output)
: base("Damage Control", Command.DAM)
: base("Damage Control", Command.DAM, output)
{
_enterprise = enterprise;
_output = output;
}
public override CommandResult ExecuteCommand(Quadrant quadrant)
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
{
if (IsDamaged)
{

View File

@@ -15,27 +15,22 @@ namespace SuperStarTrek.Systems
private readonly Output _output;
public LongRangeSensors(Galaxy galaxy, Output output)
: base("Long Range Sensors", Command.LRS)
: base("Long Range Sensors", Command.LRS, output)
{
_galaxy = galaxy;
_output = output;
}
public override CommandResult ExecuteCommand(Quadrant quadrant)
protected override bool CanExecuteCommand() => IsOperational("{name} are inoperable");
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
{
if (Condition < 0)
_output.WriteLine($"Long range scan for quadrant {quadrant.Coordinates}");
_output.WriteLine("-------------------");
foreach (var quadrants in _galaxy.GetNeighborhood(quadrant))
{
_output.WriteLine("Long Range Sensors are inoperable");
}
else
{
_output.WriteLine($"Long range scan for quadrant {quadrant.Coordinates}");
_output.WriteLine(": " + string.Join(" : ", quadrants.Select(q => q?.Scan() ?? "***")) + " :");
_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

@@ -0,0 +1,63 @@
using SuperStarTrek.Commands;
using SuperStarTrek.Objects;
using SuperStarTrek.Space;
namespace SuperStarTrek.Systems
{
internal class PhotonTubes : Subsystem
{
private readonly int _tubeCount;
private readonly Enterprise _enterprise;
private readonly Output _output;
private readonly Input _input;
public PhotonTubes(int tubeCount, Enterprise enterprise, Output output, Input input)
: base("Photon Tubes", Command.TOR, output)
{
TorpedoCount = _tubeCount = tubeCount;
_enterprise = enterprise;
_output = output;
_input = input;
}
public 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.TryGetNumber("Photon torpedo course", 1, 9, out var direction))
{
_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))
{
_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();
}
}
}

View File

@@ -11,7 +11,7 @@ namespace SuperStarTrek.Systems
private readonly Input _input;
public ShieldControl(Enterprise enterprise, Output output, Input input)
: base("Shield Control", Command.SHE)
: base("Shield Control", Command.SHE, output)
{
_enterprise = enterprise;
_output = output;
@@ -20,21 +20,9 @@ namespace SuperStarTrek.Systems
public double ShieldEnergy { get; private set; }
public override CommandResult ExecuteCommand(Quadrant quadrant)
{
if (Condition < 0)
{
_output.WriteLine("Shield Control inoperable");
}
else
{
UpdateShields();
}
protected override bool CanExecuteCommand() => IsOperational("{name} inoperable");
return CommandResult.Ok;
}
private void UpdateShields()
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
{
_output.WriteLine($"Energy available = {_enterprise.TotalEnergy}");
var requested = _input.GetNumber($"Number of units to shields");
@@ -47,6 +35,8 @@ namespace SuperStarTrek.Systems
{
_output.WriteLine("<SHIELDS UNCHANGED>");
}
return CommandResult.Ok;
}
private bool Validate(double requested)
@@ -59,5 +49,7 @@ namespace SuperStarTrek.Systems
return requested >= 0 && requested != ShieldEnergy;
}
internal void AbsorbHit(int hitStrength) => ShieldEnergy -= hitStrength;
}
}

View File

@@ -17,7 +17,7 @@ namespace SuperStarTrek.Systems
private readonly Output _output;
public ShortRangeSensors(Enterprise enterprise, Galaxy galaxy, Game game, Output output)
: base("Short Range Sensors", Command.SRS)
: base("Short Range Sensors", Command.SRS, output)
{
_enterprise = enterprise;
_galaxy = galaxy;
@@ -25,7 +25,7 @@ namespace SuperStarTrek.Systems
_output = output;
}
public override CommandResult ExecuteCommand(Quadrant quadrant)
protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
{
if (_enterprise.IsDocked)
{

View File

@@ -1,3 +1,4 @@
using System;
using SuperStarTrek.Commands;
using SuperStarTrek.Space;
@@ -5,11 +6,14 @@ namespace SuperStarTrek.Systems
{
internal abstract class Subsystem
{
protected Subsystem(string name, Command command)
private readonly Output _output;
protected Subsystem(string name, Command command, Output output)
{
Name = name;
Command = command;
Condition = 0;
_output = output;
}
public string Name { get; }
@@ -17,10 +21,33 @@ namespace SuperStarTrek.Systems
public bool IsDamaged => Condition < 0;
public Command Command { get; }
public abstract CommandResult ExecuteCommand(Quadrant quadrant);
public void Repair()
protected virtual bool CanExecuteCommand() => true;
protected bool IsOperational(string notOperationalMessage)
{
if (Condition < 0) { Condition = 0; }
if (IsDamaged)
{
_output.WriteLine(notOperationalMessage.Replace("{name}", Name));
return false;
}
return true;
}
public CommandResult ExecuteCommand(Quadrant quadrant)
=> CanExecuteCommand() ? ExecuteCommandCore(quadrant) : CommandResult.Ok;
protected abstract CommandResult ExecuteCommandCore(Quadrant quadrant);
public virtual void Repair()
{
if (IsDamaged) { Condition = 0; }
}
internal void TakeDamage(double damage)
{
Condition -= damage;
_output.WriteLine($"Damage Control reports, '{Name} damaged by the hit.'");
}
}
}