Skip to content

Commit

Permalink
Extract common search logic to base classes
Browse files Browse the repository at this point in the history
  • Loading branch information
ApmeM committed Jul 20, 2024
1 parent b4dffcf commit 9508aea
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 357 deletions.
5 changes: 3 additions & 2 deletions BrainAI.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,9 @@ public void Pathfinding()
var end = this.GraphType == GraphTypes.Grid ? new Point((int)MapSize - 1, (int)MapSize - 1) : new Point((int)MapSize - 5, (int)MapSize - 4);
for (var i = 0; i < (int)this.RunsCount; i++)
{
var pathData = this.pathfinder!.Search(start, end);
if (pathData == null)
this.pathfinder!.Search(start, end);
var pathData = this.pathfinder!.ResultPath;
if (pathData.Count == 0)
{
throw new System.Exception("Path not found.");
}
Expand Down
111 changes: 2 additions & 109 deletions BrainAI/Pathfinding/AStar/AStarPathfinder.cs
Original file line number Diff line number Diff line change
@@ -1,107 +1,17 @@
namespace BrainAI.Pathfinding
{
using System;
using System.Collections.Generic;

public class AStarPathfinder<T> : IPathfinder<T>
public class AStarPathfinder<T> : Pathfinder<T>
{
public Dictionary<T, T> VisitedNodes { get; } = new Dictionary<T, T>();

public List<T> ResultPath { get; set; } = new List<T>();

private readonly HashSet<T> tmpGoals = new HashSet<T>();

private T searchStart;

private readonly IAstarGraph<T> graph;

private readonly Dictionary<T, int> costSoFar = new Dictionary<T, int>();

private readonly PriorityQueue<(int, T), int> frontier = new PriorityQueue<(int, T), int>();

private List<T> neighbours = new List<T>();

public AStarPathfinder(IAstarGraph<T> graph)
{
this.graph = graph;
}

public void Search(T start, T goal)
{
this.PrepareSearch();
this.StartNewSearch(start);

tmpGoals.Add(goal);

ContinueSearch();
}

public void Search(T start, HashSet<T> goals)
{
this.PrepareSearch();
this.StartNewSearch(start);

foreach (var goal in goals)
{
this.tmpGoals.Add(goal);
}

ContinueSearch();
}

public void Search(T start, int additionalDepth)
{
this.PrepareSearch();
this.StartNewSearch(start);

InternalSearch(additionalDepth);
}

public void Search(T start, T goal, int additionalDepth)
{
this.PrepareSearch();
this.StartNewSearch(start);

this.tmpGoals.Add(goal);

ContinueSearch(additionalDepth);
}

public void Search(T start, HashSet<T> goals, int additionalDepth)
{
this.PrepareSearch();
this.StartNewSearch(start);

foreach (var goal in goals)
{
this.tmpGoals.Add(goal);
}

ContinueSearch(additionalDepth);
}

public void ContinueSearch()
{
this.ResultPath.Clear();
if (tmpGoals.Count == 0)
{
return;
}

ContinueSearch(int.MaxValue);
}

public void ContinueSearch(int additionalDepth)
{
this.ResultPath.Clear();
var (target, isFound) = InternalSearch(additionalDepth);
if (isFound)
{
PathConstructor.RecontructPath(VisitedNodes, searchStart, target, this.ResultPath);
}
}

private ValueTuple<T, bool> InternalSearch(int additionalDepth)
internal override ValueTuple<T, bool> InternalSearch(int additionalDepth)
{
var goal = this.GetFirstGoal();

Expand Down Expand Up @@ -145,22 +55,5 @@ private T GetFirstGoal()

throw new Exception("No goals found.");
}

private void PrepareSearch()
{
this.ResultPath.Clear();
this.frontier.Clear();
this.VisitedNodes.Clear();
this.tmpGoals.Clear();
this.costSoFar.Clear();
}

private void StartNewSearch(T start)
{
this.searchStart = start;
this.VisitedNodes.Add(start, start);
this.frontier.Enqueue(new ValueTuple<int, T>(0, start), 0);
this.costSoFar[start] = 0;
}
}
}
127 changes: 14 additions & 113 deletions BrainAI/Pathfinding/BreadthFirst/BreadthFirstPathfinder.cs
Original file line number Diff line number Diff line change
@@ -1,149 +1,50 @@
namespace BrainAI.Pathfinding
{
using System;
using System.Collections.Generic;

/// <summary>
/// Calculates paths given an IUnweightedGraph and start/goal positions
/// </summary>
public class BreadthFirstPathfinder<T> : IPathfinder<T>, ICoveragePathfinder<T>
public class BreadthFirstPathfinder<T> : CoveragePathfinder<T>
{
public Dictionary<T, T> VisitedNodes { get; } = new Dictionary<T, T>();

public List<T> ResultPath { get; set; } = new List<T>();

private readonly HashSet<T> tmpGoals = new HashSet<T>();

private T searchStart;

private readonly IUnweightedGraph<T> graph;

private readonly Queue<T> frontier = new Queue<T>();

private readonly List<T> neighbours = new List<T>();

public BreadthFirstPathfinder(IUnweightedGraph<T> graph)
{
this.graph = graph;
}

public void Search(T start, T goal)
{
this.PrepareSearch();
this.StartNewSearch(start);

tmpGoals.Add(goal);

ContinueSearch();
}

public void Search(T start, HashSet<T> goals)
{
this.PrepareSearch();
this.StartNewSearch(start);

foreach (var goal in goals)
{
this.tmpGoals.Add(goal);
}

ContinueSearch();
}

public void Search(T start, int additionalDepth)
{
this.PrepareSearch();
this.StartNewSearch(start);

InternalSearch(additionalDepth);
}

public void Search(T start, T goal, int additionalDepth)
{
this.PrepareSearch();
this.StartNewSearch(start);

this.tmpGoals.Add(goal);

ContinueSearch(additionalDepth);
}

public void Search(T start, HashSet<T> goals, int additionalDepth)
{
this.PrepareSearch();
this.StartNewSearch(start);

foreach (var goal in goals)
{
this.tmpGoals.Add(goal);
}

ContinueSearch(additionalDepth);
}

public void ContinueSearch()
{
this.ResultPath.Clear();
if (tmpGoals.Count == 0)
{
return;
}

ContinueSearch(int.MaxValue);
}

public void ContinueSearch(int additionalDepth)
{
this.ResultPath.Clear();
var (target, isFound) = InternalSearch(additionalDepth);
if (isFound)
{
PathConstructor.RecontructPath(VisitedNodes, searchStart, target, this.ResultPath);
}
}

private ValueTuple<T, bool> InternalSearch(int additionalDepth)
internal override ValueTuple<T, bool> InternalSearch(int additionalDepth)
{
while (frontier.Count > 0 && additionalDepth > 0)
{
additionalDepth--;
var current = frontier.Peek();

if (tmpGoals.Contains(current))
if (tmpGoals.Contains(current.Item2))
{
tmpGoals.Remove(current);
return (current, true);
tmpGoals.Remove(current.Item2);
return (current.Item2, true);
}

frontier.Dequeue();
graph.GetNeighbors(current, neighbours);

graph.GetNeighbors(current.Item2, neighbours);

foreach (var next in neighbours)
{
if (!VisitedNodes.ContainsKey(next))
var newCost = costSoFar[current.Item2] + 1;
if (!costSoFar.ContainsKey(next) || newCost < costSoFar[next])
{
frontier.Enqueue(next);
VisitedNodes.Add(next, current);
costSoFar[next] = newCost;
var priority = newCost;
frontier.Enqueue(new ValueTuple<int, T>(priority, next), priority);
VisitedNodes[next] = current.Item2;
}
}
}

return (default(T), false);
}

private void PrepareSearch()
{
this.frontier.Clear();
this.VisitedNodes.Clear();
this.tmpGoals.Clear();
}

private void StartNewSearch(T start)
{
this.searchStart = start;
this.frontier.Enqueue(start);
this.VisitedNodes.Add(start, start);
}
}
}
13 changes: 13 additions & 0 deletions BrainAI/Pathfinding/CoveragePathfinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace BrainAI.Pathfinding
{
public abstract class CoveragePathfinder<T> : Pathfinder<T>, ICoveragePathfinder<T>
{
public void Search(T start, int additionalDepth)
{
this.PrepareSearch();
this.StartNewSearch(start);

InternalSearch(additionalDepth);
}
}
}
Loading

0 comments on commit 9508aea

Please sign in to comment.