1 | var BinaryHeap = require("./binary-heap");
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 | function AStar(isWalkable) {
|
10 | this.destX = 0;
|
11 | this.destY = 0;
|
12 | this.scaleX = 1;
|
13 | this.scaleY = 1;
|
14 | this.openNodes = {};
|
15 | this.closedNodes = {};
|
16 | this.openHeap = new BinaryHeap(function(a, b) {
|
17 | return a.f - b.f;
|
18 | });
|
19 | this.isWalkable = isWalkable;
|
20 | }
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 | AStar.prototype.heuristic = function(x, y) {
|
27 |
|
28 | var dx = Math.abs(x - this.destX) / this.scaleX;
|
29 | var dy = Math.abs(y - this.destY) / this.scaleY;
|
30 | return dx + dy;
|
31 | };
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 | AStar.prototype.makeNode = function(x, y, parent, g) {
|
41 | g += parent.g;
|
42 | var h = this.heuristic(x, y);
|
43 |
|
44 | return {
|
45 | x: x,
|
46 | y: y,
|
47 | parent: parent,
|
48 | f: g + h,
|
49 | g: parent.g + g,
|
50 | h: h
|
51 | };
|
52 | };
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 | AStar.prototype.updateOpenNode = function(key, parent, g) {
|
61 | var node = this.openNodes[key];
|
62 | if (!node) {
|
63 | return false;
|
64 | }
|
65 |
|
66 | var newG = parent.g + g;
|
67 |
|
68 | if (newG >= node.g) {
|
69 | return true;
|
70 | }
|
71 |
|
72 | node.parent = parent;
|
73 | node.g = newG;
|
74 | node.f = node.g + node.h;
|
75 |
|
76 | var pos = this.openHeap.indexOf(node);
|
77 | this.openHeap.bubbleUp(pos);
|
78 |
|
79 | return true;
|
80 | };
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 | AStar.prototype.insertNeighbor = function(key, x, y, parent, g) {
|
91 | var node = this.makeNode(x, y, parent, g);
|
92 | this.openNodes[key] = node;
|
93 | this.openHeap.insert(node);
|
94 | };
|
95 | AStar.prototype.tryNeighbor = function(x, y, parent, g) {
|
96 | var key = makeKey(x, y);
|
97 | if (this.closedNodes[key]) {
|
98 | return;
|
99 | }
|
100 | if (!this.isWalkable(x, y)) {
|
101 | return;
|
102 | }
|
103 | if (!this.updateOpenNode(key, parent, g)) {
|
104 | this.insertNeighbor(key, x, y, parent, g);
|
105 | }
|
106 | };
|
107 | AStar.prototype.getNeighbors = function getNeighbors(parent) {
|
108 | var diagonalCost = 1.4;
|
109 | var straightCost = 1;
|
110 | this.tryNeighbor(parent.x - this.scaleX, parent.y - this.scaleY, parent, diagonalCost);
|
111 | this.tryNeighbor(parent.x, parent.y - this.scaleY, parent, straightCost);
|
112 | this.tryNeighbor(parent.x + this.scaleX, parent.y - this.scaleY, parent, diagonalCost);
|
113 |
|
114 | this.tryNeighbor(parent.x - this.scaleX, parent.y, parent, straightCost);
|
115 | this.tryNeighbor(parent.x + this.scaleX, parent.y, parent, straightCost);
|
116 |
|
117 | this.tryNeighbor(parent.x - this.scaleX, parent.y + this.scaleY, parent, diagonalCost);
|
118 | this.tryNeighbor(parent.x, parent.y + this.scaleY, parent, straightCost);
|
119 | this.tryNeighbor(parent.x + this.scaleX, parent.y + this.scaleY, parent, diagonalCost);
|
120 | };
|
121 |
|
122 | function generatePath(node) {
|
123 | var path = [];
|
124 | while (node.parent) {
|
125 | var ix = node.x;
|
126 | var iy = node.y;
|
127 | while (ix !== node.parent.x || iy !== node.parent.y) {
|
128 | path.unshift({ x: ix, y: iy });
|
129 |
|
130 | var dx = node.parent.x - ix;
|
131 | if (dx > 0) {
|
132 | ix++;
|
133 | } else if (dx < 0) {
|
134 | ix--;
|
135 | }
|
136 | var dy = node.parent.y - iy;
|
137 | if (dy > 0) {
|
138 | iy++;
|
139 | } else if (dy < 0) {
|
140 | iy--;
|
141 | }
|
142 | }
|
143 | node = node.parent;
|
144 | }
|
145 | return path;
|
146 | }
|
147 |
|
148 | function makeKey(x, y) {
|
149 | return x + "," + y;
|
150 | }
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 | AStar.prototype.search = function aStar(srcX, srcY, destX, destY) {
|
161 | function scale(c, s) {
|
162 | var downscaled = Math.floor(c / s);
|
163 | return downscaled * s;
|
164 | }
|
165 | srcX = scale(srcX, this.scaleX);
|
166 | srcY = scale(srcY, this.scaleY);
|
167 | this.destX = scale(destX, this.scaleX);
|
168 | this.destY = scale(destY, this.scaleY);
|
169 |
|
170 | if (!this.isWalkable(this.destX, this.destY)) {
|
171 | return [];
|
172 | }
|
173 |
|
174 | var srcKey = makeKey(srcX, srcY);
|
175 | var srcNode = {
|
176 | x: srcX,
|
177 | y: srcY,
|
178 | g: 0,
|
179 | h: this.heuristic(srcX, srcY)
|
180 | };
|
181 | srcNode.f = srcNode.h;
|
182 | this.openNodes = {};
|
183 | this.openNodes[srcKey] = srcNode;
|
184 | this.openHeap = new BinaryHeap(function(a, b) {
|
185 | return a.f - b.f;
|
186 | });
|
187 | this.openHeap.insert(srcNode);
|
188 | this.closedNodes = {};
|
189 |
|
190 | var node = this.openHeap.deleteRoot();
|
191 | while (node) {
|
192 | var key = makeKey(node.x, node.y);
|
193 | delete this.openNodes[key];
|
194 | this.closedNodes[key] = node;
|
195 | if (node.x === this.destX && node.y === this.destY) {
|
196 | return generatePath(node);
|
197 | }
|
198 | this.getNeighbors(node);
|
199 | node = this.openHeap.deleteRoot();
|
200 | }
|
201 | return [];
|
202 | };
|
203 |
|
204 | module.exports = AStar;
|