Skip to content

Commit 01c86e2

Browse files
committed
refactor #21 to include getCost(), make a separate graph type for cities, and document the new search interface
1 parent 526acd4 commit 01c86e2

File tree

3 files changed

+114
-105
lines changed

3 files changed

+114
-105
lines changed

README.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,29 @@ If you want just the A* search code (not the demo visualization), use code like
1515
[0,1,1,0],
1616
[0,0,1,1]
1717
]);
18-
var start = graph.nodes[0][0];
19-
var end = graph.nodes[1][2];
20-
var result = astar.search(graph.nodes, start, end);
18+
var start = graph.grid[0][0];
19+
var end = graph.grid[1][2];
20+
var result = astar.search(graph, start, end);
2121
// result is an array containing the shortest path
2222

23-
var resultWithDiagonals = astar.search(graph.nodes, start, end, true);
24-
// result now searches diagonal neighbors as well
23+
var graphDiagonal = new Graph([
24+
[1,1,1,1],
25+
[0,1,1,0],
26+
[0,0,1,1]
27+
], { diagonal: true });
28+
var start = graphDiagonal.grid[0][0];
29+
var end = graphDiagonal.grid[1][2];
30+
var resultWithDiagonals = astar.search(graphDiagonal, start, end);
2531

2632
// Weight can easily be added by increasing the values within the graph, and where 0 is infinite (a wall)
2733
var graphWithWeight = new Graph([
2834
[1,1,2,30],
2935
[0,4,1.3,0],
3036
[0,0,5,1]
3137
]);
32-
var startWithWeight = graphWithWeight.nodes[0][0];
33-
var endWithWeight = graphWithWeight.nodes[1][2];
34-
var resultWithWeight = astar.search(graphWithWeight.nodes, startWithWeight, endWithWeight);
38+
var startWithWeight = graphWithWeight.grid[0][0];
39+
var endWithWeight = graphWithWeight.grid[1][2];
40+
var resultWithWeight = astar.search(graphWithWeight, startWithWeight, endWithWeight);
3541

3642
// resultWithWeight is an array containing the shortest path taking into account the weight of a node
3743
</script>

astar.js

