diff --git a/86 Target/csharp/Target.sln b/86 Target/csharp/Target.sln new file mode 100644 index 00000000..3e111702 --- /dev/null +++ b/86 Target/csharp/Target.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Target", "Target\Target.csproj", "{8B0B5114-1D05-4F8D-B328-EA2FB89992E7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8B0B5114-1D05-4F8D-B328-EA2FB89992E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B0B5114-1D05-4F8D-B328-EA2FB89992E7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B0B5114-1D05-4F8D-B328-EA2FB89992E7}.Debug|x64.ActiveCfg = Debug|Any CPU + {8B0B5114-1D05-4F8D-B328-EA2FB89992E7}.Debug|x64.Build.0 = Debug|Any CPU + {8B0B5114-1D05-4F8D-B328-EA2FB89992E7}.Debug|x86.ActiveCfg = Debug|Any CPU + {8B0B5114-1D05-4F8D-B328-EA2FB89992E7}.Debug|x86.Build.0 = Debug|Any CPU + {8B0B5114-1D05-4F8D-B328-EA2FB89992E7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B0B5114-1D05-4F8D-B328-EA2FB89992E7}.Release|Any CPU.Build.0 = Release|Any CPU + {8B0B5114-1D05-4F8D-B328-EA2FB89992E7}.Release|x64.ActiveCfg = Release|Any CPU + {8B0B5114-1D05-4F8D-B328-EA2FB89992E7}.Release|x64.Build.0 = Release|Any CPU + {8B0B5114-1D05-4F8D-B328-EA2FB89992E7}.Release|x86.ActiveCfg = Release|Any CPU + {8B0B5114-1D05-4F8D-B328-EA2FB89992E7}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/86 Target/csharp/Target/Angle.cs b/86 Target/csharp/Target/Angle.cs new file mode 100644 index 00000000..a14dd782 --- /dev/null +++ b/86 Target/csharp/Target/Angle.cs @@ -0,0 +1,18 @@ +namespace Target +{ + internal class Angle + { + // Use same precision for constants as original code + private const float PI = 3.14159f; + private const float DegreesPerRadian = 57.296f; + + private readonly float _radians; + + private Angle(float radians) => _radians = radians; + + public static Angle InDegrees(float degrees) => new (degrees / DegreesPerRadian); + public static Angle InRotations(float rotations) => new (2 * PI * rotations); + + public static implicit operator float(Angle angle) => angle._radians; + } +} diff --git a/86 Target/csharp/Target/Explosion.cs b/86 Target/csharp/Target/Explosion.cs new file mode 100644 index 00000000..89a94a93 --- /dev/null +++ b/86 Target/csharp/Target/Explosion.cs @@ -0,0 +1,22 @@ +namespace Target +{ + internal class Explosion + { + private readonly Point _position; + + public Explosion(Point position, Offset targetOffset) + { + _position = position; + FromTarget = targetOffset; + DistanceToTarget = targetOffset.Distance; + } + + public Point Position => _position; + public Offset FromTarget { get; } + public float DistanceToTarget { get; } + public string GetBearing() => _position.GetBearing(); + + public bool IsHit => DistanceToTarget <= 20; + public bool IsTooClose => _position.Distance < 20; + } +} diff --git a/86 Target/csharp/Target/FiringRange.cs b/86 Target/csharp/Target/FiringRange.cs new file mode 100644 index 00000000..368dc453 --- /dev/null +++ b/86 Target/csharp/Target/FiringRange.cs @@ -0,0 +1,26 @@ +using System; + +namespace Target +{ + internal class FiringRange + { + private readonly Random random; + + public FiringRange() + { + random = new Random(); + NextTarget(); + } + + public Point TargetPosition { get; private set; } + + public void NextTarget() => TargetPosition = random.NextPosition(); + + public Explosion Fire(Angle angleFromX, Angle angleFromZ, float distance) + { + var explosionPosition = new Point(angleFromX, angleFromZ, distance); + var targetOffset = explosionPosition - TargetPosition; + return new (explosionPosition, targetOffset); + } + } +} diff --git a/86 Target/csharp/Target/Game.cs b/86 Target/csharp/Target/Game.cs new file mode 100644 index 00000000..2cef015a --- /dev/null +++ b/86 Target/csharp/Target/Game.cs @@ -0,0 +1,89 @@ +using System; + +namespace Target +{ + internal class Game + { + private readonly FiringRange _firingRange; + private int _shotCount; + + private Game(FiringRange firingRange) + { + _firingRange = firingRange; + } + + public static void Play(FiringRange firingRange) => new Game(firingRange).Play(); + + private void Play() + { + var target = _firingRange.TargetPosition; + Console.WriteLine(target.GetBearing()); + Console.WriteLine($"Target sighted: approximate coordinates: {target}"); + + while (true) + { + Console.WriteLine($" Estimated distance: {target.EstimateDistance()}"); + Console.WriteLine(); + + var explosion = Shoot(); + + if (explosion.IsTooClose) + { + Console.WriteLine("You blew yourself up!!"); + return; + } + + Console.WriteLine(explosion.GetBearing()); + + if (explosion.IsHit) + { + ReportHit(explosion.DistanceToTarget); + return; + } + + ReportMiss(explosion); + } + } + + private Explosion Shoot() + { + var input = Input.ReadNumbers("Input angle deviation from X, angle deviation from Z, distance", 3); + _shotCount++; + Console.WriteLine(); + + return _firingRange.Fire(Angle.InDegrees(input[0]), Angle.InDegrees(input[1]), input[2]); + } + + private void ReportHit(float distance) + { + Console.WriteLine(); + Console.WriteLine($" * * * HIT * * * Target is non-functional"); + Console.WriteLine(); + Console.WriteLine($"Distance of explosion from target was {distance} kilometers."); + Console.WriteLine(); + Console.WriteLine($"Mission accomplished in {_shotCount} shots."); + } + + private void ReportMiss(Explosion explosion) + { + ReportMiss(explosion.FromTarget); + Console.WriteLine($"Approx position of explosion: {explosion.Position}"); + Console.WriteLine($" Distance from target = {explosion.DistanceToTarget}"); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + } + + private void ReportMiss(Offset targetOffset) + { + ReportMiss(targetOffset.DeltaX, "in front of", "behind"); + ReportMiss(targetOffset.DeltaY, "to left of", "to right of"); + ReportMiss(targetOffset.DeltaZ, "above", "below"); + } + + private void ReportMiss(float delta, string positiveText, string negativeText) => + Console.WriteLine(delta >= 0 ? GetOffsetText(positiveText, delta) : GetOffsetText(negativeText, -delta)); + + private static string GetOffsetText(string text, float distance) => $"Shot {text} target {distance} kilometers."; + } +} diff --git a/86 Target/csharp/Target/Input.cs b/86 Target/csharp/Target/Input.cs new file mode 100644 index 00000000..3cfce2c0 --- /dev/null +++ b/86 Target/csharp/Target/Input.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; + +namespace Target +{ + // Provides input methods which emulate the BASIC interpreter's keyboard input routines + internal static class Input + { + internal static void Prompt(string text = "") => Console.Write($"{text}? "); + + internal static List ReadNumbers(string prompt, int requiredCount) + { + var numbers = new List(); + + while (!TryReadNumbers(prompt, requiredCount, numbers)) + { + numbers.Clear(); + prompt = ""; + } + + return numbers; + } + + private static bool TryReadNumbers(string prompt, int requiredCount, List numbers) + { + Prompt(prompt); + var inputValues = ReadStrings(); + + foreach (var value in inputValues) + { + if (numbers.Count == requiredCount) + { + Console.WriteLine("!Extra input ingored"); + break; + } + + if (!TryParseNumber(value, out var number)) + { + return false; + } + + numbers.Add(number); + } + + return numbers.Count == requiredCount || TryReadNumbers("?", requiredCount, numbers); + } + + private static string[] ReadStrings() => Console.ReadLine().Split(',', StringSplitOptions.TrimEntries); + + private static bool TryParseNumber(string text, out float number) + { + if (float.TryParse(text, out number)) { return true; } + + Console.WriteLine("!Number expected - retry input line"); + number = default; + return false; + } + } +} diff --git a/86 Target/csharp/Target/Offset.cs b/86 Target/csharp/Target/Offset.cs new file mode 100644 index 00000000..2bd8710c --- /dev/null +++ b/86 Target/csharp/Target/Offset.cs @@ -0,0 +1,21 @@ +using System; + +namespace Target +{ + internal class Offset + { + public Offset(float deltaX, float deltaY, float deltaZ) + { + DeltaX = deltaX; + DeltaY = deltaY; + DeltaZ = deltaZ; + + Distance = (float)Math.Sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ + deltaZ); + } + + public float DeltaX { get; } + public float DeltaY { get; } + public float DeltaZ { get; } + public float Distance { get; } + } +} diff --git a/86 Target/csharp/Target/Point.cs b/86 Target/csharp/Target/Point.cs new file mode 100644 index 00000000..f004b1c4 --- /dev/null +++ b/86 Target/csharp/Target/Point.cs @@ -0,0 +1,47 @@ +using System; + +namespace Target +{ + internal class Point + { + private readonly float _angleFromX; + private readonly float _angleFromZ; + + private readonly float _x; + private readonly float _y; + private readonly float _z; + + private int _estimateCount; + + public Point(Angle angleFromX, Angle angleFromZ, float distance) + { + _angleFromX = angleFromX; + _angleFromZ = angleFromZ; + Distance = distance; + + _x = distance * (float)Math.Sin(_angleFromZ) * (float)Math.Cos(_angleFromX); + _y = distance * (float)Math.Sin(_angleFromZ) * (float)Math.Sin(_angleFromX); + _z = distance * (float)Math.Cos(_angleFromZ); + } + + public float Distance { get; } + + public float EstimateDistance() => + ++_estimateCount switch + { + 1 => EstimateDistance(20), + 2 => EstimateDistance(10), + 3 => EstimateDistance(5), + 4 => EstimateDistance(1), + _ => Distance + }; + + public float EstimateDistance(int precision) => (float)Math.Floor(Distance / precision) * precision; + + public string GetBearing() => $"Radians from X axis = {_angleFromX} from Z axis = {_angleFromZ}"; + + public override string ToString() => $"X= {_x} Y = {_y} Z= {_z}"; + + public static Offset operator -(Point p1, Point p2) => new (p1._x - p2._x, p1._y - p2._y, p1._z - p2._z); + } +} diff --git a/86 Target/csharp/Target/Program.cs b/86 Target/csharp/Target/Program.cs new file mode 100644 index 00000000..b861f0e6 --- /dev/null +++ b/86 Target/csharp/Target/Program.cs @@ -0,0 +1,39 @@ +using System; +using System.Reflection; + +namespace Target +{ + class Program + { + static void Main(string[] args) + { + DisplayTitleAndInstructions(); + + var firingRange = new FiringRange(); + + while (true) + { + Game.Play(firingRange); + + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine("Next target..."); + Console.WriteLine(); + + firingRange.NextTarget(); + } + } + + private static void DisplayTitleAndInstructions() + { + using var stream = Assembly.GetExecutingAssembly() + .GetManifestResourceStream("Target.Strings.TitleAndInstructions.txt"); + using var stdout = Console.OpenStandardOutput(); + + stream.CopyTo(stdout); + } + } +} diff --git a/86 Target/csharp/Target/RandomExtensions.cs b/86 Target/csharp/Target/RandomExtensions.cs new file mode 100644 index 00000000..45443c26 --- /dev/null +++ b/86 Target/csharp/Target/RandomExtensions.cs @@ -0,0 +1,14 @@ +using System; + +namespace Target +{ + internal static class RandomExtensions + { + public static float NextFloat(this Random rnd) => (float)rnd.NextDouble(); + + public static Point NextPosition(this Random rnd) => new ( + Angle.InRotations(rnd.NextFloat()), + Angle.InRotations(rnd.NextFloat()), + 100000 * rnd.NextFloat() + rnd.NextFloat()); + } +} diff --git a/86 Target/csharp/Target/Strings/TitleAndInstructions.txt b/86 Target/csharp/Target/Strings/TitleAndInstructions.txt new file mode 100644 index 00000000..0142e420 --- /dev/null +++ b/86 Target/csharp/Target/Strings/TitleAndInstructions.txt @@ -0,0 +1,18 @@ + Target + Creative Computing Morristown, New Jersey + + + +You are the weapons officer on the Starship Enterprise +and this is a test to see how accurate a shot you +are in a three-dimensional range. You will be told +the radian offset for the X and Z axes, the location +of the target in three dimensional rectangular coordinates, +the approximate number of degrees from the X and Z +axes, and the approximate distance to the target. +You will then proceed to shoot at the target until it is +destroyed! + +Good luck!! + + diff --git a/86 Target/csharp/Target/Target.csproj b/86 Target/csharp/Target/Target.csproj new file mode 100644 index 00000000..2df33b67 --- /dev/null +++ b/86 Target/csharp/Target/Target.csproj @@ -0,0 +1,12 @@ + + + + Exe + net5.0 + + + + + + +