Skip to content

Commit ec6f0bb

Browse files
author
MannyC
committed
Closest implementation, with relevant update to demo page and initial test.
1 parent cbbd1de commit ec6f0bb

File tree

5 files changed

+82
-17
lines changed

5 files changed

+82
-17
lines changed

astar.js

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ var astar = {
4343
// {
4444
// heuristic: heuristic function to use
4545
// diagonal: boolean specifying whether diagonal moves are allowed
46+
// closest: boolean specifying whether to return closest node if
47+
// target is unreachable
48+
// closestHeuristic: heuristic function to use to determine closeness.
49+
// Will use main heuristic if not specified.
4650
// }
4751
search: function(grid, start, end, options) {
4852
astar.init(grid);
@@ -52,9 +56,28 @@ var astar = {
5256
var diagonal = !!options.diagonal;
5357
var costDiagonal = options.costDiagonal || 1;
5458
var costStraight = options.costStraight || 1;
59+
var closest = options.closest || false;
60+
var closestHeuristic = options.closestHeuristic || heuristic;
5561

5662
var openHeap = astar.heap();
5763

64+
// set the start node to be the closest if required
65+
var closestNode = start;
66+
if(closest){
67+
start.c = closestHeuristic(start.pos, end.pos);
68+
}
69+
70+
function pathTo( node ){
71+
var curr = node;
72+
var path = [];
73+
while(curr.parent) {
74+
path.push(curr);
75+
curr = curr.parent;
76+
}
77+
return path.reverse();
78+
}
79+
80+
5881
openHeap.push(start);
5982

6083
while(openHeap.size() > 0) {
@@ -64,13 +87,7 @@ var astar = {
6487

6588
// End case -- result has been found, return the traced path.
6689
if(currentNode === end) {
67-
var curr = currentNode;
68-
var ret = [];
69-
while(curr.parent) {
70-
ret.push(curr);
71-
curr = curr.parent;
72-
}
73-
return ret.reverse();
90+
return pathTo( currentNode );
7491
}
7592

7693
// Normal case -- move currentNode from open to closed, process each of its neighbors.
@@ -101,6 +118,19 @@ var astar = {
101118
neighbor.g = gScore;
102119
neighbor.f = neighbor.g + neighbor.h;
103120

121+
if( closest ){
122+
neighbor.c =
123+
(closestHeuristic === heuristic) ? neighbor.h : closestHeuristic(neighbor.pos, end.pos);
124+
// If the neighbour is closer than the current closestNode or if it's equally close but has
125+
// a cheaper path than the current closest node then it becomes the closest node
126+
if(neighbor.c < closestNode.c || (neighbor.c === closestNode.c && neighbor.g < closestNode.g)){
127+
128+
closestNode = neighbor;
129+
}
130+
}
131+
132+
133+
104134
if (!beenVisited) {
105135
// Pushing to heap will put it in proper place based on the 'f' value.
106136
openHeap.push(neighbor);
@@ -113,6 +143,10 @@ var astar = {
113143
}
114144
}
115145

146+
if(closest){
147+
return pathTo(closestNode);
148+
}
149+
116150
// No result was found - empty array signifies failure to find path.
117151
return [];
118152
},

demo/demo.js

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* demo.js http://github.com/bgrins/javascript-astar
22
MIT License
3-
3+
44
Set up the demo page for the A* Search
55
*/
66

@@ -45,12 +45,15 @@ $(function() {
4545
var $selectGridSize = $("#selectGridSize");
4646
var $checkDebug = $("#checkDebug");
4747
var $searchDiagonal = $("#searchDiagonal");
48+
var $checkClosest = $("#checkClosest");
49+
4850

4951
var opts = {
5052
wallFrequency: $selectWallFrequency.val(),
5153
gridSize: $selectGridSize.val(),
5254
debug: $checkDebug.is("checked"),
53-
diagonal: $searchDiagonal.is("checked")
55+
diagonal: $searchDiagonal.is("checked"),
56+
closest: $checkClosest.is("checked")
5457
};
5558

5659
var grid = new GraphSearch($grid, opts, astar.search);
@@ -72,10 +75,15 @@ $(function() {
7275
$checkDebug.change(function() {
7376
grid.setOption({debug: $(this).is(":checked")});
7477
});
75-
78+
7679
$searchDiagonal.change(function() {
7780
grid.setOption({diagonal: $(this).is(":checked")});
7881
});
82+
83+
$checkClosest.change(function() {
84+
grid.setOption({closest: $(this).is(":checked")});
85+
});
86+
7987
$("#generateWeights").click( function () {
8088
if ($("#generateWeights").prop("checked")) {
8189
$('#weightsKey').slideDown();
@@ -171,7 +179,8 @@ GraphSearch.prototype.cellClicked = function($end) {
171179

172180
var sTime = new Date();
173181
var path = this.search(this.graph.nodes, start, end, {
174-
diagonal: this.opts.diagonal
182+
diagonal: this.opts.diagonal,
183+
closest: this.opts.closest
175184
});
176185
var fTime = new Date();
177186

@@ -191,7 +200,7 @@ GraphSearch.prototype.drawDebugInfo = function(show) {
191200
this.$cells.html(" ");
192201
var that = this;
193202
if(show) {
194-
that.$cells.each(function(i) {
203+
that.$cells.each(function(i) {
195204
var node = that.nodeFromElement($(this));
196205
var debug = false;
197206
if (node.visited) {
@@ -226,11 +235,21 @@ GraphSearch.prototype.animatePath = function(path) {
226235
return grid[node.x][node.y];
227236
};
228237

238+
var self = this;
239+
// will add start class if final
229240
var removeClass = function(path, i) {
230-
if(i>=path.length) return;
231-
elementFromNode(path[i]).removeClass(css.active);
241+
if(i>=path.length){ // finished removing path, set start positions
242+
return setStartClass(path, i);
243+
}
244+
elementFromNode(path[i]).removeClass(css.active);
232245
setTimeout( function() { removeClass(path, i+1) }, timeout*path[i].cost);
233246
}
247+
var setStartClass = function(path, i){
248+
if(i === path.length){
249+
self.$graph.find("." + css.start).removeClass(css.start);
250+
elementFromNode(path[i-1]).addClass(css.start);
251+
}
252+
}
234253
var addClass = function(path, i) {
235254
if(i>=path.length) { // Finished showing path, now remove
236255
return removeClass(path, 0);

demo/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ <h2>Demonstration</h2>
5353
<label for="searchDiagonal">Allow diagonal movement?</label>
5454
<input type="checkbox" name="searchDiagonal" id="searchDiagonal" />
5555
<br />
56+
<label for="checkClosest">Closest node if target unreachable?</label>
57+
<input type="checkbox" name="checkClosest" id="checkClosest" />
58+
<br />
5659
<label for="generateWeights">Add random weights?</label>
5760
<input type="checkbox" name="generateWeights" id="generateWeights" />
5861
<br />

test/index.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
<div id="qunit-fixture"></div>
1111
<script src="qunit.js"></script>
1212
<script src="tests.js"></script>
13-
<script src="../graph.js"></script>
1413
<script src="../astar.js"></script>
1514
</body>
1615
</html>

test/tests.js

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

55-
function runSearch(grid, start, end) {
55+
test( "Pathfinding to closest", function() {
56+
var result1 = runSearch([
57+
[1,1,1,1],
58+
[0,1,1,0],
59+
[0,0,1,1]
60+
], [0,0], [2,1], {closest: true});
61+
62+
equal (result1.text, "(0,1)(1,1)", "Result is expected");
63+
});
64+
65+
function runSearch(grid, start, end, options) {
5666
var graph = new Graph(grid);
5767
var start = graph.nodes[start[0]][start[1]];
5868
var end = graph.nodes[end[0]][end[1]];
5969
var sTime = new Date();
60-
var result = astar.search(graph.nodes, start, end);
70+
var result = astar.search(graph.nodes, start, end, options);
6171
var eTime = new Date();
6272
return {
6373
result: result,

0 commit comments

Comments
 (0)