Tidy up shot selection

This commit is contained in:
drewjcooper
2023-05-20 17:12:48 +10:00
parent e27b5ec6e5
commit e02a4ca5da
8 changed files with 67 additions and 64 deletions

View File

@@ -22,8 +22,8 @@ internal class Game
var computerGrid = new Grid(_random);
var humanGrid = new Grid(_io);
var humanShotSelector = new HumanShotSelector(humanGrid, computerGrid, _io);
var computerShotSelector = new ComputerShotSelector(computerGrid, humanGrid, _random);
var humanShotSelector = new HumanShotSelector(humanGrid, _io);
var computerShotSelector = new ComputerShotSelector(computerGrid, _random);
var startResponse = _io.ReadString(Prompts.Start);
while (startResponse == Strings.WhereAreYourShips)
{
@@ -43,14 +43,14 @@ L1980: _io.Write(Strings.Turn(turnNumber));
L1990: var numberOfShots = humanShotSelector.NumberOfShots;
L2220: _io.Write(Strings.YouHaveShots(numberOfShots));
if (numberOfShots == 0) { goto L2270; }
L2230: if (numberOfShots > computerGrid.UntriedSquareCount)
L2230: if (humanShotSelector.CanTargetAllRemainingSquares)
{
_io.WriteLine(Streams.YouHaveMoreShotsThanSquares);
L2250: goto L2890;
}
foreach (var shot1 in humanShotSelector.GetShots())
foreach (var shot1 in humanShotSelector.GetShots(turnNumber))
{
if (computerGrid.IsHit(shot1, turnNumber, out var ship))
if (computerGrid.IsHit(shot1, out var ship))
{
_io.Write(Strings.YouHit(ship.Name));
}
@@ -60,15 +60,15 @@ L2640: turnNumber++;
L2660: _io.Write(Strings.Turn(turnNumber));
L2670: numberOfShots = computerShotSelector.NumberOfShots;
L2840: _io.Write(Strings.IHaveShots(numberOfShots));
L2850: if (humanGrid.UntriedSquareCount > numberOfShots) { goto L2880; }
L2850: if (!computerShotSelector.CanTargetAllRemainingSquares) { goto L2880; }
L2860: _io.Write(Streams.IHaveMoreShotsThanSquares);
L2270: _io.Write(Streams.IWon);
return;
L2880: if (numberOfShots == 0) { goto L2960; }
L2880: if (numberOfShots > 0) { goto L2960; }
L2890: _io.Write(Streams.YouWon);
L2900: return;
L2960: temp = computerShotSelector.GetShots().ToArray();
L2960: temp = computerShotSelector.GetShots(turnNumber).ToArray();
// display shots
L3380: if (seeShotsResponse == "YES")
{
@@ -79,7 +79,7 @@ L3380: if (seeShotsResponse == "YES")
}
foreach (var shot in temp)
{
if (humanGrid.IsHit(shot, turnNumber, out var ship))
if (humanGrid.IsHit(shot, out var ship))
{
_io.Write(Strings.IHit(ship.Name));
computerShotSelector.RecordHit(ship, turnNumber);

View File

@@ -6,12 +6,6 @@ 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)
{
@@ -57,16 +51,10 @@ internal class Grid
}
}
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, [NotNullWhen(true)] out Ship? ship)
internal bool IsHit(Position position, [NotNullWhen(true)] out Ship? ship)
{
_shots[position] = turnNumber;
ship = _ships.FirstOrDefault(s => s.IsHit(position));
if (ship == null) { return false; }

View File

@@ -3,19 +3,19 @@ namespace Salvo.Targetting;
internal class ComputerShotSelector : ShotSelector
{
private readonly KnownHitsShotSelectionStrategy _knownHitsStrategy;
private readonly SearchPatternShotSelector _searchPatternShotSelector;
private readonly SearchPatternShotSelectionStrategy _searchPatternStrategy;
internal ComputerShotSelector(Grid source, Grid target, IRandom random)
: base(source, target)
internal ComputerShotSelector(Grid source, IRandom random)
: base(source)
{
_knownHitsStrategy = new KnownHitsShotSelectionStrategy(target);
_searchPatternShotSelector = new SearchPatternShotSelector(source, target, random);
_knownHitsStrategy = new KnownHitsShotSelectionStrategy(this);
_searchPatternStrategy = new SearchPatternShotSelectionStrategy(this, random);
}
internal override IEnumerable<Position> GetShots()
{
return _knownHitsStrategy.GetShots(NumberOfShots) ?? _searchPatternShotSelector.GetShots();
}
protected override IEnumerable<Position> GetShots() => GetSelectionStrategy().GetShots(NumberOfShots);
internal void RecordHit(Ship ship, int turn) => _knownHitsStrategy.RecordHit(ship, turn);
private ShotSelectionStrategy GetSelectionStrategy()
=> _knownHitsStrategy.KnowsOfDamagedShips ? _knownHitsStrategy : _searchPatternStrategy;
}

View File

@@ -4,13 +4,13 @@ internal class HumanShotSelector : ShotSelector
{
private readonly IReadWrite _io;
internal HumanShotSelector(Grid source, Grid target, IReadWrite io)
: base(source, target)
internal HumanShotSelector(Grid source, IReadWrite io)
: base(source)
{
_io = io;
}
internal override IEnumerable<Position> GetShots()
protected override IEnumerable<Position> GetShots()
{
var shots = new Position[NumberOfShots];
@@ -19,7 +19,7 @@ internal class HumanShotSelector : ShotSelector
while (true)
{
var position = _io.ReadValidPosition();
if (Target.WasTargetedAt(position, out var turnTargeted))
if (WasSelectedPreviously(position, out var turnTargeted))
{
_io.WriteLine($"YOU SHOT THERE BEFORE ON TURN {turnTargeted}");
continue;

View File

@@ -4,15 +4,15 @@ internal class KnownHitsShotSelectionStrategy : ShotSelectionStrategy
{
private readonly List<(int Turn, Ship Ship)> _damagedShips = new();
internal KnownHitsShotSelectionStrategy(Grid target)
: base(target)
internal KnownHitsShotSelectionStrategy(ShotSelector shotSelector)
: base(shotSelector)
{
}
internal IEnumerable<Position>? GetShots(int numberOfShots)
{
if (!_damagedShips.Any()) { return null; }
internal bool KnowsOfDamagedShips => _damagedShips.Any();
internal override IEnumerable<Position> GetShots(int numberOfShots)
{
var tempGrid = Position.All.ToDictionary(x => x, _ => 0);
var shots = Enumerable.Range(1, numberOfShots).Select(x => new Position(x, x)).ToArray();

View File

@@ -1,30 +1,30 @@
namespace Salvo.Targetting;
internal class SearchPatternShotSelector : ShotSelector
internal class SearchPatternShotSelectionStrategy : ShotSelectionStrategy
{
private const int MaxSearchPatternAttempts = 100;
private readonly IRandom _random;
private readonly SearchPattern _searchPattern = new();
private readonly List<Position> _shots = new();
internal SearchPatternShotSelector(Grid source, Grid target, IRandom random)
: base(source, target)
internal SearchPatternShotSelectionStrategy(ShotSelector shotSelector, IRandom random)
: base(shotSelector)
{
_random = random;
}
internal override IEnumerable<Position> GetShots()
internal override IEnumerable<Position> GetShots(int numberOfShots)
{
_shots.Clear();
while(_shots.Count < NumberOfShots)
while(_shots.Count < numberOfShots)
{
var (seed, _) = _random.NextShipPosition();
SearchFrom(seed);
SearchFrom(numberOfShots, seed);
}
return _shots;
}
private void SearchFrom(Position candidateShot)
private void SearchFrom(int numberOfShots, Position candidateShot)
{
var attemptsLeft = MaxSearchPatternAttempts;
while (true)
@@ -32,18 +32,18 @@ internal class SearchPatternShotSelector : ShotSelector
_searchPattern.Reset();
if (attemptsLeft-- == 0) { return; }
candidateShot = candidateShot.BringIntoRange(_random);
if (FindValidShots(ref candidateShot)) { return; }
if (FindValidShots(numberOfShots, ref candidateShot)) { return; }
}
}
private bool FindValidShots(ref Position candidateShot)
private bool FindValidShots(int numberOfShots, ref Position candidateShot)
{
while (true)
{
if (IsValidShot(candidateShot))
{
_shots.Add(candidateShot);
if (_shots.Count == NumberOfShots) { return true; }
if (_shots.Count == numberOfShots) { return true; }
}
if (!_searchPattern.TryGetOffset(out var offset)) { return false; }
candidateShot += offset;
@@ -51,5 +51,5 @@ internal class SearchPatternShotSelector : ShotSelector
}
private bool IsValidShot(Position candidate)
=> candidate.IsInRange && !Target.WasTargetedAt(candidate, out _) && !_shots.Contains(candidate);
=> candidate.IsInRange && !WasSelectedPreviously(candidate) && !_shots.Contains(candidate);
}

View File

@@ -2,15 +2,16 @@ namespace Salvo.Targetting;
internal abstract class ShotSelectionStrategy
{
private readonly Grid _target;
protected ShotSelectionStrategy(Grid target)
private readonly ShotSelector _shotSelector;
protected ShotSelectionStrategy(ShotSelector shotSelector)
{
_target = target;
_shotSelector = shotSelector;
}
protected bool WasSelectedPreviously(Position position)
=> _target.WasTargetedAt(position, out _);
internal abstract IEnumerable<Position> GetShots(int numberOfShots);
protected bool WasSelectedPreviously(Position position) => _shotSelector.WasSelectedPreviously(position);
protected bool WasSelectedPreviously(Position position, out int turn)
=> _target.WasTargetedAt(position, out turn);
=> _shotSelector.WasSelectedPreviously(position, out turn);
}

View File

@@ -2,16 +2,30 @@ namespace Salvo.Targetting;
internal abstract class ShotSelector
{
internal ShotSelector(Grid source, Grid target)
private readonly Grid _source;
private readonly Dictionary<Position, int> _previousShots = new();
internal ShotSelector(Grid source)
{
Source = source;
Target = target;
_source = source;
}
protected Grid Source { get; }
protected Grid Target { get; }
internal int NumberOfShots => _source.Ships.Sum(s => s.Shots);
internal bool CanTargetAllRemainingSquares => NumberOfShots >= 100 - _previousShots.Count;
internal int NumberOfShots => Source.Ships.Sum(s => s.Shots);
internal bool WasSelectedPreviously(Position position) => _previousShots.ContainsKey(position);
internal abstract IEnumerable<Position> GetShots();
internal bool WasSelectedPreviously(Position position, out int turn)
=> _previousShots.TryGetValue(position, out turn);
internal IEnumerable<Position> GetShots(int turnNumber)
{
foreach (var shot in GetShots())
{
_previousShots.Add(shot, turnNumber);
yield return shot;
}
}
protected abstract IEnumerable<Position> GetShots();
}