The last part mentioned the basic concepts such as node, cost, and valuation formula. With this knowledge, we can start our journey!
For example, this is a grid with five rows and eight columns. The yellow node is the start point, the red node is the end point, and the black node is an obstacle (node ).
The routing process can be considered as follows:
1. First, start from the center, expand the circle to the surrounding area, and calculate the cost of a single step g (that is, the cost of moving from the center point to the adjacent grid) of the surrounding nodes (up to 8: the horizontal or vertical value is 1, and the diagonal value is 1.4). Then, calculate the estimated cost H from each node to the end point (using the estimation formula mentioned last in the previous section ), the total cost of each node is obtained. f = G + H.
2. Prepare two arrays at the same time. One is called the open list and the other is called the closed list. Then, add the surrounding nodes to the open array, then, the F value of the peripheral node is sorted from small to large using array sorting, so as to find the node with the minimum cost-the minimum cost, which means that the node should be moved. Then place the center point in the current round of calculation into the Close array (that is, since the next step is found, this step does not need to be considered)
3. Use the node with the minimum cost in step 1 as the center point and delete the node from the open array (since the correct step has been found, this node does not need to participate in the next sorting.) continue to expand outward and get the new peripheral nodes. The G value and H value are also calculated, but pay attention to the following points: because the current center is not the starting point, the calculation of the G value is actually composed of two parts, one part is the G value (counted as G1) from the starting point of the central node ), some are the G values (marked as G2) from the central node to the surrounding node, and the G of each peripheral node is G1 + G2. Note that: the relative position of a node may change. For example, some nodes in the previous round are in a horizontal or vertical position relative to the central node in the previous round. In this round of calculation, because the center is moving one bit forward, therefore, in this round of calculation, the same node may become a diagonal node of the central node (that is, the G2 node changes from 1 to 1.4), and vice versa. The H value is calculated as before, similar to the previous processing, the final price f = G + H is obtained. Similarly, the new peripheral node is put into the open array (if not put ). The node that has already been placed in the open array or Close array in step 1. Because the G value may change, we need to re-compare the total cost of this operation, the total cost of the last calculation is F. If the value of F in this calculation is smaller, the value of F, H, and G in this calculation prevails ), in the same way, the F value of the open array is sorted from small to large, and the new node with the minimum cost is obtained as the center of the next round of computing. (Of course, before the next round of calculation, we also need to delete the node with the least cost found from the open array, and put the center of this calculation into the Close array)
4. According to the previous handling process, the path will be continuously extended (of course, the extension process may be tortuous or repeated ), but because we have been replacing the smallest node with the lowest price, we must be getting closer and closer to the end.
5. During the above process, once we find that the center point of this round of computing is the end point, congratulations! We have reached the end point!
It seems to be complicated, but in fact I figured it out, that is, constantly calculating G, H, F, then constantly sorting the F value of the open array, and then finding the node with the minimum F value, keep moving forward! To facilitate the test of the peripheral nodes G, H, F in each round of computing, I wrote a rough Test Program (many parameters need to be adjusted manually ):
Package {import flash. display. sprite; public class gridtest extends sprite {private VaR _ endnode: node; // end point private VaR _ startnode: node; // start point private VaR _ centernode: node; // The calculation center point private VaR _ straightcost: Number = 1.0; private VaR _ diagcost: Number = math. sqrt2; Public Function gridtest () {var G: Grid = new grid (8, 5); // generate a grid G with 5 rows and 8 columns. setstartnode (1, 1); // set the start point G. setendnode (6, 3); // set endpoint _ endnode = G. endnode; _ startnode = G. startnode; _ centernode = G. getnode (); // you can adjust the center point to observe the F, H, and g Values of peripheral nodes. // The third-party ASCB library is used to format the numbers, take a decimal point and a decimal point (if you do not have the official ASCB library, you can also directly output a number) var FMR: numberformat = new numberformat (); FMR. mask = "#. 0 "; VaR _ g1: Number = diagonal (_ centernode, _ startnode ); // G value of the relative start point of the center. // here, X is the X range of the nodes around the center (which can be adjusted manually) for (var x: uint = 1; x <= 3; X ++) {// here, Y is the y range of the nodes around the center (which can be adjusted manually) for (var y: uint = 0; y <= 2; Y ++) {var test: node = G. getnode (x, y); VaR _ H: Number = diagonal (test, _ endnode); VaR _ G2: Number = diagonal (test, _ centernode); VaR _ g: number = _ G1 + _ G2; // calculate the G value VaR _ F: Number = _ H + _ g; trace ("x =", test. x, ", y =", test. y, ", F =", FMR. format (_ f), ", G =", FMR. format (_ g), ", H =", FMR. format (_ H) ;}}// diagonal estimate private function diagonal (node: node, target: node): number {var DX: Number = math. ABS (node. x-target. x); var DY: Number = math. ABS (node. y-target. y); var diag: Number = math. min (dx, Dy); var straight: Number = dx + dy; return _ diagcost * diag + _ straightcost * (straight-2 * DIAG );}}}
Run the command to obtain the following output results:
X = 1, y = 0, F = 8.7, G = 2.4, H = 6.2
X = 1, y = 1, F = 7.8, G = 2.0, H = 5.8
X = 1, y = 2, F = 7.8, G = 2.4, H = 5.4
X = 2, y = 0, F = 7.2, G = 2.0, H = 5.2
X = 2, y = 1, F = 5.8, G = 1.0, H = 4.8
X = 2, y = 2, F = 6.4, G = 2.0, H = 4.4
X = 3, y = 0, F = 6.7, G = 2.4, H = 4.2
X = 3, y = 1, F = 5.8, G = 2.0, H = 3.8
X = 3, y = 2, F = 5.8, G = 2.4, H = 3.4
OK, there is another important problem. Even if we reach the end point, we also get the last center point (actually the end point). How can we restore the path to build? Because there are no other variables in the processing process to save the intermediate calculation results (that is, the minimum cost nodule found each time )? In addition, even if a variable is used as an intermediary to store the best node in each round of computing, as mentioned above, the process of finding the path to the surrounding area is likely to show twists and turns, does the final path still need to be circled (or closed? If so, it violates the original intention of finding the best (shortest) path in the previous section.
In fact, it is not difficult to solve this problem: the previous section mentioned the concept of a parent node! During each round of computing, the central node is the parent node of other nodes (or it can be understood that all the peripheral nodes point to it) relative to the adjacent nodes around the central point !) If the minimum cost node is found in each round of computing, direct its parent node to the central node (that is, the minimum cost node found in the previous round) so that the end point is reached, by pointing to the parent node, you can obtain the optimal path from the end point to the start point.
No picture, no truth, please refer to the previous figure.
Of course, I only took two steps forward. A patient person can draw out all the remaining steps based on the test procedure given above (I have painted them almost all day ), it takes 13 rounds of computing to reach the end (because many rounds of computing will be repeated in the middle)
Note: Some people have noticed that no. At the end of the first round of calculation, there are two nodes x2y1 and x2y2 in the peripheral nodes. The total cost is 5.8 at the minimum value. Why should we choose x2y1, instead of x2y2? In fact, this depends on the order of the loop. When traversing the surrounding nodes, it is usually processed by a double loop. If we traverse the nodes in the order of X and Y, after sorting the open array, x2y1 will be ranked first. If it is traversed in the order of first y and then X, x2y2 will be ranked first after the Open array is sorted, so it doesn't matter, it depends entirely on how your loop is written. (This article uses the sequential traversal of first X and then y)
Now, the astar. As class is available, and all the analysis process is encapsulated in it:
Package {import flash. display. sprite; public class astar extends sprite {private VaR _ open: array; // open list private VaR _ closed: array; // closed list private VaR _ grid: grid; private VaR _ endnode: node; // end point private VaR _ startnode: node; // start point private VaR _ path: array; // The final path node // Private VaR _ heuristic: function = Manhattan; // Private VaR _ heuristic: function = Euclidian; private VaR _ heuristic: function = diagonal; // Estimation Formula private VaR _ straightcost: Number = 1.0; // linear cost Private VaR _ diagcost: Number = math. sqrt2; // diagonal line price public function astar () {}// judge whether the node is open list private function isopen (node: node): Boolean {for (var I: Int = 0; I <_ open. length; I ++) {If (_ open [I] = node) {return true ;}} return false ;} // determine whether to close the list of nodes. Private function isclosed (node: node): Boolean {for (var I: Int = 0; I <_ closed. length; I ++) {If (_ closed [I] = node) {return true ;}} return False;} // search for the specified network path public function findpath (Grid: grid): Boolean {_ grid = grid; _ open = new array (); _ closed = new array (); _ startnode = _ grid. startnode; _ endnode = _ grid. endnode; _ startnode. G = 0; _ startnode. H = _ heuristic (_ startnode); _ startnode. F = _ startnode. G + _ startnode. h; return search () ;}// key processing function for calculating the cost of surrounding nodes public function search (): Boolean {VaR _ T: uint = 1; var node: node = _ startnode; // if the current node is not the destination while (node! = _ Endnode) {// locate the X of the adjacent node, and the Y range is Var startx: Int = math. max (0, node. x-1); var endx: Int = math. min (_ grid. numcols-1, node. X + 1); var starty: Int = math. max (0, node. y-1); var Endy: Int = math. min (_ grid. numrows-1, node. Y + 1); // cyclically process all adjacent nodes for (var I: Int = startx; I <= endx; I ++) {for (VAR J: Int = starty; j <= Endy; j ++) {var test: node = _ grid. getnode (I, j); // skip if (test = node |! Test. retriable) {continue;} var cost: Number = _ straightcost; // if it is an object line, use the diagonal line price if (! (Node. X = test. x) | (node. y = test. y) {cost = _ diagcost;} // calculate the total cost of the test node var G: Number = node. G + cost * test. costmultiplier; var H: Number = _ heuristic (TEST); var F: Number = G + H; // if the point is in the open or close list if (isopen (test) | isclosed (TEST) {// if the cost of this calculation is lower, this calculation prevails if (F <test. f) {trace ("\ n", _ T, "Wheel, with nodes pointing back, x =", I, ", y =", J ,", G = ", G,", H = ", H,", F = ", F,", test = ", test. tostring (); test. F = f; test. G = g; test. H = H; test. parent = node; // weight The new parent node of this point is the current calculation Center} else // if it is not in the open list, in addition to the update cost and setting the parent node, the open Array {test. F = f; test. G = g; test. H = H; test. parent = node; _ open. push (TEST) ;}}_ closed. push (node); // Add the processed center nodes to the close node for secondary debugging, and output the nodes in the open array for (I = 0; I <_ open. length; I ++) {trace (_ open [I]. tostring ();} If (_ open. length = 0) {trace ("no best node found, no way to go! "); Return false} _ open. sorton ("F", array. numeric); // sort node = _ open at the total cost. shift () as node; // Delete the less costly nodules from the open array, and assign the node to node as the next Central Point Trace ("nth", _ t, "The best node for wheel fetch is:", node. tostring (); _ t ++;} // After the loop ends, the build path buildpath (); Return true ;}// points, reversely connects to the start point private function buildpath (): void {_ Path = new array (); var node: node = _ endnode; _ path. push (node); While (node! = _ Startnode) {node = node. parent; _ path. unshift (node) ;}}// Manhattan (node: node): number {return math. ABS (node. x-_ endnode. x) * _ straightcost + math. ABS (node. y-_ endnode. y) * _ straightcost;} // geometric valuation method private function Euclidian (node: node): number {var DX: Number = node. x-_ endnode. x; var DY: Number = node. y-_ endnode. y; return math. SQRT (dx * dx + dy * Dy) * _ straightcost;} // method of diagonal estimation private function diagonal (node: node): number {var DX: Number = math. ABS (node. x-_ endnode. x); var DY: Number = math. ABS (node. y-_ endnode. y); var diag: Number = math. min (dx, Dy); var straight: Number = dx + dy; return _ diagcost * diag + _ straightcost * (straight-2 * DIAG );} // return all compute nodes (Auxiliary Functions) Public Function get visited (): Array {return _ closed. concat (_ open);} // returns the open array public function get openarray (): Array {return this. _ open;} // return the Close array public function get closedarray (): Array {return this. _ closed;} public function get path (): Array {return _ path ;}}}
To facilitate debugging of output information, I also added a tostring method in node..
// The tostring function public function tostring (): String {var FMR: numberformat = new numberformat (); FMR. mask = "#. 0 "; Return" x = "+ this. x. tostring () + ", y =" + this. y. tostring () + ", G =" + FMR. format (this. g) + ", H =" + FMR. format (this. h) + ", F =" + FMR. format (this. f );}
In order to facilitate the test, another type of gridview. As is created, which encapsulates operations such as grid painting, open array and closed array highlighting, and painting path:
Package {import flash. display. sprite; import flash. events. mouseevent; public class gridview extends sprite {private VaR _ cellsize: Int = 40; private VaR _ grid: grid; // constructor public function gridview (Grid: grid) {_ grid = grid; drawgrid (); findpath (); addeventlistener (mouseevent. click, ongridclick);} // draw the grid public function drawgrid (): void {graphics. clear (); For (var I: Int = 0; I <_ grid. numcols; I ++) {for (VAR J: Int = 0; j <_ gr Id. numrows; j ++) {var node: node = _ grid. getnode (I, j); graphics. linestyle (0); graphics. beginfill (getcolor (node); graphics. drawrect (I * _ cellsize, J * _ cellsize, _ cellsize, _ cellsize) ;}}// obtain the node color private function getcolor (node: node ): uint {If (! Node. retriable) {return 0;} If (node = _ grid. startnode) {return 0xffff00;} If (node = _ grid. endnode) {return 0xff0000;} return 0 xffffff;} // Private function ongridclick (Event: mouseevent): void {var xpos: Int = math. floor (event. localx/_ cellsize); var ypos: Int = math. floor (event. localy/_ cellsize); _ grid. setretriable (xpos, ypos ,! _ Grid. getnode (xpos, ypos ). retriable); drawgrid (); findpath () ;}// find the path private function findpath (): void {var astar: astar = new astar; If (astar. findpath (_ grid) {showvisited (astar); showpath (astar) ;}// display the open list and close list private function showvisited (astar: astar ): void {var opened: array = astar. openarray; For (var I: Int = 0; I <opened. length; I ++) {var node: node = opened [I] As node; graphics. beginfill (0 xcccccc); If (node = _ grid. startnode) {graphics. beginfill (0xffff00);} graphics. drawrect (opened [I]. x * _ cellsize, opened [I]. y * _ cellsize, _ cellsize, _ cellsize); graphics. endfill ();} var closed: array = astar. closedarray; for (I = 0; I <closed. length; I ++) {node = opened [I] As node; graphics. beginfill (0xffff00); graphics. drawrect (closed [I]. x * _ cellsize, closed [I]. y * _ cellsize, _ cellsize, _ cellsize); graphics. endfill () ;}}// display path private function showpath (astar: astar): void {var path: array = astar. path; For (var I: Int = 0; I <path. length; I ++) {graphics. linestyle (0); graphics. beginfill (0); graphics. drawcircle (path [I]. x * _ cellsize + _ cellsize/2, path [I]. y * _ cellsize + _ cellsize/2, _ cellsize/3 );}}}}
Formal test:
Package {import flash. display. sprite; import flash. display. stagealign; import flash. display. stagescalemode; import flash. events. mouseevent; [SWF (backgroundcolor = 0 xffffff, width = 360, Height = 240)] public class pathfinding extends sprite {private VaR _ grid: grid; private VaR _ gridview: gridview; public Function pathfinding () {stage. align = stagealign. top_left; stage. scalemode = stagescalemode. no_scale; _ grid = new grid (8, 5); _ grid. setstartnode (1, 1); _ grid. setendnode (6, 3); // set obstacle _ grid. getnode (4,0 ). retriable = false; _ grid. getnode (4, 1 ). retriable = false; _ grid. getnode (4, 2 ). retriable = false; _ grid. getnode (4, 3 ). retriable = false; _ gridview = new gridview (_ grid); _ gridview. X = 20; _ gridview. y = 20; addchild (_ gridview );}}}
The open list is displayed in yellow, and the closed list is displayed in gray. Click on each node to switch the corresponding node to an obstacle node or a common node.
Of course, there is a small bug here. I don't know if you can see it. I will explain the possible problems in the next step.