public static List <Loc> FindAPath(Loc rectStart, Loc rectSize, Loc start, Loc[] ends, LocTest checkBlock, LocTest checkDiagBlock) { // searches for one specific path (ends[0]), but doesn't mind hitting the other ones PathTile[][] tiles = new PathTile[rectSize.X][]; for (int ii = 0; ii < rectSize.X; ii++) { tiles[ii] = new PathTile[rectSize.Y]; for (int jj = 0; jj < rectSize.Y; jj++) { tiles[ii][jj] = new PathTile(new Loc(rectStart.X + ii, rectStart.Y + jj)); } } Loc offset_start = start - rectStart; StablePriorityQueue <double, PathTile> candidates = new StablePriorityQueue <double, PathTile>(); PathTile start_tile = tiles[offset_start.X][offset_start.Y]; start_tile.Heuristic = Math.Sqrt((ends[0] - start).DistSquared()); start_tile.Cost = 0; candidates.Enqueue(start_tile.Heuristic + start_tile.Cost, start_tile); PathTile farthest_tile = start_tile; while (candidates.Count > 0) { PathTile currentTile = candidates.Dequeue(); for (int ii = 0; ii < ends.Length; ii++) { if (currentTile.Location == ends[ii]) { return(GetBackreference(currentTile)); } } if (currentTile.Heuristic < farthest_tile.Heuristic) { farthest_tile = currentTile; } currentTile.Traversed = true; foreach (Dir8 dir in DirExt.VALID_DIR8) { if (!IsDirBlocked(currentTile.Location, dir, checkBlock, checkDiagBlock)) { Loc newLoc = currentTile.Location - rectStart + dir.GetLoc(); if (Collision.InBounds(rectSize.X, rectSize.Y, newLoc)) { PathTile tile = tiles[newLoc.X][newLoc.Y]; if (tile.Traversed) { continue; } int newCost = currentTile.Cost + 1; if (tile.Cost == -1 || newCost < tile.Cost) { tile.Cost = newCost; tile.Heuristic = Math.Sqrt((ends[0] - tile.Location).DistSquared()); tile.BackReference = currentTile; candidates.AddOrSetPriority(tile.Heuristic + tile.Cost, tile); } } } } } return(GetBackreference(farthest_tile)); }
public static Loc?FindClosestConnectedTile(Loc rectStart, Loc rectSize, LocTest checkFree, LocTest checkBlock, LocTest checkDiagBlock, Loc loc) { foreach (Loc returnLoc in FindClosestConnectedTiles(rectStart, rectSize, checkFree, checkBlock, checkDiagBlock, loc, 1)) { return(returnLoc); } return(null); }
public static List <Loc> FindConnectedTiles(Loc rectStart, Loc rectSize, LocTest checkOp, LocTest checkBlock, LocTest checkDiagBlock, Loc loc) { if (checkOp == null) { throw new ArgumentNullException(nameof(checkOp)); } // use efficient fill method List <Loc> locList = new List <Loc>(); void Action(Loc actLoc) { if (checkOp(actLoc)) { locList.Add(actLoc); } } AffectConnectedTiles(rectStart, rectSize, Action, checkBlock, checkDiagBlock, loc); return(locList); }
public static void AffectConnectedTiles(Loc rectStart, Loc rectSize, LocAction action, LocTest checkBlock, LocTest checkDiagBlock, Loc loc) { if (action == null) { throw new ArgumentNullException(nameof(action)); } if (checkBlock == null) { throw new ArgumentNullException(nameof(checkBlock)); } if (checkDiagBlock == null) { throw new ArgumentNullException(nameof(checkDiagBlock)); } // create an array to cache which tiles were already traversed bool[][] fillArray = new bool[rectSize.X][]; for (int ii = 0; ii < rectSize.X; ii++) { fillArray[ii] = new bool[rectSize.Y]; } FloodFill( new Rect(rectStart, rectSize), (Loc testLoc) => (checkBlock(testLoc) || fillArray[testLoc.X - rectStart.X][testLoc.Y - rectStart.Y]), (Loc testLoc) => (checkDiagBlock(testLoc) || fillArray[testLoc.X - rectStart.X][testLoc.Y - rectStart.Y]), (Loc actLoc) => { action(actLoc); fillArray[actLoc.X - rectStart.X][actLoc.Y - rectStart.Y] = true; }, loc); }
private static List <Loc>[] FindMultiPaths(Loc rectStart, Loc rectSize, Loc start, Loc[] ends, LocTest checkBlock, LocTest checkDiagBlock, EvalPaths eval, EvalFallback fallback) { if (ends.Length == 0) { return(new List <Loc> [0]); } PathTile[][] tiles = new PathTile[rectSize.X][]; for (int ii = 0; ii < rectSize.X; ii++) { tiles[ii] = new PathTile[rectSize.Y]; for (int jj = 0; jj < rectSize.Y; jj++) { tiles[ii][jj] = new PathTile(new Loc(rectStart.X + ii, rectStart.Y + jj)); } } Loc offset_start = start - rectStart; StablePriorityQueue <double, PathTile> candidates = new StablePriorityQueue <double, PathTile>(); PathTile start_tile = tiles[offset_start.X][offset_start.Y]; start_tile.UpdateHeuristics(ends); start_tile.Cost = 0; candidates.Enqueue(start_tile.MinHeuristic + start_tile.Cost, start_tile); List <Loc>[] resultPaths = new List <Loc> [ends.Length]; PathTile[] farthestTiles = new PathTile[ends.Length]; for (int ii = 0; ii < farthestTiles.Length; ii++) { farthestTiles[ii] = start_tile; } while (candidates.Count > 0) { PathTile currentTile = candidates.Dequeue(); if (eval(ends, resultPaths, farthestTiles, currentTile)) { return(resultPaths); } currentTile.Traversed = true; foreach (Dir8 dir in DirExt.VALID_DIR8) { Loc newLoc = currentTile.Location - rectStart + dir.GetLoc(); if (Collision.InBounds(rectSize.X, rectSize.Y, newLoc)) { if (!IsDirBlocked(currentTile.Location, dir, checkBlock, checkDiagBlock)) { PathTile tile = tiles[newLoc.X][newLoc.Y]; if (tile.Traversed) { continue; } int newCost = currentTile.Cost + 1; if (tile.Cost == -1 || newCost < tile.Cost) { tile.Cost = newCost; tile.UpdateHeuristics(ends); tile.BackReference = currentTile; candidates.AddOrSetPriority(tile.MinHeuristic + tile.Cost, tile); } } } } } fallback(resultPaths, farthestTiles); return(resultPaths); }
public static List <Loc> FindPath(Loc rectStart, Loc rectSize, Loc start, Loc end, LocTest checkBlock, LocTest checkDiagBlock) { Loc[] ends = new Loc[1]; ends[0] = end; return(FindAPath(rectStart, rectSize, start, ends, checkBlock, checkDiagBlock)); }
private static void ScanFill( Rect rect, LocTest checkBlock, LocTest checkDiagBlock, LocAction fillOp, int min, int max, int range_min, int range_max, int y, bool isNext, DirV dir, Stack <ScanLineTile> stack) { if (checkBlock == null) { throw new ArgumentNullException(nameof(checkBlock)); } if (fillOp == null) { throw new ArgumentNullException(nameof(fillOp)); } // move y down or up int new_y = y + dir.GetLoc().Y; // for diagonal checking: check slightly further int sub = (range_min > rect.Start.X) ? 1 : 0; int add = (range_max < rect.Start.X + rect.Size.X - 1) ? 1 : 0; int line_start = -1; int x = range_min - sub; for (; x <= range_max + add; x++) { bool unblocked = !checkBlock(new Loc(x, new_y)); // check diagonal if applicable if (x < range_min) { unblocked &= !IsDirBlocked(new Loc(range_min, y), DirExt.Combine(DirH.Left, dir), checkBlock, checkDiagBlock); } else if (x > range_max) { unblocked &= !IsDirBlocked(new Loc(range_max, y), DirExt.Combine(DirH.Right, dir), checkBlock, checkDiagBlock); } // skip testing, if testing previous line within previous range bool empty = (isNext || (x <min || x> max)) && unblocked; if (line_start == -1 && empty) { line_start = x; } else if (line_start > -1 && !empty) { stack.Push(new ScanLineTile(new IntRange(line_start, x - 1), new_y, dir, line_start <= range_min, x > range_max)); line_start = -1; } if (line_start > -1) { fillOp(new Loc(x, new_y)); } if (!isNext && x == min) { x = max; } } if (line_start > -1) { stack.Push(new ScanLineTile(new IntRange(line_start, x - 1), new_y, dir, line_start <= range_min, true)); } }
/// <summary> /// Searches for the N fastest paths to any of the endpoints. /// </summary> /// <param name="rectStart"></param> /// <param name="rectSize"></param> /// <param name="start"></param> /// <param name="ends">The list of goal points to path to. Increase in count increases runtime linearly.</param> /// <param name="checkBlock"></param> /// <param name="checkDiagBlock"></param> /// <param name="amt">Top N</param> /// <param name="multiTie">If multiple are tied it returns all involved in the tie.</param> public static List <Loc>[] FindNPaths(Loc rectStart, Loc rectSize, Loc start, Loc[] ends, LocTest checkBlock, LocTest checkDiagBlock, int amt, bool multiTie) { EvalPaths evalPathTied = (Loc[] evalEnds, List <Loc>[] resultPaths, PathTile[] farthestTiles, PathTile currentTile) => { int foundResults = 0; int highestBackRefCount = 0; for (int ii = 0; ii < resultPaths.Length; ii++) { if (resultPaths[ii] != null) { foundResults++; highestBackRefCount = Math.Max(highestBackRefCount, resultPaths[ii].Count); } } bool addedResult = false; for (int ii = 0; ii < evalEnds.Length; ii++) { if (currentTile.Location == evalEnds[ii]) { List <Loc> proposedBackRef = GetBackreference(currentTile); // in multiTie mode, we are waiting for confirmation that we've found all ties. This means having found something that fails a tie. if (multiTie && foundResults >= amt) { // if this new proposed backref takes more steps than the current longest, then it is a failed tie if (proposedBackRef.Count > highestBackRefCount) { return(true); } } resultPaths[ii] = proposedBackRef; } if (currentTile.Heuristic[ii] < farthestTiles[ii].Heuristic[ii]) { farthestTiles[ii] = currentTile; } } // if we're just looking for the amount and not paying attention to tiebreakers, we can call it a day when we reach the amount. if (!multiTie && addedResult) { if (foundResults + 1 >= amt) { return(true); } } return(false); }; EvalFallback evalFallbackTied = (List <Loc>[] resultPaths, PathTile[] farthestTiles) => { int foundResults = 0; int highestBackRefCount = 0; StablePriorityQueue <int, int> lowestStepsQueue = new StablePriorityQueue <int, int>(); List <Loc>[] proposedPaths = new List <Loc> [resultPaths.Length]; for (int ii = 0; ii < resultPaths.Length; ii++) { if (resultPaths[ii] != null) { foundResults++; highestBackRefCount = Math.Max(highestBackRefCount, resultPaths[ii].Count); } else { proposedPaths[ii] = GetBackreference(farthestTiles[ii]); lowestStepsQueue.AddOrSetPriority(proposedPaths[ii].Count, ii); } } while (lowestStepsQueue.Count > 0) { int newIdx = lowestStepsQueue.Dequeue(); if (multiTie && foundResults >= amt) { // we've run out of ties. stop adding paths if (proposedPaths[newIdx].Count > highestBackRefCount) { break; } } resultPaths[newIdx] = proposedPaths[newIdx]; highestBackRefCount = proposedPaths[newIdx].Count; foundResults++; if (!multiTie) { if (foundResults >= amt) { break; } } } }; return(FindMultiPaths(rectStart, rectSize, start, ends, checkBlock, checkDiagBlock, evalPathTied, evalFallbackTied)); }
public static bool IsDirBlocked(Loc loc, Dir8 dir, LocTest checkBlock, LocTest checkDiagBlock) { return(IsDirBlocked(loc, dir, checkBlock, checkDiagBlock, 1)); }
public static List <Loc>[] FindAllPaths(Loc rectStart, Loc rectSize, Loc start, Loc[] ends, LocTest checkBlock, LocTest checkDiagBlock) { return(FindMultiPaths(rectStart, rectSize, start, ends, checkBlock, checkDiagBlock, EvalPathAll, EvalFallbackAll)); }
/// <summary> /// Determines if blocking off a specific tile would cause a path leading through it to become inaccessible. /// If a tile is a choke point, there are no alternate paths. /// </summary> /// <param name="rectStart">Top-Left of the rectangle to search for alternate paths.</param> /// <param name="rectSize">Dimensions of the rectangle to search for alternate paths.</param> /// <param name="point">The tile to block off.</param> /// <param name="checkBlock">Determines if a tile is blocked.</param> /// <param name="checkDiagBlock">Determines if a tile checked diagonally is blocked.</param> /// <returns></returns> public static bool IsChokePoint(Loc rectStart, Loc rectSize, Loc point, LocTest checkBlock, LocTest checkDiagBlock) { if (checkBlock == null) { throw new ArgumentNullException(nameof(checkBlock)); } if (checkDiagBlock == null) { throw new ArgumentNullException(nameof(checkDiagBlock)); } List <Dir8> forks = GetForkDirs(point, checkBlock, checkDiagBlock); if (forks.Count < 2) { return(false); } bool[][] fillArray = new bool[rectSize.X][]; for (int ii = 0; ii < rectSize.X; ii++) { fillArray[ii] = new bool[rectSize.Y]; } Loc offset_point = point - rectStart; fillArray[offset_point.X][offset_point.Y] = true; List <Loc> forkList = new List <Loc>(); for (int ii = 0; ii < forks.Count; ii++) { Loc loc = point + forks[ii].GetLoc(); forkList.Add(loc); } bool CheckChokeBlock(Loc loc) { if (fillArray[loc.X - rectStart.X][loc.Y - rectStart.Y]) { return(true); } return(checkBlock(loc)); } bool CheckDiagChokeBlock(Loc loc) { if (fillArray[loc.X - rectStart.X][loc.Y - rectStart.Y]) { return(true); } return(checkDiagBlock(loc)); } void Fill(Loc loc) { fillArray[loc.X - rectStart.X][loc.Y - rectStart.Y] = true; if (forkList.Contains(loc)) { forkList.Remove(loc); } } FloodFill(new Rect(rectStart, rectSize), CheckChokeBlock, CheckDiagChokeBlock, Fill, forkList[0]); return(forkList.Count > 0); }
public static IEnumerable <Loc> FindClosestConnectedTiles(Loc rectStart, Loc rectSize, LocTest checkFree, LocTest checkBlock, LocTest checkDiagBlock, Loc loc, int amount) { if (checkFree == null) { throw new ArgumentNullException(nameof(checkFree)); } // create an array to cache which tiles were already traversed bool[][] fillArray = new bool[rectSize.X][]; for (int ii = 0; ii < rectSize.X; ii++) { fillArray[ii] = new bool[rectSize.Y]; } // use typical fill method StablePriorityQueue <int, Loc> locList = new StablePriorityQueue <int, Loc>(); Loc offset_loc = loc - rectStart; locList.Enqueue(0, offset_loc); fillArray[offset_loc.X][offset_loc.Y] = true; int found = 0; while (locList.Count > 0) { Loc candidate = locList.Dequeue(); if (checkFree(candidate + rectStart)) { yield return(candidate + rectStart); found++; if (found >= amount) { yield break; } } foreach (Dir8 dir in DirExt.VALID_DIR8) { Loc movedLoc = candidate + dir.GetLoc(); if (Collision.InBounds(rectSize.X, rectSize.Y, movedLoc) && !fillArray[movedLoc.X][movedLoc.Y] && !IsDirBlocked(candidate + rectStart, dir, checkBlock, checkDiagBlock)) { Loc diff = movedLoc - offset_loc; locList.Enqueue(diff.DistSquared(), movedLoc); fillArray[movedLoc.X][movedLoc.Y] = true; } } } }
/// <summary> /// Searches for the fastest path to any of the endpoints. If multiple are tied it picks the first one. /// </summary> /// <param name="rectStart"></param> /// <param name="rectSize"></param> /// <param name="start"></param> /// <param name="ends">The list of goal points to path to. Increase in count increases runtime linearly.</param> /// <param name="checkBlock"></param> /// <param name="checkDiagBlock"></param> /// <returns></returns> public static List <Loc> FindAPath(Loc rectStart, Loc rectSize, Loc start, Loc[] ends, LocTest checkBlock, LocTest checkDiagBlock) { List <Loc>[] multiEnds = FindMultiPaths(rectStart, rectSize, start, ends, checkBlock, checkDiagBlock, EvalPathOne, EvalFallbackOne); for (int ii = 0; ii < multiEnds.Length; ii++) { if (multiEnds[ii] != null) { return(multiEnds[ii]); } } throw new InvalidOperationException("Could not find a path!"); }