Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 

435 righe
11 KiB

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