mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2026-01-04 09:07:41 -08:00
Tidy up shot selection
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user