mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-12 15:50:20 -08:00
Merge pull request #673 from drewjcooper/rework-with-common-lib
Rework with common lib
This commit is contained in:
@@ -12,4 +12,6 @@ public sealed class ConsoleIO : TextIO
|
||||
: base(Console.In, Console.Out)
|
||||
{
|
||||
}
|
||||
|
||||
public override char ReadCharacter() => Console.ReadKey(intercept: true).KeyChar;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,12 @@ namespace Games.Common.IO;
|
||||
/// </summary>
|
||||
public interface IReadWrite
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads a character from input.
|
||||
/// </summary>
|
||||
/// <returns>The character read.</returns>
|
||||
char ReadCharacter();
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <see cref="float" /> value from input.
|
||||
/// </summary>
|
||||
@@ -82,9 +88,35 @@ public interface IReadWrite
|
||||
/// <param name="value">The <see cref="float" /> to be written.</param>
|
||||
void WriteLine(float value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an <see cref="object" /> to output.
|
||||
/// </summary>
|
||||
/// <param name="value">The <see cref="object" /> to be written.</param>
|
||||
void Write(object value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an <see cref="object" /> to output.
|
||||
/// </summary>
|
||||
/// <param name="value">The <see cref="object" /> to be written.</param>
|
||||
void WriteLine(object value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a formatted string to output.
|
||||
/// </summary>
|
||||
/// <param name="format">The format <see cref="string" /> to be written.</param>
|
||||
/// <param name="value">The values to be inserted into the format.</param>
|
||||
void Write(string format, params object[] values);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a formatted string to output followed by a new-line.
|
||||
/// </summary>
|
||||
/// <param name="format">The format <see cref="string" /> to be written.</param>
|
||||
/// <param name="value">The values to be inserted into the format.</param>
|
||||
void WriteLine(string format, params object[] values);
|
||||
|
||||
/// <summary>
|
||||
/// Writes the contents of a <see cref="Stream" /> to output.
|
||||
/// </summary>
|
||||
/// <param name="stream">The <see cref="Stream" /> to be written.</param>
|
||||
void Write(Stream stream);
|
||||
void Write(Stream stream, bool keepOpen = false);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -95,13 +104,23 @@ 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(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)
|
||||
{
|
||||
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} ";
|
||||
|
||||
@@ -6,68 +6,67 @@ using System.Text;
|
||||
|
||||
using static Hexapawn.Pawn;
|
||||
|
||||
namespace Hexapawn
|
||||
namespace Hexapawn;
|
||||
|
||||
internal class Board : IEnumerable<Pawn>, IEquatable<Board>
|
||||
{
|
||||
internal class Board : IEnumerable<Pawn>, IEquatable<Board>
|
||||
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<Pawn> GetEnumerator() => _cells.OfType<Pawn>().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<Pawn> GetEnumerator() => _cells.OfType<Pawn>().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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Cell> 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<Cell> 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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Encapsulates the logic of the computer player.
|
||||
/// </summary>
|
||||
internal class Computer
|
||||
{
|
||||
/// <summary>
|
||||
/// Encapsulates the logic of the computer player.
|
||||
/// </summary>
|
||||
internal class Computer : IPlayer
|
||||
private readonly TextIO _io;
|
||||
private readonly IRandom _random;
|
||||
private readonly Dictionary<Board, List<Move>> _potentialMoves;
|
||||
private (List<Move>, Move) _lastMove;
|
||||
public Computer(TextIO io, IRandom random)
|
||||
{
|
||||
private readonly Random _random = new();
|
||||
private readonly Dictionary<Board, List<Move>> _potentialMoves;
|
||||
private (List<Move>, 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<Move> 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<Move> 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<Move> 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<Move> 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<Move> 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<Move> Moves(params Move[] moves) => moves.ToList();
|
||||
|
||||
public bool IsFullyAdvanced(Board board) =>
|
||||
board[9] == Black || board[8] == Black || board[7] == Black;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<object, int> _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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,15 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\*.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\00_Common\dotnet\Games.Common\Games.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
42
46_Hexapawn/csharp/IReadWriteExtensions.cs
Normal file
42
46_Hexapawn/csharp/IReadWriteExtensions.cs
Normal file
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +1,67 @@
|
||||
using static Hexapawn.Pawn;
|
||||
|
||||
namespace Hexapawn
|
||||
namespace Hexapawn;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a move which may, or may not, be legal.
|
||||
/// </summary>
|
||||
internal class Move
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a move which may, or may not, be legal.
|
||||
/// </summary>
|
||||
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}";
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
30
46_Hexapawn/csharp/Resources/Instructions.txt
Normal file
30
46_Hexapawn/csharp/Resources/Instructions.txt
Normal file
@@ -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.
|
||||
|
||||
17
46_Hexapawn/csharp/Resources/Resource.cs
Normal file
17
46_Hexapawn/csharp/Resources/Resource.cs
Normal file
@@ -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");
|
||||
}
|
||||
5
46_Hexapawn/csharp/Resources/Title.txt
Normal file
5
46_Hexapawn/csharp/Resources/Title.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
Hexapawn
|
||||
Creative Computing Morristown, New Jersey
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,15 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Strings\Intro.txt" />
|
||||
<EmbeddedResource Include="Resources\Intro.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\00_Common\dotnet\Games.Common\Games.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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'
|
||||
|
||||
16
58_Love/csharp/Resources/Resource.cs
Normal file
16
58_Love/csharp/Resources/Resource.cs
Normal file
@@ -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");
|
||||
}
|
||||
@@ -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<char> GetCharacters(int count)
|
||||
{
|
||||
var span = new ReadOnlySpan<char>(_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<char> GetCharacters(int count)
|
||||
{
|
||||
var span = new ReadOnlySpan<char>(_chars[_currentRow], _currentIndex, count);
|
||||
|
||||
_currentRow = 1 - _currentRow;
|
||||
_currentIndex += count;
|
||||
if (_currentIndex >= _lineLength)
|
||||
{
|
||||
_currentIndex = _currentRow = 0;
|
||||
}
|
||||
|
||||
return span;
|
||||
}
|
||||
}
|
||||
|
||||
16
58_Love/csharp/StringBuilderExtensions.cs
Normal file
16
58_Love/csharp/StringBuilderExtensions.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
13
62_Mugwump/csharp/Distance.cs
Normal file
13
62_Mugwump/csharp/Distance.cs
Normal file
@@ -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");
|
||||
}
|
||||
@@ -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<bool> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Mugwump> _mugwumps;
|
||||
|
||||
public Grid(TextIO io, IRandom random)
|
||||
{
|
||||
private readonly List<Mugwump> _mugwumps;
|
||||
_io = io;
|
||||
_mugwumps = Enumerable.Range(1, 4).Select(id => new Mugwump(id, random.NextPosition(10, 10))).ToList();
|
||||
}
|
||||
|
||||
public Grid(IEnumerable<Mugwump> 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
7
62_Mugwump/csharp/IRandomExtensions.cs
Normal file
7
62_Mugwump/csharp/IRandomExtensions.cs
Normal file
@@ -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));
|
||||
}
|
||||
@@ -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<float> ReadNumbers(string prompt, int requiredCount)
|
||||
{
|
||||
var numbers = new List<float>();
|
||||
|
||||
while (!TryReadNumbers(prompt, requiredCount, numbers))
|
||||
{
|
||||
prompt = "";
|
||||
}
|
||||
|
||||
return numbers;
|
||||
}
|
||||
|
||||
private static bool TryReadNumbers(string prompt, int requiredCount, List<float> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}";
|
||||
}
|
||||
|
||||
@@ -2,10 +2,15 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Strings\Intro.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\00_Common\dotnet\Games.Common\Games.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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}";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
13
62_Mugwump/csharp/TextIOExtensions.cs
Normal file
13
62_Mugwump/csharp/TextIOExtensions.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<bool> 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}.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
6
82_Stars/csharp/Resources/Instructions.txt
Normal file
6
82_Stars/csharp/Resources/Instructions.txt
Normal file
@@ -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.
|
||||
28
82_Stars/csharp/Resources/Resource.cs
Normal file
28
82_Stars/csharp/Resources/Resource.cs
Normal file
@@ -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");
|
||||
}
|
||||
5
82_Stars/csharp/Resources/Title.txt
Normal file
5
82_Stars/csharp/Resources/Title.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
Stars
|
||||
Creative Computing Morristown, New Jersey
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,15 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources/*.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\00_Common\dotnet\Games.Common\Games.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<DescriptionAttribute>()
|
||||
.Description;
|
||||
}
|
||||
internal static string GetDescription(this Command command) =>
|
||||
typeof(Command)
|
||||
.GetField(command.ToString())
|
||||
.GetCustomAttribute<DescriptionAttribute>()
|
||||
.Description;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
16
84_Super_Star_Trek/csharp/IRandomExtensions.cs
Normal file
16
84_Super_Star_Trek/csharp/IRandomExtensions.cs
Normal file
@@ -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);
|
||||
}
|
||||
89
84_Super_Star_Trek/csharp/IReadWriteExtensions.cs
Normal file
89
84_Super_Star_Trek/csharp/IReadWriteExtensions.cs
Normal file
@@ -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<Command>())
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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<Command>())
|
||||
{
|
||||
_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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Subsystem> _systems;
|
||||
private readonly Dictionary<Command, Subsystem> _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<Subsystem> _systems;
|
||||
private readonly Dictionary<Command, Subsystem> _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<Subsystem>();
|
||||
_commandExecutors = new Dictionary<Command, Subsystem>();
|
||||
_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<Subsystem> 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($" <Shields down to {ShieldControl.ShieldEnergy} units>");
|
||||
|
||||
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<string>();
|
||||
|
||||
foreach (var system in _systems.Where(s => s.IsDamaged))
|
||||
{
|
||||
if (system.Repair(repairWorkDone))
|
||||
{
|
||||
repairedSystems.Add(system.Name);
|
||||
}
|
||||
}
|
||||
|
||||
if (repairedSystems.Any())
|
||||
{
|
||||
_output.WriteLine("Damage Control report:");
|
||||
foreach (var systemName in repairedSystems)
|
||||
{
|
||||
_output.WriteLine($" {systemName} repair completed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void VaryConditionOfRandomSystem()
|
||||
{
|
||||
if (_random.GetFloat() > 0.2f) { return; }
|
||||
|
||||
var system = _systems[_random.Get1To8Inclusive() - 1];
|
||||
_output.Write($"Damage Control report: {system.Name} ");
|
||||
if (_random.GetFloat() >= 0.6)
|
||||
{
|
||||
system.Repair(_random.GetFloat() * 3 + 1);
|
||||
_output.WriteLine("state of repair improved");
|
||||
}
|
||||
else
|
||||
{
|
||||
system.TakeDamage(_random.GetFloat() * 5 + 1);
|
||||
_output.WriteLine("damaged");
|
||||
}
|
||||
}
|
||||
|
||||
internal float Move(Course course, float warpFactor, int distance)
|
||||
{
|
||||
var (quadrant, sector) = MoveWithinQuadrant(course, distance) ?? MoveBeyondQuadrant(course, distance);
|
||||
|
||||
if (quadrant != _quadrant.Coordinates)
|
||||
{
|
||||
_quadrant = new Quadrant(_quadrant.Galaxy[quadrant], this, _random, _quadrant.Galaxy, _input, _output);
|
||||
}
|
||||
_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<Subsystem>();
|
||||
_commandExecutors = new Dictionary<Command, Subsystem>();
|
||||
_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<Subsystem> 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($" <Shields down to {ShieldControl.ShieldEnergy} units>");
|
||||
|
||||
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<string>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
namespace SuperStarTrek.Objects
|
||||
namespace SuperStarTrek.Objects;
|
||||
|
||||
internal class Star
|
||||
{
|
||||
internal class Star
|
||||
{
|
||||
public override string ToString() => " * ";
|
||||
}
|
||||
public override string ToString() => " * ";
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
|
||||
Starfleet Command reviewing your record to consider
|
||||
court martial!
|
||||
court martial!
|
||||
@@ -16,3 +16,9 @@
|
||||
'----------------'
|
||||
|
||||
THE USS ENTERPRISE --- NCC-1701
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
Science Officer Spock reports, 'Sensors show no enemy ships
|
||||
in this quadrant'
|
||||
in this quadrant'
|
||||
@@ -1 +1 @@
|
||||
Mr. Spock reports, 'Sensors show no starbases in this quadrant.'
|
||||
Mr. Spock reports, 'Sensors show no starbases in this quadrant.'
|
||||
@@ -1,2 +1,3 @@
|
||||
|
||||
Now entering {0} quadrant . . .
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
Starbase shields protect the Enterprise
|
||||
Starbase shields protect the Enterprise
|
||||
@@ -5,4 +5,4 @@
|
||||
Canopus Aldebaran
|
||||
Altair Regulus
|
||||
Sagittarius Arcturus
|
||||
Pollux Spica
|
||||
Pollux Spica
|
||||
@@ -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!!
|
||||
@@ -1 +1 @@
|
||||
Will you authorize the repair order (Y/N)
|
||||
Will you authorize the repair order (Y/N)
|
||||
@@ -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'
|
||||
@@ -1 +1 @@
|
||||
Shields dropped for docking purposes
|
||||
Shields dropped for docking purposes
|
||||
@@ -2,3 +2,4 @@
|
||||
|
||||
Your mission begins with your starship located
|
||||
in the galactic quadrant, '{0}'.
|
||||
|
||||
|
||||
@@ -17,3 +17,11 @@
|
||||
* *
|
||||
* *
|
||||
*************************************
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Coordinates> 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<Coordinates> 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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<IEnumerable<QuadrantInfo>> Quadrants => _quadrants;
|
||||
|
||||
private static string GetQuadrantName(Coordinates coordinates) =>
|
||||
$"{_regionNames[coordinates.RegionIndex]} {_subRegionIdentifiers[coordinates.SubRegionIndex]}";
|
||||
|
||||
internal IEnumerable<IEnumerable<QuadrantInfo>> GetNeighborhood(Quadrant quadrant) =>
|
||||
Enumerable.Range(-1, 3)
|
||||
.Select(dx => dx + quadrant.Coordinates.X)
|
||||
.Select(x => GetNeighborhoodRow(quadrant, x));
|
||||
private IEnumerable<QuadrantInfo> 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<IEnumerable<QuadrantInfo>> Quadrants => _quadrants;
|
||||
|
||||
private static string GetQuadrantName(Coordinates coordinates) =>
|
||||
$"{_regionNames[coordinates.RegionIndex]} {_subRegionIdentifiers[coordinates.SubRegionIndex]}";
|
||||
|
||||
internal IEnumerable<IEnumerable<QuadrantInfo>> GetNeighborhood(Quadrant quadrant) =>
|
||||
Enumerable.Range(-1, 3)
|
||||
.Select(dx => dx + quadrant.Coordinates.X)
|
||||
.Select(x => GetNeighborhoodRow(quadrant, x));
|
||||
private IEnumerable<QuadrantInfo> 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]);
|
||||
}
|
||||
|
||||
@@ -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<Coordinates, object> _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<Coordinates, object> _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<Klingon> Klingons => _sectors.Values.OfType<Klingon>();
|
||||
|
||||
public override string ToString() => _info.Name;
|
||||
|
||||
private T PositionObject<T>(Func<Coordinates, T> objectFactory)
|
||||
{
|
||||
var sector = GetRandomEmptySector();
|
||||
_sectors[sector] = objectFactory.Invoke(sector);
|
||||
return (T)_sectors[sector];
|
||||
}
|
||||
|
||||
private void PositionObject(Func<Coordinates, object> 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<Klingon> Klingons => _sectors.Values.OfType<Klingon>();
|
||||
|
||||
public override string ToString() => _info.Name;
|
||||
|
||||
private T PositionObject<T>(Func<Coordinates, T> 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<Coordinates, object> 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<string> GetDisplayLines() => Enumerable.Range(0, 8).Select(x => GetDisplayLine(x));
|
||||
internal IEnumerable<string> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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" : "");
|
||||
}
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\*.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\00_Common\dotnet\Games.Common\Games.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<string> 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<string> GetRowData() =>
|
||||
Galaxy.Quadrants.Select(row => " " + string.Join(" ", row));
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string> 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<string> GetRowData();
|
||||
|
||||
internal sealed override void Execute(Quadrant quadrant)
|
||||
{
|
||||
WriteHeader(quadrant);
|
||||
Output.WriteLine(" 1 2 3 4 5 6 7 8")
|
||||
.WriteLine(" ----- ----- ----- ----- ----- ----- ----- -----");
|
||||
|
||||
foreach (var (row, index) in GetRowData().Select((r, i) => (r, i)))
|
||||
{
|
||||
Output.WriteLine($" {index+1} {row}")
|
||||
.WriteLine(" ----- ----- ----- ----- ----- ----- ----- -----");
|
||||
}
|
||||
IO.WriteLine($" {index+1} {row}");
|
||||
IO.WriteLine(" ----- ----- ----- ----- ----- ----- ----- -----");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string> GetRowData() =>
|
||||
Strings.RegionNames.Split('\n').Select(n => n.TrimEnd('\r'));
|
||||
}
|
||||
|
||||
protected override void WriteHeader(Quadrant quadrant) =>
|
||||
IO.WriteLine(" The Galaxy");
|
||||
|
||||
protected override IEnumerable<string> GetRowData() =>
|
||||
Strings.RegionNames.Split('\n').Select(n => n.TrimEnd('\r'));
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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("<SHIELDS UNCHANGED>");
|
||||
}
|
||||
|
||||
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("<SHIELDS UNCHANGED>");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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<string> 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<string> 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}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user