/* * public functions */ /// <summary> /// A*, modified to find a nearest path in case of failure /// </summary> /// <param name="map">The Map</param> /// <param name="startNode">The starting Node</param> /// <param name="endNode">The ending Node</param> /// <returns>The path as a list of waypoints</returns> public static List<Node> findPath(NodeMap map, Node startNode, Node endNode) { // initialize data PQueue open = new PQueue(); open.enqueue(startNode); List<Node> adjacentNodes = new List<Node>(); // good ol' A* while (open.Count > 0) { // find the open Node with the lowest Fscore and remove it from the open PQueue Node current = open.dequeue(); // if this is our destination Node, we're done; otherwise, close it so we don't travel to it again if (current == endNode) return reconstruct(endNode); current.close(); // find every valid Node adjacent to the current Node adjacentNodes = map.getAdjacentNodes(current); // iterate over all of them for (int i = 0; i < adjacentNodes.Count; i++) { // grab an adjacent Node and calculate a new GScore and HScore for it Node adjacent = adjacentNodes[i]; int tempGScore = current.Gscore + map.pathDistance(current, adjacent); int tempHScore = map.pathDistance(adjacent, endNode); // if we have not opened this Cell, give it new stats and open it if (!adjacent.isOpen) { setAdjacentStats(adjacent, current, tempGScore, tempHScore); open.enqueue(adjacent); } // otherwise, if we have opened it but the new path to it is shorter than the old one, give it new stats and reset its position in the open PQueue else if (tempGScore < adjacent.Gscore) { setAdjacentStats(adjacent, current, tempGScore, tempHScore); //open.resetPosition(adjacent); } } } // no valid path exists, so find the nearest path List<Node> closed = createClosed(map); Node nearestNode = closed[0]; // find the closed Node with the lowest Hscore (distance from the intended end); return a path to that Node if (closed.Count > 0) { for (int i = 1; i < closed.Count; i++) { if (closed[i].Hscore < nearestNode.Hscore) nearestNode = closed[i]; } } return reconstruct(nearestNode); }
/// <summary> /// Copy constructor /// </summary> public PQueue(PQueue p) { for (int i = 0; i < p.pq.Count(); i++) { pq.Add(p.pq[i]); } }
/// <summary> /// Copy constructor /// </summary> /// <param name="p">The PQueue to copy</param> public PQueue(PQueue p) { this.pq.Clear(); for (int i = 0; i < p.pq.Count; i++) { this.pq.Add(p.pq[i]); } recomputeStats(); }
/* * public functions */ /// <summary> /// Given an invalid Node, returns the approximate nearest valid Node. /// </summary> /// <param name="end"></param> /// <returns></returns> public static Node nearestValidEnd(NodeMap map, Node start, Node end) { int max = Math.Max(map.height, map.width); PQueue ring = new PQueue(); // iterate outward from the end Node in progressively larger rings, putting all valid Nodes into a PQueue. // Continue until we find a ring with a valid Node. for (int i = 1; i < max; i++) { ring = getRing(map, end, i); if (ring.Count != 0) { break; } } // find the valid Node nearest the intended end Node newEnd = ring.peek(0); // if the PQueue has a lead tie (i.e. there are 2+ nodes of equal distance from the center node), // pick the one closest to the intended start Node. if (ring.hasLeadTie) { int distToStart = map.pathDistance(start, newEnd); int distToEnd = map.pathDistance(end, newEnd); for (int i = 1; i < ring.Count; i++) { if (map.pathDistance(ring.peek(i), end) > distToEnd) { break; } int currentDist = map.pathDistance(ring.peek(i), start); if (currentDist < distToStart) { distToStart = currentDist; newEnd = ring.peek(i); } } } // clean all enqueued Nodes so they can be properly used by the pathfinder for (int i = 0; i < ring.Count; i++) { ring.peek(i).clean(); } return(newEnd); }
/* * public functions */ /// <summary> /// Given an invalid Node, returns the approximate nearest valid Node. /// </summary> /// <param name="end"></param> /// <returns></returns> public static Node nearestValidEnd(NodeMap map, Node start, Node end) { int max = Math.Max(map.height, map.width); PQueue ring = new PQueue(); // iterate outward from the end Node in progressively larger rings, putting all valid Nodes into a PQueue. // Continue until we find a ring with a valid Node. for (int i = 1; i < max; i++) { ring = getRing(map, end, i); if (ring.Count != 0) break; } // if this is a completely invalid NodeMap (yeah, right, I know), return null if (ring.Count == 0) return null; // find the valid Node nearest the intended end Node newEnd = ring.peek(0); // if the PQueue has a lead tie (i.e. there are 2+ nodes of equal distance from the center node), // pick the one closest to the intended start Node. if (ring.hasLeadTie) { int distToStart = map.pathDistance(start, newEnd); int distToEnd = map.pathDistance(end, newEnd); for (int i = 1; i < ring.Count; i++) { if (map.pathDistance(ring.peek(i), end) > distToEnd) break; int currentDist = map.pathDistance(ring.peek(i), start); if (currentDist < distToStart) { distToStart = currentDist; newEnd = ring.peek(i); } } } // clean the FScores of all enqueued Nodes so they can be properly used by the pathfinder for (int i = 0; i < ring.Count; i++) ring.peek(i).Fscore = 0; return newEnd; }
/* * helper functions */ /// <summary> /// Enqueues all valid Nodes in a ring around a center Node, offset from the center by [offset] Nodes. /// For example, an offset of 2 searches a 5x5 ring (x+-2, y+-2) around the center Node. /// Since modifying the Node's Fscores is necessary for enqueueing, each enqueued node's Fscore must be reset after use. /// </summary> /// <param name="map">The NodeMap to search over</param> /// <param name="center">The center Node to search around</param> /// <param name="offset">The ring "radius"</param> /// <returns>A PQueue containing all valid Nodes found in the ring</returns> private static PQueue getRing(NodeMap map, Node center, int offset) { PQueue ring = new PQueue(); int x = center.Xcoord; int y = center.Ycoord; // grab left and right columns for (int i = y - offset; i <= y + offset; i++) { Node Xmin = map.getNode(x - offset, i); Node Xmax = map.getNode(x + offset, i); if (Xmin.isValid) { Xmin.Fscore = map.pathDistance(Xmin, center); ring.enqueue(Xmin); } if (Xmax.isValid) { Xmax.Fscore = map.pathDistance(Xmax, center); ring.enqueue(Xmax); } } // grab remainder of top and bottom rows for (int i = x - offset + 1; i < x + offset; i++) { Node Ymin = map.getNode(i, y - offset); Node Ymax = map.getNode(i, y + offset); if (Ymin.isValid) { Ymin.Fscore = map.pathDistance(Ymin, center); ring.enqueue(Ymin); } if (Ymax.isValid) { Ymax.Fscore = map.pathDistance(Ymax, center); ring.enqueue(Ymax); } } return ring; }
/* * helper functions */ /// <summary> /// Enqueues all valid Nodes in a ring around a center Node, offset from the center by [offset] Nodes. /// For example, an offset of 2 searches a 5x5 ring (x+-2, y+-2) around the center Node. /// Since modifying the Node's Fscores is necessary for enqueueing, each enqueued node's Fscore must be reset after use. /// </summary> /// <param name="map">The NodeMap to search over</param> /// <param name="center">The center Node to search around</param> /// <param name="offset">The ring "radius"</param> /// <returns>A PQueue containing all valid Nodes found in the ring</returns> private static PQueue getRing(NodeMap map, Node center, int offset) { PQueue ring = new PQueue(); int x = center.X; int y = center.Y; // grab left and right columns for (int i = Math.Max(y - offset, 0); i <= y + offset; i++) { Node Xmin = map.getNode(x - offset, i); Node Xmax = map.getNode(x + offset, i); if (Xmin != null && Xmin.isValid) { Xmin.Fscore = map.pathDistance(Xmin, center); ring.enqueue(Xmin); } if (Xmax != null && Xmax.isValid) { Xmax.Fscore = map.pathDistance(Xmax, center); ring.enqueue(Xmax); } } // grab remainder of top and bottom rows for (int i = x - offset + 1; i < x + offset; i++) { Node Ymin = map.getNode(i, Math.Max(0, y - offset)); Node Ymax = map.getNode(i, Math.Min(map.width - 1, y + offset)); if (Ymin != null && Ymin.isValid) { Ymin.Fscore = map.pathDistance(Ymin, center); ring.enqueue(Ymin); } if (Ymax != null && Ymax.isValid) { Ymax.Fscore = map.pathDistance(Ymax, center); ring.enqueue(Ymax); } } return(ring); }
/* * public functions */ /// <summary> /// A*, modified to find a nearest path in case of failure /// </summary> /// <param name="map">The Map</param> /// <param name="startCell">The starting Cell</param> /// <param name="endCell">The ending Cell</param> /// <returns>The path as a list of waypoints</returns> public static List<Cell> findPath(Map map, Cell startCell, Cell endCell) { /* * Terminology * Gscore cumulative calculated distance from the start Cell to the given Cell * Hscore estimated distance from the given Cell to the end Cell. * Overestimating this can result in the calculation of an incorrect (inefficient) path, * but the more it is underestimated, the longer correct path calculation will take * Fscore Gscore + Hscore; estimated total distance from start to end on a path through the given Cell. * Priority queues (PQueues) are ordered by ascending Fscore, so shortest estimated paths are examined first * open list A PQueue of Cells to be examined. Initially contains the start Cell * closed list A List<Cell> of Cells that have been examined * adjacent list A PQueue of Cells adjacent to the current Cell */ // initialize the lists PQueue open = new PQueue(); open.enqueue(startCell); List<Cell> closed = new List<Cell>(); PQueue adjacentCells = new PQueue(); iterations = 0; // good ol' A* while (open.count() > 0) // iterate until we have examined every appropriate Cell { //open.print("Open", true); Cell currentCell = open.dequeue(); // look at the Cell with the lowest Fscore and remove it from the open list if (currentCell == endCell) // if this is our destination Cell, we're done! return reconstruct(endCell); // so return the path closed.Add(currentCell); // otherwise, close this Cell so we don't travel to it again adjacentCells = getAdjacentCells(map, open, closed, currentCell); // now find every valid Cell adjacent to the current Cell //adjacentCells.print("Adjacent", false); while (adjacentCells.count() != 0) // iterate over all of them, from lowest to highest Fscore { Cell adjacentCell = adjacentCells.dequeue(); // grab the current adjacent Cell int tempGScore = currentCell.Gscore + pathDistance(currentCell, adjacentCell); // calculate a temporary Gscore as if we were traveling to this Cell from the current Cell if (!open.contains(adjacentCell) || tempGScore < adjacentCell.Gscore) // if this Cell has not been added to the open list, or if tempGscore is less than the Cell's current Gscore { int h = pathDistance(adjacentCell, endCell); // estimate the Cell's Hscore adjacentCell.prev = currentCell; // set the Cell's prev pointer to the current Cell adjacentCell.Hscore = h; // set the Cell's Hscore adjacentCell.Gscore = tempGScore; // set the Cell's Gscore adjacentCell.Fscore = tempGScore + h; // set the Cell's Fscore } if (!open.contains(adjacentCell)) // if the adjacent Cell we just examined is not yet on the open list, add it open.enqueue(adjacentCell); } iterations++; } // no valid path exists, so find the nearest path closed.RemoveAt(0); // remove the start Cell from the closed List if (closed.Count > 0) // if there are still Cells on the closed list { Cell nearestCell = closed[0]; // find the closed Cell with the lowest Hscore; for (int i = 1; i < closed.Count; i++) // this should be the Cell closest to the desired destination, { // so return a path ending with that Cell. if (closed[i].Hscore < nearestCell.Hscore) nearestCell = closed[i]; } return reconstruct(nearestCell); } else { List<Cell> path = new List<Cell>(); // otherwise, our only path was the start Cell (i.e. we are completely trapped); path.Add(endCell); // so return a path with just that Cell. return path; } }
/* * helper functions */ /// <summary> /// Puts all valid Cells adjacent to the given Cell in a PQueue and returns it /// </summary> /// <param name="map">The Map</param> /// <param name="closed">The closed list</param> /// <param name="currentCell">The current (center) Cell</param> /// <returns>A PQueue of all traversable adjacent Cells</returns> private static PQueue getAdjacentCells(Map map, PQueue open, List<Cell> closed, Cell currentCell) { int x = currentCell.Xcoord; int y = currentCell.Ycoord; List<Cell> immediate = new List<Cell>(); List<Cell> diagonal = new List<Cell>(); PQueue adjacentCells = new PQueue(); // grab all adjacent Cells (or null values) and store them here Cell[,] temp = map.getCells(x - 1, y - 1, 3, 3); // iterate over all adjacent Cells; add the ones that are open and in bounds to the appropriate List<Cell> for (int j = 0; j < 3; j++) { for (int i = 0; i < 3; i++) { if (temp[i, j] != null && !closed.Contains(temp[i, j])) { // if the Cell is horizontally or vertically adjacent, // add the Cell to the list of immediately adjacent Cells if (Math.Abs(2 - i - j) == 1) immediate.Add(temp[i, j]); // otherwise, if the Cell is valid, add it to the list of diagonally adjacent Cells else if (temp[i, j].isValid) diagonal.Add(temp[i, j]); } } } // iterate over all immediately adjacent Cells. If they are valid, enqueue them; // otherwise, remove the neighboring diagonally adjacent Cells from the diagonal List for (int i = 0; i < immediate.Count(); i++) { if (!immediate[i].isValid) { Cell one, two = null; if (immediate[i].Xcoord == x) // the Cell is vertically adjacent { one = map.getCell(x + 1, immediate[i].Ycoord); two = map.getCell(x - 1, immediate[i].Ycoord); } else // the Cell is horizontally adjacent { one = map.getCell(immediate[i].Xcoord, y - 1); two = map.getCell(immediate[i].Xcoord, y + 1); } if (one != null) diagonal.Remove(one); if (two != null) diagonal.Remove(two); } else { adjacentCells.enqueue(immediate[i]); } } // enqueue all remaining diagonally adjacent Cells for (int i = 0; i < diagonal.Count(); i++) adjacentCells.enqueue(diagonal[i]); // return the finished PQueue return adjacentCells; }
/* * public functions */ /// <summary> /// A*, modified to find a nearest path in case of failure /// </summary> /// <param name="map">The Map</param> /// <param name="startNode">The starting Node</param> /// <param name="endNode">The ending Node</param> /// <returns>The path as a list of waypoints</returns> public static List <Node> findPath(NodeMap map, Node startNode, Node endNode) { // initialize data PQueue open = new PQueue(); open.enqueue(startNode); List <Node> adjacentNodes = new List <Node>(); // good ol' A* while (open.Count > 0) { // find the open Node with the lowest Fscore and remove it from the open PQueue Node current = open.dequeue(); // if this is our destination Node, we're done; otherwise, close it so we don't travel to it again if (current == endNode) { return(reconstruct(endNode)); } current.close(); // find every valid Node adjacent to the current Node adjacentNodes = map.getAdjacentNodes(current); // iterate over all of them for (int i = 0; i < adjacentNodes.Count; i++) { // grab an adjacent Node and calculate a new GScore and HScore for it Node adjacent = adjacentNodes[i]; int tempGScore = current.Gscore + map.pathDistance(current, adjacent); int tempHScore = map.pathDistance(adjacent, endNode); // if we have not opened this Cell, give it new stats and open it if (!adjacent.isOpen) { setAdjacentStats(adjacent, current, tempGScore, tempHScore); open.enqueue(adjacent); } // otherwise, if we have opened it but the new path to it is shorter than the old one, give it new stats and reset its position in the open PQueue else if (tempGScore < adjacent.Gscore) { setAdjacentStats(adjacent, current, tempGScore, tempHScore); //open.resetPosition(adjacent); } } } // no valid path exists, so find the nearest path List <Node> closed = createClosed(map); Node nearestNode = closed[0]; // find the closed Node with the lowest Hscore (distance from the intended end); return a path to that Node if (closed.Count > 0) { for (int i = 1; i < closed.Count; i++) { if (closed[i].Hscore < nearestNode.Hscore) { nearestNode = closed[i]; } } } return(reconstruct(nearestNode)); }
/* * public functions */ /// <summary> /// A*, modified to find a nearest path in case of failure /// </summary> /// <param name="map">The Map</param> /// <param name="startCell">The starting Cell</param> /// <param name="endCell">The ending Cell</param> /// <returns>The path as a list of waypoints</returns> public static List <Cell> findPath(Map map, Cell startCell, Cell endCell) { /* * Terminology * Gscore cumulative calculated distance from the start Cell to the given Cell * Hscore estimated distance from the given Cell to the end Cell. * Overestimating this can result in the calculation of an incorrect (inefficient) path, * but the more it is underestimated, the longer correct path calculation will take * Fscore Gscore + Hscore; estimated total distance from start to end on a path through the given Cell. * Priority queues (PQueues) are ordered by ascending Fscore, so shortest estimated paths are examined first * open list A PQueue of Cells to be examined. Initially contains the start Cell * closed list A List<Cell> of Cells that have been examined * adjacent list A PQueue of Cells adjacent to the current Cell */ // initialize the lists PQueue open = new PQueue(); open.enqueue(startCell); List <Cell> closed = new List <Cell>(); PQueue adjacentCells = new PQueue(); iterations = 0; // good ol' A* while (open.count() > 0) // iterate until we have examined every appropriate Cell { //open.print("Open", true); Cell currentCell = open.dequeue(); // look at the Cell with the lowest Fscore and remove it from the open list if (currentCell == endCell) // if this is our destination Cell, we're done! { return(reconstruct(endCell)); // so return the path } closed.Add(currentCell); // otherwise, close this Cell so we don't travel to it again adjacentCells = getAdjacentCells(map, open, closed, currentCell); // now find every valid Cell adjacent to the current Cell //adjacentCells.print("Adjacent", false); while (adjacentCells.count() != 0) // iterate over all of them, from lowest to highest Fscore { Cell adjacentCell = adjacentCells.dequeue(); // grab the current adjacent Cell int tempGScore = currentCell.Gscore + pathDistance(currentCell, adjacentCell); // calculate a temporary Gscore as if we were traveling to this Cell from the current Cell if (!open.contains(adjacentCell) || tempGScore < adjacentCell.Gscore) // if this Cell has not been added to the open list, or if tempGscore is less than the Cell's current Gscore { int h = pathDistance(adjacentCell, endCell); // estimate the Cell's Hscore adjacentCell.prev = currentCell; // set the Cell's prev pointer to the current Cell adjacentCell.Hscore = h; // set the Cell's Hscore adjacentCell.Gscore = tempGScore; // set the Cell's Gscore adjacentCell.Fscore = tempGScore + h; // set the Cell's Fscore } if (!open.contains(adjacentCell)) // if the adjacent Cell we just examined is not yet on the open list, add it { open.enqueue(adjacentCell); } } iterations++; } // no valid path exists, so find the nearest path closed.RemoveAt(0); // remove the start Cell from the closed List if (closed.Count > 0) // if there are still Cells on the closed list { Cell nearestCell = closed[0]; // find the closed Cell with the lowest Hscore; for (int i = 1; i < closed.Count; i++) // this should be the Cell closest to the desired destination, { // so return a path ending with that Cell. if (closed[i].Hscore < nearestCell.Hscore) { nearestCell = closed[i]; } } return(reconstruct(nearestCell)); } else { List <Cell> path = new List <Cell>(); // otherwise, our only path was the start Cell (i.e. we are completely trapped); path.Add(endCell); // so return a path with just that Cell. return(path); } }
/* * helper functions */ /// <summary> /// Puts all valid Cells adjacent to the given Cell in a PQueue and returns it /// </summary> /// <param name="map">The Map</param> /// <param name="closed">The closed list</param> /// <param name="currentCell">The current (center) Cell</param> /// <returns>A PQueue of all traversable adjacent Cells</returns> private static PQueue getAdjacentCells(Map map, PQueue open, List <Cell> closed, Cell currentCell) { int x = currentCell.Xcoord; int y = currentCell.Ycoord; List <Cell> immediate = new List <Cell>(); List <Cell> diagonal = new List <Cell>(); PQueue adjacentCells = new PQueue(); // grab all adjacent Cells (or null values) and store them here Cell[,] temp = map.getCells(x - 1, y - 1, 3, 3); // iterate over all adjacent Cells; add the ones that are open and in bounds to the appropriate List<Cell> for (int j = 0; j < 3; j++) { for (int i = 0; i < 3; i++) { if (temp[i, j] != null && !closed.Contains(temp[i, j])) { // if the Cell is horizontally or vertically adjacent, // add the Cell to the list of immediately adjacent Cells if (Math.Abs(2 - i - j) == 1) { immediate.Add(temp[i, j]); } // otherwise, if the Cell is valid, add it to the list of diagonally adjacent Cells else if (temp[i, j].isValid) { diagonal.Add(temp[i, j]); } } } } // iterate over all immediately adjacent Cells. If they are valid, enqueue them; // otherwise, remove the neighboring diagonally adjacent Cells from the diagonal List for (int i = 0; i < immediate.Count(); i++) { if (!immediate[i].isValid) { Cell one, two = null; if (immediate[i].Xcoord == x) // the Cell is vertically adjacent { one = map.getCell(x + 1, immediate[i].Ycoord); two = map.getCell(x - 1, immediate[i].Ycoord); } else // the Cell is horizontally adjacent { one = map.getCell(immediate[i].Xcoord, y - 1); two = map.getCell(immediate[i].Xcoord, y + 1); } if (one != null) { diagonal.Remove(one); } if (two != null) { diagonal.Remove(two); } } else { adjacentCells.enqueue(immediate[i]); } } // enqueue all remaining diagonally adjacent Cells for (int i = 0; i < diagonal.Count(); i++) { adjacentCells.enqueue(diagonal[i]); } // return the finished PQueue return(adjacentCells); }
/// <summary> /// Copy constructor /// </summary> public PQueue(PQueue p) { for (int i = 0; i < p.pq.Count(); i++) pq.Add(p.pq[i]); }