From 0f30e7408f4dc0a65d5ba282dc1c840297869391 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Sun, 13 Mar 2022 19:16:41 +1100 Subject: [PATCH 1/5] Update 62_Mugwump --- 46_Hexapawn/csharp/Hexapawn.csproj | 6 ++- 62_Mugwump/csharp/Distance.cs | 13 +++++ 62_Mugwump/csharp/Game.cs | 73 ++++++++++++++++---------- 62_Mugwump/csharp/Grid.cs | 54 +++++++++---------- 62_Mugwump/csharp/IRandomExtensions.cs | 7 +++ 62_Mugwump/csharp/Input.cs | 66 ----------------------- 62_Mugwump/csharp/Mugwump.cs | 31 ++++++----- 62_Mugwump/csharp/Mugwump.csproj | 7 ++- 62_Mugwump/csharp/Offset.cs | 16 ------ 62_Mugwump/csharp/Position.cs | 13 +++-- 62_Mugwump/csharp/Program.cs | 37 +++---------- 62_Mugwump/csharp/TextIOExtensions.cs | 13 +++++ 12 files changed, 146 insertions(+), 190 deletions(-) create mode 100644 62_Mugwump/csharp/Distance.cs create mode 100644 62_Mugwump/csharp/IRandomExtensions.cs delete mode 100644 62_Mugwump/csharp/Input.cs delete mode 100644 62_Mugwump/csharp/Offset.cs create mode 100644 62_Mugwump/csharp/TextIOExtensions.cs diff --git a/46_Hexapawn/csharp/Hexapawn.csproj b/46_Hexapawn/csharp/Hexapawn.csproj index 20827042..5c31ddf9 100644 --- a/46_Hexapawn/csharp/Hexapawn.csproj +++ b/46_Hexapawn/csharp/Hexapawn.csproj @@ -2,7 +2,11 @@ Exe - net5.0 + net6.0 + + + + diff --git a/62_Mugwump/csharp/Distance.cs b/62_Mugwump/csharp/Distance.cs new file mode 100644 index 00000000..30185157 --- /dev/null +++ b/62_Mugwump/csharp/Distance.cs @@ -0,0 +1,13 @@ +namespace Mugwump; + +internal struct Distance +{ + private readonly float _value; + + public Distance(float deltaX, float deltaY) + { + _value = (float)Math.Sqrt(deltaX * deltaX + deltaY * deltaY); + } + + public override string ToString() => _value.ToString("0.0"); +} diff --git a/62_Mugwump/csharp/Game.cs b/62_Mugwump/csharp/Game.cs index bf72d44e..2f790c0d 100644 --- a/62_Mugwump/csharp/Game.cs +++ b/62_Mugwump/csharp/Game.cs @@ -1,36 +1,55 @@ -using System; -using System.Linq; +using System.Reflection; -namespace Mugwump +namespace Mugwump; + +internal class Game { - internal class Game + private readonly TextIO _io; + private readonly IRandom _random; + + internal Game(TextIO io, IRandom random) { - private readonly Grid _grid; + _io = io; + _random = random; + } - private Game(Random random) + internal void Play(Func playAgain = null) + { + DisplayIntro(); + + while (playAgain?.Invoke() ?? true) { - _grid = new Grid(Enumerable.Range(1, 4).Select(id => new Mugwump(id, random.Next(10), random.Next(10)))); - } + Play(new Grid(_io, _random)); - public static void Play(Random random) => new Game(random).Play(); - - private void Play() - { - for (int turn = 1; turn <= 10; turn++) - { - var guess = Input.ReadGuess($"Turn no. {turn} -- what is your guess"); - - if (_grid.Check(guess)) - { - Console.WriteLine(); - Console.WriteLine($"You got them all in {turn} turns!"); - return; - } - } - - Console.WriteLine(); - Console.WriteLine("Sorry, that's 10 tries. Here is where they're hiding:"); - _grid.Reveal(); + _io.WriteLine(); + _io.WriteLine("That was fun! Let's play again......."); + _io.WriteLine("Four more mugwumps are now in hiding."); } } + + private void DisplayIntro() + { + using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Mugwump.Strings.Intro.txt"); + + _io.Write(stream); + } + + private void Play(Grid grid) + { + for (int turn = 1; turn <= 10; turn++) + { + var guess = _io.ReadGuess($"Turn no. {turn} -- what is your guess"); + + if (grid.Check(guess)) + { + _io.WriteLine(); + _io.WriteLine($"You got them all in {turn} turns!"); + return; + } + } + + _io.WriteLine(); + _io.WriteLine("Sorry, that's 10 tries. Here is where they're hiding:"); + grid.Reveal(); + } } diff --git a/62_Mugwump/csharp/Grid.cs b/62_Mugwump/csharp/Grid.cs index dd89c638..8fb63ddc 100644 --- a/62_Mugwump/csharp/Grid.cs +++ b/62_Mugwump/csharp/Grid.cs @@ -1,40 +1,40 @@ -using System; using System.Collections.Generic; using System.Linq; -namespace Mugwump +namespace Mugwump; + +internal class Grid { - internal class Grid + private readonly TextIO _io; + private readonly List _mugwumps; + + public Grid(TextIO io, IRandom random) { - private readonly List _mugwumps; + _io = io; + _mugwumps = Enumerable.Range(1, 4).Select(id => new Mugwump(id, random.NextPosition(10, 10))).ToList(); + } - public Grid(IEnumerable mugwumps) + public bool Check(Position guess) + { + foreach (var mugwump in _mugwumps.ToList()) { - _mugwumps = mugwumps.ToList(); + var (found, distance) = mugwump.FindFrom(guess); + + _io.WriteLine(found ? $"You have found {mugwump}" : $"You are {distance} units from {mugwump}"); + if (found) + { + _mugwumps.Remove(mugwump); + } } - public bool Check(Position guess) + return _mugwumps.Count == 0; + } + + public void Reveal() + { + foreach (var mugwump in _mugwumps) { - foreach (var mugwump in _mugwumps.ToList()) - { - var (found, distance) = mugwump.FindFrom(guess); - - Console.WriteLine(found ? $"You have found {mugwump}" : $"You are {distance} units from {mugwump}"); - if (found) - { - _mugwumps.Remove(mugwump); - } - } - - return _mugwumps.Count == 0; - } - - public void Reveal() - { - foreach (var mugwump in _mugwumps.ToList()) - { - Console.WriteLine(mugwump.Reveal()); - } + _io.WriteLine(mugwump.Reveal()); } } } diff --git a/62_Mugwump/csharp/IRandomExtensions.cs b/62_Mugwump/csharp/IRandomExtensions.cs new file mode 100644 index 00000000..7ef7a55b --- /dev/null +++ b/62_Mugwump/csharp/IRandomExtensions.cs @@ -0,0 +1,7 @@ +namespace Mugwump; + +internal static class IRandomExtensions +{ + internal static Position NextPosition(this IRandom random, int maxX, int maxY) => + new(random.Next(maxX), random.Next(maxY)); +} diff --git a/62_Mugwump/csharp/Input.cs b/62_Mugwump/csharp/Input.cs deleted file mode 100644 index 97222c3e..00000000 --- a/62_Mugwump/csharp/Input.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Mugwump -{ - // Provides input methods which emulate the BASIC interpreter's keyboard input routines - internal static class Input - { - internal static Position ReadGuess(string prompt) - { - Console.WriteLine(); - Console.WriteLine(); - var input = ReadNumbers(prompt, 2); - return new Position(input[0], input[1]); - } - - private static void Prompt(string text = "") => Console.Write($"{text}? "); - - private static List ReadNumbers(string prompt, int requiredCount) - { - var numbers = new List(); - - while (!TryReadNumbers(prompt, requiredCount, numbers)) - { - 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"); - return true; - } - - 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/62_Mugwump/csharp/Mugwump.cs b/62_Mugwump/csharp/Mugwump.cs index bd117c63..6b5a398f 100644 --- a/62_Mugwump/csharp/Mugwump.cs +++ b/62_Mugwump/csharp/Mugwump.cs @@ -1,20 +1,19 @@ -namespace Mugwump +namespace Mugwump; + +internal class Mugwump { - internal class Mugwump + private readonly int _id; + private readonly Position _position; + + public Mugwump(int id, Position position) { - private readonly int _id; - private readonly Position _position; - - public Mugwump(int id, int x, int y) - { - _id = id; - _position = new Position(x, y); - } - - public (bool, Distance) FindFrom(Position guess) => (guess == _position, guess - _position); - - public string Reveal() => $"{this} is at {_position}"; - - public override string ToString() => $"Mugwump {_id}"; + _id = id; + _position = position; } + + public (bool, Distance) FindFrom(Position guess) => (guess == _position, guess - _position); + + public string Reveal() => $"{this} is at {_position}"; + + public override string ToString() => $"Mugwump {_id}"; } diff --git a/62_Mugwump/csharp/Mugwump.csproj b/62_Mugwump/csharp/Mugwump.csproj index fc2efa30..c6c4a891 100644 --- a/62_Mugwump/csharp/Mugwump.csproj +++ b/62_Mugwump/csharp/Mugwump.csproj @@ -2,10 +2,15 @@ Exe - net5.0 + net6.0 + + + + + diff --git a/62_Mugwump/csharp/Offset.cs b/62_Mugwump/csharp/Offset.cs deleted file mode 100644 index c62e3862..00000000 --- a/62_Mugwump/csharp/Offset.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace Mugwump -{ - internal class Distance - { - private readonly float _value; - - public Distance(float deltaX, float deltaY) - { - _value = (float)Math.Sqrt(deltaX * deltaX + deltaY * deltaY); - } - - public override string ToString() => $"{_value:0.0}"; - } -} diff --git a/62_Mugwump/csharp/Position.cs b/62_Mugwump/csharp/Position.cs index 6005ae8b..5f808cc3 100644 --- a/62_Mugwump/csharp/Position.cs +++ b/62_Mugwump/csharp/Position.cs @@ -1,9 +1,8 @@ -namespace Mugwump -{ - internal record Position(float X, float Y) - { - public override string ToString() => $"( {X} , {Y} )"; +namespace Mugwump; - public static Distance operator -(Position p1, Position p2) => new(p1.X - p2.X, p1.Y - p2.Y); - } +internal record struct Position(float X, float Y) +{ + public override string ToString() => $"( {X} , {Y} )"; + + public static Distance operator -(Position p1, Position p2) => new(p1.X - p2.X, p1.Y - p2.Y); } diff --git a/62_Mugwump/csharp/Program.cs b/62_Mugwump/csharp/Program.cs index 6121b81d..f5031b88 100644 --- a/62_Mugwump/csharp/Program.cs +++ b/62_Mugwump/csharp/Program.cs @@ -1,33 +1,12 @@ -using System; -using System.Reflection; +global using System; +global using Games.Common.IO; +global using Games.Common.Randomness; -namespace Mugwump -{ - class Program - { - static void Main(string[] args) - { - DisplayIntro(); +using Mugwump; - var random = new Random(); +var random = new RandomNumberGenerator(); +var io = new ConsoleIO(); - while (true) - { - Game.Play(random); +var game = new Game(io, random); - Console.WriteLine(); - Console.WriteLine("That was fun! Let's play again......."); - Console.WriteLine("Four more mugwumps are now in hiding."); - } - } - - private static void DisplayIntro() - { - using var stream = Assembly.GetExecutingAssembly() - .GetManifestResourceStream("Mugwump.Strings.Intro.txt"); - using var stdout = Console.OpenStandardOutput(); - - stream.CopyTo(stdout); - } - } -} +game.Play(); diff --git a/62_Mugwump/csharp/TextIOExtensions.cs b/62_Mugwump/csharp/TextIOExtensions.cs new file mode 100644 index 00000000..a21039e4 --- /dev/null +++ b/62_Mugwump/csharp/TextIOExtensions.cs @@ -0,0 +1,13 @@ +namespace Mugwump; + +// Provides input methods which emulate the BASIC interpreter's keyboard input routines +internal static class TextIOExtensions +{ + internal static Position ReadGuess(this TextIO io, string prompt) + { + io.WriteLine(); + io.WriteLine(); + var (x, y) = io.Read2Numbers(prompt); + return new Position(x, y); + } +} From 1a055a22255527c6fd2082bab9b9621ee2920f93 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Fri, 18 Mar 2022 07:05:27 +1100 Subject: [PATCH 2/5] Convert Hexapawn to common library --- .../dotnet/Games.Common/IO/IReadWrite.cs | 14 +- 00_Common/dotnet/Games.Common/IO/TextIO.cs | 8 +- 46_Hexapawn/csharp/Board.cs | 103 ++++--- 46_Hexapawn/csharp/Cell.cs | 76 +++-- 46_Hexapawn/csharp/Computer.cs | 262 +++++++++--------- 46_Hexapawn/csharp/Game.cs | 68 ++--- 46_Hexapawn/csharp/GameSeries.cs | 54 ++-- 46_Hexapawn/csharp/Hexapawn.csproj | 4 + 46_Hexapawn/csharp/Human.cs | 101 +++---- 46_Hexapawn/csharp/IPlayer.cs | 8 - 46_Hexapawn/csharp/IReadWriteExtensions.cs | 42 +++ 46_Hexapawn/csharp/Input.cs | 112 -------- 46_Hexapawn/csharp/Move.cs | 123 ++++---- 46_Hexapawn/csharp/Pawn.cs | 30 +- 46_Hexapawn/csharp/Program.cs | 73 +---- 46_Hexapawn/csharp/Resources/Instructions.txt | 30 ++ 46_Hexapawn/csharp/Resources/Resource.cs | 17 ++ 46_Hexapawn/csharp/Resources/Title.txt | 5 + 18 files changed, 527 insertions(+), 603 deletions(-) delete mode 100644 46_Hexapawn/csharp/IPlayer.cs create mode 100644 46_Hexapawn/csharp/IReadWriteExtensions.cs delete mode 100644 46_Hexapawn/csharp/Input.cs create mode 100644 46_Hexapawn/csharp/Resources/Instructions.txt create mode 100644 46_Hexapawn/csharp/Resources/Resource.cs create mode 100644 46_Hexapawn/csharp/Resources/Title.txt diff --git a/00_Common/dotnet/Games.Common/IO/IReadWrite.cs b/00_Common/dotnet/Games.Common/IO/IReadWrite.cs index 27bd6986..24529947 100644 --- a/00_Common/dotnet/Games.Common/IO/IReadWrite.cs +++ b/00_Common/dotnet/Games.Common/IO/IReadWrite.cs @@ -82,9 +82,21 @@ public interface IReadWrite /// The to be written. void WriteLine(float value); + /// + /// Writes an to output. + /// + /// The to be written. + void Write(object value); + + /// + /// Writes an to output. + /// + /// The to be written. + void WriteLine(object value); + /// /// Writes the contents of a to output. /// /// The to be written. - void Write(Stream stream); + void Write(Stream stream, bool keepOpen = false); } diff --git a/00_Common/dotnet/Games.Common/IO/TextIO.cs b/00_Common/dotnet/Games.Common/IO/TextIO.cs index d5e9f9f6..a96935af 100644 --- a/00_Common/dotnet/Games.Common/IO/TextIO.cs +++ b/00_Common/dotnet/Games.Common/IO/TextIO.cs @@ -95,13 +95,19 @@ public class TextIO : IReadWrite public void WriteLine(float value) => _output.WriteLine(GetString(value)); - public void Write(Stream stream) + public void Write(object value) => _output.Write(value.ToString()); + + public void WriteLine(object value) => _output.WriteLine(value.ToString()); + + public void Write(Stream stream, bool keepOpen = false) { using var reader = new StreamReader(stream); while (!reader.EndOfStream) { _output.WriteLine(reader.ReadLine()); } + + if (!keepOpen) { stream?.Dispose(); } } private string GetString(float value) => value < 0 ? $"{value} " : $" {value} "; diff --git a/46_Hexapawn/csharp/Board.cs b/46_Hexapawn/csharp/Board.cs index ba7d19ad..dbca8681 100644 --- a/46_Hexapawn/csharp/Board.cs +++ b/46_Hexapawn/csharp/Board.cs @@ -6,68 +6,67 @@ using System.Text; using static Hexapawn.Pawn; -namespace Hexapawn +namespace Hexapawn; + +internal class Board : IEnumerable, IEquatable { - internal class Board : IEnumerable, IEquatable + private readonly Pawn[] _cells; + + public Board() { - private readonly Pawn[] _cells; - - public Board() + _cells = new[] { - _cells = new[] + Black, Black, Black, + None, None, None, + White, White, White + }; + } + + public Board(params Pawn[] cells) + { + _cells = cells; + } + + public Pawn this[int index] + { + get => _cells[index - 1]; + set => _cells[index - 1] = value; + } + + public Board Reflected => new(Cell.AllCells.Select(c => this[c.Reflected]).ToArray()); + + public IEnumerator GetEnumerator() => _cells.OfType().GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public override string ToString() + { + var builder = new StringBuilder().AppendLine(); + for (int row = 0; row < 3; row++) + { + builder.Append(" "); + for (int col = 0; col < 3; col++) { - Black, Black, Black, - None, None, None, - White, White, White - }; - } - - public Board(params Pawn[] cells) - { - _cells = cells; - } - - public Pawn this[int index] - { - get => _cells[index - 1]; - set => _cells[index - 1] = value; - } - - public Board Reflected => new(Cell.AllCells.Select(c => this[c.Reflected]).ToArray()); - - public IEnumerator GetEnumerator() => _cells.OfType().GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public override string ToString() - { - var builder = new StringBuilder().AppendLine(); - for (int row = 0; row < 3; row++) - { - builder.Append(" "); - for (int col = 0; col < 3; col++) - { - builder.Append(_cells[row * 3 + col]); - } - builder.AppendLine(); + builder.Append(_cells[row * 3 + col]); } - return builder.ToString(); + builder.AppendLine(); } + return builder.ToString(); + } - public bool Equals(Board other) => other?.Zip(this).All(x => x.First == x.Second) ?? false; + public bool Equals(Board other) => other?.Zip(this).All(x => x.First == x.Second) ?? false; - public override bool Equals(object obj) => Equals(obj as Board); + public override bool Equals(object obj) => Equals(obj as Board); - public override int GetHashCode() + public override int GetHashCode() + { + var hash = 19; + + for (int i = 0; i < 9; i++) { - var hash = 19; - - for (int i = 0; i < 9; i++) - { - hash = hash * 53 + _cells[i].GetHashCode(); - } - - return hash; + hash = hash * 53 + _cells[i].GetHashCode(); } + + return hash; } } diff --git a/46_Hexapawn/csharp/Cell.cs b/46_Hexapawn/csharp/Cell.cs index 7647e0bc..ac244ed5 100644 --- a/46_Hexapawn/csharp/Cell.cs +++ b/46_Hexapawn/csharp/Cell.cs @@ -1,53 +1,41 @@ using System; using System.Collections.Generic; -namespace Hexapawn +namespace Hexapawn; + +// Represents a cell on the board, numbered 1 to 9, with support for finding the reflection of the reference around +// the middle column of the board. +internal class Cell { - // Represents a cell on the board, numbered 1 to 9, with support for finding the reflection of the reference around - // the middle column of the board. - internal class Cell + private static readonly Cell[] _cells = new Cell[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + private static readonly Cell[] _reflected = new Cell[] { 3, 2, 1, 6, 5, 4, 9, 8, 7 }; + private readonly int _number; + private Cell(int number) { - private static readonly Cell[] _cells = new Cell[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - private static readonly Cell[] _reflected = new Cell[] { 3, 2, 1, 6, 5, 4, 9, 8, 7 }; - - private readonly int _number; - - private Cell(int number) + if (number < 1 || number > 9) { - if (number < 1 || number > 9) - { - throw new ArgumentOutOfRangeException(nameof(number), number, "Must be from 1 to 9"); - } - - _number = number; + throw new ArgumentOutOfRangeException(nameof(number), number, "Must be from 1 to 9"); } - - // Facilitates enumerating all the cells. - public static IEnumerable AllCells => _cells; - - // Takes a value input by the user and attempts to create a Cell reference - public static bool TryCreate(float input, out Cell cell) - { - if (IsInteger(input) && input >= 1 && input <= 9) - { - cell = (int)input; - return true; - } - - cell = default; - return false; - - static bool IsInteger(float value) => value - (int)value == 0; - } - - // Returns the reflection of the cell reference about the middle column of the board. - public Cell Reflected => _reflected[_number - 1]; - - // Allows the cell reference to be used where an int is expected, such as the indexer in Board. - public static implicit operator int(Cell c) => c._number; - - public static implicit operator Cell(int number) => new(number); - - public override string ToString() => _number.ToString(); + _number = number; } + // Facilitates enumerating all the cells. + public static IEnumerable AllCells => _cells; + // Takes a value input by the user and attempts to create a Cell reference + public static bool TryCreate(float input, out Cell cell) + { + if (IsInteger(input) && input >= 1 && input <= 9) + { + cell = (int)input; + return true; + } + cell = default; + return false; + static bool IsInteger(float value) => value - (int)value == 0; + } + // Returns the reflection of the cell reference about the middle column of the board. + public Cell Reflected => _reflected[_number - 1]; + // Allows the cell reference to be used where an int is expected, such as the indexer in Board. + public static implicit operator int(Cell c) => c._number; + public static implicit operator Cell(int number) => new(number); + public override string ToString() => _number.ToString(); } diff --git a/46_Hexapawn/csharp/Computer.cs b/46_Hexapawn/csharp/Computer.cs index 41e2b4a2..9411a4a8 100644 --- a/46_Hexapawn/csharp/Computer.cs +++ b/46_Hexapawn/csharp/Computer.cs @@ -1,146 +1,138 @@ using System; using System.Collections.Generic; using System.Linq; +using Games.Common.IO; +using Games.Common.Randomness; using static Hexapawn.Pawn; -using static Hexapawn.Cell; -namespace Hexapawn +namespace Hexapawn; + +/// +/// Encapsulates the logic of the computer player. +/// +internal class Computer { - /// - /// Encapsulates the logic of the computer player. - /// - internal class Computer : IPlayer + private readonly TextIO _io; + private readonly IRandom _random; + private readonly Dictionary> _potentialMoves; + private (List, Move) _lastMove; + public Computer(TextIO io, IRandom random) { - private readonly Random _random = new(); - private readonly Dictionary> _potentialMoves; - private (List, Move) _lastMove; + _io = io; + _random = random; - public Computer() + // This dictionary implements the data in the original code, which encodes board positions for which the + // computer has a legal move, and the list of possible moves for each position: + // 900 DATA -1,-1,-1,1,0,0,0,1,1,-1,-1,-1,0,1,0,1,0,1 + // 905 DATA -1,0,-1,-1,1,0,0,0,1,0,-1,-1,1,-1,0,0,0,1 + // 910 DATA -1,0,-1,1,1,0,0,1,0,-1,-1,0,1,0,1,0,0,1 + // 915 DATA 0,-1,-1,0,-1,1,1,0,0,0,-1,-1,-1,1,1,1,0,0 + // 920 DATA -1,0,-1,-1,0,1,0,1,0,0,-1,-1,0,1,0,0,0,1 + // 925 DATA 0,-1,-1,0,1,0,1,0,0,-1,0,-1,1,0,0,0,0,1 + // 930 DATA 0,0,-1,-1,-1,1,0,0,0,-1,0,0,1,1,1,0,0,0 + // 935 DATA 0,-1,0,-1,1,1,0,0,0,-1,0,0,-1,-1,1,0,0,0 + // 940 DATA 0,0,-1,-1,1,0,0,0,0,0,-1,0,1,-1,0,0,0,0 + // 945 DATA -1,0,0,-1,1,0,0,0,0 + // 950 DATA 24,25,36,0,14,15,36,0,15,35,36,47,36,58,59,0 + // 955 DATA 15,35,36,0,24,25,26,0,26,57,58,0 + // 960 DATA 26,35,0,0,47,48,0,0,35,36,0,0,35,36,0,0 + // 965 DATA 36,0,0,0,47,58,0,0,15,0,0,0 + // 970 DATA 26,47,0,0,47,58,0,0,35,36,47,0,28,58,0,0,15,47,0,0 + // + // The original code loaded this data into two arrays. + // 40 FOR I=1 TO 19: FOR J=1 TO 9: READ B(I,J): NEXT J: NEXT I + // 45 FOR I=1 TO 19: FOR J=1 TO 4: READ M(I,J): NEXT J: NEXT I + // + // When finding moves for the computer the first array was searched for the current board position, or the + // reflection of it, and the resulting index was used in the second array to get the possible moves. + // With this dictionary we can just use the current board as the index, and retrieve a list of moves for + // consideration by the computer. + _potentialMoves = new() { - // This dictionary implements the data in the original code, which encodes board positions for which the - // computer has a legal move, and the list of possible moves for each position: - // 900 DATA -1,-1,-1,1,0,0,0,1,1,-1,-1,-1,0,1,0,1,0,1 - // 905 DATA -1,0,-1,-1,1,0,0,0,1,0,-1,-1,1,-1,0,0,0,1 - // 910 DATA -1,0,-1,1,1,0,0,1,0,-1,-1,0,1,0,1,0,0,1 - // 915 DATA 0,-1,-1,0,-1,1,1,0,0,0,-1,-1,-1,1,1,1,0,0 - // 920 DATA -1,0,-1,-1,0,1,0,1,0,0,-1,-1,0,1,0,0,0,1 - // 925 DATA 0,-1,-1,0,1,0,1,0,0,-1,0,-1,1,0,0,0,0,1 - // 930 DATA 0,0,-1,-1,-1,1,0,0,0,-1,0,0,1,1,1,0,0,0 - // 935 DATA 0,-1,0,-1,1,1,0,0,0,-1,0,0,-1,-1,1,0,0,0 - // 940 DATA 0,0,-1,-1,1,0,0,0,0,0,-1,0,1,-1,0,0,0,0 - // 945 DATA -1,0,0,-1,1,0,0,0,0 - // 950 DATA 24,25,36,0,14,15,36,0,15,35,36,47,36,58,59,0 - // 955 DATA 15,35,36,0,24,25,26,0,26,57,58,0 - // 960 DATA 26,35,0,0,47,48,0,0,35,36,0,0,35,36,0,0 - // 965 DATA 36,0,0,0,47,58,0,0,15,0,0,0 - // 970 DATA 26,47,0,0,47,58,0,0,35,36,47,0,28,58,0,0,15,47,0,0 - // - // The original code loaded this data into two arrays. - // 40 FOR I=1 TO 19: FOR J=1 TO 9: READ B(I,J): NEXT J: NEXT I - // 45 FOR I=1 TO 19: FOR J=1 TO 4: READ M(I,J): NEXT J: NEXT I - // - // When finding moves for the computer the first array was searched for the current board position, or the - // reflection of it, and the resulting index was used in the second array to get the possible moves. - // With this dictionary we can just use the current board as the index, and retrieve a list of moves for - // consideration by the computer. - _potentialMoves = new() - { - [new(Black, Black, Black, White, None, None, None, White, White)] = Moves((2, 4), (2, 5), (3, 6)), - [new(Black, Black, Black, None, White, None, White, None, White)] = Moves((1, 4), (1, 5), (3, 6)), - [new(Black, None, Black, Black, White, None, None, None, White)] = Moves((1, 5), (3, 5), (3, 6), (4, 7)), - [new(None, Black, Black, White, Black, None, None, None, White)] = Moves((3, 6), (5, 8), (5, 9)), - [new(Black, None, Black, White, White, None, None, White, None)] = Moves((1, 5), (3, 5), (3, 6)), - [new(Black, Black, None, White, None, White, None, None, White)] = Moves((2, 4), (2, 5), (2, 6)), - [new(None, Black, Black, None, Black, White, White, None, None)] = Moves((2, 6), (5, 7), (5, 8)), - [new(None, Black, Black, Black, White, White, White, None, None)] = Moves((2, 6), (3, 5)), - [new(Black, None, Black, Black, None, White, None, White, None)] = Moves((4, 7), (4, 8)), - [new(None, Black, Black, None, White, None, None, None, White)] = Moves((3, 5), (3, 6)), - [new(None, Black, Black, None, White, None, White, None, None)] = Moves((3, 5), (3, 6)), - [new(Black, None, Black, White, None, None, None, None, White)] = Moves((3, 6)), - [new(None, None, Black, Black, Black, White, None, None, None)] = Moves((4, 7), (5, 8)), - [new(Black, None, None, White, White, White, None, None, None)] = Moves((1, 5)), - [new(None, Black, None, Black, White, White, None, None, None)] = Moves((2, 6), (4, 7)), - [new(Black, None, None, Black, Black, White, None, None, None)] = Moves((4, 7), (5, 8)), - [new(None, None, Black, Black, White, None, None, None, None)] = Moves((3, 5), (3, 6), (4, 7)), - [new(None, Black, None, White, Black, None, None, None, None)] = Moves((2, 8), (5, 8)), - [new(Black, None, None, Black, White, None, None, None, None)] = Moves((1, 5), (4, 7)) - }; - } - - public int Wins { get; private set; } - - public void AddWin() => Wins++; - - // Try to make a move. We first try to find a legal move for the current board position. - public bool TryMove(Board board) - { - if (TryGetMoves(board, out var moves, out var reflected) && - TrySelectMove(moves, out var move)) - { - // We've found a move, so we record it as the last move made, and then announce and make the move. - _lastMove = (moves, move); - - // If we found the move from a reflacted match of the board we need to make the reflected move. - if (reflected) { move = move.Reflected; } - - Console.WriteLine($"I move {move}"); - move.Execute(board); - return true; - } - - // We haven't found a move for this board position, so remove the previous move that led to this board - // position from future consideration. We don't want to make that move again, because we now know it's a - // non-winning move. - ExcludeLastMoveFromFuturePlay(); - - return false; - } - - // Looks up the given board and its reflection in the potential moves dictionary. If it's found then we have a - // list of potential moves. If the board is not found in the dictionary then the computer has no legal moves, - // and the human player wins. - private bool TryGetMoves(Board board, out List moves, out bool reflected) - { - if (_potentialMoves.TryGetValue(board, out moves)) - { - reflected = false; - return true; - } - - if (_potentialMoves.TryGetValue(board.Reflected, out moves)) - { - reflected = true; - return true; - } - - reflected = default; - return false; - } - - // Get a random move from the list. If the list is empty, then we've previously eliminated all the moves for - // this board position as being non-winning moves. We therefore resign the game. - private bool TrySelectMove(List moves, out Move move) - { - if (moves.Any()) - { - move = moves[_random.Next(moves.Count)]; - return true; - } - - Console.Write("I resign."); - move = null; - return false; - } - - private void ExcludeLastMoveFromFuturePlay() - { - var (moves, move) = _lastMove; - moves.Remove(move); - } - - private static List Moves(params Move[] moves) => moves.ToList(); - - public bool IsFullyAdvanced(Board board) => - board[9] == Black || board[8] == Black || board[7] == Black; + [new(Black, Black, Black, White, None, None, None, White, White)] = Moves((2, 4), (2, 5), (3, 6)), + [new(Black, Black, Black, None, White, None, White, None, White)] = Moves((1, 4), (1, 5), (3, 6)), + [new(Black, None, Black, Black, White, None, None, None, White)] = Moves((1, 5), (3, 5), (3, 6), (4, 7)), + [new(None, Black, Black, White, Black, None, None, None, White)] = Moves((3, 6), (5, 8), (5, 9)), + [new(Black, None, Black, White, White, None, None, White, None)] = Moves((1, 5), (3, 5), (3, 6)), + [new(Black, Black, None, White, None, White, None, None, White)] = Moves((2, 4), (2, 5), (2, 6)), + [new(None, Black, Black, None, Black, White, White, None, None)] = Moves((2, 6), (5, 7), (5, 8)), + [new(None, Black, Black, Black, White, White, White, None, None)] = Moves((2, 6), (3, 5)), + [new(Black, None, Black, Black, None, White, None, White, None)] = Moves((4, 7), (4, 8)), + [new(None, Black, Black, None, White, None, None, None, White)] = Moves((3, 5), (3, 6)), + [new(None, Black, Black, None, White, None, White, None, None)] = Moves((3, 5), (3, 6)), + [new(Black, None, Black, White, None, None, None, None, White)] = Moves((3, 6)), + [new(None, None, Black, Black, Black, White, None, None, None)] = Moves((4, 7), (5, 8)), + [new(Black, None, None, White, White, White, None, None, None)] = Moves((1, 5)), + [new(None, Black, None, Black, White, White, None, None, None)] = Moves((2, 6), (4, 7)), + [new(Black, None, None, Black, Black, White, None, None, None)] = Moves((4, 7), (5, 8)), + [new(None, None, Black, Black, White, None, None, None, None)] = Moves((3, 5), (3, 6), (4, 7)), + [new(None, Black, None, White, Black, None, None, None, None)] = Moves((2, 8), (5, 8)), + [new(Black, None, None, Black, White, None, None, None, None)] = Moves((1, 5), (4, 7)) + }; } + + // Try to make a move. We first try to find a legal move for the current board position. + public bool TryMove(Board board) + { + if (TryGetMoves(board, out var moves, out var reflected) && + TrySelectMove(moves, out var move)) + { + // We've found a move, so we record it as the last move made, and then announce and make the move. + _lastMove = (moves, move); + // If we found the move from a reflacted match of the board we need to make the reflected move. + if (reflected) { move = move.Reflected; } + _io.WriteLine($"I move {move}"); + move.Execute(board); + return true; + } + // We haven't found a move for this board position, so remove the previous move that led to this board + // position from future consideration. We don't want to make that move again, because we now know it's a + // non-winning move. + ExcludeLastMoveFromFuturePlay(); + return false; + } + + // Looks up the given board and its reflection in the potential moves dictionary. If it's found then we have a + // list of potential moves. If the board is not found in the dictionary then the computer has no legal moves, + // and the human player wins. + private bool TryGetMoves(Board board, out List moves, out bool reflected) + { + if (_potentialMoves.TryGetValue(board, out moves)) + { + reflected = false; + return true; + } + if (_potentialMoves.TryGetValue(board.Reflected, out moves)) + { + reflected = true; + return true; + } + reflected = default; + return false; + } + + // Get a random move from the list. If the list is empty, then we've previously eliminated all the moves for + // this board position as being non-winning moves. We therefore resign the game. + private bool TrySelectMove(List moves, out Move move) + { + if (moves.Any()) + { + move = moves[_random.Next(moves.Count)]; + return true; + } + _io.WriteLine("I resign."); + move = null; + return false; + } + + private void ExcludeLastMoveFromFuturePlay() + { + var (moves, move) = _lastMove; + moves.Remove(move); + } + + private static List Moves(params Move[] moves) => moves.ToList(); + + public bool IsFullyAdvanced(Board board) => + board[9] == Black || board[8] == Black || board[7] == Black; } diff --git a/46_Hexapawn/csharp/Game.cs b/46_Hexapawn/csharp/Game.cs index 95e321ff..335da020 100644 --- a/46_Hexapawn/csharp/Game.cs +++ b/46_Hexapawn/csharp/Game.cs @@ -1,48 +1,40 @@ using System; +using Games.Common.IO; -namespace Hexapawn +namespace Hexapawn; + +// A single game of Hexapawn +internal class Game { - // Runs a single game of Hexapawn - internal class Game + private readonly TextIO _io; + private readonly Board _board; + + public Game(TextIO io) { - private readonly Board _board; - private readonly Human _human; - private readonly Computer _computer; + _board = new Board(); + _io = io; + } - public Game(Human human, Computer computer) + public object Play(Human human, Computer computer) + { + _io.WriteLine(_board); + while(true) { - _board = new Board(); - _human = human; - _computer = computer; - } - - public IPlayer Play() - { - Console.WriteLine(_board); - - while(true) + human.Move(_board); + _io.WriteLine(_board); + if (!computer.TryMove(_board)) { - _human.Move(_board); - - Console.WriteLine(_board); - - if (!_computer.TryMove(_board)) - { - return _human; - } - - Console.WriteLine(_board); - - if (_computer.IsFullyAdvanced(_board) || _human.HasNoPawns(_board)) - { - return _computer; - } - - if (!_human.HasLegalMove(_board)) - { - Console.Write("You can't move, so "); - return _computer; - } + return human; + } + _io.WriteLine(_board); + if (computer.IsFullyAdvanced(_board) || human.HasNoPawns(_board)) + { + return computer; + } + if (!human.HasLegalMove(_board)) + { + _io.Write("You can't move, so "); + return computer; } } } diff --git a/46_Hexapawn/csharp/GameSeries.cs b/46_Hexapawn/csharp/GameSeries.cs index 6f45cab7..2c021cc5 100644 --- a/46_Hexapawn/csharp/GameSeries.cs +++ b/46_Hexapawn/csharp/GameSeries.cs @@ -1,27 +1,47 @@ -using System; +using System.Collections.Generic; +using System.Linq; +using Games.Common.IO; +using Games.Common.Randomness; +using Hexapawn.Resources; -namespace Hexapawn +namespace Hexapawn; + +// Runs series of games between the computer and the human player +internal class GameSeries { - // Runs series of games between the computer and the human player - internal class GameSeries + private readonly TextIO _io; + private readonly Computer _computer; + private readonly Human _human; + private readonly Dictionary _wins; + + public GameSeries(TextIO io, IRandom random) { - private readonly Computer _computer = new(); - private readonly Human _human = new(); + _io = io; + _computer = new(io, random); + _human = new(io); + _wins = new() { [_computer] = 0, [_human] = 0 }; + } - public void Play() + public void Play() + { + _io.Write(Resource.Streams.Title); + + if (_io.GetYesNo("Instructions") == 'Y') { - while (true) - { - var game = new Game(_human, _computer); + _io.Write(Resource.Streams.Instructions); + } - var winner = game.Play(); - winner.AddWin(); - Console.WriteLine(winner == _computer ? "I win." : "You win."); + while (true) + { + var game = new Game(_io); - Console.Write($"I have won {_computer.Wins} and you {_human.Wins}"); - Console.WriteLine($" out of {_computer.Wins + _human.Wins} games."); - Console.WriteLine(); - } + var winner = game.Play(_human, _computer); + _wins[winner]++; + _io.WriteLine(winner == _computer ? "I win." : "You win."); + + _io.Write($"I have won {_wins[_computer]} and you {_wins[_human]}"); + _io.WriteLine($" out of {_wins.Values.Sum()} games."); + _io.WriteLine(); } } } diff --git a/46_Hexapawn/csharp/Hexapawn.csproj b/46_Hexapawn/csharp/Hexapawn.csproj index 5c31ddf9..de228ec6 100644 --- a/46_Hexapawn/csharp/Hexapawn.csproj +++ b/46_Hexapawn/csharp/Hexapawn.csproj @@ -5,6 +5,10 @@ net6.0 + + + + diff --git a/46_Hexapawn/csharp/Human.cs b/46_Hexapawn/csharp/Human.cs index d2d24118..7d21d004 100644 --- a/46_Hexapawn/csharp/Human.cs +++ b/46_Hexapawn/csharp/Human.cs @@ -1,64 +1,67 @@ using System; using System.Linq; +using Games.Common.IO; using static Hexapawn.Cell; using static Hexapawn.Move; using static Hexapawn.Pawn; -namespace Hexapawn +namespace Hexapawn; + +internal class Human { - internal class Human : IPlayer + private readonly TextIO _io; + + public Human(TextIO io) { - public int Wins { get; private set; } + _io = io; + } - public void Move(Board board) + public void Move(Board board) + { + while (true) { - while (true) - { - var move = Input.GetMove("Your move"); + var move = _io.ReadMove("Your move"); - if (TryExecute(board, move)) { return; } + if (TryExecute(board, move)) { return; } - Console.WriteLine("Illegal move."); - } - } - - public void AddWin() => Wins++; - - public bool HasLegalMove(Board board) - { - foreach (var from in AllCells.Where(c => c > 3)) - { - if (board[from] != White) { continue; } - - if (HasLegalMove(board, from)) - { - return true; - } - } - - return false; - } - - private bool HasLegalMove(Board board, Cell from) => - Right(from).IsRightDiagonalToCapture(board) || - Straight(from).IsStraightMoveToEmptySpace(board) || - from > 4 && Left(from).IsLeftDiagonalToCapture(board); - - public bool HasNoPawns(Board board) => board.All(c => c != White); - - public bool TryExecute(Board board, Move move) - { - if (board[move.From] != White) { return false; } - - if (move.IsStraightMoveToEmptySpace(board) || - move.IsLeftDiagonalToCapture(board) || - move.IsRightDiagonalToCapture(board)) - { - move.Execute(board); - return true; - } - - return false; + _io.WriteLine("Illegal move."); } } + + public bool HasLegalMove(Board board) + { + foreach (var from in AllCells.Where(c => c > 3)) + { + if (board[from] != White) { continue; } + + if (HasLegalMove(board, from)) + { + return true; + } + } + + return false; + } + + private bool HasLegalMove(Board board, Cell from) => + Right(from).IsRightDiagonalToCapture(board) || + Straight(from).IsStraightMoveToEmptySpace(board) || + from > 4 && Left(from).IsLeftDiagonalToCapture(board); + + public bool HasNoPawns(Board board) => board.All(c => c != White); + + public bool TryExecute(Board board, Move move) + { + if (board[move.From] != White) { return false; } + + if (move.IsStraightMoveToEmptySpace(board) || + move.IsLeftDiagonalToCapture(board) || + move.IsRightDiagonalToCapture(board)) + { + move.Execute(board); + return true; + } + + return false; + } } diff --git a/46_Hexapawn/csharp/IPlayer.cs b/46_Hexapawn/csharp/IPlayer.cs deleted file mode 100644 index 7313904e..00000000 --- a/46_Hexapawn/csharp/IPlayer.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Hexapawn -{ - // An interface implemented by a player of the game to track the number of wins. - internal interface IPlayer - { - void AddWin(); - } -} diff --git a/46_Hexapawn/csharp/IReadWriteExtensions.cs b/46_Hexapawn/csharp/IReadWriteExtensions.cs new file mode 100644 index 00000000..7b11354a --- /dev/null +++ b/46_Hexapawn/csharp/IReadWriteExtensions.cs @@ -0,0 +1,42 @@ +using System; +using System.Linq; +using Games.Common.IO; + +namespace Hexapawn; + +// Provides input methods which emulate the BASIC interpreter's keyboard input routines +internal static class IReadWriteExtensions +{ + internal static char GetYesNo(this IReadWrite io, string prompt) + { + while (true) + { + var response = io.ReadString($"{prompt} (Y-N)").FirstOrDefault(); + if ("YyNn".Contains(response)) + { + return char.ToUpperInvariant(response); + } + } + } + + // Implements original code: + // 120 PRINT "YOUR MOVE"; + // 121 INPUT M1,M2 + // 122 IF M1=INT(M1)AND M2=INT(M2)AND M1>0 AND M1<10 AND M2>0 AND M2<10 THEN 130 + // 123 PRINT "ILLEGAL CO-ORDINATES." + // 124 GOTO 120 + internal static Move ReadMove(this IReadWrite io, string prompt) + { + while(true) + { + var (from, to) = io.Read2Numbers(prompt); + + if (Move.TryCreate(from, to, out var move)) + { + return move; + } + + io.WriteLine("Illegal Coordinates."); + } + } +} diff --git a/46_Hexapawn/csharp/Input.cs b/46_Hexapawn/csharp/Input.cs deleted file mode 100644 index d169f2f6..00000000 --- a/46_Hexapawn/csharp/Input.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using System.Linq; - -namespace Hexapawn -{ - // Provides input methods which emulate the BASIC interpreter's keyboard input routines - internal static class Input - { - internal static char GetYesNo(string prompt) - { - while (true) - { - Console.Write($"{prompt} (Y-N)? "); - var response = Console.ReadLine().FirstOrDefault(); - if ("YyNn".Contains(response)) - { - return char.ToUpperInvariant(response); - } - } - } - - // Implements original code: - // 120 PRINT "YOUR MOVE"; - // 121 INPUT M1,M2 - // 122 IF M1=INT(M1)AND M2=INT(M2)AND M1>0 AND M1<10 AND M2>0 AND M2<10 THEN 130 - // 123 PRINT "ILLEGAL CO-ORDINATES." - // 124 GOTO 120 - internal static Move GetMove(string prompt) - { - while(true) - { - ReadNumbers(prompt, out var from, out var to); - - if (Move.TryCreate(from, to, out var move)) - { - return move; - } - - Console.WriteLine("Illegal Coordinates."); - } - } - - internal static void Prompt(string text = "") => Console.Write($"{text}? "); - - internal static void ReadNumbers(string prompt, out float number1, out float number2) - { - while (!TryReadNumbers(prompt, out number1, out number2)) - { - prompt = ""; - } - } - - private static bool TryReadNumbers(string prompt, out float number1, out float number2) - { - Prompt(prompt); - var inputValues = ReadStrings(); - - if (!TryParseNumber(inputValues[0], out number1)) - { - number2 = default; - return false; - } - - if (inputValues.Length == 1) - { - return TryReadNumber("?", out number2); - } - - if (!TryParseNumber(inputValues[1], out number2)) - { - number2 = default; - return false; - } - - if (inputValues.Length > 2) - { - Console.WriteLine("!Extra input ingored"); - } - - return true; - } - - private static bool TryReadNumber(string prompt, out float number) - { - Prompt(prompt); - var inputValues = ReadStrings(); - - if (!TryParseNumber(inputValues[0], out number)) - { - return false; - } - - if (inputValues.Length > 1) - { - Console.WriteLine("!Extra input ingored"); - } - - return true; - } - - 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/46_Hexapawn/csharp/Move.cs b/46_Hexapawn/csharp/Move.cs index 9581a959..0d465575 100644 --- a/46_Hexapawn/csharp/Move.cs +++ b/46_Hexapawn/csharp/Move.cs @@ -1,68 +1,67 @@ using static Hexapawn.Pawn; -namespace Hexapawn +namespace Hexapawn; + +/// +/// Represents a move which may, or may not, be legal. +/// +internal class Move { - /// - /// Represents a move which may, or may not, be legal. - /// - internal class Move + private readonly Cell _from; + private readonly Cell _to; + private readonly int _metric; + + public Move(Cell from, Cell to) { - private readonly Cell _from; - private readonly Cell _to; - private readonly int _metric; - - public Move(Cell from, Cell to) - { - _from = from; - _to = to; - _metric = _from - _to; - } - - public void Deconstruct(out Cell from, out Cell to) - { - from = _from; - to = _to; - } - - public Cell From => _from; - - // Produces the mirror image of the current moved, reflected around the central column of the board. - public Move Reflected => (_from.Reflected, _to.Reflected); - - // Allows a tuple of two ints to be implicitly converted to a Move. - public static implicit operator Move((int From, int To) value) => new(value.From, value.To); - - // Takes floating point coordinates, presumably from keyboard input, and attempts to create a Move object. - public static bool TryCreate(float input1, float input2, out Move move) - { - if (Cell.TryCreate(input1, out var from) && - Cell.TryCreate(input2, out var to)) - { - move = (from, to); - return true; - } - - move = default; - return false; - } - - public static Move Right(Cell from) => (from, from - 2); - public static Move Straight(Cell from) => (from, from - 3); - public static Move Left(Cell from) => (from, from - 4); - - public bool IsStraightMoveToEmptySpace(Board board) => _metric == 3 && board[_to] == None; - - public bool IsLeftDiagonalToCapture(Board board) => _metric == 4 && _from != 7 && board[_to] == Black; - - public bool IsRightDiagonalToCapture(Board board) => - _metric == 2 && _from != 9 && _from != 6 && board[_to] == Black; - - public void Execute(Board board) - { - board[_to] = board[_from]; - board[_from] = None; - } - - public override string ToString() => $"from {_from} to {_to}"; + _from = from; + _to = to; + _metric = _from - _to; } + + public void Deconstruct(out Cell from, out Cell to) + { + from = _from; + to = _to; + } + + public Cell From => _from; + + // Produces the mirror image of the current moved, reflected around the central column of the board. + public Move Reflected => (_from.Reflected, _to.Reflected); + + // Allows a tuple of two ints to be implicitly converted to a Move. + public static implicit operator Move((int From, int To) value) => new(value.From, value.To); + + // Takes floating point coordinates, presumably from keyboard input, and attempts to create a Move object. + public static bool TryCreate(float input1, float input2, out Move move) + { + if (Cell.TryCreate(input1, out var from) && + Cell.TryCreate(input2, out var to)) + { + move = (from, to); + return true; + } + + move = default; + return false; + } + + public static Move Right(Cell from) => (from, from - 2); + public static Move Straight(Cell from) => (from, from - 3); + public static Move Left(Cell from) => (from, from - 4); + + public bool IsStraightMoveToEmptySpace(Board board) => _metric == 3 && board[_to] == None; + + public bool IsLeftDiagonalToCapture(Board board) => _metric == 4 && _from != 7 && board[_to] == Black; + + public bool IsRightDiagonalToCapture(Board board) => + _metric == 2 && _from != 9 && _from != 6 && board[_to] == Black; + + public void Execute(Board board) + { + board[_to] = board[_from]; + board[_from] = None; + } + + public override string ToString() => $"from {_from} to {_to}"; } diff --git a/46_Hexapawn/csharp/Pawn.cs b/46_Hexapawn/csharp/Pawn.cs index 7a4c5633..f8bcae22 100644 --- a/46_Hexapawn/csharp/Pawn.cs +++ b/46_Hexapawn/csharp/Pawn.cs @@ -1,19 +1,19 @@ -namespace Hexapawn +namespace Hexapawn; + +// Represents the contents of a cell on the board +internal class Pawn { - // Represents the contents of a cell on the board - internal class Pawn + public static readonly Pawn Black = new('X'); + public static readonly Pawn White = new('O'); + public static readonly Pawn None = new('.'); + + private readonly char _symbol; + + private Pawn(char symbol) { - public static readonly Pawn Black = new('X'); - public static readonly Pawn White = new('O'); - public static readonly Pawn None = new('.'); - - private readonly char _symbol; - - private Pawn(char symbol) - { - _symbol = symbol; - } - - public override string ToString() => _symbol.ToString(); + _symbol = symbol; } + + public override string ToString() => _symbol.ToString(); } + diff --git a/46_Hexapawn/csharp/Program.cs b/46_Hexapawn/csharp/Program.cs index 59917dfa..d00a7056 100644 --- a/46_Hexapawn/csharp/Program.cs +++ b/46_Hexapawn/csharp/Program.cs @@ -1,71 +1,6 @@ -using System; +using Games.Common.IO; +using Games.Common.Randomness; +using Hexapawn; -namespace Hexapawn -{ - // Hexapawn: Interpretation of hexapawn game as presented in - // Martin Gardner's "The Unexpected Hanging and Other Mathematic - // al Diversions", Chapter Eight: A Matchbox Game-Learning Machine. - // Original version for H-P timeshare system by R.A. Kaapke 5/5/76 - // Instructions by Jeff Dalton - // Conversion to MITS BASIC by Steve North - // Conversion to C# by Andrew Cooper - class Program - { - static void Main() - { - DisplayTitle(); +new GameSeries(new ConsoleIO(), new RandomNumberGenerator()).Play(); - if (Input.GetYesNo("Instructions") == 'Y') - { - DisplayInstructions(); - } - - var games = new GameSeries(); - - games.Play(); - } - - private static void DisplayTitle() - { - Console.WriteLine(" Hexapawn"); - Console.WriteLine(" Creative Computing Morristown, New Jersey"); - Console.WriteLine(); - Console.WriteLine(); - Console.WriteLine(); - } - - private static void DisplayInstructions() - { - Console.WriteLine(); - Console.WriteLine("This program plays the game of Hexapawn."); - Console.WriteLine("Hexapawn is played with Chess pawns on a 3 by 3 board."); - Console.WriteLine("The pawns are move as in Chess - one space forward to"); - Console.WriteLine("an empty space, or one space forward and diagonally to"); - Console.WriteLine("capture an opposing man. On the board, your pawns"); - Console.WriteLine("are 'O', the computer's pawns are 'X', and empty"); - Console.WriteLine("squares are '.'. To enter a move, type the number of"); - Console.WriteLine("the square you are moving from, followed by the number"); - Console.WriteLine("of the square you will move to. The numbers must be"); - Console.WriteLine("separated by a comma."); - Console.WriteLine(); - Console.WriteLine("The computer starts a series of games knowing only when"); - Console.WriteLine("the game is won (a draw is impossible) and how to move."); - Console.WriteLine("It has no strategy at first and just moves randomly."); - Console.WriteLine("However, it learns from each game. Thus winning becomes"); - Console.WriteLine("more and more difficult. Also, to help offset your"); - Console.WriteLine("initial advantage, you will not be told how to win the"); - Console.WriteLine("game but must learn this by playing."); - Console.WriteLine(); - Console.WriteLine("The numbering of the board is as follows:"); - Console.WriteLine(" 123"); - Console.WriteLine(" 456"); - Console.WriteLine(" 789"); - Console.WriteLine(); - Console.WriteLine("For example, to move your rightmost pawn forward,"); - Console.WriteLine("you would type 9,6 in response to the question"); - Console.WriteLine("'Your move ?'. Since I'm a good sport, you'll always"); - Console.WriteLine("go first."); - Console.WriteLine(); - } - } -} diff --git a/46_Hexapawn/csharp/Resources/Instructions.txt b/46_Hexapawn/csharp/Resources/Instructions.txt new file mode 100644 index 00000000..9c96a266 --- /dev/null +++ b/46_Hexapawn/csharp/Resources/Instructions.txt @@ -0,0 +1,30 @@ + +This program plays the game of Hexapawn. +Hexapawn is played with Chess pawns on a 3 by 3 board. +The pawns are move as in Chess - one space forward to +an empty space, or one space forward and diagonally to +capture an opposing man. On the board, your pawns +are 'O', the computer's pawns are 'X', and empty +squares are '.'. To enter a move, type the number of +the square you are moving from, followed by the number +of the square you will move to. The numbers must be +separated by a comma. + +The computer starts a series of games knowing only when +the game is won (a draw is impossible) and how to move. +It has no strategy at first and just moves randomly. +However, it learns from each game. Thus winning becomes +more and more difficult. Also, to help offset your +initial advantage, you will not be told how to win the +game but must learn this by playing. + +The numbering of the board is as follows: + 123 + 456 + 789 + +For example, to move your rightmost pawn forward, +you would type 9,6 in response to the question +'Your move ?'. Since I'm a good sport, you'll always +go first. + diff --git a/46_Hexapawn/csharp/Resources/Resource.cs b/46_Hexapawn/csharp/Resources/Resource.cs new file mode 100644 index 00000000..49eaa08d --- /dev/null +++ b/46_Hexapawn/csharp/Resources/Resource.cs @@ -0,0 +1,17 @@ +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Hexapawn.Resources; + +internal static class Resource +{ + internal static class Streams + { + public static Stream Instructions => GetStream(); + public static Stream Title => GetStream(); + } + + private static Stream GetStream([CallerMemberName] string name = null) + => Assembly.GetExecutingAssembly().GetManifestResourceStream($"Hexapawn.Resources.{name}.txt"); +} \ No newline at end of file diff --git a/46_Hexapawn/csharp/Resources/Title.txt b/46_Hexapawn/csharp/Resources/Title.txt new file mode 100644 index 00000000..df31d6b9 --- /dev/null +++ b/46_Hexapawn/csharp/Resources/Title.txt @@ -0,0 +1,5 @@ + Hexapawn + Creative Computing Morristown, New Jersey + + + From 140b1659697974a8b7de6b805ced828cae05b3be Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Fri, 18 Mar 2022 07:33:28 +1100 Subject: [PATCH 3/5] Use common library in Love --- 58_Love/csharp/Input.cs | 26 ------ 58_Love/csharp/Love.csproj | 9 +- 58_Love/csharp/LovePattern.cs | 84 +++++++++---------- 58_Love/csharp/Program.cs | 34 ++------ .../csharp/{Strings => Resources}/Intro.txt | 1 + 58_Love/csharp/Resources/Resource.cs | 16 ++++ 58_Love/csharp/SourceCharacters.cs | 57 +++++++------ 58_Love/csharp/StringBuilderExtensions.cs | 16 ++++ 8 files changed, 116 insertions(+), 127 deletions(-) delete mode 100644 58_Love/csharp/Input.cs rename 58_Love/csharp/{Strings => Resources}/Intro.txt (99%) create mode 100644 58_Love/csharp/Resources/Resource.cs create mode 100644 58_Love/csharp/StringBuilderExtensions.cs diff --git a/58_Love/csharp/Input.cs b/58_Love/csharp/Input.cs deleted file mode 100644 index 030e3ead..00000000 --- a/58_Love/csharp/Input.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Love -{ - // Provides input methods which emulate the BASIC interpreter's keyboard input routines - internal static class Input - { - private static void Prompt(string text = "") => Console.Write($"{text}? "); - - public static string ReadLine(string prompt) - { - Prompt(prompt); - var values = ReadStrings(); - - if (values.Length > 1) - { - Console.WriteLine("!Extra input ingored"); - } - - return values[0]; - } - - private static string[] ReadStrings() => Console.ReadLine().Split(',', StringSplitOptions.TrimEntries); - } -} diff --git a/58_Love/csharp/Love.csproj b/58_Love/csharp/Love.csproj index fc2efa30..523087a3 100644 --- a/58_Love/csharp/Love.csproj +++ b/58_Love/csharp/Love.csproj @@ -2,10 +2,15 @@ Exe - net5.0 + net6.0 - + + + + + + diff --git a/58_Love/csharp/LovePattern.cs b/58_Love/csharp/LovePattern.cs index 4c24ed84..37e6703c 100644 --- a/58_Love/csharp/LovePattern.cs +++ b/58_Love/csharp/LovePattern.cs @@ -1,57 +1,55 @@ using System.IO; +using System.Text; -namespace Love +namespace Love; + +internal class LovePattern { - internal class LovePattern + private const int _lineLength = 60; + private readonly int[] _segmentLengths = new[] { + 60, 1, 12, 26, 9, 12, 3, 8, 24, 17, 8, 4, 6, 23, 21, 6, 4, 6, 22, 12, 5, + 6, 5, 4, 6, 21, 11, 8, 6, 4, 4, 6, 21, 10, 10, 5, 4, 4, 6, 21, 9, 11, 5, + 4, 4, 6, 21, 8, 11, 6, 4, 4, 6, 21, 7, 11, 7, 4, 4, 6, 21, 6, 11, 8, 4, + 4, 6, 19, 1, 1, 5, 11, 9, 4, 4, 6, 19, 1, 1, 5, 10, 10, 4, 4, 6, 18, 2, + 1, 6, 8, 11, 4, 4, 6, 17, 3, 1, 7, 5, 13, 4, 4, 6, 15, 5, 2, 23, 5, 1, + 29, 5, 17, 8, 1, 29, 9, 9, 12, 1, 13, 5, 40, 1, 1, 13, 5, 40, 1, 4, 6, + 13, 3, 10, 6, 12, 5, 1, 5, 6, 11, 3, 11, 6, 14, 3, 1, 5, 6, 11, 3, 11, + 6, 15, 2, 1, 6, 6, 9, 3, 12, 6, 16, 1, 1, 6, 6, 9, 3, 12, 6, 7, 1, 10, + 7, 6, 7, 3, 13, 6, 6, 2, 10, 7, 6, 7, 3, 13, 14, 10, 8, 6, 5, 3, 14, 6, + 6, 2, 10, 8, 6, 5, 3, 14, 6, 7, 1, 10, 9, 6, 3, 3, 15, 6, 16, 1, 1, 9, + 6, 3, 3, 15, 6, 15, 2, 1, 10, 6, 1, 3, 16, 6, 14, 3, 1, 10, 10, 16, 6, + 12, 5, 1, 11, 8, 13, 27, 1, 11, 8, 13, 27, 1, 60 + }; + private readonly StringBuilder _pattern = new(); + + public LovePattern(string message) { - private readonly int[] _segmentLengths = new[] { - 60, 1, 12, 26, 9, 12, 3, 8, 24, 17, 8, 4, 6, 23, 21, 6, 4, 6, 22, 12, 5, - 6, 5, 4, 6, 21, 11, 8, 6, 4, 4, 6, 21, 10, 10, 5, 4, 4, 6, 21, 9, 11, 5, - 4, 4, 6, 21, 8, 11, 6, 4, 4, 6, 21, 7, 11, 7, 4, 4, 6, 21, 6, 11, 8, 4, - 4, 6, 19, 1, 1, 5, 11, 9, 4, 4, 6, 19, 1, 1, 5, 10, 10, 4, 4, 6, 18, 2, - 1, 6, 8, 11, 4, 4, 6, 17, 3, 1, 7, 5, 13, 4, 4, 6, 15, 5, 2, 23, 5, 1, - 29, 5, 17, 8, 1, 29, 9, 9, 12, 1, 13, 5, 40, 1, 1, 13, 5, 40, 1, 4, 6, - 13, 3, 10, 6, 12, 5, 1, 5, 6, 11, 3, 11, 6, 14, 3, 1, 5, 6, 11, 3, 11, - 6, 15, 2, 1, 6, 6, 9, 3, 12, 6, 16, 1, 1, 6, 6, 9, 3, 12, 6, 7, 1, 10, - 7, 6, 7, 3, 13, 6, 6, 2, 10, 7, 6, 7, 3, 13, 14, 10, 8, 6, 5, 3, 14, 6, - 6, 2, 10, 8, 6, 5, 3, 14, 6, 7, 1, 10, 9, 6, 3, 3, 15, 6, 16, 1, 1, 9, - 6, 3, 3, 15, 6, 15, 2, 1, 10, 6, 1, 3, 16, 6, 14, 3, 1, 10, 10, 16, 6, - 12, 5, 1, 11, 8, 13, 27, 1, 11, 8, 13, 27, 1, 60 - }; + Fill(new SourceCharacters(_lineLength, message)); + } - public int LineLength => 60; + private void Fill(SourceCharacters source) + { + var lineLength = 0; - internal void Write(SourceCharacters source, Stream destination) + foreach (var segmentLength in _segmentLengths) { - using var writer = new StreamWriter(destination); - - WritePadding(writer); - - var lineLength = 0; - - foreach (var segmentLength in _segmentLengths) + foreach (var character in source.GetCharacters(segmentLength)) { - foreach (var character in source.GetCharacters(segmentLength)) - { - writer.Write(character); - } - lineLength += segmentLength; - if (lineLength >= LineLength) - { - writer.WriteLine(); - lineLength = 0; - } + _pattern.Append(character); } - - WritePadding(writer); - } - - private void WritePadding(StreamWriter writer) - { - for (int i = 0; i < 10; i++) + lineLength += segmentLength; + if (lineLength >= _lineLength) { - writer.WriteLine(); + _pattern.AppendLine(); + lineLength = 0; } } } + + public override string ToString() => + new StringBuilder() + .AppendLines(10) + .Append(_pattern) + .AppendLines(10) + .ToString(); } diff --git a/58_Love/csharp/Program.cs b/58_Love/csharp/Program.cs index a9784466..575bbec2 100644 --- a/58_Love/csharp/Program.cs +++ b/58_Love/csharp/Program.cs @@ -1,31 +1,11 @@ -using System; -using System.Reflection; +using Games.Common.IO; +using Love; +using Love.Resources; -namespace Love -{ - internal class Program - { - static void Main(string[] args) - { - DisplayIntro(); +var io = new ConsoleIO(); - var message = Input.ReadLine("Your message, please"); - var pattern = new LovePattern(); +io.Write(Resource.Streams.Intro); - var source = new SourceCharacters(pattern.LineLength, message); +var message = io.ReadString("Your message, please"); - using var destination = Console.OpenStandardOutput(); - - pattern.Write(source, destination); - } - - private static void DisplayIntro() - { - using var stream = Assembly.GetExecutingAssembly() - .GetManifestResourceStream("Love.Strings.Intro.txt"); - using var stdout = Console.OpenStandardOutput(); - - stream.CopyTo(stdout); - } - } -} +io.Write(new LovePattern(message)); diff --git a/58_Love/csharp/Strings/Intro.txt b/58_Love/csharp/Resources/Intro.txt similarity index 99% rename from 58_Love/csharp/Strings/Intro.txt rename to 58_Love/csharp/Resources/Intro.txt index cc10189e..bcf1afe2 100644 --- a/58_Love/csharp/Strings/Intro.txt +++ b/58_Love/csharp/Resources/Intro.txt @@ -7,3 +7,4 @@ A tribute to the great American artist, Robert Indiana. His greatest work will be reproduced with a message of your choice up to 60 characters. If you can't think of a message, simply type the word 'LOVE' + diff --git a/58_Love/csharp/Resources/Resource.cs b/58_Love/csharp/Resources/Resource.cs new file mode 100644 index 00000000..fcbe6add --- /dev/null +++ b/58_Love/csharp/Resources/Resource.cs @@ -0,0 +1,16 @@ +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Love.Resources; + +internal static class Resource +{ + internal static class Streams + { + public static Stream Intro => GetStream(); + } + + private static Stream GetStream([CallerMemberName] string name = null) + => Assembly.GetExecutingAssembly().GetManifestResourceStream($"Love.Resources.{name}.txt"); +} \ No newline at end of file diff --git a/58_Love/csharp/SourceCharacters.cs b/58_Love/csharp/SourceCharacters.cs index 64716f9c..f6015754 100644 --- a/58_Love/csharp/SourceCharacters.cs +++ b/58_Love/csharp/SourceCharacters.cs @@ -1,38 +1,37 @@ using System; -namespace Love +namespace Love; + +internal class SourceCharacters { - internal class SourceCharacters + private readonly int _lineLength; + private readonly char[][] _chars; + private int _currentRow; + private int _currentIndex; + + public SourceCharacters(int lineLength, string message) { - private readonly int _lineLength; - private readonly char[][] _chars; - private int _currentRow; - private int _currentIndex; + _lineLength = lineLength; + _chars = new[] { new char[lineLength], new char[lineLength] }; - public SourceCharacters(int lineLength, string message) + for (int i = 0; i < lineLength; i++) { - _lineLength = lineLength; - _chars = new[] { new char[lineLength], new char[lineLength] }; - - for (int i = 0; i < lineLength; i++) - { - _chars[0][i] = message[i % message.Length]; - _chars[1][i] = ' '; - } - } - - public ReadOnlySpan GetCharacters(int count) - { - var span = new ReadOnlySpan(_chars[_currentRow], _currentIndex, count); - - _currentRow = 1 - _currentRow; - _currentIndex += count; - if (_currentIndex >= _lineLength) - { - _currentIndex = _currentRow = 0; - } - - return span; + _chars[0][i] = message[i % message.Length]; + _chars[1][i] = ' '; } } + + public ReadOnlySpan GetCharacters(int count) + { + var span = new ReadOnlySpan(_chars[_currentRow], _currentIndex, count); + + _currentRow = 1 - _currentRow; + _currentIndex += count; + if (_currentIndex >= _lineLength) + { + _currentIndex = _currentRow = 0; + } + + return span; + } } diff --git a/58_Love/csharp/StringBuilderExtensions.cs b/58_Love/csharp/StringBuilderExtensions.cs new file mode 100644 index 00000000..8a985ed9 --- /dev/null +++ b/58_Love/csharp/StringBuilderExtensions.cs @@ -0,0 +1,16 @@ +using System.Text; + +namespace Love; + +internal static class StringBuilderExtensions +{ + internal static StringBuilder AppendLines(this StringBuilder builder, int count) + { + for (int i = 0; i < count; i++) + { + builder.AppendLine(); + } + + return builder; + } +} From a098157353336a7cb3eb158fca74a76ea7070a76 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Sun, 20 Mar 2022 17:19:29 +1100 Subject: [PATCH 4/5] Update 82_Stars with common library --- .../dotnet/Games.Common/IO/IReadWrite.cs | 7 + 00_Common/dotnet/Games.Common/IO/TextIO.cs | 2 + 82_Stars/csharp/Game.cs | 159 ++++++++++-------- 82_Stars/csharp/Input.cs | 31 ---- 82_Stars/csharp/Program.cs | 33 +--- 82_Stars/csharp/Resources/Instructions.txt | 6 + 82_Stars/csharp/Resources/Resource.cs | 28 +++ 82_Stars/csharp/Resources/Title.txt | 5 + 82_Stars/csharp/Stars.csproj | 10 +- 9 files changed, 147 insertions(+), 134 deletions(-) delete mode 100644 82_Stars/csharp/Input.cs create mode 100644 82_Stars/csharp/Resources/Instructions.txt create mode 100644 82_Stars/csharp/Resources/Resource.cs create mode 100644 82_Stars/csharp/Resources/Title.txt diff --git a/00_Common/dotnet/Games.Common/IO/IReadWrite.cs b/00_Common/dotnet/Games.Common/IO/IReadWrite.cs index 24529947..02ab573e 100644 --- a/00_Common/dotnet/Games.Common/IO/IReadWrite.cs +++ b/00_Common/dotnet/Games.Common/IO/IReadWrite.cs @@ -94,6 +94,13 @@ public interface IReadWrite /// The to be written. void WriteLine(object value); + /// + /// Writes a formatted string to output. + /// + /// The format to be written. + /// The values to be inserted into the format. + void WriteLine(string format, params object[] values); + /// /// Writes the contents of a to output. /// diff --git a/00_Common/dotnet/Games.Common/IO/TextIO.cs b/00_Common/dotnet/Games.Common/IO/TextIO.cs index a96935af..c2584e69 100644 --- a/00_Common/dotnet/Games.Common/IO/TextIO.cs +++ b/00_Common/dotnet/Games.Common/IO/TextIO.cs @@ -99,6 +99,8 @@ public class TextIO : IReadWrite public void WriteLine(object value) => _output.WriteLine(value.ToString()); + public void WriteLine(string format, params object[] values) => _output.WriteLine(format, values); + public void Write(Stream stream, bool keepOpen = false) { using var reader = new StreamReader(stream); diff --git a/82_Stars/csharp/Game.cs b/82_Stars/csharp/Game.cs index 9711dd82..f3944737 100644 --- a/82_Stars/csharp/Game.cs +++ b/82_Stars/csharp/Game.cs @@ -1,94 +1,105 @@ using System; +using Games.Common.IO; +using Games.Common.Randomness; +using Stars.Resources; -namespace Stars +namespace Stars; + +internal class Game { - internal class Game - { - private readonly int _maxNumber; - private readonly int _maxGuessCount; - private readonly Random _random; + private readonly TextIO _io; + private readonly IRandom _random; + private readonly int _maxNumber; + private readonly int _maxGuessCount; - public Game(int maxNumber, int maxGuessCount) + public Game(TextIO io, IRandom random, int maxNumber, int maxGuessCount) + { + _io = io; + _random = random; + _maxNumber = maxNumber; + _maxGuessCount = maxGuessCount; + } + + internal void Play(Func playAgain) + { + DisplayIntroduction(); + + do { - _maxNumber = maxNumber; - _maxGuessCount = maxGuessCount; - _random = new Random(); + Play(); + } while (playAgain.Invoke()); + } + + private void DisplayIntroduction() + { + _io.Write(Resource.Streams.Title); + + if (_io.ReadString("Do you want instructions").Equals("N", StringComparison.InvariantCultureIgnoreCase)) + { + return; } - internal void DisplayInstructions() + _io.WriteLine(Resource.Formats.Instructions, _maxNumber, _maxGuessCount); + } + + private void Play() + { + _io.WriteLine(); + _io.WriteLine(); + + var target = _random.Next(_maxNumber) + 1; + + _io.WriteLine("Ok, I am thinking of a number. Start guessing."); + + AcceptGuesses(target); + } + + private void AcceptGuesses(int target) + { + for (int guessCount = 1; guessCount <= _maxGuessCount; guessCount++) { - if (Input.GetString("Do you want instructions? ").Equals("N", StringComparison.InvariantCultureIgnoreCase)) + _io.WriteLine(); + var guess = _io.ReadNumber("Your guess"); + + if (guess == target) { + DisplayWin(guessCount); return; } - Console.WriteLine($"I am thinking of a number between 1 and {_maxNumber}."); - Console.WriteLine("Try to guess my number. After you guess, I"); - Console.WriteLine("will type one or more stars (*). The more"); - Console.WriteLine("stars I type, the close you are to my number."); - Console.WriteLine("One star (*) means far away, seven stars (*******)"); - Console.WriteLine($"means really close! You get {_maxGuessCount} guesses."); + DisplayStars(target, guess); } - internal void Play() + DisplayLoss(target); + } + + private void DisplayStars(int target, float guess) + { + var stars = Math.Abs(guess - target) switch { - Console.WriteLine(); - Console.WriteLine(); + >= 64 => "*", + >= 32 => "**", + >= 16 => "***", + >= 8 => "****", + >= 4 => "*****", + >= 2 => "******", + _ => "*******" + }; - var target = _random.Next(_maxNumber) + 1; + _io.WriteLine(stars); + } - Console.WriteLine("Ok, I am thinking of a number. Start guessing."); + private void DisplayWin(int guessCount) + { + _io.WriteLine(); + _io.WriteLine(new string('*', 79)); + _io.WriteLine(); + _io.WriteLine($"You got it in {guessCount} guesses!!! Let's play again..."); + } - AcceptGuesses(target); - } - - private void AcceptGuesses(int target) - { - for (int guessCount = 1; guessCount <= _maxGuessCount; guessCount++) - { - Console.WriteLine(); - var guess = Input.GetNumber("Your guess? "); - - if (guess == target) - { - DisplayWin(guessCount); - return; - } - - DisplayStars(target, guess); - } - - DisplayLoss(target); - } - - private static void DisplayStars(int target, float guess) - { - var stars = Math.Abs(guess - target) switch - { - >= 64 => "*", - >= 32 => "**", - >= 16 => "***", - >= 8 => "****", - >= 4 => "*****", - >= 2 => "******", - _ => "*******" - }; - - Console.WriteLine(stars); - } - - private static void DisplayWin(int guessCount) - { - Console.WriteLine(); - Console.WriteLine(new string('*', 79)); - Console.WriteLine(); - Console.WriteLine($"You got it in {guessCount} guesses!!! Let's play again..."); - } - - private void DisplayLoss(int target) - { - Console.WriteLine(); - Console.WriteLine($"Sorry, that's {_maxGuessCount} guesses. The number was {target}."); - } + private void DisplayLoss(int target) + { + _io.WriteLine(); + _io.WriteLine($"Sorry, that's {_maxGuessCount} guesses. The number was {target}."); } } diff --git a/82_Stars/csharp/Input.cs b/82_Stars/csharp/Input.cs deleted file mode 100644 index 6ba6e3a1..00000000 --- a/82_Stars/csharp/Input.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; - -namespace Stars -{ - internal static class Input - { - // Float, because that's what the BASIC input operation returns - internal static float GetNumber(string prompt) - { - Console.Write(prompt); - - while (true) - { - var response = Console.ReadLine(); - if (float.TryParse(response, out var value)) - { - return value; - } - - Console.WriteLine("!Number expected - retry input line"); - Console.Write("? "); - } - } - - internal static string GetString(string prompt) - { - Console.Write(prompt); - return Console.ReadLine(); - } - } -} diff --git a/82_Stars/csharp/Program.cs b/82_Stars/csharp/Program.cs index b072595c..cf6e9e4a 100644 --- a/82_Stars/csharp/Program.cs +++ b/82_Stars/csharp/Program.cs @@ -1,30 +1,7 @@ -using System; +using Games.Common.IO; +using Games.Common.Randomness; +using Stars; -namespace Stars -{ - class Program - { - static void Main(string[] args) - { - DisplayTitle(); +var game = new Game(new ConsoleIO(), new RandomNumberGenerator(), maxNumber: 100, maxGuessCount: 7); - var game = new Game(maxNumber: 100, maxGuessCount: 7); - - game.DisplayInstructions(); - - while (true) - { - game.Play(); - } - } - - private static void DisplayTitle() - { - Console.WriteLine(" Stars"); - Console.WriteLine(" Creative Computing Morristown, New Jersey"); - Console.WriteLine(); - Console.WriteLine(); - Console.WriteLine(); - } - } -} +game.Play(() => true); diff --git a/82_Stars/csharp/Resources/Instructions.txt b/82_Stars/csharp/Resources/Instructions.txt new file mode 100644 index 00000000..372c7809 --- /dev/null +++ b/82_Stars/csharp/Resources/Instructions.txt @@ -0,0 +1,6 @@ +I am thinking of a number between 1 and {0}. +Try to guess my number. After you guess, I +will type one or more stars (*). The more +stars I type, the closer you are to my number. +One star (*) means far away, seven stars (*******) +means really close! You get {1} guesses. \ No newline at end of file diff --git a/82_Stars/csharp/Resources/Resource.cs b/82_Stars/csharp/Resources/Resource.cs new file mode 100644 index 00000000..f3a6e47b --- /dev/null +++ b/82_Stars/csharp/Resources/Resource.cs @@ -0,0 +1,28 @@ +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Stars.Resources; + +internal static class Resource +{ + internal static class Streams + { + public static Stream Title => GetStream(); + } + + internal static class Formats + { + public static string Instructions => GetString(); + } + + private static string GetString([CallerMemberName] string name = null) + { + using var stream = GetStream(name); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + private static Stream GetStream([CallerMemberName] string name = null) + => Assembly.GetExecutingAssembly().GetManifestResourceStream($"Stars.Resources.{name}.txt"); +} \ No newline at end of file diff --git a/82_Stars/csharp/Resources/Title.txt b/82_Stars/csharp/Resources/Title.txt new file mode 100644 index 00000000..67ccb545 --- /dev/null +++ b/82_Stars/csharp/Resources/Title.txt @@ -0,0 +1,5 @@ + Stars + Creative Computing Morristown, New Jersey + + + diff --git a/82_Stars/csharp/Stars.csproj b/82_Stars/csharp/Stars.csproj index 20827042..f8fe6e62 100644 --- a/82_Stars/csharp/Stars.csproj +++ b/82_Stars/csharp/Stars.csproj @@ -2,7 +2,15 @@ Exe - net5.0 + net6.0 + + + + + + + + From db2b33f6ca29a6a68f6a214b18bdc0f548e89467 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Tue, 22 Mar 2022 17:55:44 +1100 Subject: [PATCH 5/5] Use common library in SuperStarTrek --- 00_Common/dotnet/Games.Common/IO/ConsoleIO.cs | 2 + .../dotnet/Games.Common/IO/IReadWrite.cs | 13 + 00_Common/dotnet/Games.Common/IO/TextIO.cs | 11 + 84_Super_Star_Trek/csharp/Commands/Command.cs | 43 +- .../csharp/Commands/CommandExtensions.cs | 17 +- .../csharp/Commands/CommandResult.cs | 37 +- 84_Super_Star_Trek/csharp/Game.cs | 237 +++++----- .../csharp/IRandomExtensions.cs | 16 + .../csharp/IReadWriteExtensions.cs | 89 ++++ 84_Super_Star_Trek/csharp/Input.cs | 159 ------- .../csharp/Objects/Enterprise.cs | 435 +++++++++--------- 84_Super_Star_Trek/csharp/Objects/Klingon.cs | 72 +-- 84_Super_Star_Trek/csharp/Objects/Star.cs | 9 +- 84_Super_Star_Trek/csharp/Objects/Starbase.cs | 75 ++- 84_Super_Star_Trek/csharp/Output.cs | 40 -- 84_Super_Star_Trek/csharp/Program.cs | 34 +- 84_Super_Star_Trek/csharp/Random.cs | 25 - .../csharp/Resources/CourtMartial.txt | 2 +- .../csharp/Resources/Enterprise.txt | 6 + .../csharp/Resources/Instructions.txt | 1 + .../csharp/Resources/NoEnemyShips.txt | 2 +- .../csharp/Resources/NoStarbase.txt | 2 +- .../csharp/Resources/NowEntering.txt | 1 + .../csharp/Resources/Orders.txt | 1 + .../csharp/Resources/Protected.txt | 2 +- .../csharp/Resources/RegionNames.txt | 2 +- .../csharp/Resources/RelievedOfCommand.txt | 2 +- .../csharp/Resources/RepairPrompt.txt | 2 +- .../csharp/Resources/ReplayPrompt.txt | 2 +- .../csharp/Resources/ShieldsDropped.txt | 2 +- .../csharp/Resources/StartText.txt | 1 + 84_Super_Star_Trek/csharp/Resources/Title.txt | 8 + .../csharp/Space/Coordinates.cs | 113 +++-- 84_Super_Star_Trek/csharp/Space/Course.cs | 159 ++++--- 84_Super_Star_Trek/csharp/Space/Galaxy.cs | 96 ++-- 84_Super_Star_Trek/csharp/Space/Quadrant.cs | 310 ++++++------- .../csharp/Space/QuadrantInfo.cs | 123 ++--- 84_Super_Star_Trek/csharp/StringExtensions.cs | 9 +- .../csharp/SuperStarTrek.csproj | 6 +- .../ComputerFunctions/ComputerFunction.cs | 26 +- .../CumulativeGalacticRecord.cs | 29 +- .../DirectionDistanceCalculator.cs | 42 +- .../ComputerFunctions/GalacticReport.cs | 48 +- .../ComputerFunctions/GalaxyRegionMap.cs | 24 +- .../ComputerFunctions/NavigationCalculator.cs | 40 +- .../StarbaseDataCalculator.cs | 34 +- .../Systems/ComputerFunctions/StatusReport.cs | 60 +-- .../TorpedoDataCalculator.cs | 42 +- .../csharp/Systems/DamageControl.cs | 69 +-- .../csharp/Systems/LibraryComputer.cs | 64 ++- .../csharp/Systems/LongRangeSensors.cs | 45 +- .../csharp/Systems/PhaserControl.cs | 143 +++--- .../csharp/Systems/PhotonTubes.cs | 114 +++-- .../csharp/Systems/ShieldControl.cs | 98 ++-- .../csharp/Systems/ShortRangeSensors.cs | 84 ++-- .../csharp/Systems/Subsystem.cs | 122 ++--- .../csharp/Systems/WarpEngines.cs | 27 +- 57 files changed, 1595 insertions(+), 1682 deletions(-) create mode 100644 84_Super_Star_Trek/csharp/IRandomExtensions.cs create mode 100644 84_Super_Star_Trek/csharp/IReadWriteExtensions.cs delete mode 100644 84_Super_Star_Trek/csharp/Input.cs delete mode 100644 84_Super_Star_Trek/csharp/Output.cs delete mode 100644 84_Super_Star_Trek/csharp/Random.cs diff --git a/00_Common/dotnet/Games.Common/IO/ConsoleIO.cs b/00_Common/dotnet/Games.Common/IO/ConsoleIO.cs index a24238bf..a1f77083 100644 --- a/00_Common/dotnet/Games.Common/IO/ConsoleIO.cs +++ b/00_Common/dotnet/Games.Common/IO/ConsoleIO.cs @@ -12,4 +12,6 @@ public sealed class ConsoleIO : TextIO : base(Console.In, Console.Out) { } + + public override char ReadCharacter() => Console.ReadKey(intercept: true).KeyChar; } diff --git a/00_Common/dotnet/Games.Common/IO/IReadWrite.cs b/00_Common/dotnet/Games.Common/IO/IReadWrite.cs index 02ab573e..3d2cfc7e 100644 --- a/00_Common/dotnet/Games.Common/IO/IReadWrite.cs +++ b/00_Common/dotnet/Games.Common/IO/IReadWrite.cs @@ -8,6 +8,12 @@ namespace Games.Common.IO; /// public interface IReadWrite { + /// + /// Reads a character from input. + /// + /// The character read. + char ReadCharacter(); + /// /// Reads a value from input. /// @@ -99,6 +105,13 @@ public interface IReadWrite /// /// The format to be written. /// The values to be inserted into the format. + void Write(string format, params object[] values); + + /// + /// Writes a formatted string to output followed by a new-line. + /// + /// The format to be written. + /// The values to be inserted into the format. void WriteLine(string format, params object[] values); /// diff --git a/00_Common/dotnet/Games.Common/IO/TextIO.cs b/00_Common/dotnet/Games.Common/IO/TextIO.cs index c2584e69..5a31e37c 100644 --- a/00_Common/dotnet/Games.Common/IO/TextIO.cs +++ b/00_Common/dotnet/Games.Common/IO/TextIO.cs @@ -29,6 +29,15 @@ public class TextIO : IReadWrite _numberTokenReader = TokenReader.ForNumbers(this); } + public virtual char ReadCharacter() + { + while(true) + { + var ch = _input.Read(); + if (ch != -1) { return (char)ch; } + } + } + public float ReadNumber(string prompt) => ReadNumbers(prompt, 1)[0]; public (float, float) Read2Numbers(string prompt) @@ -99,6 +108,8 @@ public class TextIO : IReadWrite public void WriteLine(object value) => _output.WriteLine(value.ToString()); + public void Write(string format, params object[] values) => _output.Write(format, values); + public void WriteLine(string format, params object[] values) => _output.WriteLine(format, values); public void Write(Stream stream, bool keepOpen = false) diff --git a/84_Super_Star_Trek/csharp/Commands/Command.cs b/84_Super_Star_Trek/csharp/Commands/Command.cs index f0d569f8..e6b65db9 100644 --- a/84_Super_Star_Trek/csharp/Commands/Command.cs +++ b/84_Super_Star_Trek/csharp/Commands/Command.cs @@ -1,34 +1,33 @@ using System.ComponentModel; -namespace SuperStarTrek.Commands +namespace SuperStarTrek.Commands; + +internal enum Command { - internal enum Command - { - [Description("To set course")] - NAV, + [Description("To set course")] + NAV, - [Description("For short range sensor scan")] - SRS, + [Description("For short range sensor scan")] + SRS, - [Description("For long range sensor scan")] - LRS, + [Description("For long range sensor scan")] + LRS, - [Description("To fire phasers")] - PHA, + [Description("To fire phasers")] + PHA, - [Description("To fire photon torpedoes")] - TOR, + [Description("To fire photon torpedoes")] + TOR, - [Description("To raise or lower shields")] - SHE, + [Description("To raise or lower shields")] + SHE, - [Description("For damage control reports")] - DAM, + [Description("For damage control reports")] + DAM, - [Description("To call on library-computer")] - COM, + [Description("To call on library-computer")] + COM, - [Description("To resign your command")] - XXX - } + [Description("To resign your command")] + XXX } diff --git a/84_Super_Star_Trek/csharp/Commands/CommandExtensions.cs b/84_Super_Star_Trek/csharp/Commands/CommandExtensions.cs index f8f7e9da..e3e2f7aa 100644 --- a/84_Super_Star_Trek/csharp/Commands/CommandExtensions.cs +++ b/84_Super_Star_Trek/csharp/Commands/CommandExtensions.cs @@ -1,14 +1,13 @@ using System.Reflection; using System.ComponentModel; -namespace SuperStarTrek.Commands +namespace SuperStarTrek.Commands; + +internal static class CommandExtensions { - internal static class CommandExtensions - { - internal static string GetDescription(this Command command) => - typeof(Command) - .GetField(command.ToString()) - .GetCustomAttribute() - .Description; - } + internal static string GetDescription(this Command command) => + typeof(Command) + .GetField(command.ToString()) + .GetCustomAttribute() + .Description; } diff --git a/84_Super_Star_Trek/csharp/Commands/CommandResult.cs b/84_Super_Star_Trek/csharp/Commands/CommandResult.cs index 1b02780c..be95526f 100644 --- a/84_Super_Star_Trek/csharp/Commands/CommandResult.cs +++ b/84_Super_Star_Trek/csharp/Commands/CommandResult.cs @@ -1,23 +1,22 @@ -namespace SuperStarTrek.Commands +namespace SuperStarTrek.Commands; + +internal class CommandResult { - internal class CommandResult + public static readonly CommandResult Ok = new(false); + public static readonly CommandResult GameOver = new(true); + + private CommandResult(bool isGameOver) { - public static readonly CommandResult Ok = new(false); - public static readonly CommandResult GameOver = new(true); - - private CommandResult(bool isGameOver) - { - IsGameOver = isGameOver; - } - - private CommandResult(float timeElapsed) - { - TimeElapsed = timeElapsed; - } - - public bool IsGameOver { get; } - public float TimeElapsed { get; } - - public static CommandResult Elapsed(float timeElapsed) => new(timeElapsed); + IsGameOver = isGameOver; } + + private CommandResult(float timeElapsed) + { + TimeElapsed = timeElapsed; + } + + public bool IsGameOver { get; } + public float TimeElapsed { get; } + + public static CommandResult Elapsed(float timeElapsed) => new(timeElapsed); } diff --git a/84_Super_Star_Trek/csharp/Game.cs b/84_Super_Star_Trek/csharp/Game.cs index 274e2948..918b9f65 100644 --- a/84_Super_Star_Trek/csharp/Game.cs +++ b/84_Super_Star_Trek/csharp/Game.cs @@ -1,131 +1,128 @@ using System; +using Games.Common.IO; +using Games.Common.Randomness; using SuperStarTrek.Objects; using SuperStarTrek.Resources; using SuperStarTrek.Space; using SuperStarTrek.Systems; using SuperStarTrek.Systems.ComputerFunctions; -namespace SuperStarTrek +namespace SuperStarTrek; + +internal class Game { - internal class Game + private readonly TextIO _io; + private readonly IRandom _random; + + private int _initialStardate; + private int _finalStarDate; + private float _currentStardate; + private Coordinates _currentQuadrant; + private Galaxy _galaxy; + private int _initialKlingonCount; + private Enterprise _enterprise; + + internal Game(TextIO io, IRandom random) { - private readonly Output _output; - private readonly Input _input; - private readonly Random _random; - - private int _initialStardate; - private int _finalStarDate; - private float _currentStardate; - private Coordinates _currentQuadrant; - private Galaxy _galaxy; - private int _initialKlingonCount; - private Enterprise _enterprise; - - internal Game(Output output, Input input, Random random) - { - _output = output; - _input = input; - _random = random; - } - - internal float Stardate => _currentStardate; - - internal float StardatesRemaining => _finalStarDate - _currentStardate; - - internal void DoIntroduction() - { - _output.Write(Strings.Title); - - if (_input.GetYesNo("Do you need instructions", Input.YesNoMode.FalseOnN)) - { - _output.Write(Strings.Instructions); - - _input.WaitForAnyKeyButEnter("to continue"); - } - } - - internal void Play() - { - Initialise(); - var gameOver = false; - - while (!gameOver) - { - var command = _input.GetCommand(); - - var result = _enterprise.Execute(command); - - gameOver = result.IsGameOver || CheckIfStranded(); - _currentStardate += result.TimeElapsed; - gameOver |= _currentStardate > _finalStarDate; - } - - if (_galaxy.KlingonCount > 0) - { - _output.Write(Strings.EndOfMission, _currentStardate, _galaxy.KlingonCount); - } - else - { - _output.Write(Strings.Congratulations, GetEfficiency()); - } - } - - private void Initialise() - { - _currentStardate = _initialStardate = _random.GetInt(20, 40) * 100; - _finalStarDate = _initialStardate + _random.GetInt(25, 35); - - _currentQuadrant = _random.GetCoordinate(); - - _galaxy = new Galaxy(_random); - _initialKlingonCount = _galaxy.KlingonCount; - - _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 PhotonTubes(10, _enterprise, _output, _input)) - .Add(new ShieldControl(_enterprise, _output, _input)) - .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( - Strings.Orders, - _galaxy.KlingonCount, - _finalStarDate, - _finalStarDate - _initialStardate, - _galaxy.StarbaseCount > 1 ? "are" : "is", - _galaxy.StarbaseCount, - _galaxy.StarbaseCount > 1 ? "s" : ""); - - _input.WaitForAnyKeyButEnter("when ready to accept command"); - - _enterprise.StartIn(BuildCurrentQuadrant()); - } - - private Quadrant BuildCurrentQuadrant() => - new Quadrant(_galaxy[_currentQuadrant], _enterprise, _random, _galaxy, _input, _output); - - internal bool Replay() => _galaxy.StarbaseCount > 0 && _input.GetString(Strings.ReplayPrompt, "Aye"); - - private bool CheckIfStranded() - { - if (_enterprise.IsStranded) { _output.Write(Strings.Stranded); } - return _enterprise.IsStranded; - } - - private float GetEfficiency() => - 1000 * (float)Math.Pow(_initialKlingonCount / (_currentStardate - _initialStardate), 2); + _io = io; + _random = random; } + + internal float Stardate => _currentStardate; + + internal float StardatesRemaining => _finalStarDate - _currentStardate; + + internal void DoIntroduction() + { + _io.Write(Strings.Title); + + if (_io.GetYesNo("Do you need instructions", IReadWriteExtensions.YesNoMode.FalseOnN)) + { + _io.Write(Strings.Instructions); + + _io.WaitForAnyKeyButEnter("to continue"); + } + } + + internal void Play() + { + Initialise(); + var gameOver = false; + + while (!gameOver) + { + var command = _io.ReadCommand(); + + var result = _enterprise.Execute(command); + + gameOver = result.IsGameOver || CheckIfStranded(); + _currentStardate += result.TimeElapsed; + gameOver |= _currentStardate > _finalStarDate; + } + + if (_galaxy.KlingonCount > 0) + { + _io.Write(Strings.EndOfMission, _currentStardate, _galaxy.KlingonCount); + } + else + { + _io.Write(Strings.Congratulations, CalculateEfficiency()); + } + } + + private void Initialise() + { + _currentStardate = _initialStardate = _random.Next(20, 40) * 100; + _finalStarDate = _initialStardate + _random.Next(25, 35); + + _currentQuadrant = _random.NextCoordinate(); + + _galaxy = new Galaxy(_random); + _initialKlingonCount = _galaxy.KlingonCount; + + _enterprise = new Enterprise(3000, _random.NextCoordinate(), _io, _random); + _enterprise + .Add(new WarpEngines(_enterprise, _io)) + .Add(new ShortRangeSensors(_enterprise, _galaxy, this, _io)) + .Add(new LongRangeSensors(_galaxy, _io)) + .Add(new PhaserControl(_enterprise, _io, _random)) + .Add(new PhotonTubes(10, _enterprise, _io)) + .Add(new ShieldControl(_enterprise, _io)) + .Add(new DamageControl(_enterprise, _io)) + .Add(new LibraryComputer( + _io, + new CumulativeGalacticRecord(_io, _galaxy), + new StatusReport(this, _galaxy, _enterprise, _io), + new TorpedoDataCalculator(_enterprise, _io), + new StarbaseDataCalculator(_enterprise, _io), + new DirectionDistanceCalculator(_enterprise, _io), + new GalaxyRegionMap(_io, _galaxy))); + + _io.Write(Strings.Enterprise); + _io.Write( + Strings.Orders, + _galaxy.KlingonCount, + _finalStarDate, + _finalStarDate - _initialStardate, + _galaxy.StarbaseCount > 1 ? "are" : "is", + _galaxy.StarbaseCount, + _galaxy.StarbaseCount > 1 ? "s" : ""); + + _io.WaitForAnyKeyButEnter("when ready to accept command"); + + _enterprise.StartIn(BuildCurrentQuadrant()); + } + + private Quadrant BuildCurrentQuadrant() => new(_galaxy[_currentQuadrant], _enterprise, _random, _galaxy, _io); + + internal bool Replay() => _galaxy.StarbaseCount > 0 && _io.ReadExpectedString(Strings.ReplayPrompt, "Aye"); + + private bool CheckIfStranded() + { + if (_enterprise.IsStranded) { _io.Write(Strings.Stranded); } + return _enterprise.IsStranded; + } + + private float CalculateEfficiency() => + 1000 * (float)Math.Pow(_initialKlingonCount / (_currentStardate - _initialStardate), 2); } diff --git a/84_Super_Star_Trek/csharp/IRandomExtensions.cs b/84_Super_Star_Trek/csharp/IRandomExtensions.cs new file mode 100644 index 00000000..f115aeed --- /dev/null +++ b/84_Super_Star_Trek/csharp/IRandomExtensions.cs @@ -0,0 +1,16 @@ +using Games.Common.Randomness; +using SuperStarTrek.Space; + +namespace SuperStarTrek; + +internal static class IRandomExtensions +{ + internal static Coordinates NextCoordinate(this IRandom random) => + new Coordinates(random.Next1To8Inclusive() - 1, random.Next1To8Inclusive() - 1); + + // Duplicates the algorithm used in the original code to get an integer value from 1 to 8, inclusive: + // 475 DEF FNR(R)=INT(RND(R)*7.98+1.01) + // Returns a value from 1 to 8, inclusive. + // Note there's a slight bias away from the extreme values, 1 and 8. + internal static int Next1To8Inclusive(this IRandom random) => (int)(random.NextFloat() * 7.98 + 1.01); +} diff --git a/84_Super_Star_Trek/csharp/IReadWriteExtensions.cs b/84_Super_Star_Trek/csharp/IReadWriteExtensions.cs new file mode 100644 index 00000000..f46e74f7 --- /dev/null +++ b/84_Super_Star_Trek/csharp/IReadWriteExtensions.cs @@ -0,0 +1,89 @@ +using System; +using System.Linq; +using Games.Common.IO; +using SuperStarTrek.Commands; +using SuperStarTrek.Space; +using static System.StringComparison; + +namespace SuperStarTrek; + +internal static class IReadWriteExtensions +{ + internal static void WaitForAnyKeyButEnter(this IReadWrite io, string prompt) + { + io.Write($"Hit any key but Enter {prompt} "); + while (io.ReadCharacter() == '\r'); + } + + internal static (float X, float Y) GetCoordinates(this IReadWrite io, string prompt) => + io.Read2Numbers($"{prompt} (X,Y)"); + + internal static bool TryReadNumberInRange( + this IReadWrite io, + string prompt, + float minValue, + float maxValue, + out float value) + { + value = io.ReadNumber($"{prompt} ({minValue}-{maxValue})"); + + return value >= minValue && value <= maxValue; + } + + internal static bool ReadExpectedString(this IReadWrite io, string prompt, string trueValue) => + io.ReadString(prompt).Equals(trueValue, InvariantCultureIgnoreCase); + + internal static Command ReadCommand(this IReadWrite io) + { + while(true) + { + var response = io.ReadString("Command"); + + if (response.Length >= 3 && + Enum.TryParse(response.Substring(0, 3), ignoreCase: true, out Command parsedCommand)) + { + return parsedCommand; + } + + io.WriteLine("Enter one of the following:"); + foreach (var command in Enum.GetValues(typeof(Command)).OfType()) + { + io.WriteLine($" {command} ({command.GetDescription()})"); + } + io.WriteLine(); + } + } + + internal static bool TryReadCourse(this IReadWrite io, string prompt, string officer, out Course course) + { + if (!io.TryReadNumberInRange(prompt, 1, 9, out var direction)) + { + io.WriteLine($"{officer} reports, 'Incorrect course data, sir!'"); + course = default; + return false; + } + + course = new Course(direction); + return true; + } + + internal static bool GetYesNo(this IReadWrite io, string prompt, YesNoMode mode) + { + var response = io.ReadString($"{prompt} (Y/N)").ToUpperInvariant(); + + return (mode, response) switch + { + (YesNoMode.FalseOnN, "N") => false, + (YesNoMode.FalseOnN, _) => true, + (YesNoMode.TrueOnY, "Y") => true, + (YesNoMode.TrueOnY, _) => false, + _ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "Invalid value") + }; + } + + internal enum YesNoMode + { + TrueOnY, + FalseOnN + } +} diff --git a/84_Super_Star_Trek/csharp/Input.cs b/84_Super_Star_Trek/csharp/Input.cs deleted file mode 100644 index 2b37b2ba..00000000 --- a/84_Super_Star_Trek/csharp/Input.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System; -using System.Linq; -using SuperStarTrek.Commands; -using SuperStarTrek.Space; -using static System.StringComparison; - -namespace SuperStarTrek -{ - internal class Input - { - private readonly Output _output; - - internal Input(Output output) - { - _output = output; - } - - internal void WaitForAnyKeyButEnter(string prompt) - { - _output.Write($"Hit any key but Enter {prompt} "); - while (Console.ReadKey(intercept: true).Key == ConsoleKey.Enter); - } - - internal string GetString(string prompt) - { - _output.Prompt(prompt); - return Console.ReadLine(); - } - - internal float GetNumber(string prompt) - { - _output.Prompt(prompt); - - while (true) - { - var response = Console.ReadLine(); - if (float.TryParse(response, out var value)) - { - return value; - } - - _output.WriteLine("!Number expected - retry input line"); - _output.Prompt(); - } - } - - internal (float X, float Y) GetCoordinates(string prompt) - { - _output.Prompt($"{prompt} (X,Y)"); - var responses = ReadNumbers(2); - return (responses[0], responses[1]); - } - - internal bool TryGetNumber(string prompt, float minValue, float maxValue, out float value) - { - value = GetNumber($"{prompt} ({minValue}-{maxValue})"); - - return value >= minValue && value <= maxValue; - } - - internal bool GetString(string replayPrompt, string trueValue) => - GetString(replayPrompt).Equals(trueValue, InvariantCultureIgnoreCase); - - internal Command GetCommand() - { - while(true) - { - var response = GetString("Command"); - - if (response.Length >= 3 && - Enum.TryParse(response.Substring(0, 3), ignoreCase: true, out Command parsedCommand)) - { - return parsedCommand; - } - - _output.WriteLine("Enter one of the following:"); - foreach (var command in Enum.GetValues(typeof(Command)).OfType()) - { - _output.WriteLine($" {command} ({command.GetDescription()})"); - } - _output.WriteLine(); - } - } - - internal 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; - } - - internal bool GetYesNo(string prompt, YesNoMode mode) - { - _output.Prompt($"{prompt} (Y/N)"); - var response = Console.ReadLine().ToUpperInvariant(); - - return (mode, response) switch - { - (YesNoMode.FalseOnN, "N") => false, - (YesNoMode.FalseOnN, _) => true, - (YesNoMode.TrueOnY, "Y") => true, - (YesNoMode.TrueOnY, _) => false, - _ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "Invalid value") - }; - } - - 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; - } - - internal enum YesNoMode - { - TrueOnY, - FalseOnN - } - } -} diff --git a/84_Super_Star_Trek/csharp/Objects/Enterprise.cs b/84_Super_Star_Trek/csharp/Objects/Enterprise.cs index ac9e8112..f288e920 100644 --- a/84_Super_Star_Trek/csharp/Objects/Enterprise.cs +++ b/84_Super_Star_Trek/csharp/Objects/Enterprise.cs @@ -1,231 +1,230 @@ using System; using System.Collections.Generic; using System.Linq; +using Games.Common.IO; +using Games.Common.Randomness; using SuperStarTrek.Commands; using SuperStarTrek.Resources; using SuperStarTrek.Space; using SuperStarTrek.Systems; -namespace SuperStarTrek.Objects +namespace SuperStarTrek.Objects; + +internal class Enterprise { - internal class Enterprise + private readonly int _maxEnergy; + private readonly IReadWrite _io; + private readonly List _systems; + private readonly Dictionary _commandExecutors; + private readonly IRandom _random; + private Quadrant _quadrant; + + public Enterprise(int maxEnergy, Coordinates sector, IReadWrite io, IRandom random) { - private readonly int _maxEnergy; - private readonly Output _output; - private readonly List _systems; - private readonly Dictionary _commandExecutors; - private readonly Random _random; - private readonly Input _input; - private Quadrant _quadrant; + SectorCoordinates = sector; + TotalEnergy = _maxEnergy = maxEnergy; - public Enterprise(int maxEnergy, Coordinates sector, Output output, Random random, Input input) - { - SectorCoordinates = sector; - TotalEnergy = _maxEnergy = maxEnergy; - - _systems = new List(); - _commandExecutors = new Dictionary(); - _output = output; - _random = random; - _input = input; - } - - internal Quadrant Quadrant => _quadrant; - - internal Coordinates QuadrantCoordinates => _quadrant.Coordinates; - - internal Coordinates SectorCoordinates { get; private set; } - - internal string Condition => GetCondition(); - - internal LibraryComputer Computer => (LibraryComputer)_commandExecutors[Command.COM]; - - internal ShieldControl ShieldControl => (ShieldControl)_commandExecutors[Command.SHE]; - - internal float Energy => TotalEnergy - ShieldControl.ShieldEnergy; - - internal float TotalEnergy { get; private set; } - - internal int DamagedSystemCount => _systems.Count(s => s.IsDamaged); - - internal IEnumerable Systems => _systems; - - internal PhotonTubes PhotonTubes => (PhotonTubes)_commandExecutors[Command.TOR]; - - internal bool IsDocked => _quadrant.EnterpriseIsNextToStarbase; - - internal bool IsStranded => TotalEnergy < 10 || Energy < 10 && ShieldControl.IsDamaged; - - internal Enterprise Add(Subsystem system) - { - _systems.Add(system); - _commandExecutors[system.Command] = system; - - return this; - } - - internal void StartIn(Quadrant quadrant) - { - _quadrant = quadrant; - quadrant.Display(Strings.StartText); - } - - private string GetCondition() => - IsDocked switch - { - true => "Docked", - false when _quadrant.HasKlingons => "*Red*", - false when Energy / _maxEnergy < 0.1f => "Yellow", - false => "Green" - }; - - internal CommandResult Execute(Command command) - { - if (command == Command.XXX) { return CommandResult.GameOver; } - - return _commandExecutors[command].ExecuteCommand(_quadrant); - } - - internal void Refuel() => TotalEnergy = _maxEnergy; - - public override string ToString() => "<*>"; - - internal void UseEnergy(float amountUsed) - { - TotalEnergy -= amountUsed; - } - - 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($" "); - - if (hitStrength >= 20) - { - TakeDamage(hitStrength); - } - - return CommandResult.Ok; - } - - private void TakeDamage(float hitStrength) - { - var hitShieldRatio = hitStrength / ShieldControl.ShieldEnergy; - if (_random.GetFloat() > 0.6 || hitShieldRatio <= 0.02f) - { - return; - } - - 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(); - - 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); - } - _quadrant.SetEnterpriseSector(sector); - SectorCoordinates = sector; - - TotalEnergy -= distance + 10; - if (Energy < 0) - { - _output.WriteLine("Shield Control supplies energy to complete the maneuver."); - ShieldControl.ShieldEnergy = Math.Max(0, TotalEnergy); - } - - 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; + _systems = new List(); + _commandExecutors = new Dictionary(); + _io = io; + _random = random; } + + internal Quadrant Quadrant => _quadrant; + + internal Coordinates QuadrantCoordinates => _quadrant.Coordinates; + + internal Coordinates SectorCoordinates { get; private set; } + + internal string Condition => GetCondition(); + + internal LibraryComputer Computer => (LibraryComputer)_commandExecutors[Command.COM]; + + internal ShieldControl ShieldControl => (ShieldControl)_commandExecutors[Command.SHE]; + + internal float Energy => TotalEnergy - ShieldControl.ShieldEnergy; + + internal float TotalEnergy { get; private set; } + + internal int DamagedSystemCount => _systems.Count(s => s.IsDamaged); + + internal IEnumerable Systems => _systems; + + internal PhotonTubes PhotonTubes => (PhotonTubes)_commandExecutors[Command.TOR]; + + internal bool IsDocked => _quadrant.EnterpriseIsNextToStarbase; + + internal bool IsStranded => TotalEnergy < 10 || Energy < 10 && ShieldControl.IsDamaged; + + internal Enterprise Add(Subsystem system) + { + _systems.Add(system); + _commandExecutors[system.Command] = system; + + return this; + } + + internal void StartIn(Quadrant quadrant) + { + _quadrant = quadrant; + quadrant.Display(Strings.StartText); + } + + private string GetCondition() => + IsDocked switch + { + true => "Docked", + false when _quadrant.HasKlingons => "*Red*", + false when Energy / _maxEnergy < 0.1f => "Yellow", + false => "Green" + }; + + internal CommandResult Execute(Command command) + { + if (command == Command.XXX) { return CommandResult.GameOver; } + + return _commandExecutors[command].ExecuteCommand(_quadrant); + } + + internal void Refuel() => TotalEnergy = _maxEnergy; + + public override string ToString() => "<*>"; + + internal void UseEnergy(float amountUsed) + { + TotalEnergy -= amountUsed; + } + + internal CommandResult TakeHit(Coordinates sector, int hitStrength) + { + _io.WriteLine($"{hitStrength} unit hit on Enterprise from sector {sector}"); + ShieldControl.AbsorbHit(hitStrength); + + if (ShieldControl.ShieldEnergy <= 0) + { + _io.WriteLine(Strings.Destroyed); + return CommandResult.GameOver; + } + + _io.WriteLine($" "); + + if (hitStrength >= 20) + { + TakeDamage(hitStrength); + } + + return CommandResult.Ok; + } + + private void TakeDamage(float hitStrength) + { + var hitShieldRatio = hitStrength / ShieldControl.ShieldEnergy; + if (_random.NextFloat() > 0.6 || hitShieldRatio <= 0.02f) + { + return; + } + + var system = _systems[_random.Next1To8Inclusive() - 1]; + system.TakeDamage(hitShieldRatio + 0.5f * _random.NextFloat()); + _io.WriteLine($"Damage Control reports, '{system.Name} damaged by the hit.'"); + } + + internal void RepairSystems(float repairWorkDone) + { + var repairedSystems = new List(); + + foreach (var system in _systems.Where(s => s.IsDamaged)) + { + if (system.Repair(repairWorkDone)) + { + repairedSystems.Add(system.Name); + } + } + + if (repairedSystems.Any()) + { + _io.WriteLine("Damage Control report:"); + foreach (var systemName in repairedSystems) + { + _io.WriteLine($" {systemName} repair completed."); + } + } + } + + internal void VaryConditionOfRandomSystem() + { + if (_random.NextFloat() > 0.2f) { return; } + + var system = _systems[_random.Next1To8Inclusive() - 1]; + _io.Write($"Damage Control report: {system.Name} "); + if (_random.NextFloat() >= 0.6) + { + system.Repair(_random.NextFloat() * 3 + 1); + _io.WriteLine("state of repair improved"); + } + else + { + system.TakeDamage(_random.NextFloat() * 5 + 1); + _io.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, _io); + } + _quadrant.SetEnterpriseSector(sector); + SectorCoordinates = sector; + + TotalEnergy -= distance + 10; + if (Energy < 0) + { + _io.WriteLine("Shield Control supplies energy to complete the maneuver."); + ShieldControl.ShieldEnergy = Math.Max(0, TotalEnergy); + } + + 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)) + { + _io.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) + { + _io.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; } diff --git a/84_Super_Star_Trek/csharp/Objects/Klingon.cs b/84_Super_Star_Trek/csharp/Objects/Klingon.cs index 1b55940a..7e84ae56 100644 --- a/84_Super_Star_Trek/csharp/Objects/Klingon.cs +++ b/84_Super_Star_Trek/csharp/Objects/Klingon.cs @@ -1,43 +1,43 @@ +using Games.Common.Randomness; using SuperStarTrek.Commands; using SuperStarTrek.Space; -namespace SuperStarTrek.Objects +namespace SuperStarTrek.Objects; + +internal class Klingon { - internal class Klingon + private readonly IRandom _random; + + internal Klingon(Coordinates sector, IRandom random) { - private readonly Random _random; - - internal Klingon(Coordinates sector, Random random) - { - Sector = sector; - _random = random; - Energy = _random.GetFloat(100, 300); - } - - internal float Energy { get; private set; } - - internal Coordinates Sector { get; private set; } - - public override string ToString() => "+K+"; - - internal CommandResult FireOn(Enterprise enterprise) - { - var attackStrength = _random.GetFloat(); - var distanceToEnterprise = Sector.GetDistanceTo(enterprise.SectorCoordinates); - var hitStrength = (int)(Energy * (2 + attackStrength) / distanceToEnterprise); - Energy /= 3 + attackStrength; - - return enterprise.TakeHit(Sector, hitStrength); - } - - internal bool TakeHit(int hitStrength) - { - if (hitStrength < 0.15 * Energy) { return false; } - - Energy -= hitStrength; - return true; - } - - internal void MoveTo(Coordinates newSector) => Sector = newSector; + Sector = sector; + _random = random; + Energy = _random.NextFloat(100, 300); } + + internal float Energy { get; private set; } + + internal Coordinates Sector { get; private set; } + + public override string ToString() => "+K+"; + + internal CommandResult FireOn(Enterprise enterprise) + { + var attackStrength = _random.NextFloat(); + var distanceToEnterprise = Sector.GetDistanceTo(enterprise.SectorCoordinates); + var hitStrength = (int)(Energy * (2 + attackStrength) / distanceToEnterprise); + Energy /= 3 + attackStrength; + + return enterprise.TakeHit(Sector, hitStrength); + } + + internal bool TakeHit(int hitStrength) + { + if (hitStrength < 0.15 * Energy) { return false; } + + Energy -= hitStrength; + return true; + } + + internal void MoveTo(Coordinates newSector) => Sector = newSector; } diff --git a/84_Super_Star_Trek/csharp/Objects/Star.cs b/84_Super_Star_Trek/csharp/Objects/Star.cs index 1d9eef6f..8676d3c3 100644 --- a/84_Super_Star_Trek/csharp/Objects/Star.cs +++ b/84_Super_Star_Trek/csharp/Objects/Star.cs @@ -1,7 +1,6 @@ -namespace SuperStarTrek.Objects +namespace SuperStarTrek.Objects; + +internal class Star { - internal class Star - { - public override string ToString() => " * "; - } + public override string ToString() => " * "; } diff --git a/84_Super_Star_Trek/csharp/Objects/Starbase.cs b/84_Super_Star_Trek/csharp/Objects/Starbase.cs index c9abb870..f9e5eaec 100644 --- a/84_Super_Star_Trek/csharp/Objects/Starbase.cs +++ b/84_Super_Star_Trek/csharp/Objects/Starbase.cs @@ -1,45 +1,44 @@ +using Games.Common.IO; +using Games.Common.Randomness; using SuperStarTrek.Resources; using SuperStarTrek.Space; -namespace SuperStarTrek.Objects +namespace SuperStarTrek.Objects; + +internal class Starbase { - internal class Starbase + private readonly IReadWrite _io; + private readonly float _repairDelay; + + internal Starbase(Coordinates sector, IRandom random, IReadWrite io) { - private readonly Input _input; - private readonly Output _output; - private readonly float _repairDelay; - - internal 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) - { - repairTime = enterprise.DamagedSystemCount * 0.1f + _repairDelay; - if (repairTime >= 1) { repairTime = 0.9f; } - - _output.Write(Strings.RepairEstimate, repairTime); - if (_input.GetYesNo(Strings.RepairPrompt, Input.YesNoMode.TrueOnY)) - { - foreach (var system in enterprise.Systems) - { - system.Repair(); - } - return true; - } - - repairTime = 0; - return false; - } - - internal void ProtectEnterprise() => _output.WriteLine(Strings.Protected); + Sector = sector; + _repairDelay = random.NextFloat(0.5f); + _io = io; } + + internal Coordinates Sector { get; } + + public override string ToString() => ">!<"; + + internal bool TryRepair(Enterprise enterprise, out float repairTime) + { + repairTime = enterprise.DamagedSystemCount * 0.1f + _repairDelay; + if (repairTime >= 1) { repairTime = 0.9f; } + + _io.Write(Strings.RepairEstimate, repairTime); + if (_io.GetYesNo(Strings.RepairPrompt, IReadWriteExtensions.YesNoMode.TrueOnY)) + { + foreach (var system in enterprise.Systems) + { + system.Repair(); + } + return true; + } + + repairTime = 0; + return false; + } + + internal void ProtectEnterprise() => _io.WriteLine(Strings.Protected); } diff --git a/84_Super_Star_Trek/csharp/Output.cs b/84_Super_Star_Trek/csharp/Output.cs deleted file mode 100644 index 6a377bce..00000000 --- a/84_Super_Star_Trek/csharp/Output.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; - -namespace SuperStarTrek -{ - internal class Output - { - internal Output Write(string text) - { - Console.Write(text); - return this; - } - - internal Output Write(string format, params object[] args) - { - Console.Write(format, args); - return this; - } - - internal Output WriteLine(string text = "") - { - Console.WriteLine(text); - return this; - } - - - internal Output NextLine() - { - Console.WriteLine(); - return this; - } - - - internal Output Prompt(string text = "") - { - Console.Write($"{text}? "); - return this; - } - - } -} diff --git a/84_Super_Star_Trek/csharp/Program.cs b/84_Super_Star_Trek/csharp/Program.cs index 7080df29..730d56d4 100644 --- a/84_Super_Star_Trek/csharp/Program.cs +++ b/84_Super_Star_Trek/csharp/Program.cs @@ -23,24 +23,18 @@ // **** CONVERTED TO MICROSOFT C# 2/20/21 BY ANDREW COOPER // **** -namespace SuperStarTrek +using Games.Common.IO; +using Games.Common.Randomness; +using SuperStarTrek; + +var io = new ConsoleIO(); +var random = new RandomNumberGenerator(); + +var game = new Game(io, random); + +game.DoIntroduction(); + +do { - internal class Program - { - static void Main() - { - var output = new Output(); - var input = new Input(output); - var random = new Random(); - - var game = new Game(output, input, random); - - game.DoIntroduction(); - - do - { - game.Play(); - } while (game.Replay()); - } - } -} + game.Play(); +} while (game.Replay()); diff --git a/84_Super_Star_Trek/csharp/Random.cs b/84_Super_Star_Trek/csharp/Random.cs deleted file mode 100644 index cd0ef230..00000000 --- a/84_Super_Star_Trek/csharp/Random.cs +++ /dev/null @@ -1,25 +0,0 @@ -using SuperStarTrek.Space; - -namespace SuperStarTrek -{ - internal class Random - { - private readonly System.Random _random = new(); - - internal Coordinates GetCoordinate() => new Coordinates(Get1To8Inclusive() - 1, Get1To8Inclusive() - 1); - - // Duplicates the algorithm used in the original code to get an integer value from 1 to 8, inclusive: - // 475 DEF FNR(R)=INT(RND(R)*7.98+1.01) - // Returns a value from 1 to 8, inclusive. - // Note there's a slight bias away from the extreme values, 1 and 8. - internal int Get1To8Inclusive() => (int)(GetFloat() * 7.98 + 1.01); - - internal int GetInt(int inclusiveMinValue, int exclusiveMaxValue) => - _random.Next(inclusiveMinValue, exclusiveMaxValue); - - internal float GetFloat() => (float)_random.NextDouble(); - - internal float GetFloat(float inclusiveMinValue, float exclusiveMaxValue) - => GetFloat() * (exclusiveMaxValue - inclusiveMinValue) + inclusiveMinValue; - } -} diff --git a/84_Super_Star_Trek/csharp/Resources/CourtMartial.txt b/84_Super_Star_Trek/csharp/Resources/CourtMartial.txt index a6c5285e..7d05a5b8 100644 --- a/84_Super_Star_Trek/csharp/Resources/CourtMartial.txt +++ b/84_Super_Star_Trek/csharp/Resources/CourtMartial.txt @@ -1,3 +1,3 @@ Starfleet Command reviewing your record to consider -court martial! +court martial! \ No newline at end of file diff --git a/84_Super_Star_Trek/csharp/Resources/Enterprise.txt b/84_Super_Star_Trek/csharp/Resources/Enterprise.txt index a47156a5..4310ca91 100644 --- a/84_Super_Star_Trek/csharp/Resources/Enterprise.txt +++ b/84_Super_Star_Trek/csharp/Resources/Enterprise.txt @@ -16,3 +16,9 @@ '----------------' THE USS ENTERPRISE --- NCC-1701 + + + + + + diff --git a/84_Super_Star_Trek/csharp/Resources/Instructions.txt b/84_Super_Star_Trek/csharp/Resources/Instructions.txt index 72ded5ce..34b4169c 100644 --- a/84_Super_Star_Trek/csharp/Resources/Instructions.txt +++ b/84_Super_Star_Trek/csharp/Resources/Instructions.txt @@ -104,3 +104,4 @@ COM command = Library-Computer Option 5 = Galactic Region Name Map This option prints the names of the sixteen major galactic regions referred to in the game. + diff --git a/84_Super_Star_Trek/csharp/Resources/NoEnemyShips.txt b/84_Super_Star_Trek/csharp/Resources/NoEnemyShips.txt index d4b2efb2..394f1057 100644 --- a/84_Super_Star_Trek/csharp/Resources/NoEnemyShips.txt +++ b/84_Super_Star_Trek/csharp/Resources/NoEnemyShips.txt @@ -1,2 +1,2 @@ Science Officer Spock reports, 'Sensors show no enemy ships - in this quadrant' + in this quadrant' \ No newline at end of file diff --git a/84_Super_Star_Trek/csharp/Resources/NoStarbase.txt b/84_Super_Star_Trek/csharp/Resources/NoStarbase.txt index 59c991ec..5bb4e5fb 100644 --- a/84_Super_Star_Trek/csharp/Resources/NoStarbase.txt +++ b/84_Super_Star_Trek/csharp/Resources/NoStarbase.txt @@ -1 +1 @@ -Mr. Spock reports, 'Sensors show no starbases in this quadrant.' +Mr. Spock reports, 'Sensors show no starbases in this quadrant.' \ No newline at end of file diff --git a/84_Super_Star_Trek/csharp/Resources/NowEntering.txt b/84_Super_Star_Trek/csharp/Resources/NowEntering.txt index 6e770a71..6545f9c4 100644 --- a/84_Super_Star_Trek/csharp/Resources/NowEntering.txt +++ b/84_Super_Star_Trek/csharp/Resources/NowEntering.txt @@ -1,2 +1,3 @@ Now entering {0} quadrant . . . + diff --git a/84_Super_Star_Trek/csharp/Resources/Orders.txt b/84_Super_Star_Trek/csharp/Resources/Orders.txt index 7dd142e5..7dc14b24 100644 --- a/84_Super_Star_Trek/csharp/Resources/Orders.txt +++ b/84_Super_Star_Trek/csharp/Resources/Orders.txt @@ -3,3 +3,4 @@ Your orders are as follows: the galaxy before they can attack federation headquarters on stardate {1}. This gives you {2} days. There {3} {4} starbase{5} in the galaxy for resupplying your ship. + diff --git a/84_Super_Star_Trek/csharp/Resources/Protected.txt b/84_Super_Star_Trek/csharp/Resources/Protected.txt index fe23b63b..27c4a5f8 100644 --- a/84_Super_Star_Trek/csharp/Resources/Protected.txt +++ b/84_Super_Star_Trek/csharp/Resources/Protected.txt @@ -1 +1 @@ -Starbase shields protect the Enterprise +Starbase shields protect the Enterprise \ No newline at end of file diff --git a/84_Super_Star_Trek/csharp/Resources/RegionNames.txt b/84_Super_Star_Trek/csharp/Resources/RegionNames.txt index 7556a9f8..f84fe43b 100644 --- a/84_Super_Star_Trek/csharp/Resources/RegionNames.txt +++ b/84_Super_Star_Trek/csharp/Resources/RegionNames.txt @@ -5,4 +5,4 @@ Canopus Aldebaran Altair Regulus Sagittarius Arcturus - Pollux Spica + Pollux Spica \ No newline at end of file diff --git a/84_Super_Star_Trek/csharp/Resources/RelievedOfCommand.txt b/84_Super_Star_Trek/csharp/Resources/RelievedOfCommand.txt index 31f398ba..8086e3ca 100644 --- a/84_Super_Star_Trek/csharp/Resources/RelievedOfCommand.txt +++ b/84_Super_Star_Trek/csharp/Resources/RelievedOfCommand.txt @@ -1,3 +1,3 @@ That does it, Captain!! You are hereby relieved of command -and sentenced to 99 stardates at hard labor on Cygnus 12!! +and sentenced to 99 stardates at hard labor on Cygnus 12!! \ No newline at end of file diff --git a/84_Super_Star_Trek/csharp/Resources/RepairPrompt.txt b/84_Super_Star_Trek/csharp/Resources/RepairPrompt.txt index 36428e30..feffdb27 100644 --- a/84_Super_Star_Trek/csharp/Resources/RepairPrompt.txt +++ b/84_Super_Star_Trek/csharp/Resources/RepairPrompt.txt @@ -1 +1 @@ -Will you authorize the repair order (Y/N) +Will you authorize the repair order (Y/N) \ No newline at end of file diff --git a/84_Super_Star_Trek/csharp/Resources/ReplayPrompt.txt b/84_Super_Star_Trek/csharp/Resources/ReplayPrompt.txt index 8f9c2d54..3ca3a102 100644 --- a/84_Super_Star_Trek/csharp/Resources/ReplayPrompt.txt +++ b/84_Super_Star_Trek/csharp/Resources/ReplayPrompt.txt @@ -2,4 +2,4 @@ The Federation is in need of a new starship commander for a similar mission -- if there is a volunteer -let him step forward and enter 'Aye' +let him step forward and enter 'Aye' \ No newline at end of file diff --git a/84_Super_Star_Trek/csharp/Resources/ShieldsDropped.txt b/84_Super_Star_Trek/csharp/Resources/ShieldsDropped.txt index acc87f59..9135e8b4 100644 --- a/84_Super_Star_Trek/csharp/Resources/ShieldsDropped.txt +++ b/84_Super_Star_Trek/csharp/Resources/ShieldsDropped.txt @@ -1 +1 @@ -Shields dropped for docking purposes +Shields dropped for docking purposes \ No newline at end of file diff --git a/84_Super_Star_Trek/csharp/Resources/StartText.txt b/84_Super_Star_Trek/csharp/Resources/StartText.txt index c599a2e2..3c6028a5 100644 --- a/84_Super_Star_Trek/csharp/Resources/StartText.txt +++ b/84_Super_Star_Trek/csharp/Resources/StartText.txt @@ -2,3 +2,4 @@ Your mission begins with your starship located in the galactic quadrant, '{0}'. + diff --git a/84_Super_Star_Trek/csharp/Resources/Title.txt b/84_Super_Star_Trek/csharp/Resources/Title.txt index edee3578..ccd86774 100644 --- a/84_Super_Star_Trek/csharp/Resources/Title.txt +++ b/84_Super_Star_Trek/csharp/Resources/Title.txt @@ -17,3 +17,11 @@ * * * * ************************************* + + + + + + + + diff --git a/84_Super_Star_Trek/csharp/Space/Coordinates.cs b/84_Super_Star_Trek/csharp/Space/Coordinates.cs index 4768d2c2..adfda6de 100644 --- a/84_Super_Star_Trek/csharp/Space/Coordinates.cs +++ b/84_Super_Star_Trek/csharp/Space/Coordinates.cs @@ -1,70 +1,69 @@ using System; using SuperStarTrek.Utils; -namespace SuperStarTrek.Space +namespace SuperStarTrek.Space; + +// Represents the corrdintate of a quadrant in the galaxy, or a sector in a quadrant. +// Note that the origin is top-left, x increase downwards, and y increases to the right. +internal record Coordinates { - // Represents the corrdintate of a quadrant in the galaxy, or a sector in a quadrant. - // Note that the origin is top-left, x increase downwards, and y increases to the right. - internal record Coordinates + internal Coordinates(int x, int y) { - internal Coordinates(int x, int y) - { - X = Validated(x, nameof(x)); - Y = Validated(y, nameof(y)); + X = Validated(x, nameof(x)); + Y = Validated(y, nameof(y)); - RegionIndex = (X << 1) + (Y >> 2); - SubRegionIndex = Y % 4; + RegionIndex = (X << 1) + (Y >> 2); + SubRegionIndex = Y % 4; + } + + internal int X { get; } + + internal int Y { get; } + + internal int RegionIndex { get; } + + internal int SubRegionIndex { get; } + + private static int Validated(int value, string argumentName) + { + if (value >= 0 && value <= 7) { return value; } + + 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; } - internal int X { get; } + coordinates = default; + return false; - internal int Y { get; } + static int Round(float value) => (int)Math.Round(value, MidpointRounding.AwayFromZero); + } - internal int RegionIndex { get; } + internal (float Direction, float Distance) GetDirectionAndDistanceTo(Coordinates destination) => + DirectionAndDistance.From(this).To(destination); - internal int SubRegionIndex { get; } - - private static int Validated(int value, string argumentName) - { - if (value >= 0 && value <= 7) { return value; } - - 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; - - static int Round(float value) => (int)Math.Round(value, MidpointRounding.AwayFromZero); - } - - internal (float Direction, float Distance) GetDirectionAndDistanceTo(Coordinates destination) => - DirectionAndDistance.From(this).To(destination); - - internal float GetDistanceTo(Coordinates destination) - { - var (_, distance) = GetDirectionAndDistanceTo(destination); - return distance; - } + internal float GetDistanceTo(Coordinates destination) + { + var (_, distance) = GetDirectionAndDistanceTo(destination); + return distance; } } diff --git a/84_Super_Star_Trek/csharp/Space/Course.cs b/84_Super_Star_Trek/csharp/Space/Course.cs index 6b9dfc31..f411976a 100644 --- a/84_Super_Star_Trek/csharp/Space/Course.cs +++ b/84_Super_Star_Trek/csharp/Space/Course.cs @@ -1,98 +1,97 @@ using System; using System.Collections.Generic; -namespace SuperStarTrek.Space +namespace SuperStarTrek.Space; + +// Implements the course calculations from the original code: +// 530 FORI=1TO9:C(I,1)=0:C(I,2)=0:NEXTI +// 540 C(3,1)=-1:C(2,1)=-1:C(4,1)=-1:C(4,2)=-1:C(5,2)=-1:C(6,2)=-1 +// 600 C(1,2)=1:C(2,2)=1:C(6,1)=1:C(7,1)=1:C(8,1)=1:C(8,2)=1:C(9,2)=1 +// +// 3110 X1=C(C1,1)+(C(C1+1,1)-C(C1,1))*(C1-INT(C1)) +// 3140 X2=C(C1,2)+(C(C1+1,2)-C(C1,2))*(C1-INT(C1)) +internal class Course { - // Implements the course calculations from the original code: - // 530 FORI=1TO9:C(I,1)=0:C(I,2)=0:NEXTI - // 540 C(3,1)=-1:C(2,1)=-1:C(4,1)=-1:C(4,2)=-1:C(5,2)=-1:C(6,2)=-1 - // 600 C(1,2)=1:C(2,2)=1:C(6,1)=1:C(7,1)=1:C(8,1)=1:C(8,2)=1:C(9,2)=1 - // - // 3110 X1=C(C1,1)+(C(C1+1,1)-C(C1,1))*(C1-INT(C1)) - // 3140 X2=C(C1,2)+(C(C1+1,2)-C(C1,2))*(C1-INT(C1)) - internal class Course + private static readonly (int DeltaX, int DeltaY)[] cardinals = new[] { - private static readonly (int DeltaX, int DeltaY)[] cardinals = new[] + (0, 1), + (-1, 1), + (-1, 0), + (-1, -1), + (0, -1), + (1, -1), + (1, 0), + (1, 1), + (0, 1) + }; + + internal Course(float direction) + { + if (direction < 1 || direction > 9) { - (0, 1), - (-1, 1), - (-1, 0), - (-1, -1), - (0, -1), - (1, -1), - (1, 0), - (1, 1), - (0, 1) - }; - - internal Course(float direction) - { - if (direction < 1 || direction > 9) - { - throw new ArgumentOutOfRangeException( - nameof(direction), - direction, - "Must be between 1 and 9, inclusive."); - } - - var cardinalDirection = (int)(direction - 1) % 8; - var fractionalDirection = direction - (int)direction; - - var baseCardinal = cardinals[cardinalDirection]; - var nextCardinal = cardinals[cardinalDirection + 1]; - - DeltaX = baseCardinal.DeltaX + (nextCardinal.DeltaX - baseCardinal.DeltaX) * fractionalDirection; - DeltaY = baseCardinal.DeltaY + (nextCardinal.DeltaY - baseCardinal.DeltaY) * fractionalDirection; + throw new ArgumentOutOfRangeException( + nameof(direction), + direction, + "Must be between 1 and 9, inclusive."); } - internal float DeltaX { get; } + var cardinalDirection = (int)(direction - 1) % 8; + var fractionalDirection = direction - (int)direction; - internal float DeltaY { get; } + var baseCardinal = cardinals[cardinalDirection]; + var nextCardinal = cardinals[cardinalDirection + 1]; - internal IEnumerable GetSectorsFrom(Coordinates start) + DeltaX = baseCardinal.DeltaX + (nextCardinal.DeltaX - baseCardinal.DeltaX) * fractionalDirection; + DeltaY = baseCardinal.DeltaY + (nextCardinal.DeltaY - baseCardinal.DeltaY) * fractionalDirection; + } + + internal float DeltaX { get; } + + internal float DeltaY { get; } + + internal IEnumerable GetSectorsFrom(Coordinates start) + { + (float x, float y) = start; + + while(true) { - (float x, float y) = start; + x += DeltaX; + y += DeltaY; - while(true) + if (!Coordinates.TryCreate(x, y, out var coordinates)) { - x += DeltaX; - y += DeltaY; - - if (!Coordinates.TryCreate(x, y, out var coordinates)) - { - yield break; - } - - 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 static (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 < 0) - { - newQuadrant -= 1; - newSector += 8; + yield break; } - return newQuadrant switch - { - < 0 => (false, 0, 0), - > 7 => (false, 7, 7), - _ => (true, newQuadrant, newSector) - }; + 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 static (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 < 0) + { + newQuadrant -= 1; + newSector += 8; + } + + return newQuadrant switch + { + < 0 => (false, 0, 0), + > 7 => (false, 7, 7), + _ => (true, newQuadrant, newSector) + }; + } } diff --git a/84_Super_Star_Trek/csharp/Space/Galaxy.cs b/84_Super_Star_Trek/csharp/Space/Galaxy.cs index 0b348b06..d8e990b4 100644 --- a/84_Super_Star_Trek/csharp/Space/Galaxy.cs +++ b/84_Super_Star_Trek/csharp/Space/Galaxy.cs @@ -1,64 +1,64 @@ using System.Collections.Generic; using System.Linq; +using Games.Common.Randomness; using SuperStarTrek.Resources; using static System.StringSplitOptions; -namespace SuperStarTrek.Space +namespace SuperStarTrek.Space; + +internal class Galaxy { - internal class Galaxy + private static readonly string[] _regionNames; + private static readonly string[] _subRegionIdentifiers; + private readonly QuadrantInfo[][] _quadrants; + + static Galaxy() { - private static readonly string[] _regionNames; - private static readonly string[] _subRegionIdentifiers; - private readonly QuadrantInfo[][] _quadrants; + _regionNames = Strings.RegionNames.Split(new[] { ' ', '\n' }, RemoveEmptyEntries | TrimEntries); + _subRegionIdentifiers = new[] { "I", "II", "III", "IV" }; + } - static Galaxy() - { - _regionNames = Strings.RegionNames.Split(new[] { ' ', '\n' }, RemoveEmptyEntries | TrimEntries); - _subRegionIdentifiers = new[] { "I", "II", "III", "IV" }; - } - - internal Galaxy(Random random) - { - _quadrants = Enumerable + internal Galaxy(IRandom random) + { + _quadrants = Enumerable + .Range(0, 8) + .Select(x => Enumerable .Range(0, 8) - .Select(x => Enumerable - .Range(0, 8) - .Select(y => new Coordinates(x, y)) - .Select(c => QuadrantInfo.Create(c, GetQuadrantName(c), random)) - .ToArray()) - .ToArray(); + .Select(y => new Coordinates(x, y)) + .Select(c => QuadrantInfo.Create(c, GetQuadrantName(c), random)) + .ToArray()) + .ToArray(); - if (StarbaseCount == 0) + if (StarbaseCount == 0) + { + var randomQuadrant = this[random.NextCoordinate()]; + randomQuadrant.AddStarbase(); + + if (randomQuadrant.KlingonCount < 2) { - var randomQuadrant = this[random.GetCoordinate()]; - randomQuadrant.AddStarbase(); - - if (randomQuadrant.KlingonCount < 2) - { - randomQuadrant.AddKlingon(); - } + randomQuadrant.AddKlingon(); } } - - internal QuadrantInfo this[Coordinates coordinate] => _quadrants[coordinate.X][coordinate.Y]; - - internal int KlingonCount => _quadrants.SelectMany(q => q).Sum(q => q.KlingonCount); - - internal int StarbaseCount => _quadrants.SelectMany(q => q).Count(q => q.HasStarbase); - - internal IEnumerable> Quadrants => _quadrants; - - private static string GetQuadrantName(Coordinates coordinates) => - $"{_regionNames[coordinates.RegionIndex]} {_subRegionIdentifiers[coordinates.SubRegionIndex]}"; - - internal IEnumerable> GetNeighborhood(Quadrant quadrant) => - Enumerable.Range(-1, 3) - .Select(dx => dx + quadrant.Coordinates.X) - .Select(x => GetNeighborhoodRow(quadrant, x)); - private IEnumerable GetNeighborhoodRow(Quadrant quadrant, int x) => - Enumerable.Range(-1, 3) - .Select(dy => dy + quadrant.Coordinates.Y) - .Select(y => y < 0 || y > 7 || x < 0 || x > 7 ? null : _quadrants[x][y]); } + + internal QuadrantInfo this[Coordinates coordinate] => _quadrants[coordinate.X][coordinate.Y]; + + internal int KlingonCount => _quadrants.SelectMany(q => q).Sum(q => q.KlingonCount); + + internal int StarbaseCount => _quadrants.SelectMany(q => q).Count(q => q.HasStarbase); + + internal IEnumerable> Quadrants => _quadrants; + + private static string GetQuadrantName(Coordinates coordinates) => + $"{_regionNames[coordinates.RegionIndex]} {_subRegionIdentifiers[coordinates.SubRegionIndex]}"; + + internal IEnumerable> GetNeighborhood(Quadrant quadrant) => + Enumerable.Range(-1, 3) + .Select(dx => dx + quadrant.Coordinates.X) + .Select(x => GetNeighborhoodRow(quadrant, x)); + private IEnumerable GetNeighborhoodRow(Quadrant quadrant, int x) => + Enumerable.Range(-1, 3) + .Select(dy => dy + quadrant.Coordinates.Y) + .Select(y => y < 0 || y > 7 || x < 0 || x > 7 ? null : _quadrants[x][y]); } diff --git a/84_Super_Star_Trek/csharp/Space/Quadrant.cs b/84_Super_Star_Trek/csharp/Space/Quadrant.cs index 694f7fc8..ba8f9a99 100644 --- a/84_Super_Star_Trek/csharp/Space/Quadrant.cs +++ b/84_Super_Star_Trek/csharp/Space/Quadrant.cs @@ -1,192 +1,192 @@ using System; using System.Collections.Generic; using System.Linq; +using Games.Common.IO; +using Games.Common.Randomness; using SuperStarTrek.Commands; using SuperStarTrek.Objects; using SuperStarTrek.Resources; -namespace SuperStarTrek.Space +namespace SuperStarTrek.Space; + +internal class Quadrant { - internal class Quadrant + private readonly QuadrantInfo _info; + private readonly IRandom _random; + private readonly Dictionary _sectors; + private readonly Enterprise _enterprise; + private readonly IReadWrite _io; + private bool _entered = false; + + internal Quadrant( + QuadrantInfo info, + Enterprise enterprise, + IRandom random, + Galaxy galaxy, + IReadWrite io) { - private readonly QuadrantInfo _info; - private readonly Random _random; - private readonly Dictionary _sectors; - private readonly Enterprise _enterprise; - private readonly Output _output; - private bool _entered = false; + _info = info; + _random = random; + _io = io; + Galaxy = galaxy; - internal Quadrant( - QuadrantInfo info, - Enterprise enterprise, - Random random, - Galaxy galaxy, - Input input, - Output output) + info.MarkAsKnown(); + _sectors = new() { [enterprise.SectorCoordinates] = _enterprise = enterprise }; + PositionObject(sector => new Klingon(sector, _random), _info.KlingonCount); + if (_info.HasStarbase) { - _info = info; - _random = random; - _output = output; - Galaxy = galaxy; + Starbase = PositionObject(sector => new Starbase(sector, _random, io)); + } + PositionObject(_ => new Star(), _info.StarCount); + } - info.MarkAsKnown(); - _sectors = new() { [enterprise.SectorCoordinates] = _enterprise = enterprise }; - PositionObject(sector => new Klingon(sector, _random), _info.KlingonCount); - if (_info.HasStarbase) - { - Starbase = PositionObject(sector => new Starbase(sector, _random, input, output)); - } - PositionObject(_ => new Star(), _info.StarCount); + internal Coordinates Coordinates => _info.Coordinates; + + internal bool HasKlingons => _info.KlingonCount > 0; + + internal int KlingonCount => _info.KlingonCount; + + internal bool HasStarbase => _info.HasStarbase; + + internal Starbase Starbase { get; } + + internal Galaxy Galaxy { get; } + + internal bool EnterpriseIsNextToStarbase => + _info.HasStarbase && + Math.Abs(_enterprise.SectorCoordinates.X - Starbase.Sector.X) <= 1 && + Math.Abs(_enterprise.SectorCoordinates.Y - Starbase.Sector.Y) <= 1; + + internal IEnumerable Klingons => _sectors.Values.OfType(); + + public override string ToString() => _info.Name; + + private T PositionObject(Func objectFactory) + { + var sector = GetRandomEmptySector(); + _sectors[sector] = objectFactory.Invoke(sector); + return (T)_sectors[sector]; + } + + private void PositionObject(Func objectFactory, int count) + { + for (int i = 0; i < count; i++) + { + PositionObject(objectFactory); + } + } + + internal void Display(string textFormat) + { + if (!_entered) + { + _io.Write(textFormat, this); + _entered = true; } - internal Coordinates Coordinates => _info.Coordinates; - - internal bool HasKlingons => _info.KlingonCount > 0; - - internal int KlingonCount => _info.KlingonCount; - - internal bool HasStarbase => _info.HasStarbase; - - internal Starbase Starbase { get; } - - internal Galaxy Galaxy { get; } - - internal bool EnterpriseIsNextToStarbase => - _info.HasStarbase && - Math.Abs(_enterprise.SectorCoordinates.X - Starbase.Sector.X) <= 1 && - Math.Abs(_enterprise.SectorCoordinates.Y - Starbase.Sector.Y) <= 1; - - internal IEnumerable Klingons => _sectors.Values.OfType(); - - public override string ToString() => _info.Name; - - private T PositionObject(Func objectFactory) + if (_info.KlingonCount > 0) { - var sector = GetRandomEmptySector(); - _sectors[sector] = objectFactory.Invoke(sector); - return (T)_sectors[sector]; + _io.Write(Strings.CombatArea); + if (_enterprise.ShieldControl.ShieldEnergy <= 200) { _io.Write(Strings.LowShields); } } - private void PositionObject(Func objectFactory, int count) + _enterprise.Execute(Command.SRS); + } + + internal bool HasObjectAt(Coordinates coordinates) => _sectors.ContainsKey(coordinates); + + internal bool TorpedoCollisionAt(Coordinates coordinates, out string message, out bool gameOver) + { + gameOver = false; + message = default; + + switch (_sectors.GetValueOrDefault(coordinates)) { - for (int i = 0; i < count; i++) - { - PositionObject(objectFactory); - } + case Klingon klingon: + message = Remove(klingon); + 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 void Display(string textFormat) - { - if (!_entered) - { - _output.Write(textFormat, this); - _entered = true; - } - - if (_info.KlingonCount > 0) - { - _output.Write(Strings.CombatArea); - if (_enterprise.ShieldControl.ShieldEnergy <= 200) { _output.Write(Strings.LowShields); } - } - - _enterprise.Execute(Command.SRS); - } - - internal bool HasObjectAt(Coordinates coordinates) => _sectors.ContainsKey(coordinates); - - internal bool TorpedoCollisionAt(Coordinates coordinates, out string message, out bool gameOver) - { - gameOver = false; - message = default; - - switch (_sectors.GetValueOrDefault(coordinates)) - { - case Klingon klingon: - message = Remove(klingon); - 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 string Remove(Klingon klingon) + internal string Remove(Klingon klingon) + { + _sectors.Remove(klingon.Sector); + _info.RemoveKlingon(); + return "*** Klingon destroyed ***"; + } + + internal CommandResult KlingonsMoveAndFire() + { + foreach (var klingon in Klingons.ToList()) { + var newSector = GetRandomEmptySector(); _sectors.Remove(klingon.Sector); - _info.RemoveKlingon(); - return "*** Klingon destroyed ***"; + _sectors[newSector] = klingon; + klingon.MoveTo(newSector); } - internal CommandResult KlingonsMoveAndFire() + return KlingonsFireOnEnterprise(); + } + + internal CommandResult KlingonsFireOnEnterprise() + { + if (EnterpriseIsNextToStarbase && Klingons.Any()) { - 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()) - { - Starbase.ProtectEnterprise(); - return CommandResult.Ok; - } - - foreach (var klingon in Klingons) - { - var result = klingon.FireOn(_enterprise); - if (result.IsGameOver) { return result; } - } - + Starbase.ProtectEnterprise(); return CommandResult.Ok; } - private Coordinates GetRandomEmptySector() + foreach (var klingon in Klingons) { - while (true) + var result = klingon.FireOn(_enterprise); + if (result.IsGameOver) { return result; } + } + + return CommandResult.Ok; + } + + private Coordinates GetRandomEmptySector() + { + while (true) + { + var sector = _random.NextCoordinate(); + if (!_sectors.ContainsKey(sector)) { - var sector = _random.GetCoordinate(); - if (!_sectors.ContainsKey(sector)) - { - return sector; - } + return sector; } } + } - internal IEnumerable GetDisplayLines() => Enumerable.Range(0, 8).Select(x => GetDisplayLine(x)); + internal IEnumerable GetDisplayLines() => Enumerable.Range(0, 8).Select(x => GetDisplayLine(x)); - private string GetDisplayLine(int x) => - string.Join( - " ", - Enumerable - .Range(0, 8) - .Select(y => new Coordinates(x, y)) - .Select(c => _sectors.GetValueOrDefault(c)) - .Select(o => o?.ToString() ?? " ")); + private string GetDisplayLine(int x) => + string.Join( + " ", + Enumerable + .Range(0, 8) + .Select(y => new Coordinates(x, y)) + .Select(c => _sectors.GetValueOrDefault(c)) + .Select(o => o?.ToString() ?? " ")); - internal void SetEnterpriseSector(Coordinates sector) - { - _sectors.Remove(_enterprise.SectorCoordinates); - _sectors[sector] = _enterprise; - } + internal void SetEnterpriseSector(Coordinates sector) + { + _sectors.Remove(_enterprise.SectorCoordinates); + _sectors[sector] = _enterprise; } } diff --git a/84_Super_Star_Trek/csharp/Space/QuadrantInfo.cs b/84_Super_Star_Trek/csharp/Space/QuadrantInfo.cs index 6a403c2e..0c0b65a0 100644 --- a/84_Super_Star_Trek/csharp/Space/QuadrantInfo.cs +++ b/84_Super_Star_Trek/csharp/Space/QuadrantInfo.cs @@ -1,65 +1,66 @@ -namespace SuperStarTrek.Space +using Games.Common.Randomness; + +namespace SuperStarTrek.Space; + +internal class QuadrantInfo { - internal class QuadrantInfo + private bool _isKnown; + + private QuadrantInfo(Coordinates coordinates, string name, int klingonCount, int starCount, bool hasStarbase) { - private bool _isKnown; - - private QuadrantInfo(Coordinates coordinates, string name, int klingonCount, int starCount, bool hasStarbase) - { - Coordinates = coordinates; - Name = name; - KlingonCount = klingonCount; - StarCount = starCount; - HasStarbase = hasStarbase; - } - - internal Coordinates Coordinates { get; } - - internal string Name { get; } - - internal int KlingonCount { get; private set; } - - internal bool HasStarbase { get; private set; } - - internal int StarCount { get; } - - internal static QuadrantInfo Create(Coordinates coordinates, string name, Random random) - { - var klingonCount = random.GetFloat() switch - { - > 0.98f => 3, - > 0.95f => 2, - > 0.80f => 1, - _ => 0 - }; - var hasStarbase = random.GetFloat() > 0.96f; - var starCount = random.Get1To8Inclusive(); - - return new QuadrantInfo(coordinates, name, klingonCount, starCount, hasStarbase); - } - - internal void AddKlingon() => KlingonCount += 1; - - internal void AddStarbase() => HasStarbase = true; - - internal void MarkAsKnown() => _isKnown = true; - - 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; + Coordinates = coordinates; + Name = name; + KlingonCount = klingonCount; + StarCount = starCount; + HasStarbase = hasStarbase; } + + internal Coordinates Coordinates { get; } + + internal string Name { get; } + + internal int KlingonCount { get; private set; } + + internal bool HasStarbase { get; private set; } + + internal int StarCount { get; } + + internal static QuadrantInfo Create(Coordinates coordinates, string name, IRandom random) + { + var klingonCount = random.NextFloat() switch + { + > 0.98f => 3, + > 0.95f => 2, + > 0.80f => 1, + _ => 0 + }; + var hasStarbase = random.NextFloat() > 0.96f; + var starCount = random.Next1To8Inclusive(); + + return new QuadrantInfo(coordinates, name, klingonCount, starCount, hasStarbase); + } + + internal void AddKlingon() => KlingonCount += 1; + + internal void AddStarbase() => HasStarbase = true; + + internal void MarkAsKnown() => _isKnown = true; + + 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; } diff --git a/84_Super_Star_Trek/csharp/StringExtensions.cs b/84_Super_Star_Trek/csharp/StringExtensions.cs index 4e77291a..4706e9cb 100644 --- a/84_Super_Star_Trek/csharp/StringExtensions.cs +++ b/84_Super_Star_Trek/csharp/StringExtensions.cs @@ -1,7 +1,6 @@ -namespace SuperStarTrek +namespace SuperStarTrek; + +internal static class StringExtensions { - internal static class StringExtensions - { - internal static string Pluralize(this string singular, int quantity) => singular + (quantity > 1 ? "s" : ""); - } + internal static string Pluralize(this string singular, int quantity) => singular + (quantity > 1 ? "s" : ""); } diff --git a/84_Super_Star_Trek/csharp/SuperStarTrek.csproj b/84_Super_Star_Trek/csharp/SuperStarTrek.csproj index c0de0594..de228ec6 100644 --- a/84_Super_Star_Trek/csharp/SuperStarTrek.csproj +++ b/84_Super_Star_Trek/csharp/SuperStarTrek.csproj @@ -2,11 +2,15 @@ Exe - net5.0 + net6.0 + + + + diff --git a/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/ComputerFunction.cs b/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/ComputerFunction.cs index 43553bd7..4356b859 100644 --- a/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/ComputerFunction.cs +++ b/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/ComputerFunction.cs @@ -1,19 +1,19 @@ +using Games.Common.IO; using SuperStarTrek.Space; -namespace SuperStarTrek.Systems.ComputerFunctions +namespace SuperStarTrek.Systems.ComputerFunctions; + +internal abstract class ComputerFunction { - internal abstract class ComputerFunction + protected ComputerFunction(string description, IReadWrite io) { - protected ComputerFunction(string description, Output output) - { - Description = description; - Output = output; - } - - internal string Description { get; } - - protected Output Output { get; } - - internal abstract void Execute(Quadrant quadrant); + Description = description; + IO = io; } + + internal string Description { get; } + + protected IReadWrite IO { get; } + + internal abstract void Execute(Quadrant quadrant); } diff --git a/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/CumulativeGalacticRecord.cs b/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/CumulativeGalacticRecord.cs index 920fbcca..f7c93f2b 100644 --- a/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/CumulativeGalacticRecord.cs +++ b/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/CumulativeGalacticRecord.cs @@ -1,21 +1,24 @@ -using System; using System.Collections.Generic; using System.Linq; +using Games.Common.IO; using SuperStarTrek.Space; -namespace SuperStarTrek.Systems.ComputerFunctions +namespace SuperStarTrek.Systems.ComputerFunctions; + +internal class CumulativeGalacticRecord : GalacticReport { - internal class CumulativeGalacticRecord : GalacticReport + internal CumulativeGalacticRecord(IReadWrite io, Galaxy galaxy) + : base("Cumulative galactic record", io, galaxy) { - 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 GetRowData() => - Galaxy.Quadrants.Select(row => " " + string.Join(" ", row)); } + + protected override void WriteHeader(Quadrant quadrant) + { + IO.WriteLine(); + IO.WriteLine($"Computer record of galaxy for quadrant {quadrant.Coordinates}"); + IO.WriteLine(); + } + + protected override IEnumerable GetRowData() => + Galaxy.Quadrants.Select(row => " " + string.Join(" ", row)); } diff --git a/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/DirectionDistanceCalculator.cs b/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/DirectionDistanceCalculator.cs index 3e9965db..e3402ab2 100644 --- a/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/DirectionDistanceCalculator.cs +++ b/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/DirectionDistanceCalculator.cs @@ -1,30 +1,30 @@ +using Games.Common.IO; using SuperStarTrek.Objects; using SuperStarTrek.Space; -namespace SuperStarTrek.Systems.ComputerFunctions +namespace SuperStarTrek.Systems.ComputerFunctions; + +internal class DirectionDistanceCalculator : NavigationCalculator { - internal class DirectionDistanceCalculator : NavigationCalculator + private readonly Enterprise _enterprise; + private readonly IReadWrite _io; + + internal DirectionDistanceCalculator(Enterprise enterprise, IReadWrite io) + : base("Direction/distance calculator", io) { - private readonly Enterprise _enterprise; - private readonly Input _input; + _enterprise = enterprise; + _io = io; + } - internal DirectionDistanceCalculator(Enterprise enterprise, Output output, Input input) - : base("Direction/distance calculator", output) - { - _enterprise = enterprise; - _input = input; - } + internal override void Execute(Quadrant quadrant) + { + IO.WriteLine("Direction/distance calculator:"); + IO.Write($"You are at quadrant {_enterprise.QuadrantCoordinates}"); + IO.WriteLine($" sector {_enterprise.SectorCoordinates}"); + IO.WriteLine("Please enter"); - internal override void Execute(Quadrant quadrant) - { - Output.WriteLine("Direction/distance calculator:") - .Write($"You are at quadrant {_enterprise.QuadrantCoordinates}") - .WriteLine($" sector {_enterprise.SectorCoordinates}") - .WriteLine("Please enter"); - - WriteDirectionAndDistance( - _input.GetCoordinates(" Initial coordinates"), - _input.GetCoordinates(" Final coordinates")); - } + WriteDirectionAndDistance( + _io.GetCoordinates(" Initial coordinates"), + _io.GetCoordinates(" Final coordinates")); } } diff --git a/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/GalacticReport.cs b/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/GalacticReport.cs index 1e8d5f23..49f7363d 100644 --- a/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/GalacticReport.cs +++ b/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/GalacticReport.cs @@ -1,34 +1,34 @@ using System.Collections.Generic; using System.Linq; +using Games.Common.IO; using SuperStarTrek.Space; -namespace SuperStarTrek.Systems.ComputerFunctions +namespace SuperStarTrek.Systems.ComputerFunctions; + +internal abstract class GalacticReport : ComputerFunction { - internal abstract class GalacticReport : ComputerFunction + internal GalacticReport(string description, IReadWrite io, Galaxy galaxy) + : base(description, io) { - internal 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 GetRowData(); + + internal sealed override void Execute(Quadrant quadrant) + { + WriteHeader(quadrant); + IO.WriteLine(" 1 2 3 4 5 6 7 8"); + IO.WriteLine(" ----- ----- ----- ----- ----- ----- ----- -----"); + + foreach (var (row, index) in GetRowData().Select((r, i) => (r, i))) { - Galaxy = galaxy; - } - - protected Galaxy Galaxy { get; } - - protected abstract void WriteHeader(Quadrant quadrant); - - protected abstract IEnumerable 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(" ----- ----- ----- ----- ----- ----- ----- -----"); - } + IO.WriteLine($" {index+1} {row}"); + IO.WriteLine(" ----- ----- ----- ----- ----- ----- ----- -----"); } } } diff --git a/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/GalaxyRegionMap.cs b/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/GalaxyRegionMap.cs index 12ff204b..c4850e1c 100644 --- a/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/GalaxyRegionMap.cs +++ b/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/GalaxyRegionMap.cs @@ -1,21 +1,21 @@ using System.Collections.Generic; using System.Linq; +using Games.Common.IO; using SuperStarTrek.Resources; using SuperStarTrek.Space; -namespace SuperStarTrek.Systems.ComputerFunctions +namespace SuperStarTrek.Systems.ComputerFunctions; + +internal class GalaxyRegionMap : GalacticReport { - internal class GalaxyRegionMap : GalacticReport + internal GalaxyRegionMap(IReadWrite io, Galaxy galaxy) + : base("Galaxy 'region name' map", io, galaxy) { - 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 GetRowData() => - Strings.RegionNames.Split('\n').Select(n => n.TrimEnd('\r')); } + + protected override void WriteHeader(Quadrant quadrant) => + IO.WriteLine(" The Galaxy"); + + protected override IEnumerable GetRowData() => + Strings.RegionNames.Split('\n').Select(n => n.TrimEnd('\r')); } diff --git a/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/NavigationCalculator.cs b/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/NavigationCalculator.cs index ef35f8c3..bddb00d2 100644 --- a/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/NavigationCalculator.cs +++ b/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/NavigationCalculator.cs @@ -1,29 +1,31 @@ +using Games.Common.IO; using SuperStarTrek.Space; using SuperStarTrek.Utils; -namespace SuperStarTrek.Systems.ComputerFunctions +namespace SuperStarTrek.Systems.ComputerFunctions; + +internal abstract class NavigationCalculator : ComputerFunction { - internal abstract class NavigationCalculator : ComputerFunction + protected NavigationCalculator(string description, IReadWrite io) + : base(description, io) { - 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(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); - } + 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}"); + private void Write(float direction, float distance) + { + IO.WriteLine($"Direction = {direction}"); + IO.WriteLine($"Distance = {distance}"); } } diff --git a/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/StarbaseDataCalculator.cs b/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/StarbaseDataCalculator.cs index 07f2119b..08e7eda3 100644 --- a/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/StarbaseDataCalculator.cs +++ b/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/StarbaseDataCalculator.cs @@ -1,30 +1,30 @@ +using Games.Common.IO; using SuperStarTrek.Objects; using SuperStarTrek.Resources; using SuperStarTrek.Space; -namespace SuperStarTrek.Systems.ComputerFunctions +namespace SuperStarTrek.Systems.ComputerFunctions; + +internal class StarbaseDataCalculator : NavigationCalculator { - internal class StarbaseDataCalculator : NavigationCalculator + private readonly Enterprise _enterprise; + + internal StarbaseDataCalculator(Enterprise enterprise, IReadWrite io) + : base("Starbase nav data", io) { - private readonly Enterprise _enterprise; + _enterprise = enterprise; + } - internal StarbaseDataCalculator(Enterprise enterprise, Output output) - : base("Starbase nav data", output) + internal override void Execute(Quadrant quadrant) + { + if (!quadrant.HasStarbase) { - _enterprise = enterprise; + IO.WriteLine(Strings.NoStarbase); + return; } - internal override void Execute(Quadrant quadrant) - { - if (!quadrant.HasStarbase) - { - Output.WriteLine(Strings.NoStarbase); - return; - } + IO.WriteLine("From Enterprise to Starbase:"); - Output.WriteLine("From Enterprise to Starbase:"); - - WriteDirectionAndDistance(_enterprise.SectorCoordinates, quadrant.Starbase.Sector); - } + WriteDirectionAndDistance(_enterprise.SectorCoordinates, quadrant.Starbase.Sector); } } diff --git a/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/StatusReport.cs b/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/StatusReport.cs index 6e6bf6f1..b0d6bc1d 100644 --- a/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/StatusReport.cs +++ b/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/StatusReport.cs @@ -1,41 +1,43 @@ +using Games.Common.IO; using SuperStarTrek.Commands; using SuperStarTrek.Objects; using SuperStarTrek.Space; -namespace SuperStarTrek.Systems.ComputerFunctions +namespace SuperStarTrek.Systems.ComputerFunctions; + +internal class StatusReport : ComputerFunction { - internal class StatusReport : ComputerFunction + private readonly Game _game; + private readonly Galaxy _galaxy; + private readonly Enterprise _enterprise; + + internal StatusReport(Game game, Galaxy galaxy, Enterprise enterprise, IReadWrite io) + : base("Status report", io) { - private readonly Game _game; - private readonly Galaxy _galaxy; - private readonly Enterprise _enterprise; + _game = game; + _galaxy = galaxy; + _enterprise = enterprise; + } - internal StatusReport(Game game, Galaxy galaxy, Enterprise enterprise, Output output) - : base("Status report", output) + internal override void Execute(Quadrant quadrant) + { + IO.WriteLine(" Status report:"); + IO.Write("Klingon".Pluralize(_galaxy.KlingonCount)); + IO.WriteLine($" left: {_galaxy.KlingonCount}"); + IO.WriteLine($"Mission must be completed in {_game.StardatesRemaining:0.#} stardates."); + + if (_galaxy.StarbaseCount > 0) { - _game = game; - _galaxy = galaxy; - _enterprise = enterprise; + IO.Write($"The Federation is maintaining {_galaxy.StarbaseCount} "); + IO.Write("starbase".Pluralize(_galaxy.StarbaseCount)); + IO.WriteLine(" in the galaxy."); + } + else + { + IO.WriteLine("Your stupidity has left you on your own in"); + IO.WriteLine(" the galaxy -- you have no starbases left!"); } - 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); - } + _enterprise.Execute(Command.DAM); } } diff --git a/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/TorpedoDataCalculator.cs b/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/TorpedoDataCalculator.cs index 94f8ae9a..d1de3eb7 100644 --- a/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/TorpedoDataCalculator.cs +++ b/84_Super_Star_Trek/csharp/Systems/ComputerFunctions/TorpedoDataCalculator.cs @@ -1,33 +1,33 @@ +using Games.Common.IO; using SuperStarTrek.Objects; using SuperStarTrek.Resources; using SuperStarTrek.Space; -namespace SuperStarTrek.Systems.ComputerFunctions -{ - internal class TorpedoDataCalculator : NavigationCalculator - { - private readonly Enterprise _enterprise; +namespace SuperStarTrek.Systems.ComputerFunctions; - internal TorpedoDataCalculator(Enterprise enterprise, Output output) - : base("Photon torpedo data", output) +internal class TorpedoDataCalculator : NavigationCalculator +{ + private readonly Enterprise _enterprise; + + internal TorpedoDataCalculator(Enterprise enterprise, IReadWrite io) + : base("Photon torpedo data", io) + { + _enterprise = enterprise; + } + + internal override void Execute(Quadrant quadrant) + { + if (!quadrant.HasKlingons) { - _enterprise = enterprise; + IO.WriteLine(Strings.NoEnemyShips); + return; } - internal override void Execute(Quadrant quadrant) + IO.WriteLine("From Enterprise to Klingon battle cruiser".Pluralize(quadrant.KlingonCount)); + + foreach (var klingon in quadrant.Klingons) { - 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.SectorCoordinates, klingon.Sector); - } + WriteDirectionAndDistance(_enterprise.SectorCoordinates, klingon.Sector); } } } diff --git a/84_Super_Star_Trek/csharp/Systems/DamageControl.cs b/84_Super_Star_Trek/csharp/Systems/DamageControl.cs index 8fbf21d3..53dc586e 100644 --- a/84_Super_Star_Trek/csharp/Systems/DamageControl.cs +++ b/84_Super_Star_Trek/csharp/Systems/DamageControl.cs @@ -1,54 +1,55 @@ +using Games.Common.IO; using SuperStarTrek.Commands; using SuperStarTrek.Objects; using SuperStarTrek.Space; -namespace SuperStarTrek.Systems +namespace SuperStarTrek.Systems; + +internal class DamageControl : Subsystem { - internal class DamageControl : Subsystem + private readonly Enterprise _enterprise; + private readonly IReadWrite _io; + + internal DamageControl(Enterprise enterprise, IReadWrite io) + : base("Damage Control", Command.DAM, io) { - private readonly Enterprise _enterprise; - private readonly Output _output; + _enterprise = enterprise; + _io = io; + } - internal DamageControl(Enterprise enterprise, Output output) - : base("Damage Control", Command.DAM, output) + protected override CommandResult ExecuteCommandCore(Quadrant quadrant) + { + if (IsDamaged) { - _enterprise = enterprise; - _output = output; + _io.WriteLine("Damage Control report not available"); + } + else + { + _io.WriteLine(); + WriteDamageReport(); } - protected override CommandResult ExecuteCommandCore(Quadrant quadrant) + if (_enterprise.DamagedSystemCount > 0 && _enterprise.IsDocked) { - if (IsDamaged) + if (quadrant.Starbase.TryRepair(_enterprise, out var repairTime)) { - _output.WriteLine("Damage Control report not available"); - } - else - { - _output.NextLine(); WriteDamageReport(); + return CommandResult.Elapsed(repairTime); } - - if (_enterprise.DamagedSystemCount > 0 && _enterprise.IsDocked) - { - if (quadrant.Starbase.TryRepair(_enterprise, out var repairTime)) - { - WriteDamageReport(); - return CommandResult.Elapsed(repairTime); - } - } - - return CommandResult.Ok; } - internal void WriteDamageReport() + return CommandResult.Ok; + } + + internal void WriteDamageReport() + { + _io.WriteLine(); + _io.WriteLine("Device State of Repair"); + foreach (var system in _enterprise.Systems) { - _output.NextLine().WriteLine("Device State of Repair"); - foreach (var system in _enterprise.Systems) - { - _output.Write(system.Name.PadRight(25)) - .WriteLine(((int)(system.Condition * 100) * 0.01).ToString(" 0.##;-0.##")); - } - _output.NextLine(); + _io.Write(system.Name.PadRight(25)); + _io.WriteLine((int)(system.Condition * 100) * 0.01F); } + _io.WriteLine(); } } diff --git a/84_Super_Star_Trek/csharp/Systems/LibraryComputer.cs b/84_Super_Star_Trek/csharp/Systems/LibraryComputer.cs index df5c6868..4a56909e 100644 --- a/84_Super_Star_Trek/csharp/Systems/LibraryComputer.cs +++ b/84_Super_Star_Trek/csharp/Systems/LibraryComputer.cs @@ -1,46 +1,44 @@ +using Games.Common.IO; using SuperStarTrek.Commands; using SuperStarTrek.Space; using SuperStarTrek.Systems.ComputerFunctions; -namespace SuperStarTrek.Systems +namespace SuperStarTrek.Systems; + +internal class LibraryComputer : Subsystem { - internal class LibraryComputer : Subsystem + private readonly IReadWrite _io; + private readonly ComputerFunction[] _functions; + + internal LibraryComputer(IReadWrite io, params ComputerFunction[] functions) + : base("Library-Computer", Command.COM, io) { - private readonly Output _output; - private readonly Input _input; - private readonly ComputerFunction[] _functions; + _io = io; + _functions = functions; + } - internal LibraryComputer(Output output, Input input, params ComputerFunction[] functions) - : base("Library-Computer", Command.COM, output) + protected override bool CanExecuteCommand() => IsOperational("Computer disabled"); + + protected override CommandResult ExecuteCommandCore(Quadrant quadrant) + { + var index = GetFunctionIndex(); + _io.WriteLine(); + + _functions[index].Execute(quadrant); + + return CommandResult.Ok; + } + + private int GetFunctionIndex() + { + while (true) { - _output = output; - _input = input; - _functions = functions; - } + var index = (int)_io.ReadNumber("Computer active and waiting command"); + if (index >= 0 && index <= 5) { return index; } - 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) + for (int i = 0; i < _functions.Length; i++) { - 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}"); - } + _io.WriteLine($" {i} = {_functions[i].Description}"); } } } diff --git a/84_Super_Star_Trek/csharp/Systems/LongRangeSensors.cs b/84_Super_Star_Trek/csharp/Systems/LongRangeSensors.cs index a873eeba..2e21b63c 100644 --- a/84_Super_Star_Trek/csharp/Systems/LongRangeSensors.cs +++ b/84_Super_Star_Trek/csharp/Systems/LongRangeSensors.cs @@ -1,34 +1,35 @@ using System.Linq; +using Games.Common.IO; using SuperStarTrek.Commands; using SuperStarTrek.Space; -namespace SuperStarTrek.Systems +namespace SuperStarTrek.Systems; + +internal class LongRangeSensors : Subsystem { - internal class LongRangeSensors : Subsystem + private readonly Galaxy _galaxy; + private readonly IReadWrite _io; + + internal LongRangeSensors(Galaxy galaxy, IReadWrite io) + : base("Long Range Sensors", Command.LRS, io) { - private readonly Galaxy _galaxy; - private readonly Output _output; + _galaxy = galaxy; + _io = io; + } - internal LongRangeSensors(Galaxy galaxy, Output output) - : base("Long Range Sensors", Command.LRS, output) + protected override bool CanExecuteCommand() => IsOperational("{name} are inoperable"); + + protected override CommandResult ExecuteCommandCore(Quadrant quadrant) + { + _io.WriteLine($"Long range scan for quadrant {quadrant.Coordinates}"); + _io.WriteLine("-------------------"); + foreach (var quadrants in _galaxy.GetNeighborhood(quadrant)) { - _galaxy = galaxy; - _output = output; + _io.WriteLine(": " + string.Join(" : ", quadrants.Select(q => q?.Scan() ?? "***")) + " :"); + _io.WriteLine("-------------------"); } - protected override bool CanExecuteCommand() => IsOperational("{name} are inoperable"); - - protected override CommandResult ExecuteCommandCore(Quadrant quadrant) - { - _output.WriteLine($"Long range scan for quadrant {quadrant.Coordinates}"); - _output.WriteLine("-------------------"); - foreach (var quadrants in _galaxy.GetNeighborhood(quadrant)) - { - _output.WriteLine(": " + string.Join(" : ", quadrants.Select(q => q?.Scan() ?? "***")) + " :"); - _output.WriteLine("-------------------"); - } - - return CommandResult.Ok; - } + return CommandResult.Ok; } } + diff --git a/84_Super_Star_Trek/csharp/Systems/PhaserControl.cs b/84_Super_Star_Trek/csharp/Systems/PhaserControl.cs index 9207ad94..61249d51 100644 --- a/84_Super_Star_Trek/csharp/Systems/PhaserControl.cs +++ b/84_Super_Star_Trek/csharp/Systems/PhaserControl.cs @@ -1,97 +1,96 @@ using System.Linq; +using Games.Common.IO; +using Games.Common.Randomness; using SuperStarTrek.Commands; using SuperStarTrek.Objects; using SuperStarTrek.Resources; using SuperStarTrek.Space; -namespace SuperStarTrek.Systems +namespace SuperStarTrek.Systems; + +internal class PhaserControl : Subsystem { - internal class PhaserControl : Subsystem + private readonly Enterprise _enterprise; + private readonly IReadWrite _io; + private readonly IRandom _random; + + internal PhaserControl(Enterprise enterprise, IReadWrite io, IRandom random) + : base("Phaser Control", Command.PHA, io) { - private readonly Enterprise _enterprise; - private readonly Output _output; - private readonly Input _input; - private readonly Random _random; + _enterprise = enterprise; + _io = io; + _random = random; + } - internal PhaserControl(Enterprise enterprise, Output output, Input input, Random random) - : base("Phaser Control", Command.PHA, output) + protected override bool CanExecuteCommand() => IsOperational("Phasers inoperative"); + + protected override CommandResult ExecuteCommandCore(Quadrant quadrant) + { + if (!quadrant.HasKlingons) { - _enterprise = enterprise; - _output = output; - _input = input; - _random = random; + _io.WriteLine(Strings.NoEnemyShips); + return CommandResult.Ok; } - protected override bool CanExecuteCommand() => IsOperational("Phasers inoperative"); - - protected override CommandResult ExecuteCommandCore(Quadrant quadrant) + if (_enterprise.Computer.IsDamaged) { - if (!quadrant.HasKlingons) - { - _output.WriteLine(Strings.NoEnemyShips); - return CommandResult.Ok; - } - - if (_enterprise.Computer.IsDamaged) - { - _output.WriteLine("Computer failure hampers accuracy"); - } - - _output.Write($"Phasers locked on target; "); - - var phaserStrength = GetPhaserStrength(); - if (phaserStrength < 0) { return CommandResult.Ok; } - - _enterprise.UseEnergy(phaserStrength); - - var perEnemyStrength = GetPerTargetPhaserStrength(phaserStrength, quadrant.KlingonCount); - - foreach (var klingon in quadrant.Klingons.ToList()) - { - ResolveHitOn(klingon, perEnemyStrength, quadrant); - } - - return quadrant.KlingonsFireOnEnterprise(); + _io.WriteLine("Computer failure hampers accuracy"); } - private float GetPhaserStrength() - { - while (true) - { - _output.WriteLine($"Energy available = {_enterprise.Energy} units"); - var phaserStrength = _input.GetNumber("Number of units to fire"); + _io.Write($"Phasers locked on target; "); - if (phaserStrength <= _enterprise.Energy) { return phaserStrength; } - } + var phaserStrength = GetPhaserStrength(); + if (phaserStrength < 0) { return CommandResult.Ok; } + + _enterprise.UseEnergy(phaserStrength); + + var perEnemyStrength = GetPerTargetPhaserStrength(phaserStrength, quadrant.KlingonCount); + + foreach (var klingon in quadrant.Klingons.ToList()) + { + ResolveHitOn(klingon, perEnemyStrength, quadrant); } - private float GetPerTargetPhaserStrength(float phaserStrength, int targetCount) - { - if (_enterprise.Computer.IsDamaged) - { - phaserStrength *= _random.GetFloat(); - } + return quadrant.KlingonsFireOnEnterprise(); + } - return phaserStrength / targetCount; + private float GetPhaserStrength() + { + while (true) + { + _io.WriteLine($"Energy available = {_enterprise.Energy} units"); + var phaserStrength = _io.ReadNumber("Number of units to fire"); + + if (phaserStrength <= _enterprise.Energy) { return phaserStrength; } + } + } + + private float GetPerTargetPhaserStrength(float phaserStrength, int targetCount) + { + if (_enterprise.Computer.IsDamaged) + { + phaserStrength *= _random.NextFloat(); } - private void ResolveHitOn(Klingon klingon, float perEnemyStrength, Quadrant quadrant) - { - var distance = _enterprise.SectorCoordinates.GetDistanceTo(klingon.Sector); - var hitStrength = (int)(perEnemyStrength / distance * (2 + _random.GetFloat())); + return phaserStrength / targetCount; + } - if (klingon.TakeHit(hitStrength)) - { - _output.WriteLine($"{hitStrength} unit hit on Klingon at sector {klingon.Sector}"); - _output.WriteLine( - klingon.Energy <= 0 - ? quadrant.Remove(klingon) - : $" (sensors show {klingon.Energy} units remaining)"); - } - else - { - _output.WriteLine($"Sensors show no damage to enemy at {klingon.Sector}"); - } + private void ResolveHitOn(Klingon klingon, float perEnemyStrength, Quadrant quadrant) + { + var distance = _enterprise.SectorCoordinates.GetDistanceTo(klingon.Sector); + var hitStrength = (int)(perEnemyStrength / distance * (2 + _random.NextFloat())); + + if (klingon.TakeHit(hitStrength)) + { + _io.WriteLine($"{hitStrength} unit hit on Klingon at sector {klingon.Sector}"); + _io.WriteLine( + klingon.Energy <= 0 + ? quadrant.Remove(klingon) + : $" (sensors show {klingon.Energy} units remaining)"); + } + else + { + _io.WriteLine($"Sensors show no damage to enemy at {klingon.Sector}"); } } } diff --git a/84_Super_Star_Trek/csharp/Systems/PhotonTubes.cs b/84_Super_Star_Trek/csharp/Systems/PhotonTubes.cs index 37613037..597ae3a0 100644 --- a/84_Super_Star_Trek/csharp/Systems/PhotonTubes.cs +++ b/84_Super_Star_Trek/csharp/Systems/PhotonTubes.cs @@ -1,66 +1,64 @@ +using Games.Common.IO; using SuperStarTrek.Commands; using SuperStarTrek.Objects; using SuperStarTrek.Space; -namespace SuperStarTrek.Systems +namespace SuperStarTrek.Systems; + +internal class PhotonTubes : Subsystem { - internal class PhotonTubes : Subsystem + private readonly int _tubeCount; + private readonly Enterprise _enterprise; + private readonly IReadWrite _io; + + internal PhotonTubes(int tubeCount, Enterprise enterprise, IReadWrite io) + : base("Photon Tubes", Command.TOR, io) { - private readonly int _tubeCount; - private readonly Enterprise _enterprise; - private readonly Output _output; - private readonly Input _input; - - internal PhotonTubes(int tubeCount, Enterprise enterprise, Output output, Input input) - : base("Photon Tubes", Command.TOR, output) - { - TorpedoCount = _tubeCount = tubeCount; - _enterprise = enterprise; - _output = output; - _input = input; - } - - internal 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.TryGetCourse("Photon torpedo course", "Ensign Chekov", out var course)) - { - return CommandResult.Ok; - } - - TorpedoCount -= 1; - - var isHit = false; - _output.WriteLine("Torpedo track:"); - foreach (var sector in course.GetSectorsFrom(_enterprise.SectorCoordinates)) - { - _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(); - } - - internal void ReplenishTorpedoes() => TorpedoCount = _tubeCount; + TorpedoCount = _tubeCount = tubeCount; + _enterprise = enterprise; + _io = io; } + + internal int TorpedoCount { get; private set; } + + protected override bool CanExecuteCommand() => HasTorpedoes() && IsOperational("{name} are not operational"); + + private bool HasTorpedoes() + { + if (TorpedoCount > 0) { return true; } + + _io.WriteLine("All photon torpedoes expended"); + return false; + } + + protected override CommandResult ExecuteCommandCore(Quadrant quadrant) + { + if (!_io.TryReadCourse("Photon torpedo course", "Ensign Chekov", out var course)) + { + return CommandResult.Ok; + } + + TorpedoCount -= 1; + + var isHit = false; + _io.WriteLine("Torpedo track:"); + foreach (var sector in course.GetSectorsFrom(_enterprise.SectorCoordinates)) + { + _io.WriteLine($" {sector}"); + + if (quadrant.TorpedoCollisionAt(sector, out var message, out var gameOver)) + { + _io.WriteLine(message); + isHit = true; + if (gameOver) { return CommandResult.GameOver; } + break; + } + } + + if (!isHit) { _io.WriteLine("Torpedo missed!"); } + + return quadrant.KlingonsFireOnEnterprise(); + } + + internal void ReplenishTorpedoes() => TorpedoCount = _tubeCount; } diff --git a/84_Super_Star_Trek/csharp/Systems/ShieldControl.cs b/84_Super_Star_Trek/csharp/Systems/ShieldControl.cs index de4f44d9..9d781b23 100644 --- a/84_Super_Star_Trek/csharp/Systems/ShieldControl.cs +++ b/84_Super_Star_Trek/csharp/Systems/ShieldControl.cs @@ -1,59 +1,57 @@ +using Games.Common.IO; using SuperStarTrek.Commands; using SuperStarTrek.Objects; using SuperStarTrek.Resources; using SuperStarTrek.Space; -namespace SuperStarTrek.Systems +namespace SuperStarTrek.Systems; + +internal class ShieldControl : Subsystem { - internal class ShieldControl : Subsystem + private readonly Enterprise _enterprise; + private readonly IReadWrite _io; + + internal ShieldControl(Enterprise enterprise, IReadWrite io) + : base("Shield Control", Command.SHE, io) { - private readonly Enterprise _enterprise; - private readonly Output _output; - private readonly Input _input; - - internal ShieldControl(Enterprise enterprise, Output output, Input input) - : base("Shield Control", Command.SHE, output) - { - _enterprise = enterprise; - _output = output; - _input = input; - } - - internal float ShieldEnergy { get; set; } - - protected override bool CanExecuteCommand() => IsOperational("{name} inoperable"); - - protected override CommandResult ExecuteCommandCore(Quadrant quadrant) - { - _output.WriteLine($"Energy available = {_enterprise.TotalEnergy}"); - var requested = _input.GetNumber($"Number of units to shields"); - - if (Validate(requested)) - { - ShieldEnergy = requested; - _output.Write(Strings.ShieldsSet, requested); - } - else - { - _output.WriteLine(""); - } - - return CommandResult.Ok; - } - - private bool Validate(float requested) - { - if (requested > _enterprise.TotalEnergy) - { - _output.WriteLine("Shield Control reports, 'This is not the Federation Treasury.'"); - return false; - } - - return requested >= 0 && requested != ShieldEnergy; - } - - internal void AbsorbHit(int hitStrength) => ShieldEnergy -= hitStrength; - - internal void DropShields() => ShieldEnergy = 0; + _enterprise = enterprise; + _io = io; } + + internal float ShieldEnergy { get; set; } + + protected override bool CanExecuteCommand() => IsOperational("{name} inoperable"); + + protected override CommandResult ExecuteCommandCore(Quadrant quadrant) + { + _io.WriteLine($"Energy available = {_enterprise.TotalEnergy}"); + var requested = _io.ReadNumber($"Number of units to shields"); + + if (Validate(requested)) + { + ShieldEnergy = requested; + _io.Write(Strings.ShieldsSet, requested); + } + else + { + _io.WriteLine(""); + } + + return CommandResult.Ok; + } + + private bool Validate(float requested) + { + if (requested > _enterprise.TotalEnergy) + { + _io.WriteLine("Shield Control reports, 'This is not the Federation Treasury.'"); + return false; + } + + return requested >= 0 && requested != ShieldEnergy; + } + + internal void AbsorbHit(int hitStrength) => ShieldEnergy -= hitStrength; + + internal void DropShields() => ShieldEnergy = 0; } diff --git a/84_Super_Star_Trek/csharp/Systems/ShortRangeSensors.cs b/84_Super_Star_Trek/csharp/Systems/ShortRangeSensors.cs index c37028cb..406af6b8 100644 --- a/84_Super_Star_Trek/csharp/Systems/ShortRangeSensors.cs +++ b/84_Super_Star_Trek/csharp/Systems/ShortRangeSensors.cs @@ -2,61 +2,61 @@ using System; using System.Collections.Generic; using System.Linq; +using Games.Common.IO; using SuperStarTrek.Commands; using SuperStarTrek.Objects; using SuperStarTrek.Resources; using SuperStarTrek.Space; -namespace SuperStarTrek.Systems +namespace SuperStarTrek.Systems; + +internal class ShortRangeSensors : Subsystem { - internal class ShortRangeSensors : Subsystem + private readonly Enterprise _enterprise; + private readonly Galaxy _galaxy; + private readonly Game _game; + private readonly IReadWrite _io; + + internal ShortRangeSensors(Enterprise enterprise, Galaxy galaxy, Game game, IReadWrite io) + : base("Short Range Sensors", Command.SRS, io) { - private readonly Enterprise _enterprise; - private readonly Galaxy _galaxy; - private readonly Game _game; - private readonly Output _output; + _enterprise = enterprise; + _galaxy = galaxy; + _game = game; + _io = io; + } - internal ShortRangeSensors(Enterprise enterprise, Galaxy galaxy, Game game, Output output) - : base("Short Range Sensors", Command.SRS, output) + protected override CommandResult ExecuteCommandCore(Quadrant quadrant) + { + if (_enterprise.IsDocked) { - _enterprise = enterprise; - _galaxy = galaxy; - _game = game; - _output = output; + _io.WriteLine(Strings.ShieldsDropped); } - protected override CommandResult ExecuteCommandCore(Quadrant quadrant) + if (Condition < 0) { - if (_enterprise.IsDocked) - { - _output.WriteLine(Strings.ShieldsDropped); - } - - if (Condition < 0) - { - _output.WriteLine(Strings.ShortRangeSensorsOut); - } - - _output.WriteLine("---------------------------------"); - quadrant.GetDisplayLines() - .Zip(GetStatusLines(), (sectors, status) => $" {sectors} {status}") - .ToList() - .ForEach(l => _output.WriteLine(l)); - _output.WriteLine("---------------------------------"); - - return CommandResult.Ok; + _io.WriteLine(Strings.ShortRangeSensorsOut); } - internal IEnumerable GetStatusLines() - { - yield return $"Stardate {_game.Stardate}"; - yield return $"Condition {_enterprise.Condition}"; - yield return $"Quadrant {_enterprise.QuadrantCoordinates}"; - yield return $"Sector {_enterprise.SectorCoordinates}"; - yield return $"Photon torpedoes {_enterprise.PhotonTubes.TorpedoCount}"; - yield return $"Total energy {Math.Ceiling(_enterprise.TotalEnergy)}"; - yield return $"Shields {(int)_enterprise.ShieldControl.ShieldEnergy}"; - yield return $"Klingons remaining {_galaxy.KlingonCount}"; - } + _io.WriteLine("---------------------------------"); + quadrant.GetDisplayLines() + .Zip(GetStatusLines(), (sectors, status) => $" {sectors} {status}") + .ToList() + .ForEach(l => _io.WriteLine(l)); + _io.WriteLine("---------------------------------"); + + return CommandResult.Ok; + } + + internal IEnumerable GetStatusLines() + { + yield return $"Stardate {_game.Stardate}"; + yield return $"Condition {_enterprise.Condition}"; + yield return $"Quadrant {_enterprise.QuadrantCoordinates}"; + yield return $"Sector {_enterprise.SectorCoordinates}"; + yield return $"Photon torpedoes {_enterprise.PhotonTubes.TorpedoCount}"; + yield return $"Total energy {Math.Ceiling(_enterprise.TotalEnergy)}"; + yield return $"Shields {(int)_enterprise.ShieldControl.ShieldEnergy}"; + yield return $"Klingons remaining {_galaxy.KlingonCount}"; } } diff --git a/84_Super_Star_Trek/csharp/Systems/Subsystem.cs b/84_Super_Star_Trek/csharp/Systems/Subsystem.cs index 5c11e656..5a371103 100644 --- a/84_Super_Star_Trek/csharp/Systems/Subsystem.cs +++ b/84_Super_Star_Trek/csharp/Systems/Subsystem.cs @@ -1,68 +1,68 @@ +using Games.Common.IO; using SuperStarTrek.Commands; using SuperStarTrek.Space; -namespace SuperStarTrek.Systems +namespace SuperStarTrek.Systems; + +internal abstract class Subsystem { - internal abstract class Subsystem + private readonly IReadWrite _io; + + protected Subsystem(string name, Command command, IReadWrite io) { - private readonly Output _output; - - protected Subsystem(string name, Command command, Output output) - { - Name = name; - Command = command; - Condition = 0; - _output = output; - } - - internal string Name { get; } - - internal float Condition { get; private set; } - - internal bool IsDamaged => Condition < 0; - - internal Command Command { get; } - - protected virtual bool CanExecuteCommand() => true; - - protected bool IsOperational(string notOperationalMessage) - { - if (IsDamaged) - { - _output.WriteLine(notOperationalMessage.Replace("{name}", Name)); - return false; - } - - return true; - } - - internal CommandResult ExecuteCommand(Quadrant quadrant) - => CanExecuteCommand() ? ExecuteCommandCore(quadrant) : CommandResult.Ok; - - protected abstract CommandResult ExecuteCommandCore(Quadrant quadrant); - - internal virtual void Repair() - { - if (IsDamaged) - { - Condition = 0; - } - } - - internal virtual bool Repair(float repairWorkDone) - { - if (IsDamaged) - { - Condition += repairWorkDone; - if (Condition > -0.1f && Condition < 0) - { - Condition = -0.1f; - } - } - - return !IsDamaged; - } - - internal void TakeDamage(float damage) => Condition -= damage; + Name = name; + Command = command; + Condition = 0; + _io = io; } + + internal string Name { get; } + + internal float Condition { get; private set; } + + internal bool IsDamaged => Condition < 0; + + internal Command Command { get; } + + protected virtual bool CanExecuteCommand() => true; + + protected bool IsOperational(string notOperationalMessage) + { + if (IsDamaged) + { + _io.WriteLine(notOperationalMessage.Replace("{name}", Name)); + return false; + } + + return true; + } + + internal CommandResult ExecuteCommand(Quadrant quadrant) + => CanExecuteCommand() ? ExecuteCommandCore(quadrant) : CommandResult.Ok; + + protected abstract CommandResult ExecuteCommandCore(Quadrant quadrant); + + internal virtual void Repair() + { + if (IsDamaged) + { + Condition = 0; + } + } + + internal virtual bool Repair(float repairWorkDone) + { + if (IsDamaged) + { + Condition += repairWorkDone; + if (Condition > -0.1f && Condition < 0) + { + Condition = -0.1f; + } + } + + return !IsDamaged; + } + + internal void TakeDamage(float damage) => Condition -= damage; } diff --git a/84_Super_Star_Trek/csharp/Systems/WarpEngines.cs b/84_Super_Star_Trek/csharp/Systems/WarpEngines.cs index 096d0e43..a9651abf 100644 --- a/84_Super_Star_Trek/csharp/Systems/WarpEngines.cs +++ b/84_Super_Star_Trek/csharp/Systems/WarpEngines.cs @@ -1,4 +1,5 @@ using System; +using Games.Common.IO; using SuperStarTrek.Commands; using SuperStarTrek.Objects; using SuperStarTrek.Resources; @@ -9,20 +10,18 @@ namespace SuperStarTrek.Systems internal class WarpEngines : Subsystem { private readonly Enterprise _enterprise; - private readonly Output _output; - private readonly Input _input; + private readonly IReadWrite _io; - internal WarpEngines(Enterprise enterprise, Output output, Input input) - : base("Warp Engines", Command.NAV, output) + internal WarpEngines(Enterprise enterprise, IReadWrite io) + : base("Warp Engines", Command.NAV, io) { _enterprise = enterprise; - _output = output; - _input = input; + _io = io; } protected override CommandResult ExecuteCommandCore(Quadrant quadrant) { - if (_input.TryGetCourse("Course", " Lt. Sulu", out var course) && + if (_io.TryReadCourse("Course", " Lt. Sulu", out var course) && TryGetWarpFactor(out var warpFactor) && TryGetDistanceToMove(warpFactor, out var distanceToMove)) { @@ -51,12 +50,12 @@ namespace SuperStarTrek.Systems private bool TryGetWarpFactor(out float warpFactor) { var maximumWarp = IsDamaged ? 0.2f : 8; - if (_input.TryGetNumber("Warp Factor", 0, maximumWarp, out warpFactor)) + if (_io.TryReadNumberInRange("Warp Factor", 0, maximumWarp, out warpFactor)) { return warpFactor > 0; } - _output.WriteLine( + _io.WriteLine( IsDamaged && warpFactor > maximumWarp ? "Warp engines are damaged. Maximum speed = warp 0.2" : $" Chief Engineer Scott reports, 'The engines won't take warp {warpFactor} !'"); @@ -69,14 +68,14 @@ namespace SuperStarTrek.Systems 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} !'"); + _io.WriteLine("Engineering reports, 'Insufficient energy available"); + _io.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."); + _io.Write($"Deflector control room acknowledges {_enterprise.ShieldControl.ShieldEnergy} "); + _io.WriteLine("units of energy"); + _io.WriteLine(" presently deployed to shields."); } return false;