Lines changed: 53 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ var astar = {
2424
node.f = 0;
2525
node.g = 0;
2626
node.h = 0;
27-
node.cost = node.type;
2827
node.visited = false;
2928
node.closed = false;
3029
node.parent = null;
@@ -96,7 +95,7 @@ var astar = {
9695

9796
// The g score is the shortest distance from start to current node.
9897
// We need to check if the path we have arrived at this neighbor is the shortest one we have seen yet.
99-
var gScore = currentNode.g + neighbor.cost;
98+
var gScore = currentNode.g + neighbor.getCost(currentNode);
10099
var beenVisited = neighbor.visited;
101100

102101
if(!beenVisited || gScore < neighbor.g) {
@@ -116,8 +115,6 @@ var astar = {
116115
}
117116
}
118117

119-
120-
121118
if (!beenVisited) {
122119
// Pushing to heap will put it in proper place based on the 'f' value.
123120
openHeap.push(neighbor);
@@ -157,84 +154,70 @@ var astar = {
157154
/**
158155
* A graph memory structure
159156
* @param {Array} [gridIn] Facultative grid of input weights
160-
* @param {bool} [diagonal] Specify whether diagonal moves are allowed
157+
* @param {bool} options
158+
* [diagonal] Specify whether diagonal moves are allowed
161159
*/
162-
function Graph(gridIn, diagonal) {
160+
function Graph(gridIn, options) {
161+
options = options || {};
163162
this.nodes = [];
164-
this.diagonal = !!diagonal; // Optionally find diagonal neighbors as well (false by default).
165-
166-
if (gridIn) {
167-
this.grid = [];
168-
for (var x = 0; x < gridIn.length; x++) {
169-
this.grid[x] = [];
170-
171-
for (var y = 0, row = gridIn[x]; y < row.length; y++) {
172-
var node = new GridNode(x, y, row[y]);
173-
this.grid[x][y] = node;
174-
this.nodes.push(node);
175-
}
163+
this.diagonal = !!options.diagonal;
164+
this.grid = [];
165+
for (var x = 0; x < gridIn.length; x++) {
166+
this.grid[x] = [];
167+
168+
for (var y = 0, row = gridIn[x]; y < row.length; y++) {
169+
var node = new GridNode(x, y, row[y]);
170+
this.grid[x][y] = node;
171+
this.nodes.push(node);
176172
}
177173
}
178174
}
179-
180-
Graph.prototype.add = function(node) {
181-
if (this.nodes.indexOf(node) == -1) {
182-
this.nodes.push(node);
183-
}
184-
};
185-
186175
Graph.prototype.neighbors = function(node) {
187176
var ret = [],
188177
x = node.x,
189-
y = node.y;
190-
191-
if (this.grid) {
192-
var grid = this.grid;
193-
194-
// West
195-
if(grid[x-1] && grid[x-1][y]) {
196-
ret.push(grid[x-1][y]);
197-
}
178+
y = node.y,
179+
grid = this.grid;
198180

199-
// East
200-
if(grid[x+1] && grid[x+1][y]) {
201-
ret.push(grid[x+1][y]);
202-
}
181+
// West
182+
if(grid[x-1] && grid[x-1][y]) {
183+
ret.push(grid[x-1][y]);
184+
}
203185

204-
// South
205-
if(grid[x] && grid[x][y-1]) {
206-
ret.push(grid[x][y-1]);
207-
}
186+
// East
187+
if(grid[x+1] && grid[x+1][y]) {
188+
ret.push(grid[x+1][y]);
189+
}
208190

209-
// North
210-
if(grid[x] && grid[x][y+1]) {
211-
ret.push(grid[x][y+1]);
212-
}
191+
// South
192+
if(grid[x] && grid[x][y-1]) {
193+
ret.push(grid[x][y-1]);
194+
}
213195

214-
if (this.diagonal) {
215-
// Southwest
216-
if(grid[x-1] && grid[x-1][y-1]) {
217-
ret.push(grid[x-1][y-1]);
218-
}
196+
// North
197+
if(grid[x] && grid[x][y+1]) {
198+
ret.push(grid[x][y+1]);
199+
}
219200

220-
// Southeast
221-
if(grid[x+1] && grid[x+1][y-1]) {
222-
ret.push(grid[x+1][y-1]);
223-
}
201+
if (this.diagonal) {
202+
// Southwest
203+
if(grid[x-1] && grid[x-1][y-1]) {
204+
ret.push(grid[x-1][y-1]);
205+
}
224206

225-
// Northwest
226-
if(grid[x-1] && grid[x-1][y+1]) {
227-
ret.push(grid[x-1][y+1]);
228-
}
207+
// Southeast
208+
if(grid[x+1] && grid[x+1][y-1]) {
209+
ret.push(grid[x+1][y-1]);
210+
}
229211

230-
// Northeast
231-
if(grid[x+1] && grid[x+1][y+1]) {
232-
ret.push(grid[x+1][y+1]);
233-
}
212+
// Northwest
213+
if(grid[x-1] && grid[x-1][y+1]) {
214+
ret.push(grid[x-1][y+1]);
215+
}
216+
217+
// Northeast
218+
if(grid[x+1] && grid[x+1][y+1]) {
219+
ret.push(grid[x+1][y+1]);
234220
}
235-
}
236-
else {
237-
// Your neighbors implementation!
238221
}
239222

240223
return ret;
@@ -265,6 +248,10 @@ GridNode.prototype.toString = function() {
265248
return "[" + this.x + " " + this.y + "]";
266249
};
267250

251+
GridNode.prototype.getCost = function() {
252+
return this.type;
253+
};
254+
268255
GridNode.prototype.isWall = function() {
269256
return this.type === 0;
270257
};

test/tests.js

Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ test( "Pathfinding", function() {
5252
equal (result1.text, "(0,1)(1,1)(1,2)(2,2)(2,3)", "Result is expected");
5353
});
5454

55+
test( "Diagonal Pathfinding", function() {
56+
var result1 = runSearch(new Graph([
57+
[1,1,1,1],
58+
[0,1,1,0],
59+
[0,0,1,1]
60+
], { diagonal: true}), [0,0], [2,3]);
61+
62+
equal (result1.text, "(1,1)(2,2)(2,3)", "Result is expected");
63+
});
64+
5565
test( "Pathfinding to closest", function() {
5666
var result1 = runSearch([
5767
[1,1,1,1],
@@ -78,8 +88,10 @@ test( "Pathfinding to closest", function() {
7888
equal (result3.text, "(0,1)(1,1)(2,1)", "Result is expected - target node was reachable");
7989
});
8090

81-
function runSearch(grid, start, end, options) {
82-
var graph = new Graph(grid);
91+
function runSearch(graph, start, end, options) {
92+
if (!(graph instanceof Graph)) {
93+
graph = new Graph(graph);
94+
}
8395
var start = graph.grid[start[0]][start[1]];
8496
var end = graph.grid[end[0]][end[1]];
8597
var sTime = new Date();
@@ -118,6 +130,35 @@ test( "GPS Pathfinding", function() {
118130
"Reims": ["Paris"]
119131
};
120132

133+
function CityGraph(data, links) {
134+
this.nodes = [];
135+
var cities = this.cities = {};
136+
for (var i = 0; i < data.length; ++i) {
137+
var city = data[i],
138+
obj = new CityNode(city.name, city.lat, city.lng);
139+
140+
if (this.nodes.indexOf(obj) == -1) {
141+
this.nodes.push(obj);
142+
}
143+
144+
cities[obj.name] = obj;
145+
}
146+
147+
this.cities = cities;
148+
this.links = links;
149+
}
150+
151+
CityGraph.prototype.neighbors = function (node) {
152+
var neighbors = [],
153+
ids = this.links[node.name];
154+
for (var i = 0, len = ids.length; i < len; ++i) {
155+
var name = ids[i],
156+
neighbor = this.cities[name];
157+
neighbors.push(neighbor);
158+
}
159+
return neighbors;
160+
};
161+
121162
function CityNode(name, lat, lng) {
122163
this.name = name;
123164
this.lat = lat;
@@ -140,41 +181,16 @@ test( "GPS Pathfinding", function() {
140181
return res;
141182
};
142183
// Real cost function
143-
CityNode.prototype.Real_distance = function(city) {
184+
CityNode.prototype.getCost = function(city) {
144185
// Re-use heuristic function for now
145186
// TODO: Determine the real distance between cities (from another data set)
146187
return this.GPS_distance(city);
147188
};
148189

149-
//---
150-
151-
var graph = new Graph(),
152-
cities = {};
153-
for (var i = 0; i < data.length; ++i) {
154-
var city = data[i],
155-
obj = new CityNode(city.name, city.lat, city.lng);
156-
graph.add(obj);
157-
cities[obj.name] = obj;
158-
}
159-
160-
graph.cities = cities;
161-
graph.links = links;
162-
163-
// Override neighbors function for this specific graph
164-
graph.neighbors = function (node) {
165-
var neighbors = [],
166-
ids = this.links[node.name];
167-
for (var i = 0, len = ids.length; i < len; ++i) {
168-
var name = ids[i],
169-
neighbor = this.cities[name];
170-
neighbor.cost = node.Real_distance(neighbor); // Compute real cost!
171-
neighbors.push(neighbor);
172-
}
173-
return neighbors;
174-
};
190+
var graph = new CityGraph(data, links);
175191

176-
var start = cities["Paris"],
177-
end = cities["Cannes"];
192+
var start = graph.cities["Paris"],
193+
end = graph.cities["Cannes"];
178194

179195
var GPSheuristic = function(node0, node1) {
180196
return node0.GPS_distance(node1);

0 commit comments

Comments
 (0)