mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2026-02-04 11:07:59 -08:00
Split classes to files
This commit is contained in:
45
77_Salvo/csharp/Coordinate.cs
Normal file
45
77_Salvo/csharp/Coordinate.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
namespace Salvo;
|
||||
|
||||
internal record struct Coordinate(int Value)
|
||||
{
|
||||
public const int MinValue = 0;
|
||||
public const int MaxValue = 9;
|
||||
|
||||
public static IEnumerable<Coordinate> Range => Enumerable.Range(0, 10).Select(v => new Coordinate(v));
|
||||
|
||||
public bool IsInRange => Value is >= MinValue and <= MaxValue;
|
||||
|
||||
public static Coordinate Create(float value) => new((int)value - 1);
|
||||
|
||||
public static bool TryCreateValid(float value, out Coordinate coordinate)
|
||||
{
|
||||
coordinate = default;
|
||||
if (value != (int)value) { return false; }
|
||||
|
||||
var result = Create(value);
|
||||
|
||||
if (result.IsInRange)
|
||||
{
|
||||
coordinate = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Coordinate BringIntoRange(IRandom random)
|
||||
=> Value switch
|
||||
{
|
||||
< MinValue => new(MinValue + (int)random.NextFloat(2.5F)),
|
||||
> MaxValue => new(MaxValue - (int)random.NextFloat(2.5F)),
|
||||
_ => this
|
||||
};
|
||||
|
||||
public static implicit operator Coordinate(float value) => new((int)value);
|
||||
public static implicit operator int(Coordinate coordinate) => coordinate.Value;
|
||||
|
||||
public static Coordinate operator +(Coordinate coordinate, int offset) => new(coordinate.Value + offset);
|
||||
public static int operator -(Coordinate a, Coordinate b) => a.Value - b.Value;
|
||||
|
||||
public override string ToString() => $" {Value + 1} ";
|
||||
}
|
||||
27
77_Salvo/csharp/Extensions/IOExtensions.cs
Normal file
27
77_Salvo/csharp/Extensions/IOExtensions.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace Games.Common.IO;
|
||||
|
||||
internal static class IOExtensions
|
||||
{
|
||||
internal static Position ReadPosition(this IReadWrite io) => Position.Create(io.Read2Numbers(""));
|
||||
|
||||
internal static Position ReadValidPosition(this IReadWrite io)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (Position.TryCreateValid(io.Read2Numbers(""), out var position))
|
||||
{
|
||||
return position;
|
||||
}
|
||||
io.WriteLine("ILLEGAL, ENTER AGAIN.");
|
||||
}
|
||||
}
|
||||
|
||||
internal static IEnumerable<Position> ReadPositions(this IReadWrite io, string shipName, int shipSize)
|
||||
{
|
||||
io.WriteLine(shipName);
|
||||
for (var i = 0; i < shipSize; i++)
|
||||
{
|
||||
yield return io.ReadPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
32
77_Salvo/csharp/Extensions/RandomExtensions.cs
Normal file
32
77_Salvo/csharp/Extensions/RandomExtensions.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace Games.Common.Randomness;
|
||||
|
||||
internal static class RandomExtensions
|
||||
{
|
||||
internal static (Position, Offset) NextShipPosition(this IRandom random)
|
||||
{
|
||||
var startX = random.NextCoordinate();
|
||||
var startY = random.NextCoordinate();
|
||||
var deltaY = random.NextOffset();
|
||||
var deltaX = random.NextOffset();
|
||||
return (new(startX, startY), new(deltaX, deltaY));
|
||||
}
|
||||
|
||||
private static Coordinate NextCoordinate(this IRandom random)
|
||||
=> random.Next(Coordinate.MinValue, Coordinate.MaxValue + 1);
|
||||
|
||||
private static int NextOffset(this IRandom random) => random.Next(-1, 2);
|
||||
|
||||
internal static (Position, Offset) GetRandomShipPositionInRange(this IRandom random, int shipSize)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var (start, delta) = random.NextShipPosition();
|
||||
var shipSizeLessOne = shipSize - 1;
|
||||
var end = start + delta * shipSizeLessOne;
|
||||
if (delta != 0 && end.IsInRange)
|
||||
{
|
||||
return (start, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Immutable;
|
||||
using Games.Common.Randomness;
|
||||
using Salvo.Targetting;
|
||||
|
||||
namespace Salvo;
|
||||
|
||||
@@ -224,419 +223,3 @@ L4210: ;// NoOp - NEXT S
|
||||
L4230: goto L3380;
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class ShotSelector
|
||||
{
|
||||
internal ShotSelector(Grid source, Grid target)
|
||||
{
|
||||
Source = source;
|
||||
Target = target;
|
||||
}
|
||||
|
||||
protected Grid Source { get; }
|
||||
protected Grid Target { get; }
|
||||
|
||||
public int GetShotCount() => Source.Ships.Sum(s => s.Shots);
|
||||
}
|
||||
|
||||
internal abstract class ComputerShotSelector : ShotSelector
|
||||
{
|
||||
private readonly bool _displayShots;
|
||||
|
||||
internal ComputerShotSelector(Grid source, Grid target, bool displayShots)
|
||||
: base(source, target)
|
||||
{
|
||||
_displayShots = displayShots;
|
||||
}
|
||||
|
||||
private void DisplayShots(IEnumerable<Position> shots, IReadWrite io)
|
||||
{
|
||||
if (_displayShots)
|
||||
{
|
||||
foreach (var shot in shots)
|
||||
{
|
||||
io.WriteLine(shot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class HumanShotSelector : ShotSelector
|
||||
{
|
||||
public HumanShotSelector(Grid source, Grid target)
|
||||
: base(source, target)
|
||||
{
|
||||
}
|
||||
|
||||
public IEnumerable<Position> GetShots(IReadWrite io)
|
||||
{
|
||||
var shots = new Position[GetShotCount()];
|
||||
|
||||
for (var i = 0; i < shots.Length; i++)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var position = io.ReadValidPosition();
|
||||
if (Target.WasTargetedAt(position, out var turnTargeted))
|
||||
{
|
||||
io.WriteLine($"YOU SHOT THERE BEFORE ON TURN {turnTargeted}");
|
||||
continue;
|
||||
}
|
||||
shots[i] = position;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return shots;
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class Ship
|
||||
{
|
||||
private readonly List<Position> _positions = new();
|
||||
|
||||
protected Ship(IReadWrite io, string? nameSuffix = null)
|
||||
{
|
||||
Name = GetType().Name + nameSuffix;
|
||||
_positions = io.ReadPositions(Name, Size).ToList();
|
||||
}
|
||||
|
||||
protected Ship(IRandom random, string? nameSuffix = null)
|
||||
{
|
||||
Name = GetType().Name + nameSuffix;
|
||||
|
||||
var (start, delta) = random.GetRandomShipPositionInRange(Size);
|
||||
for (var i = 0; i < Size; i++)
|
||||
{
|
||||
_positions.Add(start + delta * i);
|
||||
}
|
||||
}
|
||||
|
||||
internal string Name { get; }
|
||||
internal abstract int Shots { get; }
|
||||
internal abstract int Size { get; }
|
||||
internal abstract float Value { get; }
|
||||
internal IEnumerable<Position> Positions => _positions;
|
||||
internal bool IsDestroyed => _positions.Count == 0;
|
||||
|
||||
internal bool IsHit(Position position) => _positions.Remove(position);
|
||||
|
||||
internal float DistanceTo(Ship other)
|
||||
=> _positions.SelectMany(a => other._positions.Select(b => a.DistanceTo(b))).Min();
|
||||
|
||||
public override string ToString()
|
||||
=> string.Join(Environment.NewLine, _positions.Select(p => p.ToString()).Prepend(Name));
|
||||
}
|
||||
|
||||
internal sealed class Battleship : Ship
|
||||
{
|
||||
internal Battleship(IReadWrite io)
|
||||
: base(io)
|
||||
{
|
||||
}
|
||||
|
||||
internal Battleship(IRandom random)
|
||||
: base(random)
|
||||
{
|
||||
}
|
||||
|
||||
internal override int Shots => 3;
|
||||
internal override int Size => 5;
|
||||
internal override float Value => 3;
|
||||
}
|
||||
|
||||
internal sealed class Cruiser : Ship
|
||||
{
|
||||
internal Cruiser(IReadWrite io)
|
||||
: base(io)
|
||||
{
|
||||
}
|
||||
|
||||
internal Cruiser(IRandom random)
|
||||
: base(random)
|
||||
{
|
||||
}
|
||||
|
||||
internal override int Shots => 2;
|
||||
internal override int Size => 3;
|
||||
internal override float Value => 2;
|
||||
}
|
||||
|
||||
internal sealed class Destroyer : Ship
|
||||
{
|
||||
internal Destroyer(string nameIndex, IReadWrite io)
|
||||
: base(io, $"<{nameIndex}>")
|
||||
{
|
||||
}
|
||||
|
||||
internal Destroyer(string nameIndex, IRandom random)
|
||||
: base(random, $"<{nameIndex}>")
|
||||
{
|
||||
}
|
||||
|
||||
internal override int Shots => 1;
|
||||
internal override int Size => 2;
|
||||
internal override float Value => Name.EndsWith("<A>") ? 1 : 0.5F;
|
||||
}
|
||||
|
||||
internal static class RandomExtensions
|
||||
{
|
||||
internal static (Position, Offset) NextShipPosition(this IRandom random)
|
||||
{
|
||||
var startX = random.NextCoordinate();
|
||||
var startY = random.NextCoordinate();
|
||||
var deltaY = random.NextOffset();
|
||||
var deltaX = random.NextOffset();
|
||||
return (new(startX, startY), new(deltaX, deltaY));
|
||||
}
|
||||
|
||||
private static Coordinate NextCoordinate(this IRandom random)
|
||||
=> random.Next(Coordinate.MinValue, Coordinate.MaxValue + 1);
|
||||
|
||||
private static int NextOffset(this IRandom random) => random.Next(-1, 2);
|
||||
|
||||
internal static (Position, Offset) GetRandomShipPositionInRange(this IRandom random, int shipSize)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var (start, delta) = random.NextShipPosition();
|
||||
var shipSizeLessOne = shipSize - 1;
|
||||
var end = start + delta * shipSizeLessOne;
|
||||
if (delta != 0 && end.IsInRange)
|
||||
{
|
||||
return (start, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class Grid
|
||||
{
|
||||
private readonly List<Ship> _ships;
|
||||
private readonly Dictionary<Position, int> _shots = new();
|
||||
|
||||
internal Grid()
|
||||
{
|
||||
_ships = new();
|
||||
}
|
||||
|
||||
internal Grid(IReadWrite io)
|
||||
{
|
||||
io.WriteLine("ENTER COORDINATES FOR...");
|
||||
_ships = new()
|
||||
{
|
||||
new Battleship(io),
|
||||
new Cruiser(io),
|
||||
new Destroyer("A", io),
|
||||
new Destroyer("B", io)
|
||||
};
|
||||
}
|
||||
|
||||
internal Grid(IRandom random)
|
||||
{
|
||||
_ships = new();
|
||||
while (true)
|
||||
{
|
||||
_ships.Add(new Battleship(random));
|
||||
if (TryPositionShip(() => new Cruiser(random)) &&
|
||||
TryPositionShip(() => new Destroyer("A", random)) &&
|
||||
TryPositionShip(() => new Destroyer("B", random)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
_ships.Clear();
|
||||
}
|
||||
|
||||
bool TryPositionShip(Func<Ship> shipFactory)
|
||||
{
|
||||
var shipGenerationAttempts = 0;
|
||||
while (true)
|
||||
{
|
||||
var ship = shipFactory.Invoke();
|
||||
shipGenerationAttempts++;
|
||||
if (shipGenerationAttempts > 25) { return false; }
|
||||
if (_ships.Min(ship.DistanceTo) >= 3.59)
|
||||
{
|
||||
_ships.Add(ship);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float this[Position position]
|
||||
{
|
||||
get => _shots.TryGetValue(position, out var value)
|
||||
? value + 10
|
||||
: _ships.FirstOrDefault(s => s.Positions.Contains(position))?.Value ?? 0;
|
||||
set
|
||||
{
|
||||
_ = _ships.FirstOrDefault(s => s.IsHit(position));
|
||||
_shots[position] = (int)value - 10;
|
||||
}
|
||||
}
|
||||
|
||||
internal int UntriedSquareCount => 100 - _shots.Count;
|
||||
internal IEnumerable<Ship> Ships => _ships.AsEnumerable();
|
||||
|
||||
internal bool WasTargetedAt(Position position, out int turnTargeted)
|
||||
=> _shots.TryGetValue(position, out turnTargeted);
|
||||
|
||||
internal bool IsHit(Position position, int turnNumber, out string? shipName)
|
||||
{
|
||||
shipName = null;
|
||||
_shots[position] = turnNumber;
|
||||
|
||||
var ship = _ships.FirstOrDefault(s => s.IsHit(position));
|
||||
if (ship == null) { return false; }
|
||||
|
||||
if (ship.IsDestroyed) { _ships.Remove(ship); }
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class IOExtensions
|
||||
{
|
||||
internal static Position ReadPosition(this IReadWrite io) => Position.Create(io.Read2Numbers(""));
|
||||
|
||||
internal static Position ReadValidPosition(this IReadWrite io)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (Position.TryCreateValid(io.Read2Numbers(""), out var position))
|
||||
{
|
||||
return position;
|
||||
}
|
||||
io.WriteLine("ILLEGAL, ENTER AGAIN.");
|
||||
}
|
||||
}
|
||||
|
||||
internal static IEnumerable<Position> ReadPositions(this IReadWrite io, string shipName, int shipSize)
|
||||
{
|
||||
io.WriteLine(shipName);
|
||||
for (var i = 0; i < shipSize; i++)
|
||||
{
|
||||
yield return io.ReadPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal record struct Position(Coordinate X, Coordinate Y)
|
||||
{
|
||||
public bool IsInRange => X.IsInRange && Y.IsInRange;
|
||||
public bool IsOnDiagonal => X == Y;
|
||||
|
||||
public static Position Create((float X, float Y) coordinates) => new(coordinates.X, coordinates.Y);
|
||||
|
||||
public static bool TryCreateValid((float X, float Y) coordinates, out Position position)
|
||||
{
|
||||
if (Coordinate.TryCreateValid(coordinates.X, out var x) && Coordinate.TryCreateValid(coordinates.Y, out var y))
|
||||
{
|
||||
position = new(x, y);
|
||||
return true;
|
||||
}
|
||||
|
||||
position = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static IEnumerable<Position> All
|
||||
=> Coordinate.Range.SelectMany(x => Coordinate.Range.Select(y => new Position(x, y)));
|
||||
|
||||
public IEnumerable<Position> Neighbours
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var offset in Offset.Units)
|
||||
{
|
||||
var neighbour = this + offset;
|
||||
if (neighbour.IsInRange) { yield return neighbour; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal float DistanceTo(Position other)
|
||||
{
|
||||
var (deltaX, deltaY) = (X - other.X, Y - other.Y);
|
||||
return (float)Math.Sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
}
|
||||
|
||||
internal Position BringIntoRange(IRandom random)
|
||||
=> IsInRange ? this : new(X.BringIntoRange(random), Y.BringIntoRange(random));
|
||||
|
||||
public static Position operator +(Position position, Offset offset)
|
||||
=> new(position.X + offset.X, position.Y + offset.Y);
|
||||
|
||||
public static implicit operator Position(int value) => new(value, value);
|
||||
|
||||
public override string ToString() => $"{X}{Y}";
|
||||
}
|
||||
|
||||
internal record struct Coordinate(int Value)
|
||||
{
|
||||
public const int MinValue = 0;
|
||||
public const int MaxValue = 9;
|
||||
|
||||
public static IEnumerable<Coordinate> Range => Enumerable.Range(0, 10).Select(v => new Coordinate(v));
|
||||
|
||||
public bool IsInRange => Value is >= MinValue and <= MaxValue;
|
||||
|
||||
public static Coordinate Create(float value) => new((int)value - 1);
|
||||
|
||||
public static bool TryCreateValid(float value, out Coordinate coordinate)
|
||||
{
|
||||
coordinate = default;
|
||||
if (value != (int)value) { return false; }
|
||||
|
||||
var result = Create(value);
|
||||
|
||||
if (result.IsInRange)
|
||||
{
|
||||
coordinate = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Coordinate BringIntoRange(IRandom random)
|
||||
=> Value switch
|
||||
{
|
||||
< MinValue => new(MinValue + (int)random.NextFloat(2.5F)),
|
||||
> MaxValue => new(MaxValue - (int)random.NextFloat(2.5F)),
|
||||
_ => this
|
||||
};
|
||||
|
||||
public static implicit operator Coordinate(float value) => new((int)value);
|
||||
public static implicit operator int(Coordinate coordinate) => coordinate.Value;
|
||||
|
||||
public static Coordinate operator +(Coordinate coordinate, int offset) => new(coordinate.Value + offset);
|
||||
public static int operator -(Coordinate a, Coordinate b) => a.Value - b.Value;
|
||||
|
||||
public override string ToString() => $" {Value + 1} ";
|
||||
}
|
||||
|
||||
internal record struct Offset(int X, int Y)
|
||||
{
|
||||
public static readonly Offset Zero = 0;
|
||||
|
||||
public static Offset operator *(Offset offset, int scale) => new(offset.X * scale, offset.Y * scale);
|
||||
|
||||
public static implicit operator Offset(int value) => new(value, value);
|
||||
|
||||
public static IEnumerable<Offset> Units
|
||||
{
|
||||
get
|
||||
{
|
||||
for (int x = -1; x <= 1; x++)
|
||||
{
|
||||
for (int y = -1; y <= 1; y++)
|
||||
{
|
||||
var offset = new Offset(x, y);
|
||||
if (offset != Zero) { yield return offset; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
89
77_Salvo/csharp/Grid.cs
Normal file
89
77_Salvo/csharp/Grid.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Salvo;
|
||||
|
||||
internal class Grid
|
||||
{
|
||||
private readonly List<Ship> _ships;
|
||||
private readonly Dictionary<Position, int> _shots = new();
|
||||
|
||||
internal Grid()
|
||||
{
|
||||
_ships = new();
|
||||
}
|
||||
|
||||
internal Grid(IReadWrite io)
|
||||
{
|
||||
io.WriteLine("ENTER COORDINATES FOR...");
|
||||
_ships = new()
|
||||
{
|
||||
new Battleship(io),
|
||||
new Cruiser(io),
|
||||
new Destroyer("A", io),
|
||||
new Destroyer("B", io)
|
||||
};
|
||||
}
|
||||
|
||||
internal Grid(IRandom random)
|
||||
{
|
||||
_ships = new();
|
||||
while (true)
|
||||
{
|
||||
_ships.Add(new Battleship(random));
|
||||
if (TryPositionShip(() => new Cruiser(random)) &&
|
||||
TryPositionShip(() => new Destroyer("A", random)) &&
|
||||
TryPositionShip(() => new Destroyer("B", random)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
_ships.Clear();
|
||||
}
|
||||
|
||||
bool TryPositionShip(Func<Ship> shipFactory)
|
||||
{
|
||||
var shipGenerationAttempts = 0;
|
||||
while (true)
|
||||
{
|
||||
var ship = shipFactory.Invoke();
|
||||
shipGenerationAttempts++;
|
||||
if (shipGenerationAttempts > 25) { return false; }
|
||||
if (_ships.Min(ship.DistanceTo) >= 3.59)
|
||||
{
|
||||
_ships.Add(ship);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float this[Position position]
|
||||
{
|
||||
get => _shots.TryGetValue(position, out var value)
|
||||
? value + 10
|
||||
: _ships.FirstOrDefault(s => s.Positions.Contains(position))?.Value ?? 0;
|
||||
set
|
||||
{
|
||||
_ = _ships.FirstOrDefault(s => s.IsHit(position));
|
||||
_shots[position] = (int)value - 10;
|
||||
}
|
||||
}
|
||||
|
||||
internal int UntriedSquareCount => 100 - _shots.Count;
|
||||
internal IEnumerable<Ship> Ships => _ships.AsEnumerable();
|
||||
|
||||
internal bool WasTargetedAt(Position position, out int turnTargeted)
|
||||
=> _shots.TryGetValue(position, out turnTargeted);
|
||||
|
||||
internal bool IsHit(Position position, int turnNumber, out string? shipName)
|
||||
{
|
||||
shipName = null;
|
||||
_shots[position] = turnNumber;
|
||||
|
||||
var ship = _ships.FirstOrDefault(s => s.IsHit(position));
|
||||
if (ship == null) { return false; }
|
||||
|
||||
if (ship.IsDestroyed) { _ships.Remove(ship); }
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
25
77_Salvo/csharp/Offset.cs
Normal file
25
77_Salvo/csharp/Offset.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace Salvo;
|
||||
|
||||
internal record struct Offset(int X, int Y)
|
||||
{
|
||||
public static readonly Offset Zero = 0;
|
||||
|
||||
public static Offset operator *(Offset offset, int scale) => new(offset.X * scale, offset.Y * scale);
|
||||
|
||||
public static implicit operator Offset(int value) => new(value, value);
|
||||
|
||||
public static IEnumerable<Offset> Units
|
||||
{
|
||||
get
|
||||
{
|
||||
for (int x = -1; x <= 1; x++)
|
||||
{
|
||||
for (int y = -1; y <= 1; y++)
|
||||
{
|
||||
var offset = new Offset(x, y);
|
||||
if (offset != Zero) { yield return offset; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
77_Salvo/csharp/Position.cs
Normal file
52
77_Salvo/csharp/Position.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
namespace Salvo;
|
||||
|
||||
internal record struct Position(Coordinate X, Coordinate Y)
|
||||
{
|
||||
public bool IsInRange => X.IsInRange && Y.IsInRange;
|
||||
public bool IsOnDiagonal => X == Y;
|
||||
|
||||
public static Position Create((float X, float Y) coordinates) => new(coordinates.X, coordinates.Y);
|
||||
|
||||
public static bool TryCreateValid((float X, float Y) coordinates, out Position position)
|
||||
{
|
||||
if (Coordinate.TryCreateValid(coordinates.X, out var x) && Coordinate.TryCreateValid(coordinates.Y, out var y))
|
||||
{
|
||||
position = new(x, y);
|
||||
return true;
|
||||
}
|
||||
|
||||
position = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static IEnumerable<Position> All
|
||||
=> Coordinate.Range.SelectMany(x => Coordinate.Range.Select(y => new Position(x, y)));
|
||||
|
||||
public IEnumerable<Position> Neighbours
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var offset in Offset.Units)
|
||||
{
|
||||
var neighbour = this + offset;
|
||||
if (neighbour.IsInRange) { yield return neighbour; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal float DistanceTo(Position other)
|
||||
{
|
||||
var (deltaX, deltaY) = (X - other.X, Y - other.Y);
|
||||
return (float)Math.Sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
}
|
||||
|
||||
internal Position BringIntoRange(IRandom random)
|
||||
=> IsInRange ? this : new(X.BringIntoRange(random), Y.BringIntoRange(random));
|
||||
|
||||
public static Position operator +(Position position, Offset offset)
|
||||
=> new(position.X + offset.X, position.Y + offset.Y);
|
||||
|
||||
public static implicit operator Position(int value) => new(value, value);
|
||||
|
||||
public override string ToString() => $"{X}{Y}";
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
global using System;
|
||||
global using Games.Common.IO;
|
||||
global using Games.Common.Randomness;
|
||||
global using Salvo;
|
||||
global using Salvo.Ships;
|
||||
global using static Salvo.Resources.Resource;
|
||||
using Salvo;
|
||||
|
||||
new Game(new ConsoleIO(), new RandomNumberGenerator()).Play();
|
||||
|
||||
18
77_Salvo/csharp/Ships/Battleship.cs
Normal file
18
77_Salvo/csharp/Ships/Battleship.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace Salvo.Ships;
|
||||
|
||||
internal sealed class Battleship : Ship
|
||||
{
|
||||
internal Battleship(IReadWrite io)
|
||||
: base(io)
|
||||
{
|
||||
}
|
||||
|
||||
internal Battleship(IRandom random)
|
||||
: base(random)
|
||||
{
|
||||
}
|
||||
|
||||
internal override int Shots => 3;
|
||||
internal override int Size => 5;
|
||||
internal override float Value => 3;
|
||||
}
|
||||
18
77_Salvo/csharp/Ships/Cruiser.cs
Normal file
18
77_Salvo/csharp/Ships/Cruiser.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace Salvo.Ships;
|
||||
|
||||
internal sealed class Cruiser : Ship
|
||||
{
|
||||
internal Cruiser(IReadWrite io)
|
||||
: base(io)
|
||||
{
|
||||
}
|
||||
|
||||
internal Cruiser(IRandom random)
|
||||
: base(random)
|
||||
{
|
||||
}
|
||||
|
||||
internal override int Shots => 2;
|
||||
internal override int Size => 3;
|
||||
internal override float Value => 2;
|
||||
}
|
||||
18
77_Salvo/csharp/Ships/Destroyer.cs
Normal file
18
77_Salvo/csharp/Ships/Destroyer.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace Salvo.Ships;
|
||||
|
||||
internal sealed class Destroyer : Ship
|
||||
{
|
||||
internal Destroyer(string nameIndex, IReadWrite io)
|
||||
: base(io, $"<{nameIndex}>")
|
||||
{
|
||||
}
|
||||
|
||||
internal Destroyer(string nameIndex, IRandom random)
|
||||
: base(random, $"<{nameIndex}>")
|
||||
{
|
||||
}
|
||||
|
||||
internal override int Shots => 1;
|
||||
internal override int Size => 2;
|
||||
internal override float Value => Name.EndsWith("<A>") ? 1 : 0.5F;
|
||||
}
|
||||
38
77_Salvo/csharp/Ships/Ship.cs
Normal file
38
77_Salvo/csharp/Ships/Ship.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace Salvo.Ships;
|
||||
|
||||
internal abstract class Ship
|
||||
{
|
||||
private readonly List<Position> _positions = new();
|
||||
|
||||
protected Ship(IReadWrite io, string? nameSuffix = null)
|
||||
{
|
||||
Name = GetType().Name + nameSuffix;
|
||||
_positions = io.ReadPositions(Name, Size).ToList();
|
||||
}
|
||||
|
||||
protected Ship(IRandom random, string? nameSuffix = null)
|
||||
{
|
||||
Name = GetType().Name + nameSuffix;
|
||||
|
||||
var (start, delta) = random.GetRandomShipPositionInRange(Size);
|
||||
for (var i = 0; i < Size; i++)
|
||||
{
|
||||
_positions.Add(start + delta * i);
|
||||
}
|
||||
}
|
||||
|
||||
internal string Name { get; }
|
||||
internal abstract int Shots { get; }
|
||||
internal abstract int Size { get; }
|
||||
internal abstract float Value { get; }
|
||||
internal IEnumerable<Position> Positions => _positions;
|
||||
internal bool IsDestroyed => _positions.Count == 0;
|
||||
|
||||
internal bool IsHit(Position position) => _positions.Remove(position);
|
||||
|
||||
internal float DistanceTo(Ship other)
|
||||
=> _positions.SelectMany(a => other._positions.Select(b => a.DistanceTo(b))).Min();
|
||||
|
||||
public override string ToString()
|
||||
=> string.Join(Environment.NewLine, _positions.Select(p => p.ToString()).Prepend(Name));
|
||||
}
|
||||
23
77_Salvo/csharp/Targetting/ComputerShotSelector.cs
Normal file
23
77_Salvo/csharp/Targetting/ComputerShotSelector.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace Salvo.Targetting;
|
||||
|
||||
internal abstract class ComputerShotSelector : ShotSelector
|
||||
{
|
||||
private readonly bool _displayShots;
|
||||
|
||||
internal ComputerShotSelector(Grid source, Grid target, bool displayShots)
|
||||
: base(source, target)
|
||||
{
|
||||
_displayShots = displayShots;
|
||||
}
|
||||
|
||||
private void DisplayShots(IEnumerable<Position> shots, IReadWrite io)
|
||||
{
|
||||
if (_displayShots)
|
||||
{
|
||||
foreach (var shot in shots)
|
||||
{
|
||||
io.WriteLine(shot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
77_Salvo/csharp/Targetting/HumanShotSelector.cs
Normal file
31
77_Salvo/csharp/Targetting/HumanShotSelector.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace Salvo.Targetting;
|
||||
|
||||
internal class HumanShotSelector : ShotSelector
|
||||
{
|
||||
public HumanShotSelector(Grid source, Grid target)
|
||||
: base(source, target)
|
||||
{
|
||||
}
|
||||
|
||||
public IEnumerable<Position> GetShots(IReadWrite io)
|
||||
{
|
||||
var shots = new Position[GetShotCount()];
|
||||
|
||||
for (var i = 0; i < shots.Length; i++)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var position = io.ReadValidPosition();
|
||||
if (Target.WasTargetedAt(position, out var turnTargeted))
|
||||
{
|
||||
io.WriteLine($"YOU SHOT THERE BEFORE ON TURN {turnTargeted}");
|
||||
continue;
|
||||
}
|
||||
shots[i] = position;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return shots;
|
||||
}
|
||||
}
|
||||
15
77_Salvo/csharp/Targetting/ShotSelector.cs
Normal file
15
77_Salvo/csharp/Targetting/ShotSelector.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Salvo.Targetting;
|
||||
|
||||
internal abstract class ShotSelector
|
||||
{
|
||||
internal ShotSelector(Grid source, Grid target)
|
||||
{
|
||||
Source = source;
|
||||
Target = target;
|
||||
}
|
||||
|
||||
protected Grid Source { get; }
|
||||
protected Grid Target { get; }
|
||||
|
||||
public int GetShotCount() => Source.Ships.Sum(s => s.Shots);
|
||||
}
|
||||
Reference in New Issue
Block a user