/// <summary> /// This constructs a path from the specified start point to /// some point in the set of destinations. /// /// Will only construct paths up to a certain maximum COST; /// as of now, going from one square to another costs 1 unit. /// So the maximum cost is the maximum path length. /// /// Returns null if no path was found. /// /// Running time (if no path is found, the worst case) is O(k+n^2), where /// n=maxCost /// k=destinations.Count /// /// Assumes no negative cost paths, as in Djikstra's algorithm /// </summary> /// <param name="startPoints">The set of start points we could be happy with.</param> /// <param name="goalPoints">The set of possible destinations we could be happy with.</param> /// <param name="maxCost">Maximum cost of any path.</param> /// <param name="manager">The WorldManager that we use to do our pathing.</param> /// <param name="startTime">The time we start the algorithm. Use GameTime.TotalGameTime</param> /// <returns>The shortest path from here to somewhere in there; returns null iff there is no path of cost less than maxCost.</returns> public static Path GetPath <T, S, M>(HashSet <Point> startPoints, HashSet <Point> goalPoints, int maxCost, TileMapManager <T, S, M> manager, TimeSpan startTime) where T : InGameObject where S : MapCell, Copyable <S> where M : TileMap <S>, new() { //check for trivialities- we can't find a path to nowhere or from nowhere if (goalPoints.Count == 0 || startPoints.Count == 0) { return(null); } Heap <Path> heap = null; if (goalPoints.Count == 1) { heap = new PathToPointHeap(goalPoints.Single()); } else { heap = new PathHeap(); } //now set up a list of points we've seen before, so as to not //check the same position a billion times Dictionary <Point, int> bestDistancesFound = new Dictionary <Point, int>(); foreach (Point startPoint in startPoints) { bestDistancesFound[startPoint] = 0; heap.Add(new Path(startPoint)); if (goalPoints.Contains(startPoint)) { return(new Path(startPoint)); } } while (heap.Count > 0) { //if there's new passability information, start the process over if (manager.LastGeneralPassabilityUpdate != null && //if it's null, we're definitely OK startTime < manager.LastGeneralPassabilityUpdate) { startTime = manager.LastGeneralPassabilityUpdate; heap.Clear(); bestDistancesFound = new Dictionary <Point, int>(); foreach (Point startPoint in startPoints) { bestDistancesFound[startPoint] = 0; heap.Add(new Path(startPoint)); } } Path bestPath = heap.Pop(); //if we didn't cap out our path length yet, //check out the adjacent points to form longer paths if (bestPath.Cost < maxCost) { int newCost = bestPath.Cost + 1; //for each possible extension ... foreach (Point adj in manager.GetAdjacentPoints(bestPath.End.X, bestPath.End.Y)) { //if we hit a destination, great, stop if (goalPoints.Contains(adj)) { return(new Path(bestPath, adj, newCost)); } //don't bother adding this possible extension back on unless //it's either a completely new point or a strict improvement over another path if (bestDistancesFound.Keys.Contains(adj) && bestDistancesFound[adj] <= newCost) { continue; } //otherwise, this is a perfectly serviceable path extension and we should look into it bestDistancesFound[adj] = newCost; heap.Add(new Path(bestPath, adj, newCost)); } } } return(null); }