public PathfinderNode(T location, int cost, PathfinderNode <T> previousNode, int minRemaining = 0) { Location = location; Cost = cost; PreviousNode = previousNode; MinimumCostRemaining = minRemaining; }
public static IEnumerable <PathfinderNode <T> > CreateDijkstraMap <T>(T start, T end, ConnectivityGraph <T> graph) { // pathfind! // step 1: empty priority queue with cost to reach each node var queue = new List <PathfinderNode <T> >(); // step 2: empty set of previously visited nodes, along with costs and previous-node references var visited = new SafeDictionary <T, PathfinderNode <T> >(); // step 3: add start node and cost queue.Add(new PathfinderNode <T>(start, 0, null)); // step 4: quit if there are no nodes (all paths exhausted without finding goal) bool success = false; while (queue.Any() && !success) { // step 5: take lowest cost node out of queue // also prefer straight line movement to diagonal var minCost = queue.Min(n => n.Cost); var node = queue.Where(n => n.Cost == minCost).First(); queue.Remove(node); // step 6: if node is the goal, stop - success! if (node.Location.Equals(end)) { success = true; } // step 7: check possible moves var moves = graph.GetExits(node.Location); // step 7a: remove blocked points (aka calculate cost) // nothing to do here // step 7b: update priority queue foreach (var move in moves) { if (!visited.ContainsKey(move)) { // didn't visit yet var newnode = new PathfinderNode <T>(move, node.Cost + 1, node); queue.Add(newnode); visited.Add(move, newnode); } else { // already visited - but is it by a longer path? var items = queue.Where(n => n.Location.Equals(move) && n.Cost > node.Cost + 1); if (items.Any()) { foreach (var old in items.ToArray()) { queue.Remove(old); } var newnode = new PathfinderNode <T>(move, node.Cost + 1, node); queue.Add(newnode); visited.Add(move, newnode); } } } } return(visited.Values); }
public static IDictionary <PathfinderNode <Sector>, ISet <PathfinderNode <Sector> > > CreateDijkstraMap(IMobileSpaceObject me, Sector start, Sector end, bool avoidEnemies, bool avoidDamagingSectors) { // step 2a (do it here so we can return map if start or end is null): empty map with nodes, their costs, and previous-node references var map = new Dictionary <PathfinderNode <Sector>, ISet <PathfinderNode <Sector> > >(); // if we are nowhere or we're going nowhere, that's impossible! if (start == null || end == null) { return(map); } var startSys = start.StarSystem; // pathfind! // step 1: empty priority queue with cost to reach each node var queue = new Dictionary <int, ISet <PathfinderNode <Sector> > >(); // step 2b: empty set of previously visited nodes var visited = new HashSet <Sector>(); // step 3: add start node and cost queue.Add(0, new HashSet <PathfinderNode <Sector> >()); queue[0].Add(new PathfinderNode <Sector>(start, 0, null, EstimateDistance(start, end, me == null ? null : me.Owner))); // step 4: quit if there are no nodes (all paths exhausted without finding goal) bool success = false; while (queue.SelectMany(kvp => kvp.Value).Any() && !success) { // step 5: take lowest cost node out of queue // TODO - also prefer straight line movement to diagonal? var minCost = queue.Keys.Min(); while (!queue[minCost].Any()) { queue.Remove(minCost); minCost = queue.Keys.Min(); } var node = queue[minCost].First(); queue[minCost].Remove(node); map.Add(node, new HashSet <PathfinderNode <Sector> >()); // step 6: if node is the goal, stop after it's done - success! if (node.Location == end) { success = true; } // step 7: check possible moves var moves = GetPossibleMoves(node.Location, me == null ? true : me.CanWarp, me == null ? null : me.Owner); // step 7a: remove blocked points (aka calculate cost) if (avoidEnemies) { // avoid enemies, except at the destination moves = moves.Where(m => m == null || m == end || !m.SpaceObjects.Where(sobj => sobj.CheckVisibility(me.Owner) >= Visibility.Visible || sobj.FindMemory(me.Owner)?.Sector == m).OfType <ICombatant>().Any(sobj => sobj.IsHostileTo(me == null ? null : me.Owner))); } if (avoidDamagingSectors) { // don't avoid the destination, even if it is a damaging sector moves = moves.Where(m => m == end || m == null || !m.SpaceObjects.Where(sobj => sobj.CheckVisibility(me.Owner) >= Visibility.Visible || sobj.FindMemory(me.Owner)?.Sector == m).Any(sobj => sobj.GetAbilityValue("Sector - Damage").ToInt() > 0)); } // step 7b: update priority queue Action <Sector> f = move => { // When we lock the queue, we do so because it is being checked and modified by other threads, // and we don't want them stepping on each other's toes. // e.g. thread 1 is checking for the existence of an item in the queue // while thread 2 is busy adding that same item! if (!visited.Contains(move)) { // didn't visit yet var newnode = new PathfinderNode <Sector>(move, node.Cost + 1, node, EstimateDistance(move, end, me == null ? null : me.Owner)); lock (queue) { if (!queue.ContainsKey(newnode.Cost)) { queue.Add(newnode.Cost, new HashSet <PathfinderNode <Sector> >()); } queue[newnode.Cost].Add(newnode); } if (!map.ContainsKey(node)) // don't need to lock map, we're only adding a node which should only get added once per run { map.Add(node, new HashSet <PathfinderNode <Sector> >()); } map[node].Add(newnode); visited.Add(move); } else { // already visited - but is it by a longer path? var moreCost = queue.Where(kvp => kvp.Key > node.Cost + 1).SelectMany(kvp => kvp.Value); var items = moreCost.Where(n => n.Location == move && n.Cost > node.Cost + 1); if (items.Any()) { PathfinderNode <Sector> newnode; foreach (var old in items.ToArray()) { lock (queue[old.Cost]) queue[old.Cost].Remove(old); map.Remove(old); } newnode = new PathfinderNode <Sector>(move, node.Cost + 1, node); lock (queue[newnode.Cost]) queue[newnode.Cost].Add(newnode); if (!map.ContainsKey(node)) // don't need to lock map, we're only adding a node which should only get added once per run { map.Add(node, new HashSet <PathfinderNode <Sector> >()); } map[node].Add(newnode); } } }; moves.SafeForeach(f); } return(map); }