Skip to content

Commit

Permalink
Refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
kalina559 committed Jun 1, 2024
1 parent 73a068d commit 8b810af
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 84 deletions.
92 changes: 38 additions & 54 deletions Battleships.AI/Strategies/HeuristicStrategy.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Battleships.Common.GameClasses;
using Battleships.Common.Helpers;
using System.Diagnostics.Metrics;
using System.Collections.Generic;

namespace Battleships.AI.Strategies
{
Expand All @@ -11,8 +11,10 @@ public class HeuristicStrategy : IAiStrategy
private readonly static int POSSIBLE_SHIP_LOCATION_WEIGHT = 1;
private readonly static int NEXT_TO_A_SINGLE_HIT_WEIGHT = 50;
private readonly static int IN_LINE_WITH_OTHER_HITS_WEIGHT = 100;

//X - vertical axis
//Y - horizontal axis
// TODO fix that

public (int X, int Y) GenerateMove(GameState gameState)
{
Expand Down Expand Up @@ -43,8 +45,6 @@ private static int[] GenerateProbabilityMap(GameState gameState)

AdjustProbabilityForShipLocations(gameState, probabilityMap);

//AdjustProbabilityForSingleHits(gameState, probabilityMap);

AdjustProbabilityForHitClusters(gameState, probabilityMap);

AdjustProbabilityForSunkShips(gameState, probabilityMap);
Expand Down Expand Up @@ -87,26 +87,7 @@ private static void AdjustProbabilityForShipLocations(GameState gameState, int[]
}
}
}
}

private static void AdjustProbabilityForSingleHits(GameState gameState, int[] probabilityMap)
{
foreach (var shot in gameState.OpponentShots)
{
if (shot.IsHit && !GridHelper.IsPartOfSunkShip(shot.X, shot.Y, gameState))
{
var adjacentCells = GridHelper.GetSideAdjacentCells(shot.X, shot.Y);

foreach (var cell in adjacentCells)
{
if (GridHelper.IsWithinBounds(cell.X, cell.Y) && GridHelper.IsCellAvailable(gameState, cell.X, cell.Y))
{
probabilityMap[(cell.Y * 10) + cell.X] += NEXT_TO_A_SINGLE_HIT_WEIGHT;
}
}
}
}
}
}

