You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

435 line
11 KiB

  1. /* eslint-disable */
  2. // javascript-astar 0.4.1
  3. // http://github.com/bgrins/javascript-astar
  4. // Freely distributable under the MIT License.
  5. // Implements the astar search algorithm in javascript using a Binary Heap.
  6. // Includes Binary Heap (with modifications) from Marijn Haverbeke.
  7. // http://eloquentjavascript.net/appendix2.html
  8. (function(definition) {
  9. /* global module, define */
  10. if (typeof module === 'object' && typeof module.exports === 'object') {
  11. module.exports = definition();
  12. } else if (typeof define === 'function' && define.amd) {
  13. define([], definition);
  14. } else {
  15. var exports = definition();
  16. window.astar = exports.astar;
  17. window.Graph = exports.Graph;
  18. }
  19. })(function() {
  20. function pathTo(node) {
  21. var curr = node;
  22. var path = [];
  23. while (curr.parent) {
  24. path.unshift(curr);
  25. curr = curr.parent;
  26. }
  27. return path;
  28. }
  29. function getHeap() {
  30. return new BinaryHeap(function(node) {
  31. return node.f;
  32. });
  33. }
  34. var astar = {
  35. /**
  36. * Perform an A* Search on a graph given a start and end node.
  37. * @param {Graph} graph
  38. * @param {GridNode} start
  39. * @param {GridNode} end
  40. * @param {Object} [options]
  41. * @param {bool} [options.closest] Specifies whether to return the
  42. path to the closest node if the target is unreachable.
  43. * @param {Function} [options.heuristic] Heuristic function (see
  44. * astar.heuristics).
  45. */
  46. search: function(graph, start, end, options) {
  47. start = graph.grid[start.x][start.y] || start;
  48. end = graph.grid[end.x][end.y] || end;
  49. graph.cleanDirty();
  50. options = options || {};
  51. var heuristic = options.heuristic || astar.heuristics.manhattan;
  52. var closest = options.closest || false;
  53. var distance = options.distance;
  54. if (distance)
  55. heuristic = astar.heuristics.manhattanDistance;
  56. var openHeap = getHeap();
  57. var closestNode = start; // set the start node to be the closest if required
  58. start.h = heuristic(start, end, distance);
  59. graph.markDirty(start);
  60. openHeap.push(start);
  61. while (openHeap.size() > 0) {
  62. // Grab the lowest f(x) to process next. Heap keeps this sorted for us.
  63. var currentNode = openHeap.pop();
  64. var onWall = !currentNode.isWall || currentNode.isWall();
  65. if (!onWall) {
  66. if (distance) {
  67. if (currentNode.h == distance)
  68. return pathTo(currentNode);
  69. }
  70. else {
  71. // End case -- result has been found, return the traced path.
  72. if (currentNode === end) {
  73. return pathTo(currentNode);
  74. }
  75. }
  76. }
  77. // Normal case -- move currentNode from open to closed, process each of its neighbors.
  78. currentNode.closed = true;
  79. // Find all neighbors for the current node.
  80. var neighbors = graph.neighbors(currentNode);
  81. for (var i = 0, il = neighbors.length; i < il; ++i) {
  82. var neighbor = neighbors[i];
  83. if (neighbor.closed || neighbor.isWall()) {
  84. // Not a valid node to process, skip to next neighbor.
  85. continue;
  86. }
  87. // The g score is the shortest distance from start to current node.
  88. // We need to check if the path we have arrived at this neighbor is the shortest one we have seen yet.
  89. var gScore = currentNode.g + neighbor.getCost(currentNode);
  90. var beenVisited = neighbor.visited;
  91. if (!beenVisited || gScore < neighbor.g) {
  92. // Found an optimal (so far) path to this node. Take score for node to see how good it is.
  93. neighbor.visited = true;
  94. neighbor.parent = currentNode;
  95. neighbor.h = neighbor.h || heuristic(neighbor, end, distance);
  96. neighbor.g = gScore;
  97. neighbor.f = neighbor.g + neighbor.h;
  98. graph.markDirty(neighbor);
  99. if (closest) {
  100. // If the neighbour is closer than the current closestNode or if it's equally close but has
  101. // a cheaper path than the current closest node then it becomes the closest node
  102. if (neighbor.h < closestNode.h || (neighbor.h === closestNode.h && neighbor.g < closestNode.g)) {
  103. closestNode = neighbor;
  104. }
  105. }
  106. if (!beenVisited) {
  107. // Pushing to heap will put it in proper place based on the 'f' value.
  108. openHeap.push(neighbor);
  109. } else {
  110. // Already seen the node, but since it has been rescored we need to reorder it in the heap
  111. openHeap.rescoreElement(neighbor);
  112. }
  113. }
  114. }
  115. }
  116. if (closest) {
  117. return pathTo(closestNode);
  118. }
  119. // No result was found - empty array signifies failure to find path.
  120. return [];
  121. },
  122. // See list of heuristics: http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html
  123. heuristics: {
  124. manhattan: function(pos0, pos1) {
  125. var d1 = Math.abs(pos1.x - pos0.x);
  126. var d2 = Math.abs(pos1.y - pos0.y);
  127. return Math.max(d1, d2);
  128. },
  129. manhattanDistance: function(pos0, pos1, distance) {
  130. var d1 = Math.abs(pos1.x - pos0.x);
  131. var d2 = Math.abs(pos1.y - pos0.y);
  132. return Math.abs(distance - Math.max(d1, d2)) + 1;
  133. },
  134. diagonal: function(pos0, pos1) {
  135. var D = 1;
  136. var D2 = Math.sqrt(2);
  137. var d1 = Math.abs(pos1.x - pos0.x);
  138. var d2 = Math.abs(pos1.y - pos0.y);
  139. return (D * (d1 + d2)) + ((D2 - (2 * D)) * Math.min(d1, d2));
  140. }
  141. },
  142. cleanNode: function(node) {
  143. if (!node)
  144. return;
  145. node.f = 0;
  146. node.g = 0;
  147. node.h = 0;
  148. node.visited = false;
  149. node.closed = false;
  150. node.parent = null;
  151. }
  152. };
  153. /**
  154. * A graph memory structure
  155. * @param {Array} gridIn 2D array of input weights
  156. * @param {Object} [options]
  157. * @param {bool} [options.diagonal] Specifies whether diagonal moves are allowed
  158. */
  159. function Graph(gridIn, options) {
  160. options = options || {};
  161. this.nodes = [];
  162. this.diagonal = !!options.diagonal;
  163. this.grid = [];
  164. for (var x = 0; x < gridIn.length; x++) {
  165. this.grid[x] = [];
  166. for (var y = 0, row = gridIn[x]; y < row.length; y++) {
  167. if (!row[y]) {
  168. node = new GridNode(x, y, row[y] ? 0 : 1);
  169. this.grid[x][y] = node;
  170. this.nodes.push(node);
  171. }
  172. }
  173. }
  174. this.init();
  175. }
  176. Graph.prototype.init = function() {
  177. this.dirtyNodes = [];
  178. for (var i = 0; i < this.nodes.length; i++) {
  179. astar.cleanNode(this.nodes[i]);
  180. }
  181. };
  182. Graph.prototype.cleanDirty = function() {
  183. for (var i = 0; i < this.dirtyNodes.length; i++) {
  184. astar.cleanNode(this.dirtyNodes[i]);
  185. }
  186. this.dirtyNodes = [];
  187. };
  188. Graph.prototype.markDirty = function(node) {
  189. this.dirtyNodes.push(node);
  190. };
  191. Graph.prototype.neighbors = function(node) {
  192. var ret = [];
  193. var x = node.x;
  194. var y = node.y;
  195. var grid = this.grid;
  196. // West
  197. if (grid[x - 1] && grid[x - 1][y]) {
  198. ret.push(grid[x - 1][y]);
  199. }
  200. // East
  201. if (grid[x + 1] && grid[x + 1][y]) {
  202. ret.push(grid[x + 1][y]);
  203. }
  204. // South
  205. if (grid[x] && grid[x][y - 1]) {
  206. ret.push(grid[x][y - 1]);
  207. }
  208. // North
  209. if (grid[x] && grid[x][y + 1]) {
  210. ret.push(grid[x][y + 1]);
  211. }
  212. if (this.diagonal) {
  213. // Southwest
  214. if (grid[x - 1] && grid[x - 1][y - 1]) {
  215. ret.push(grid[x - 1][y - 1]);
  216. }
  217. // Southeast
  218. if (grid[x + 1] && grid[x + 1][y - 1]) {
  219. ret.push(grid[x + 1][y - 1]);
  220. }
  221. // Northwest
  222. if (grid[x - 1] && grid[x - 1][y + 1]) {
  223. ret.push(grid[x - 1][y + 1]);
  224. }
  225. // Northeast
  226. if (grid[x + 1] && grid[x + 1][y + 1]) {
  227. ret.push(grid[x + 1][y + 1]);
  228. }
  229. }
  230. return ret;
  231. };
  232. Graph.prototype.toString = function() {
  233. var graphString = [];
  234. var nodes = this.grid;
  235. for (var x = 0; x < nodes.length; x++) {
  236. var rowDebug = [];
  237. var row = nodes[x];
  238. for (var y = 0; y < row.length; y++) {
  239. rowDebug.push(row[y].weight);
  240. }
  241. graphString.push(rowDebug.join(" "));
  242. }
  243. return graphString.join("\n");
  244. };
  245. function GridNode(x, y, weight) {
  246. this.x = x;
  247. this.y = y;
  248. this.weight = weight;
  249. }
  250. GridNode.prototype.toString = function() {
  251. return "[" + this.x + " " + this.y + "]";
  252. };
  253. GridNode.prototype.getCost = function(fromNeighbor) {
  254. // Take diagonal weight into consideration.
  255. if (fromNeighbor && fromNeighbor.x != this.x && fromNeighbor.y != this.y) {
  256. return this.weight * 1.41421;
  257. }
  258. return this.weight;
  259. };
  260. GridNode.prototype.isWall = function() {
  261. return this.weight === 0;
  262. };
  263. function BinaryHeap(scoreFunction) {
  264. this.content = [];
  265. this.scoreFunction = scoreFunction;
  266. }
  267. BinaryHeap.prototype = {
  268. push: function(element) {
  269. // Add the new element to the end of the array.
  270. this.content.push(element);
  271. // Allow it to sink down.
  272. this.sinkDown(this.content.length - 1);
  273. },
  274. pop: function() {
  275. // Store the first element so we can return it later.
  276. var result = this.content[0];
  277. // Get the element at the end of the array.
  278. var end = this.content.pop();
  279. // If there are any elements left, put the end element at the
  280. // start, and let it bubble up.
  281. if (this.content.length > 0) {
  282. this.content[0] = end;
  283. this.bubbleUp(0);
  284. }
  285. return result;
  286. },
  287. remove: function(node) {
  288. var i = this.content.indexOf(node);
  289. // When it is found, the process seen in 'pop' is repeated
  290. // to fill up the hole.
  291. var end = this.content.pop();
  292. if (i !== this.content.length - 1) {
  293. this.content[i] = end;
  294. if (this.scoreFunction(end) < this.scoreFunction(node)) {
  295. this.sinkDown(i);
  296. } else {
  297. this.bubbleUp(i);
  298. }
  299. }
  300. },
  301. size: function() {
  302. return this.content.length;
  303. },
  304. rescoreElement: function(node) {
  305. this.sinkDown(this.content.indexOf(node));
  306. },
  307. sinkDown: function(n) {
  308. // Fetch the element that has to be sunk.
  309. var element = this.content[n];
  310. // When at 0, an element can not sink any further.
  311. while (n > 0) {
  312. // Compute the parent element's index, and fetch it.
  313. var parentN = ((n + 1) >> 1) - 1;
  314. var parent = this.content[parentN];
  315. // Swap the elements if the parent is greater.
  316. if (this.scoreFunction(element) < this.scoreFunction(parent)) {
  317. this.content[parentN] = element;
  318. this.content[n] = parent;
  319. // Update 'n' to continue at the new position.
  320. n = parentN;
  321. }
  322. // Found a parent that is less, no need to sink any further.
  323. else {
  324. break;
  325. }
  326. }
  327. },
  328. bubbleUp: function(n) {
  329. // Look up the target element and its score.
  330. var length = this.content.length;
  331. var element = this.content[n];
  332. var elemScore = this.scoreFunction(element);
  333. while (true) {
  334. // Compute the indices of the child elements.
  335. var child2N = (n + 1) << 1;
  336. var child1N = child2N - 1;
  337. // This is used to store the new position of the element, if any.
  338. var swap = null;
  339. var child1Score;
  340. // If the first child exists (is inside the array)...
  341. if (child1N < length) {
  342. // Look it up and compute its score.
  343. var child1 = this.content[child1N];
  344. child1Score = this.scoreFunction(child1);
  345. // If the score is less than our element's, we need to swap.
  346. if (child1Score < elemScore) {
  347. swap = child1N;
  348. }
  349. }
  350. // Do the same checks for the other child.
  351. if (child2N < length) {
  352. var child2 = this.content[child2N];
  353. var child2Score = this.scoreFunction(child2);
  354. if (child2Score < (swap == null ? elemScore : child1Score)) {
  355. swap = child2N;
  356. }
  357. }
  358. // If the element needs to be moved, swap it, and continue.
  359. if (swap != null) {
  360. this.content[n] = this.content[swap];
  361. this.content[swap] = element;
  362. n = swap;
  363. }
  364. // Otherwise, we are done.
  365. else {
  366. break;
  367. }
  368. }
  369. }
  370. };
  371. return {
  372. astar: astar,
  373. Graph: Graph,
  374. gridNode: GridNode
  375. };
  376. });