diff --git a/astar.js b/astar.js index ece7d08..90dc740 100644 --- a/astar.js +++ b/astar.js @@ -56,13 +56,11 @@ var astar = { astar.init(graph); options = options || {}; - var heuristic = options.heuristic || astar.heuristics.manhattan; - var closest = options.closest || false; + var heuristic = options.heuristic || astar.heuristics.manhattan, + closest = options.closest || false; - var openHeap = astar.heap(); - - // set the start node to be the closest if required - var closestNode = start; + var openHeap = astar.heap(), + closestNode = start; // set the start node to be the closest if required start.h = heuristic(start, end); @@ -84,7 +82,7 @@ var astar = { // Find all neighbors for the current node. var neighbors = graph.neighbors(currentNode); - for(var i=0, il = neighbors.length; i < il; i++) { + for(var i = 0, il = neighbors.length; i < il; ++i) { var neighbor = neighbors[i]; if(neighbor.closed || neighbor.isWall()) { @@ -94,8 +92,8 @@ var astar = { // The g score is the shortest distance from start to current node. // We need to check if the path we have arrived at this neighbor is the shortest one we have seen yet. - var gScore = currentNode.g + neighbor.getCost(currentNode); - var beenVisited = neighbor.visited; + var gScore = currentNode.g + neighbor.getCost(currentNode), + beenVisited = neighbor.visited; if(!beenVisited || gScore < neighbor.g) { @@ -136,15 +134,15 @@ var astar = { // See list of heuristics: http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html heuristics: { manhattan: function(pos0, pos1) { - var d1 = Math.abs (pos1.x - pos0.x); - var d2 = Math.abs (pos1.y - pos0.y); + var d1 = Math.abs(pos1.x - pos0.x); + var d2 = Math.abs(pos1.y - pos0.y); return d1 + d2; }, diagonal: function(pos0, pos1) { var D = 1; var D2 = Math.sqrt(2); - var d1 = Math.abs (pos1.x - pos0.x); - var d2 = Math.abs (pos1.y - pos0.y); + var d1 = Math.abs(pos1.x - pos0.x); + var d2 = Math.abs(pos1.y - pos0.y); return (D * (d1 + d2)) + ((D2 - (2 * D)) * Math.min(d1, d2)); } } @@ -322,7 +320,6 @@ BinaryHeap.prototype = { // Update 'n' to continue at the new position. n = parentN; } - // Found a parent that is less, no need to sink any further. else { break; @@ -337,11 +334,11 @@ BinaryHeap.prototype = { while(true) { // Compute the indices of the child elements. - var child2N = (n + 1) << 1, child1N = child2N - 1; - // This is used to store the new position of the element, - // if any. - var swap = null; - var child1Score; + var child2N = (n + 1) << 1, + child1N = child2N - 1; + // This is used to store the new position of the element, if any. + var swap = null, + child1Score; // If the first child exists (is inside the array)... if (child1N < length) { // Look it up and compute its score. @@ -369,7 +366,6 @@ BinaryHeap.prototype = { this.content[swap] = element; n = swap; } - // Otherwise, we are done. else { break; diff --git a/benchmark/benchmark.js b/benchmark/benchmark.js index 8daf24d..5bb1359 100644 --- a/benchmark/benchmark.js +++ b/benchmark/benchmark.js @@ -6,18 +6,18 @@ $(function() { } running = true; - var graph = new Graph(grid); - var start = graph.grid[0][0]; - var end = graph.grid[140][140]; - var results = []; - var times = 0; - + var graph = new Graph(grid), + start = graph.grid[0][0], + end = graph.grid[140][140], + results = [], + times = 0; + for (var i = 0; i < 1000; i++) { - var startTime = performance ? performance.now() : new Date().getTime(); - var result = astar.search(graph, start, end); - var endTime = performance ? performance.now() : new Date().getTime(); + var startTime = performance ? performance.now() : new Date().getTime(), + result = astar.search(graph, start, end), + endTime = performance ? performance.now() : new Date().getTime(); times = times + (endTime - startTime); - + results.push( '
  • Found path with ' + result.length + ' steps. ' + 'Took ' + (endTime - startTime).toFixed(2) + ' milliseconds.
  • ' diff --git a/demo/demo.js b/demo/demo.js index bf0df4a..968639c 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -1,52 +1,47 @@ -/* demo.js http://github.com/bgrins/javascript-astar - MIT License +/* demo.js http://github.com/bgrins/javascript-astar + MIT License - Set up the demo page for the A* Search + Set up the demo page for the A* Search */ window.log = function(){ - if(this.console){ - console.log( Array.prototype.slice.call(arguments) ); - } + if(this.console){ + console.log( Array.prototype.slice.call(arguments) ); + } }; -var WALL = 0; -var OPEN = 1; +var WALL = 0, + OPEN = 1; var generateRandom = function (width, height, wallFrequency) { - - var nodes = []; - - for (var x=0; x < width; x++) { - var nodeRow = []; - var gridRow = []; - - for(var y=0; y < height; y++) { - - var isWall = Math.floor(Math.random()*(1/wallFrequency)); - if(isWall == 0) { - nodeRow.push(WALL); - } - else { - nodeRow.push(OPEN); - } - } - nodes.push(nodeRow); + var nodes = []; + for (var x = 0; x < width; x++) { + var nodeRow = [], + gridRow = []; + + for (var y = 0; y < height; y++) { + var isWall = Math.floor(Math.random()*(1/wallFrequency)); + if (isWall === 0) { + nodeRow.push(WALL); + } + else { + nodeRow.push(OPEN); + } + } + nodes.push(nodeRow); } - return new Graph(nodes); }; $(function() { - var $grid = $("#search_grid"); - var $selectWallFrequency = $("#selectWallFrequency"); - var $selectGridSize = $("#selectGridSize"); - var $checkDebug = $("#checkDebug"); - var $searchDiagonal = $("#searchDiagonal"); - var $checkClosest = $("#checkClosest"); - + var $grid = $("#search_grid"), + $selectWallFrequency = $("#selectWallFrequency"), + $selectGridSize = $("#selectGridSize"), + $checkDebug = $("#checkDebug"), + $searchDiagonal = $("#searchDiagonal"), + $checkClosest = $("#checkClosest"); var opts = { wallFrequency: $selectWallFrequency.val(), @@ -59,7 +54,7 @@ $(function() { var grid = new GraphSearch($grid, opts, astar.search); $("#btnGenerate").click(function() { - grid.initialize(); + grid.initialize(); }); $selectWallFrequency.change(function() { @@ -101,119 +96,117 @@ var css = { start: "start", finish: "finish", wall: "wall", active: "active" }; function GraphSearch($graph, options, implementation) { this.$graph = $graph; this.search = implementation; - this.opts = $.extend({wallFrequency:.1, debug:true, gridSize:10}, options); + this.opts = $.extend({wallFrequency:0.1, debug:true, gridSize:10}, options); this.initialize(); } GraphSearch.prototype.setOption = function(opt) { this.opts = $.extend(this.opts, opt); - if(opt["debug"]||opt["debug"]==false) { - this.drawDebugInfo(opt["debug"]); - } + this.drawDebugInfo(); }; GraphSearch.prototype.initialize = function() { - - var self = this; - this.grid = []; - var nodes = []; - var $graph = this.$graph; - - $graph.empty(); - - var cellWidth = ($graph.width()/this.opts.gridSize)-2; // -2 for border - var cellHeight = ($graph.height()/this.opts.gridSize)-2; - var $cellTemplate = $("").addClass("grid_item").width(cellWidth).height(cellHeight); - var startSet = false; - - for(var x=0;x"); - - var nodeRow = []; - var gridRow = []; - - for(var y=0;y").addClass("grid_item").width(cellWidth).height(cellHeight), + startSet = false; + + for(var x = 0; x < this.opts.gridSize; x++) { + var $row = $("
    "), + nodeRow = [], + gridRow = []; + + for(var y = 0; y < this.opts.gridSize; y++) { + var id = "cell_"+x+"_"+y, + $cell = $cellTemplate.clone(); + $cell.attr("id", id).attr("x", x).attr("y", y); + $row.append($cell); + gridRow.push($cell); + + var isWall = Math.floor(Math.random()*(1/self.opts.wallFrequency)); + if(isWall === 0) { + nodeRow.push(WALL); + $cell.addClass(css.wall); + } + else { var cell_weight = ($("#generateWeights").prop("checked") ? (Math.floor(Math.random() * 3)) * 2 + 1 : 1); - nodeRow.push(cell_weight); - $cell.addClass('weight' + cell_weight); - if ($("#displayWeights").prop("checked")) {$cell.html(cell_weight)}; - if (!startSet) { - $cell.addClass(css.start); - startSet = true; - } - } - } - $graph.append($row); - - this.grid.push(gridRow); - nodes.push(nodeRow); + nodeRow.push(cell_weight); + $cell.addClass('weight' + cell_weight); + if ($("#displayWeights").prop("checked")) { + $cell.html(cell_weight); + } + if (!startSet) { + $cell.addClass(css.start); + startSet = true; + } + } + } + $graph.append($row); + + this.grid.push(gridRow); + nodes.push(nodeRow); } this.graph = new Graph(nodes); // bind cell event, set start/wall positions this.$cells = $graph.find(".grid_item"); - this.$cells.click(function() { self.cellClicked($(this)) }); + this.$cells.click(function() { + self.cellClicked($(this)); + }); }; GraphSearch.prototype.cellClicked = function($end) { var end = this.nodeFromElement($end); - if($end.hasClass(css.wall) || $end.hasClass(css.start)) { - log("clicked on wall or start...", $end); - return; - } + if($end.hasClass(css.wall) || $end.hasClass(css.start)) { + log("clicked on wall or start...", $end); + return; + } - this.$cells.removeClass(css.finish); - $end.addClass("finish"); - var $start = this.$cells.filter("." + css.start); - var start = this.nodeFromElement($start); + this.$cells.removeClass(css.finish); + $end.addClass("finish"); + var $start = this.$cells.filter("." + css.start), + start = this.nodeFromElement($start); + + var sTime = performance ? performance.now() : new Date().getTime(); - var sTime = performance ? performance.now() : new Date().getTime(); var path = this.search(this.graph, start, end, { closest: this.opts.closest }); - var fTime = performance ? performance.now() : new Date().getTime(); - var duration = (fTime-sTime).toFixed(2); - - if(!path || path.length == 0) { - $("#message").text("couldn't find a path (" + duration + "ms)"); - this.animateNoPath(); - } - else { - $("#message").text("search took " + duration + "ms."); - if(this.opts.debug) { - this.drawDebugInfo(this.opts.debug); - } - this.animatePath(path); - } + var fTime = performance ? performance.now() : new Date().getTime(), + duration = (fTime-sTime).toFixed(2); + + if(path.length === 0) { + $("#message").text("couldn't find a path (" + duration + "ms)"); + this.animateNoPath(); + } + else { + $("#message").text("search took " + duration + "ms."); + this.drawDebugInfo(); + this.animatePath(path); + } }; -GraphSearch.prototype.drawDebugInfo = function(show) { +GraphSearch.prototype.drawDebugInfo = function() { this.$cells.html(" "); var that = this; - if(show) { - that.$cells.each(function(i) { - var node = that.nodeFromElement($(this)); - var debug = false; + if(this.opts.debug) { + that.$cells.each(function(i) { + var node = that.nodeFromElement($(this)), + debug = false; if (node.visited) { debug = "F: " + node.f + "
    G: " + node.g + "
    H: " + node.h; } - if (debug) { - $(this).html(debug); - } - }); - + if (debug) { + $(this).html(debug); + } + }); } }; GraphSearch.prototype.nodeFromElement = function($cell) { @@ -222,47 +215,51 @@ GraphSearch.prototype.nodeFromElement = function($cell) { GraphSearch.prototype.animateNoPath = function() { var $graph = this.$graph; var jiggle = function(lim, i) { - if(i>=lim) { $graph.css("top", 0).css("left", 0); return; } - if(!i) i=0; - i++; - $graph.css("top", Math.random()*6).css("left", Math.random()*6); - setTimeout( function() { jiggle(lim, i) }, 5 ); + if(i>=lim) { $graph.css("top", 0).css("left", 0); return; } + if(!i) i=0; + i++; + $graph.css("top", Math.random()*6).css("left", Math.random()*6); + setTimeout(function() { + jiggle(lim, i); + }, 5); }; jiggle(15); }; GraphSearch.prototype.animatePath = function(path) { - var grid = this.grid; - var timeout = 1000 / grid.length; - var elementFromNode = function(node) { - return grid[node.x][node.y]; - }; + var grid = this.grid, + timeout = 1000 / grid.length, + elementFromNode = function(node) { + return grid[node.x][node.y]; + }; var self = this; // will add start class if final var removeClass = function(path, i) { - if(i>=path.length){ // finished removing path, set start positions + if(i >= path.length) { // finished removing path, set start positions return setStartClass(path, i); } elementFromNode(path[i]).removeClass(css.active); - setTimeout( function() { removeClass(path, i+1) }, timeout*path[i].cost); - } - var setStartClass = function(path, i){ - if(i === path.length){ + setTimeout(function() { + removeClass(path, i+1); + }, timeout*path[i].getCost()); + }; + var setStartClass = function(path, i) { + if(i === path.length) { self.$graph.find("." + css.start).removeClass(css.start); elementFromNode(path[i-1]).addClass(css.start); } - } - var addClass = function(path, i) { - if(i>=path.length) { // Finished showing path, now remove - return removeClass(path, 0); - } - elementFromNode(path[i]).addClass(css.active); - setTimeout( function() { addClass(path, i+1) }, timeout*path[i].cost); + }; + var addClass = function(path, i) { + if(i >= path.length) { // Finished showing path, now remove + return removeClass(path, 0); + } + elementFromNode(path[i]).addClass(css.active); + setTimeout(function() { + addClass(path, i+1); + }, timeout*path[i].getCost()); }; - addClass(path, 0) + addClass(path, 0); this.$graph.find("." + css.start).removeClass(css.start); this.$graph.find("." + css.finish).removeClass(css.finish).addClass(css.start); }; - - diff --git a/test/tests.js b/test/tests.js index ab2d822..0e3a0ba 100644 --- a/test/tests.js +++ b/test/tests.js @@ -92,11 +92,11 @@ function runSearch(graph, start, end, options) { if (!(graph instanceof Graph)) { graph = new Graph(graph); } - var start = graph.grid[start[0]][start[1]]; - var end = graph.grid[end[0]][end[1]]; - var sTime = new Date(); - var result = astar.search(graph, start, end, options); - var eTime = new Date(); + start = graph.grid[start[0]][start[1]]; + end = graph.grid[end[0]][end[1]]; + var sTime = new Date(), + result = astar.search(graph, start, end, options), + eTime = new Date(); return { result: result, text: pathToString(result), @@ -132,7 +132,9 @@ test( "GPS Pathfinding", function() { function CityGraph(data, links) { this.nodes = []; - var cities = this.cities = {}; + this.links = links; + this.cities = {}; + for (var i = 0; i < data.length; ++i) { var city = data[i], obj = new CityNode(city.name, city.lat, city.lng); @@ -141,11 +143,8 @@ test( "GPS Pathfinding", function() { this.nodes.push(obj); } - cities[obj.name] = obj; + this.cities[obj.name] = obj; } - - this.cities = cities; - this.links = links; } CityGraph.prototype.neighbors = function (node) {