private static void AdjustProbabilityForHitClusters(GameState gameState, int[] probabilityMap)
{
Expand All @@ -115,46 +96,49 @@ private static void AdjustProbabilityForHitClusters(GameState gameState, int[] p
{
if (cluster.Count > 1)
{
var first = cluster.First();
var last = cluster.Last();
var (startX, startY) = cluster.First();
var (endX, endY) = cluster.Last();

var isHorizontal = first.X == last.X;
var isHorizontal = startX == endX;

if (isHorizontal)

if (isHorizontal)
{
// Increase probability for cells extending the horizontal cluster
if (GridHelper.IsWithinBounds(first.X, first.Y - 1) && GridHelper.IsCellAvailable(gameState, first.X, first.Y - 1))
{
probabilityMap[(first.Y - 1) * 10 + first.X] += IN_LINE_WITH_OTHER_HITS_WEIGHT;
}

if (GridHelper.IsWithinBounds(last.X, last.Y + 1) && GridHelper.IsCellAvailable(gameState, last.X, last.Y + 1))
{
probabilityMap[(last.Y + 1) * 10 + last.X] += IN_LINE_WITH_OTHER_HITS_WEIGHT;
}
IncreaseProbabilityForCell(gameState, startX, startY - 1, probabilityMap, IN_LINE_WITH_OTHER_HITS_WEIGHT);
IncreaseProbabilityForCell(gameState, endX, endY + 1, probabilityMap, IN_LINE_WITH_OTHER_HITS_WEIGHT);
}
else
{
// Increase probability for cells extending the vertical cluster
if (GridHelper.IsWithinBounds(first.X - 1, first.Y) && GridHelper.IsCellAvailable(gameState, first.X - 1, first.Y))
{
probabilityMap[(first.Y * 10) + first.X - 1] += IN_LINE_WITH_OTHER_HITS_WEIGHT;
}

if (GridHelper.IsWithinBounds(last.X + 1, last.Y) && GridHelper.IsCellAvailable(gameState, last.X + 1, last.Y))
{
probabilityMap[(last.Y * 10) + last.X + 1] += IN_LINE_WITH_OTHER_HITS_WEIGHT;
}
IncreaseProbabilityForCell(gameState, startX - 1, startY, probabilityMap, IN_LINE_WITH_OTHER_HITS_WEIGHT);
IncreaseProbabilityForCell(gameState, endX + 1, endY, probabilityMap, IN_LINE_WITH_OTHER_HITS_WEIGHT);
}
}
else
{
AdjustProbabilityForSingleHits(gameState, probabilityMap);
AdjustProbabilityForSingleHit(gameState, probabilityMap, cluster.First());
}
}
}

private static void AdjustProbabilityForSingleHit(GameState gameState, int[] probabilityMap, (int X, int Y) hit)
{
var adjacentCells = GridHelper.GetSideAdjacentCells(hit.X, hit.Y);

foreach (var (X, Y) in adjacentCells)
{
IncreaseProbabilityForCell(gameState, X, Y, probabilityMap, NEXT_TO_A_SINGLE_HIT_WEIGHT);
}
}

private static void IncreaseProbabilityForCell(GameState gameState, int x, int y, int[] probabilityMap, int weight)
{
if (GridHelper.IsWithinBounds(x, y) && GridHelper.IsCellAvailable(gameState, x, y))
{
probabilityMap[y * 10 + x] += weight;
}
}

private static List<List<(int X, int Y)>> FindHitClusters(GameState gameState)
{
var hitClusters = new List<List<(int X, int Y)>>();
Expand All @@ -179,14 +163,14 @@ private static void AdjustProbabilityForHitClusters(GameState gameState, int[] p
visited[cx, cy] = true;
cluster.Add((cx, cy));

foreach (var adj in GridHelper.GetSideAdjacentCells(cx, cy))
foreach (var (X, Y) in GridHelper.GetSideAdjacentCells(cx, cy))
{
if (GridHelper.IsWithinBounds(adj.X, adj.Y) && !visited[adj.X, adj.Y])
if (GridHelper.IsWithinBounds(X, Y) && !visited[X, Y])
{
var adjacentShot = gameState.OpponentShots.FirstOrDefault(s => s.X == adj.X && s.Y == adj.Y && s.IsHit);
var adjacentShot = gameState.OpponentShots.FirstOrDefault(s => s.X == X && s.Y == Y && s.IsHit);
if (adjacentShot != null && !GridHelper.IsPartOfSunkShip(adjacentShot.X, adjacentShot.Y, gameState))
{
queue.Enqueue((adj.X, adj.Y));
queue.Enqueue((X, Y));
}
}
}
Expand Down Expand Up @@ -215,11 +199,11 @@ private static void AdjustProbabilityForSunkShips(GameState gameState, int[] pro
foreach (var coord in ship.Coordinates)
{
var adjacentCells = GridHelper.GetAllAdjacentCells(coord.X, coord.Y);
foreach (var cell in adjacentCells)
foreach (var (X, Y) in adjacentCells)
{
if (GridHelper.IsWithinBounds(cell.X, cell.Y))
if (GridHelper.IsWithinBounds(X, Y))
{
probabilityMap[(cell.Y * 10) + cell.X] = 0;
probabilityMap[(Y * 10) + X] = 0;
}
}
}
Expand Down
64 changes: 34 additions & 30 deletions Tests/OpponentMoveServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,49 @@
using Battleships.Core.Services;
using Battleships.Common.Helpers;

public class OpponentMoveServiceTests
namespace Battleships.UnitTests
{
private readonly OpponentMoveService _opponentMoveService;

public OpponentMoveServiceTests()
public class OpponentMoveServiceTests
{
_opponentMoveService = new OpponentMoveService();
}
private readonly OpponentMoveService _opponentMoveService;

[Fact]
public void HeuristicStrategy_IgnoreCellsAdjacentToSunkShips()
{
var gameState = new GameState {
AiType = AiType.Heuristic,
OpponentShots = new List<Shot> { new Shot { X = 5, Y = 4, IsHit = true }, new Shot { X = 5, Y = 5, IsHit = true }, new Shot { X = 5, Y = 6, IsHit = true } },
UserShips =
[
new() { Size = 1, Coordinates = [new() { X = 0, Y = 0, IsHit = false }], IsSunk = false },
new() { Size = 2, Coordinates = [new() { X = 2, Y = 0, IsHit = false }, new() { X = 2, Y = 1, IsHit = false } ], IsSunk = false },
new() { Size = 3, Coordinates = [new() { X = 5, Y = 4, IsHit = true }, new() { X = 5, Y = 5, IsHit = true }, new() { X = 5,Y = 6, IsHit = true } ], IsSunk = true },
new() { Size = 4, Coordinates = [new() { X = 7, Y = 0, IsHit = false }, new() { X = 7, Y = 1, IsHit = false }, new() { X = 7, Y = 2, IsHit = false }, new() { X = 7, Y = 3, IsHit = false } ], IsSunk = false },
new() { Size = 5, Coordinates = [new() { X = 9, Y = 0, IsHit = false }, new() { X = 9, Y = 1, IsHit = false }, new() { X = 9, Y = 2, IsHit = false }, new() { X = 9, Y = 3, IsHit = false }, new() { X = 9, Y = 4, IsHit = false } ], IsSunk = false },
]
};
public OpponentMoveServiceTests()
{
_opponentMoveService = new OpponentMoveService();
}

IEnumerable<(int, int)> forbiddenCells = GridHelper.GetAllAdjacentCells(5, 4).Concat(GridHelper.GetAllAdjacentCells(5, 5)).Concat(GridHelper.GetAllAdjacentCells(5, 6));
[Fact]
public void HeuristicStrategy_IgnoreCellsAdjacentToSunkShips()
{
var gameState = new GameState
{
AiType = AiType.Heuristic,
OpponentShots = [new() { X = 5, Y = 4, IsHit = true }, new Shot { X = 5, Y = 5, IsHit = true }, new Shot { X = 5, Y = 6, IsHit = true }],
UserShips =
[
new() { Size = 1, Coordinates = [new() { X = 0, Y = 0, IsHit = false }], IsSunk = false },
new() { Size = 2, Coordinates = [new() { X = 2, Y = 0, IsHit = false }, new() { X = 2, Y = 1, IsHit = false }], IsSunk = false },
new() { Size = 3, Coordinates = [new() { X = 5, Y = 4, IsHit = true }, new() { X = 5, Y = 5, IsHit = true }, new() { X = 5, Y = 6, IsHit = true }], IsSunk = true },
new() { Size = 4, Coordinates = [new() { X = 7, Y = 0, IsHit = false }, new() { X = 7, Y = 1, IsHit = false }, new() { X = 7, Y = 2, IsHit = false }, new() { X = 7, Y = 3, IsHit = false }], IsSunk = false },
new() { Size = 5, Coordinates = [new() { X = 9, Y = 0, IsHit = false }, new() { X = 9, Y = 1, IsHit = false }, new() { X = 9, Y = 2, IsHit = false }, new() { X = 9, Y = 3, IsHit = false }, new() { X = 9, Y = 4, IsHit = false }], IsSunk = false },
]
};

var testFail = false;
IEnumerable<(int, int)> forbiddenCells = GridHelper.GetAllAdjacentCells(5, 4).Concat(GridHelper.GetAllAdjacentCells(5, 5)).Concat(GridHelper.GetAllAdjacentCells(5, 6));

var testFail = false;

for (int i = 0; i < 100; i++)
{
var move = _opponentMoveService.GenerateMove(gameState);

if (forbiddenCells.Contains(move))
for (int i = 0; i < 100; i++)
{
testFail = true;
var move = _opponentMoveService.GenerateMove(gameState);

if (forbiddenCells.Contains(move))
{
testFail = true;
}
}
Assert.False(testFail);
}
Assert.True(!testFail);
}
}
}

0 comments on commit 8b810af

Please sign in to comment.