Skip to content

Loading…

Jump Point Search implementation #13

Merged
merged 1 commit into from

2 participants

@zerowidth

As soon as I saw your visualization, I wanted to see the Jump Point Search algorithm in action. So, here it is!

@qiao qiao merged commit 96d03b8 into qiao:master
@qiao
Owner

@aniero Excellent !!! I also wanted to see it in action the moment I saw its introduction, and have a partially done implementation on my disk. You are way faster than me. Cheers :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 22, 2012
  1. @zerowidth
This page is out of date. Refresh to see the latest.
Showing with 389 additions and 3 deletions.
  1. +1 −0 README.md
  2. +0 −1 TODO
  3. +1 −1 lib/pathfinding-browser.js
  4. +360 −0 src/finders/jump_point.js
  5. +2 −1 src/pathfinding.js
  6. +2 −0 test/path_test.js
  7. +15 −0 visual/index.html
  8. +8 −0 visual/panel.js
View
1 README.md
@@ -78,6 +78,7 @@ Currently there are eight path-finders bundled in this library, namely:
* `BiBestFirstFinder`
* `BiDijkstraFinder` *
* `BiBreadthFirstFinder` *
+* `JumpPointFinder` *
The suffix `Bi` for the last four finders in the above list stands for the bi-directional searching strategy.
View
1 TODO
@@ -3,7 +3,6 @@ add info icon and text at the left top corner
implement IDS
implement IDA*
implement Dynamic Weighting
-implement Jump Point Search
implement Path Smoothing
customize heuristic functions on demo page
beautify play panel
View
2 lib/pathfinding-browser.js
@@ -7,4 +7,4 @@
* |___/ |__/
* https://github.com/qiao/PathFinding.js
*/
-var PF=function(){var a=function(b,c){var d=a.resolve(b,c||"/"),e=a.modules[d];if(!e)throw new Error("Failed to resolve module "+b+", tried "+d);var f=e._cached?e._cached:e();return f};return a.paths=[],a.modules={},a.extensions=[".js",".coffee"],a._core={assert:!0,events:!0,fs:!0,path:!0,vm:!0},a.resolve=function(){return function(b,c){function h(b){if(a.modules[b])return b;for(var c=0;c<a.extensions.length;c++){var d=a.extensions[c];if(a.modules[b+d])return b+d}}function i(b){b=b.replace(/\/+$/,"");var c=b+"/package.json";if(a.modules[c]){var e=a.modules[c](),f=e.browserify;if(typeof f=="object"&&f.main){var g=h(d.resolve(b,f.main));if(g)return g}else if(typeof f=="string"){var g=h(d.resolve(b,f));if(g)return g}else if(e.main){var g=h(d.resolve(b,e.main));if(g)return g}}return h(b+"/index")}function j(a,b){var c=k(b);for(var d=0;d<c.length;d++){var e=c[d],f=h(e+"/"+a);if(f)return f;var g=i(e+"/"+a);if(g)return g}var f=h(a);if(f)return f}function k(a){var b;a==="/"?b=[""]:b=d.normalize(a).split("/");var c=[];for(var e=b.length-1;e>=0;e--){if(b[e]==="node_modules")continue;var f=b.slice(0,e+1).join("/")+"/node_modules";c.push(f)}return c}c||(c="/");if(a._core[b])return b;var d=a.modules.path(),e=c||".";if(b.match(/^(?:\.\.?\/|\/)/)){var f=h(d.resolve(e,b))||i(d.resolve(e,b));if(f)return f}var g=j(b,e);if(g)return g;throw new Error("Cannot find module '"+b+"'")}}(),a.alias=function(b,c){var d=a.modules.path(),e=null;try{e=a.resolve(b+"/package.json","/")}catch(f){e=a.resolve(b,"/")}var g=d.dirname(e),h=(Object.keys||function(a){var b=[];for(var c in a)b.push(c);return b})(a.modules);for(var i=0;i<h.length;i++){var j=h[i];if(j.slice(0,g.length+1)===g+"/"){var k=j.slice(g.length);a.modules[c+k]=a.modules[g+k]}else j===g&&(a.modules[c]=a.modules[g])}},a.define=function(b,c){var d=a._core[b]?"":a.modules.path().dirname(b),e=function(b){return a(b,d)};e.resolve=function(b){return a.resolve(b,d)},e.modules=a.modules,e.define=a.define;var f={exports:{}};a.modules[b]=function(){return a.modules[b]._cached=f.exports,c.call(f.exports,e,f,f.exports,d,b),a.modules[b]._cached=f.exports,f.exports}},typeof process=="undefined"&&(process={}),process.nextTick||(process.nextTick=function(){var a=[],b=typeof window!="undefined"&&window.postMessage&&window.addEventListener;return b&&window.addEventListener("message",function(b){if(b.source===window&&b.data==="browserify-tick"){b.stopPropagation();if(a.length>0){var c=a.shift();c()}}},!0),function(c){b?(a.push(c),window.postMessage("browserify-tick","*")):setTimeout(c,0)}}()),process.title||(process.title="browser"),process.binding||(process.binding=function(b){if(b==="evals")return a("vm");throw new Error("No such module")}),process.cwd||(process.cwd=function(){return"."}),a.define("path",function(a,b,c,d,e){function f(a,b){var c=[];for(var d=0;d<a.length;d++)b(a[d],d,a)&&c.push(a[d]);return c}function g(a,b){var c=0;for(var d=a.length;d>=0;d--){var e=a[d];e=="."?a.splice(d,1):e===".."?(a.splice(d,1),c++):c&&(a.splice(d,1),c--)}if(b)for(;c--;c)a.unshift("..");return a}var h=/^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/;c.resolve=function(){var a="",b=!1;for(var c=arguments.length;c>=-1&&!b;c--){var d=c>=0?arguments[c]:process.cwd();if(typeof d!="string"||!d)continue;a=d+"/"+a,b=d.charAt(0)==="/"}return a=g(f(a.split("/"),function(a){return!!a}),!b).join("/"),(b?"/":"")+a||"."},c.normalize=function(a){var b=a.charAt(0)==="/",c=a.slice(-1)==="/";return a=g(f(a.split("/"),function(a){return!!a}),!b).join("/"),!a&&!b&&(a="."),a&&c&&(a+="/"),(b?"/":"")+a},c.join=function(){var a=Array.prototype.slice.call(arguments,0);return c.normalize(f(a,function(a,b){return a&&typeof a=="string"}).join("/"))},c.dirname=function(a){var b=h.exec(a)[1]||"",c=!1;return b?b.length===1||c&&b.length<=3&&b.charAt(1)===":"?b:b.substring(0,b.length-1):"."},c.basename=function(a,b){var c=h.exec(a)[2]||"";return b&&c.substr(-1*b.length)===b&&(c=c.substr(0,c.length-b.length)),c},c.extname=function(a){return h.exec(a)[3]||""}}),a.define("/core/node.js",function(a,b,c,d,e){function f(a,b,c,d){this.x=a,this.y=b,this.walkable=c===undefined?!0:c,this.parent=d===undefined?null:d}f.prototype.constructor=f,f.prototype.set=function(a,b){this[a]=b},f.prototype.get=function(a){return this[a]},f.prototype.clone=function(){return new f(this.x,this.y,this.walkable,this.parent)},b.exports=f}),a.define("/core/grid.js",function(a,b,c,d,e){function g(a,b,c){this.width=a,this.height=b,this._buildGrid(c)}var f=a("./node");g.prototype._buildGrid=function(a){var b,c,d=this.width,e=this.height,g=[],h;for(b=0;b<e;++b){g.push([]),h=g[b];for(c=0;c<d;++c)h.push(new f(c,b))}this.nodes=g;if(a===undefined)return;if(a.length!=e||a[0].length!=d)throw new Error("Matrix size does not fit");for(b=0;b<e;++b)for(c=0;c<d;++c)a[b][c]&&(g[b][c].walkable=!1)},g.prototype.getNodeAt=function(a,b){return this.nodes[b][a]},g.prototype.isWalkableAt=function(a,b){return this.getNodeAt(a,b).get("walkable")},g.prototype.setWalkableAt=function(a,b,c){this.getNodeAt(a,b).set("walkable",c)},g.prototype.isInside=function(a,b){return a>=0&&a<this.width&&b>=0&&b<this.height},g.prototype.setAttributeAt=function(a,b,c,d){this.getNodeAt(a,b).set(c,d)},g.prototype.getAttributeAt=function(a,b,c){return this.getNodeAt(a,b).get(c)},g.prototype.clone=function(){var a,b,c=this.width,d=this.height,e=this.nodes,f=new g(c,d),h=[],i;for(a=0;a<d;++a){h.push([]),i=h[a];for(b=0;b<c;++b)i.push(e[a][b].clone())}return f.nodes=h,f},b.exports=g}),a.define("/core/heap.js",function(a,b,c,d,e){function f(a){this._cmp=a||function(a,b){return a<b},this._heap=[]}f.prototype.top=function(){return this._heap[0]},f.prototype.size=function(){return this._heap.length},f.prototype.isEmpty=function(){return!this.size()},f.prototype.push=function(a){this._heap.push(a),this._siftDown(0,this._heap.length-1)},f.prototype.pop=function(){var a,b,c;return a=this._heap,b=a.pop(),a.length?(c=a[0],a[0]=b,this._siftUp(0)):c=b,c},f.prototype.replace=function(a){var b=this._heap[0];return this._heap[0]=a,this._siftUp(0),b},f.prototype.pushpop=function(a){var b=this._heap,c;return b.length&&this._cmp(b[0],a)&&(c=b[0],b[0]=a,a=c,this._siftUp(0)),a},f.prototype.heapify=function(){var a,b=this._heap.length;for(a=Math.floor(b/2)-1;a>=0;--a)this._siftUp(a)},f.prototype._siftDown=function(a,b){var c,d,e,f,g;c=this._heap,d=this._cmp,e=c[b];while(b>a){f=b-1>>1,g=c[f];if(d(e,g)){c[b]=g,b=f;continue}break}c[b]=e},f.prototype._siftUp=function(a){var b,c,d,e,f,g,h;c=this._cmp,b=this._heap,d=b.length,e=a,f=b[a],g=2*a+1;while(g<d)h=g+1,h<d&&!c(b[g],b[h])&&(g=h),b[a]=b[g],a=g,g=2*a+1;b[a]=f,this._siftDown(e,a)},b.exports=f}),a.define("/core/heuristic.js",function(a,b,c,d,e){b.exports={manhattan:function(a,b){return a+b},euclidean:function(a,b){return Math.sqrt(a*a+b*b)},chebyshev:function(a,b){return Math.max(a,b)}}}),a.define("/finders/base.js",function(a,b,c,d,e){function f(a){this.startX=null,this.startY=null,this.endX=null,this.endY=null,this.grid=null,this.gridHeight=null,this.gridWidth=null,this.allowDiagonal=a&&a.allowDiagonal,this.allowDiagonal&&this._inspectSurroundDiagonal!==undefined&&(this._inspectSurround=this._inspectSurroundDiagonal)}f.prototype.isInsideGrid=function(a,b){return this.grid.isInside(a,b)},f.prototype.setWalkableAt=function(a,b,c){this.grid.setWalkableAt(a,b,c)},f.prototype.isWalkableAt=function(a,b){return this.grid.isWalkableAt(a,b)},f.prototype.setAttributeAt=function(a,b,c,d){this.grid.setAttributeAt(a,b,c,d)},f.prototype.getAttributeAt=function(a,b,c){return this.grid.getAttributeAt(a,b,c)},f.prototype.constructor=f,f.prototype.findPath=function(a,b,c,d,e){return this.startX=a,this.startY=b,this.endX=c,this.endY=d,this.grid=e,this.gridWidth=e.width,this.gridHeight=e.height,this._find()},f.prototype._constructPath=function(){var a=this.startX,b=this.startY,c,d,e=this.grid,f=[[this.endX,this.endY]];for(;;){c=f[0][0],d=f[0][1];if(c==a&&d==b)return f;f.unshift(e.getAttributeAt(c,d,"parent"))}return[]},f.prototype._find=function(){throw new Error("Sub-classes must implement this method")},f.xOffsets=[-1,0,1,0],f.yOffsets=[0,1,0,-1],f.xDiagonalOffsets=[-1,-1,1,1],f.yDiagonalOffsets=[-1,1,1,-1],b.exports=f}),a.define("/finders/astar.js",function(a,b,c,d,e){function i(a){a=a||{},f.call(this,a),this.heuristic=a.heuristic||g.manhattan}var f=a("./base"),g=a("../core/heuristic"),h=a("../core/heap");i.prototype=new f,i.prototype.constructor=i,i.prototype._find=function(){var a,b,c=this.startX,d=this.startY,e=this.endX,f=this.endY,g=this.grid,i=new h(function(a,b){var c=g.getAttributeAt(a[0],a[1],"f"),d=g.getAttributeAt(b[0],b[1],"f");return c!=d?c<d:g.getAttributeAt(a[0],a[1],"h")<g.getAttributeAt(b[0],b[1],"h")}),j,k;this.openList=i,k=g.getNodeAt(c,d),k.set("g",0),k.set("f",0),i.push([c,d]),k.set("opened",!0);while(!i.isEmpty()){j=i.pop(),a=j[0],b=j[1],g.setAttributeAt(a,b,"closed",!0);if(a==e&&b==f)return this._constructPath();this._inspectSurround(a,b)}return[]},i.prototype._inspectSurround=function(a,b){var c=f.xOffsets,d=f.yOffsets,e=this.grid,g,h,i;for(g=0;g<c.length;++g)h=a+c[g],i=b+d[g],e.isInside(h,i)&&e.isWalkableAt(h,i)&&this._inspectNodeAt(h,i,a,b,!1)},i.prototype._inspectSurroundDiagonal=function(a,b){var c=f.xOffsets,d=f.yOffsets,e=f.xDiagonalOffsets,g=f.yDiagonalOffsets,h=this.grid,i,j,k,l=[];for(i=0;i<c.length;++i)j=a+c[i],k=b+d[i],h.isInside(j,k)&&h.isWalkableAt(j,k)&&(this._inspectNodeAt(j,k,a,b,!1),l.push(i));for(i=0;i<l.length;++i)j=a+e[l[i]],k=b+g[l[i]],h.isInside(j,k)&&h.isWalkableAt(j,k)&&this._inspectNodeAt(j,k,a,b,!0)},i.prototype._inspectNodeAt=function(a,b,c,d,e){var f=this.grid,g=this.openList,h=f.getNodeAt(a,b);if(h.get("closed"))return;h.get("opened")?this._tryUpdate(a,b,c,d,e)&&g.heapify():(h.set("opened",!0),this._tryUpdate(a,b,c,d,e),g.push([a,b]))},i.prototype._tryUpdate=function(a,b,c,d,e){var f=this.grid,g=f.getNodeAt(c,d),h=g.get("g")+(e?1.4142:1),i=f.getNodeAt(a,b);return i.get("g")===undefined||h<i.get("g")?(i.set("parent",[c,d]),i.set("g",h),i.set("h",this._calculateH(a,b)),i.set("f",i.get("g")+i.get("h")),!0):!1},i.prototype._calculateH=function(a,b){var c=Math.abs(a-this.endX),d=Math.abs(b-this.endY);return this.heuristic(c,d)},b.exports=i}),a.define("/finders/best_first.js",function(a,b,c,d,e){function g(a){f.call(this,a);var b=this.heuristic;this.heuristic=function(a,c){return b(a,c)*1e6}}var f=a("./astar");g.prototype=new f,g.prototype.constructor=g,b.exports=g}),a.define("/finders/breadth_first.js",function(a,b,c,d,e){function g(a){f.call(this,a)}var f=a("./base");g.prototype=new f,g.prototype.constructor=g,g.prototype._find=function(){var a=[],b,c,d,e,f,g=this.startX,h=this.startY,i=this.endX,j=this.endY,k=this.grid;this.openList=a,a.push([g,h]),k.setAttributeAt(g,h,"opened",!0);while(a.length){b=a.shift(),c=b[0],d=b[1],k.setAttributeAt(c,d,"closed",!0);if(c==i&&d==j)return this._constructPath();this._inspectSurround(c,d)}return[]},g.prototype._inspectNodeAt=function(a,b,c,d){var e=this.grid,f=e.getNodeAt(a,b);if(f.get("closed")||f.get("opened"))return;this.openList.push([a,b]),e.setAttributeAt(a,b,"opened",!0),e.setAttributeAt(a,b,"parent",[c,d])},g.prototype._inspectSurround=function(a,b){var c=f.xOffsets,d=f.yOffsets,e=this.grid,g,h,i;for(g=0;g<c.length;++g)h=a+c[g],i=b+d[g],e.isInside(h,i)&&e.isWalkableAt(h,i)&&this._inspectNodeAt(h,i,a,b)},g.prototype._inspectSurroundDiagonal=function(a,b){var c=f.xOffsets,d=f.yOffsets,e=f.xDiagonalOffsets,g=f.yDiagonalOffsets,h=this.grid,i,j,k,l=[];for(i=0;i<c.length;++i)j=a+c[i],k=b+d[i],h.isInside(j,k)&&h.isWalkableAt(j,k)&&(this._inspectNodeAt(j,k,a,b),l.push(i));for(i=0;i<l.length;++i)j=a+e[l[i]],k=b+g[l[i]],h.isInside(j,k)&&h.isWalkableAt(j,k)&&this._inspectNodeAt(j,k,a,b)},b.exports=g}),a.define("/finders/dijkstra.js",function(a,b,c,d,e){function g(a){f.call(this,a),this.heuristic=function(a,b){return 0}}var f=a("./astar");g.prototype=new f,g.prototype.constructor=g,b.exports=g}),a.define("/finders/bi_astar.js",function(a,b,c,d,e){function i(a){g.call(this,a)}var f=a("./base"),g=a("./astar"),h=a("../core/heap");i.prototype=new g,i.prototype.constructor=i,i.prototype._find=function(){var a,b,c=this.startX,d=this.startY,e=this.endX,f=this.endY,g=this.grid,i,j=function(a,b){var c=g.getAttributeAt(a[0],a[1],"f"),d=g.getAttributeAt(b[0],b[1],"f");return c!=d?c<d:g.getAttributeAt(a[0],a[1],"h")<g.getAttributeAt(b[0],b[1],"h")},k=new h(j),l=new h(j);this.sourceOpenList=k,this.targetOpenList=l,k.push([c,d]),i=g.getNodeAt(c,d),i.set("g",0),i.set("f",0),i.set("opened",!0),i.set("by","source"),l.push([e,f]),i=g.getNodeAt(e,f),i.set("g",0),i.set("f",0),i.set("opened",!0),i.set("by","target");while(!k.isEmpty()&&!l.isEmpty())if(this._expandSource()||this._expandTarget())return this.path;return[]},i.prototype._expandSource=function(){return this._expand("source")},i.prototype._expandTarget=function(){return this._expand("target")},i.prototype._expand=function(a){var b,c,d,e;return e=this.grid,b=this[a+"OpenList"].pop(),c=b[0],d=b[1],e.setAttributeAt(c,d,"closed",!0),e.setAttributeAt(c,d,"by",a),this._inspectSurround(c,d,a)},i.prototype._inspectSurround=function(a,b,c){var d=f.xOffsets,e=f.yOffsets,g=this.grid,h,i,j;for(h=0;h<d.length;++h){i=a+d[h],j=b+e[h];if(g.isInside(i,j)&&g.isWalkableAt(i,j)&&this._inspectNodeAt(i,j,a,b,!1,c))return!0}return!1},i.prototype._inspectSurroundDiagonal=function(a,b,c){var d=f.xOffsets,e=f.yOffsets,g=f.xDiagonalOffsets,h=f.yDiagonalOffsets,i=this.grid,j,k,l,m=[];for(j=0;j<d.length;++j){k=a+d[j],l=b+e[j];if(i.isInside(k,l)&&i.isWalkableAt(k,l)){if(this._inspectNodeAt(k,l,a,b,!1,c))return!0;m.push(j)}}for(j=0;j<m.length;++j){k=a+g[m[j]],l=b+h[m[j]];if(i.isInside(k,l)&&i.isWalkableAt(k,l)&&this._inspectNodeAt(k,l,a,b,!0,c))return!0}return!1},i.prototype._inspectNodeAt=function(a,b,c,d,e,f){var g=this.grid,h=this[f+"OpenList"],i=g.getNodeAt(a,b);if(i.get("closed"))return!1;if(i.get("opened")){if(i.get("by")!=f)return this.pathFound=!0,this.path=this._constructPath(a,b,c,d,f),!0;this._tryUpdate(a,b,c,d,e,f)&&h.heapify()}else i.set("opened",!0),i.set("by",f),this._tryUpdate(a,b,c,d,e,f),h.push([a,b]);return!1},i.prototype._tryUpdate=function(a,b,c,d,e,f){var g=this.grid,h=g.getNodeAt(c,d),i=h.get("g")+(e?1.4142:1),j=g.getNodeAt(a,b);return j.get("g")===undefined||i<j.get("g")?(j.set("parent",[c,d]),j.set("g",i),j.set("h",this._calculateH(a,b,f)),j.set("f",j.get("g")+j.get("h")),!0):!1},i.prototype._constructPath=function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n;h=this.startX,i=this.startY,j=this.endX,k=this.endY,l=this.grid,e=="source"?(m=[[c,d]],n=[[a,b]]):(m=[[a,b]],n=[[c,d]]);for(;;){f=m[0][0],g=m[0][1];if(f==h&&g==i)break;m.unshift(l.getAttributeAt(f,g,"parent"))}for(;;){f=n[0][0],g=n[0][1];if(f==j&&g==k)break;n.unshift(l.getAttributeAt(f,g,"parent"))}return n.reverse(),m.concat(n)},i.prototype._calculateH=function(a,b,c){if(c=="source"){var d=Math.abs(a-this.endX),e=Math.abs(b-this.endY);return this.heuristic(d,e)}var d=Math.abs(a-this.startX),e=Math.abs(b-this.startY);return this.heuristic(d,e)},b.exports=i}),a.define("/finders/bi_best_first.js",function(a,b,c,d,e){function g(a){f.call(this,a);var b=this.heuristic;this.heuristic=function(a,c){return b(a,c)*1e6}}var f=a("./bi_astar");g.prototype=new f,g.prototype.constructor=g,b.exports=g}),a.define("/finders/bi_breadth_first.js",function(a,b,c,d,e){function h(a){g.call(this,a)}var f=a("./base"),g=a("./breadth_first");h.prototype=new g,h.prototype.constructor=h,h.prototype._find=function(){var a,b,c,d,e,f=this.startX,g=this.startY,h=this.endX,i=this.endY,j=this.grid,k=[],l=[];this.sourceOpenList=k,this.targetOpenList=l,k.push([f,g]),j.setAttributeAt(f,g,"opened",!0),j.setAttributeAt(f,g,"by","source"),l.push([h,i]),j.setAttributeAt(h,i,"opened",!0),j.setAttributeAt(h,i,"by","target");while(k.length&&l.length)if(this._expandSource()||this._expandTarget())return this.path;return[]},h.prototype._expandSource=function(){return this._expand("source")},h.prototype._expandTarget=function(){return this._expand("target")},h.prototype._expand=function(a){var b,c,d,e=this.grid;return b=this[a+"OpenList"].shift(),c=b[0],d=b[1],e.setAttributeAt(c,d,"closed",!0),e.setAttributeAt(c,d,"by",a),this._inspectSurround(c,d,a)},h.prototype._inspectSurround=function(a,b,c){var d=f.xOffsets,e=f.yOffsets,g=this.grid,h,i,j;for(h=0;h<d.length;++h){i=a+d[h],j=b+e[h];if(g.isInside(i,j)&&g.isWalkableAt(i,j)&&this._inspectNodeAt(i,j,a,b,c))return!0}return!1},h.prototype._inspectSurroundDiagonal=function(a,b,c){var d=f.xOffsets,e=f.yOffsets,g=f.xDiagonalOffsets,h=f.yDiagonalOffsets,i=this.grid,j,k,l,m=[];for(j=0;j<d.length;++j){k=a+d[j],l=b+e[j];if(i.isInside(k,l)&&i.isWalkableAt(k,l)){if(this._inspectNodeAt(k,l,a,b,c))return!0;m.push(j)}}for(j=0;j<m.length;++j){k=a+g[m[j]],l=b+h[m[j]];if(i.isInside(k,l)&&i.isWalkableAt(k,l)&&this._inspectNodeAt(k,l,a,b,c))return!0}return!1},h.prototype._inspectNodeAt=function(a,b,c,d,e){var f=this.grid,g=f.getNodeAt(a,b);return g.get("closed")?!1:g.get("opened")?g.get("by")!=e?(this.path=this._constructPath(a,b,c,d,e),!0):!1:(this[e+"OpenList"].push([a,b]),f.setAttributeAt(a,b,"opened",!0),f.setAttributeAt(a,b,"parent",[c,d]),f.setAttributeAt(a,b,"by",e),!1)},h.prototype._constructPath=function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n;h=this.startX,i=this.startY,j=this.endX,k=this.endY,l=this.grid,e=="source"?(m=[[c,d]],n=[[a,b]]):(m=[[a,b]],n=[[c,d]]);for(;;){f=m[0][0],g=m[0][1];if(f==h&&g==i)break;m.unshift(l.getAttributeAt(f,g,"parent"))}for(;;){f=n[0][0],g=n[0][1];if(f==j&&g==k)break;n.unshift(l.getAttributeAt(f,g,"parent"))}return n.reverse(),m.concat(n)},b.exports=h}),a.define("/finders/bi_dijkstra.js",function(a,b,c,d,e){function g(a){f.call(this,a),this.heuristic=function(a,b){return 0}}var f=a("./bi_astar");g.prototype=new f,g.prototype.constructor=g,b.exports=g}),a.define("/pathfinding.js",function(a,b,c,d,e){b.exports={Node:a("./core/node"),Grid:a("./core/grid"),Heap:a("./core/heap"),Heuristic:a("./core/heuristic"),BaseFinder:a("./finders/base"),AStarFinder:a("./finders/astar"),BestFirstFinder:a("./finders/best_first"),BreadthFirstFinder:a("./finders/breadth_first"),DijkstraFinder:a("./finders/dijkstra"),BiAStarFinder:a("./finders/bi_astar"),BiBestFirstFinder:a("./finders/bi_best_first"),BiBreadthFirstFinder:a("./finders/bi_breadth_first"),BiDijkstraFinder:a("./finders/bi_dijkstra")}}),a("/pathfinding.js"),a("/pathfinding")}()
+var PF=function(){var a=function(b,c){var d=a.resolve(b,c||"/"),e=a.modules[d];if(!e)throw new Error("Failed to resolve module "+b+", tried "+d);var f=e._cached?e._cached:e();return f};return a.paths=[],a.modules={},a.extensions=[".js",".coffee"],a._core={assert:!0,events:!0,fs:!0,path:!0,vm:!0},a.resolve=function(){return function(b,c){function h(b){if(a.modules[b])return b;for(var c=0;c<a.extensions.length;c++){var d=a.extensions[c];if(a.modules[b+d])return b+d}}function i(b){b=b.replace(/\/+$/,"");var c=b+"/package.json";if(a.modules[c]){var e=a.modules[c](),f=e.browserify;if(typeof f=="object"&&f.main){var g=h(d.resolve(b,f.main));if(g)return g}else if(typeof f=="string"){var g=h(d.resolve(b,f));if(g)return g}else if(e.main){var g=h(d.resolve(b,e.main));if(g)return g}}return h(b+"/index")}function j(a,b){var c=k(b);for(var d=0;d<c.length;d++){var e=c[d],f=h(e+"/"+a);if(f)return f;var g=i(e+"/"+a);if(g)return g}var f=h(a);if(f)return f}function k(a){var b;a==="/"?b=[""]:b=d.normalize(a).split("/");var c=[];for(var e=b.length-1;e>=0;e--){if(b[e]==="node_modules")continue;var f=b.slice(0,e+1).join("/")+"/node_modules";c.push(f)}return c}c||(c="/");if(a._core[b])return b;var d=a.modules.path();c=d.resolve("/",c);var e=c||"/";if(b.match(/^(?:\.\.?\/|\/)/)){var f=h(d.resolve(e,b))||i(d.resolve(e,b));if(f)return f}var g=j(b,e);if(g)return g;throw new Error("Cannot find module '"+b+"'")}}(),a.alias=function(b,c){var d=a.modules.path(),e=null;try{e=a.resolve(b+"/package.json","/")}catch(f){e=a.resolve(b,"/")}var g=d.dirname(e),h=(Object.keys||function(a){var b=[];for(var c in a)b.push(c);return b})(a.modules);for(var i=0;i<h.length;i++){var j=h[i];if(j.slice(0,g.length+1)===g+"/"){var k=j.slice(g.length);a.modules[c+k]=a.modules[g+k]}else j===g&&(a.modules[c]=a.modules[g])}},a.define=function(b,c){var d=a._core[b]?"":a.modules.path().dirname(b),e=function(b){return a(b,d)};e.resolve=function(b){return a.resolve(b,d)},e.modules=a.modules,e.define=a.define;var f={exports:{}};a.modules[b]=function(){return a.modules[b]._cached=f.exports,c.call(f.exports,e,f,f.exports,d,b),a.modules[b]._cached=f.exports,f.exports}},typeof process=="undefined"&&(process={}),process.nextTick||(process.nextTick=function(){var a=[],b=typeof window!="undefined"&&window.postMessage&&window.addEventListener;return b&&window.addEventListener("message",function(b){if(b.source===window&&b.data==="browserify-tick"){b.stopPropagation();if(a.length>0){var c=a.shift();c()}}},!0),function(c){b?(a.push(c),window.postMessage("browserify-tick","*")):setTimeout(c,0)}}()),process.title||(process.title="browser"),process.binding||(process.binding=function(b){if(b==="evals")return a("vm");throw new Error("No such module")}),process.cwd||(process.cwd=function(){return"."}),process.env||(process.env={}),process.argv||(process.argv=[]),a.define("path",function(a,b,c,d,e){function f(a,b){var c=[];for(var d=0;d<a.length;d++)b(a[d],d,a)&&c.push(a[d]);return c}function g(a,b){var c=0;for(var d=a.length;d>=0;d--){var e=a[d];e=="."?a.splice(d,1):e===".."?(a.splice(d,1),c++):c&&(a.splice(d,1),c--)}if(b)for(;c--;c)a.unshift("..");return a}var h=/^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/;c.resolve=function(){var a="",b=!1;for(var c=arguments.length;c>=-1&&!b;c--){var d=c>=0?arguments[c]:process.cwd();if(typeof d!="string"||!d)continue;a=d+"/"+a,b=d.charAt(0)==="/"}return a=g(f(a.split("/"),function(a){return!!a}),!b).join("/"),(b?"/":"")+a||"."},c.normalize=function(a){var b=a.charAt(0)==="/",c=a.slice(-1)==="/";return a=g(f(a.split("/"),function(a){return!!a}),!b).join("/"),!a&&!b&&(a="."),a&&c&&(a+="/"),(b?"/":"")+a},c.join=function(){var a=Array.prototype.slice.call(arguments,0);return c.normalize(f(a,function(a,b){return a&&typeof a=="string"}).join("/"))},c.dirname=function(a){var b=h.exec(a)[1]||"",c=!1;return b?b.length===1||c&&b.length<=3&&b.charAt(1)===":"?b:b.substring(0,b.length-1):"."},c.basename=function(a,b){var c=h.exec(a)[2]||"";return b&&c.substr(-1*b.length)===b&&(c=c.substr(0,c.length-b.length)),c},c.extname=function(a){return h.exec(a)[3]||""}}),a.define("/core/node.js",function(a,b,c,d,e){function f(a,b,c,d){this.x=a,this.y=b,this.walkable=c===undefined?!0:c,this.parent=d===undefined?null:d}f.prototype.constructor=f,f.prototype.set=function(a,b){this[a]=b},f.prototype.get=function(a){return this[a]},f.prototype.clone=function(){return new f(this.x,this.y,this.walkable,this.parent)},b.exports=f}),a.define("/core/grid.js",function(a,b,c,d,e){function g(a,b,c){this.width=a,this.height=b,this._buildGrid(c)}var f=a("./node");g.prototype._buildGrid=function(a){var b,c,d=this.width,e=this.height,g=[],h;for(b=0;b<e;++b){g.push([]),h=g[b];for(c=0;c<d;++c)h.push(new f(c,b))}this.nodes=g;if(a===undefined)return;if(a.length!=e||a[0].length!=d)throw new Error("Matrix size does not fit");for(b=0;b<e;++b)for(c=0;c<d;++c)a[b][c]&&(g[b][c].walkable=!1)},g.prototype.getNodeAt=function(a,b){return this.nodes[b][a]},g.prototype.isWalkableAt=function(a,b){return this.getNodeAt(a,b).get("walkable")},g.prototype.setWalkableAt=function(a,b,c){this.getNodeAt(a,b).set("walkable",c)},g.prototype.isInside=function(a,b){return a>=0&&a<this.width&&b>=0&&b<this.height},g.prototype.setAttributeAt=function(a,b,c,d){this.getNodeAt(a,b).set(c,d)},g.prototype.getAttributeAt=function(a,b,c){return this.getNodeAt(a,b).get(c)},g.prototype.clone=function(){var a,b,c=this.width,d=this.height,e=this.nodes,f=new g(c,d),h=[],i;for(a=0;a<d;++a){h.push([]),i=h[a];for(b=0;b<c;++b)i.push(e[a][b].clone())}return f.nodes=h,f},b.exports=g}),a.define("/core/heap.js",function(a,b,c,d,e){function f(a){this._cmp=a||function(a,b){return a<b},this._heap=[]}f.prototype.top=function(){return this._heap[0]},f.prototype.size=function(){return this._heap.length},f.prototype.isEmpty=function(){return!this.size()},f.prototype.push=function(a){this._heap.push(a),this._siftDown(0,this._heap.length-1)},f.prototype.pop=function(){var a,b,c;return a=this._heap,b=a.pop(),a.length?(c=a[0],a[0]=b,this._siftUp(0)):c=b,c},f.prototype.replace=function(a){var b=this._heap[0];return this._heap[0]=a,this._siftUp(0),b},f.prototype.pushpop=function(a){var b=this._heap,c;return b.length&&this._cmp(b[0],a)&&(c=b[0],b[0]=a,a=c,this._siftUp(0)),a},f.prototype.heapify=function(){var a,b=this._heap.length;for(a=Math.floor(b/2)-1;a>=0;--a)this._siftUp(a)},f.prototype._siftDown=function(a,b){var c,d,e,f,g;c=this._heap,d=this._cmp,e=c[b];while(b>a){f=b-1>>1,g=c[f];if(d(e,g)){c[b]=g,b=f;continue}break}c[b]=e},f.prototype._siftUp=function(a){var b,c,d,e,f,g,h;c=this._cmp,b=this._heap,d=b.length,e=a,f=b[a],g=2*a+1;while(g<d)h=g+1,h<d&&!c(b[g],b[h])&&(g=h),b[a]=b[g],a=g,g=2*a+1;b[a]=f,this._siftDown(e,a)},b.exports=f}),a.define("/core/heuristic.js",function(a,b,c,d,e){b.exports={manhattan:function(a,b){return a+b},euclidean:function(a,b){return Math.sqrt(a*a+b*b)},chebyshev:function(a,b){return Math.max(a,b)}}}),a.define("/finders/base.js",function(a,b,c,d,e){function f(a){this.startX=null,this.startY=null,this.endX=null,this.endY=null,this.grid=null,this.gridHeight=null,this.gridWidth=null,this.allowDiagonal=a&&a.allowDiagonal,this.allowDiagonal&&this._inspectSurroundDiagonal!==undefined&&(this._inspectSurround=this._inspectSurroundDiagonal)}f.prototype.isInsideGrid=function(a,b){return this.grid.isInside(a,b)},f.prototype.setWalkableAt=function(a,b,c){this.grid.setWalkableAt(a,b,c)},f.prototype.isWalkableAt=function(a,b){return this.grid.isWalkableAt(a,b)},f.prototype.setAttributeAt=function(a,b,c,d){this.grid.setAttributeAt(a,b,c,d)},f.prototype.getAttributeAt=function(a,b,c){return this.grid.getAttributeAt(a,b,c)},f.prototype.constructor=f,f.prototype.findPath=function(a,b,c,d,e){return this.startX=a,this.startY=b,this.endX=c,this.endY=d,this.grid=e,this.gridWidth=e.width,this.gridHeight=e.height,this._find()},f.prototype._constructPath=function(){var a=this.startX,b=this.startY,c,d,e=this.grid,f=[[this.endX,this.endY]];for(;;){c=f[0][0],d=f[0][1];if(c==a&&d==b)return f;f.unshift(e.getAttributeAt(c,d,"parent"))}return[]},f.prototype._find=function(){throw new Error("Sub-classes must implement this method")},f.xOffsets=[-1,0,1,0],f.yOffsets=[0,1,0,-1],f.xDiagonalOffsets=[-1,-1,1,1],f.yDiagonalOffsets=[-1,1,1,-1],b.exports=f}),a.define("/finders/astar.js",function(a,b,c,d,e){function i(a){a=a||{},f.call(this,a),this.heuristic=a.heuristic||g.manhattan}var f=a("./base"),g=a("../core/heuristic"),h=a("../core/heap");i.prototype=new f,i.prototype.constructor=i,i.prototype._find=function(){var a,b,c=this.startX,d=this.startY,e=this.endX,f=this.endY,g=this.grid,i=new h(function(a,b){var c=g.getAttributeAt(a[0],a[1],"f"),d=g.getAttributeAt(b[0],b[1],"f");return c!=d?c<d:g.getAttributeAt(a[0],a[1],"h")<g.getAttributeAt(b[0],b[1],"h")}),j,k;this.openList=i,k=g.getNodeAt(c,d),k.set("g",0),k.set("f",0),i.push([c,d]),k.set("opened",!0);while(!i.isEmpty()){j=i.pop(),a=j[0],b=j[1],g.setAttributeAt(a,b,"closed",!0);if(a==e&&b==f)return this._constructPath();this._inspectSurround(a,b)}return[]},i.prototype._inspectSurround=function(a,b){var c=f.xOffsets,d=f.yOffsets,e=this.grid,g,h,i;for(g=0;g<c.length;++g)h=a+c[g],i=b+d[g],e.isInside(h,i)&&e.isWalkableAt(h,i)&&this._inspectNodeAt(h,i,a,b,!1)},i.prototype._inspectSurroundDiagonal=function(a,b){var c=f.xOffsets,d=f.yOffsets,e=f.xDiagonalOffsets,g=f.yDiagonalOffsets,h=this.grid,i,j,k,l=[];for(i=0;i<c.length;++i)j=a+c[i],k=b+d[i],h.isInside(j,k)&&h.isWalkableAt(j,k)&&(this._inspectNodeAt(j,k,a,b,!1),l.push(i));for(i=0;i<l.length;++i)j=a+e[l[i]],k=b+g[l[i]],h.isInside(j,k)&&h.isWalkableAt(j,k)&&this._inspectNodeAt(j,k,a,b,!0)},i.prototype._inspectNodeAt=function(a,b,c,d,e){var f=this.grid,g=this.openList,h=f.getNodeAt(a,b);if(h.get("closed"))return;h.get("opened")?this._tryUpdate(a,b,c,d,e)&&g.heapify():(h.set("opened",!0),this._tryUpdate(a,b,c,d,e),g.push([a,b]))},i.prototype._tryUpdate=function(a,b,c,d,e){var f=this.grid,g=f.getNodeAt(c,d),h=g.get("g")+(e?1.4142:1),i=f.getNodeAt(a,b);return i.get("g")===undefined||h<i.get("g")?(i.set("parent",[c,d]),i.set("g",h),i.set("h",this._calculateH(a,b)),i.set("f",i.get("g")+i.get("h")),!0):!1},i.prototype._calculateH=function(a,b){var c=Math.abs(a-this.endX),d=Math.abs(b-this.endY);return this.heuristic(c,d)},b.exports=i}),a.define("/finders/best_first.js",function(a,b,c,d,e){function g(a){f.call(this,a);var b=this.heuristic;this.heuristic=function(a,c){return b(a,c)*1e6}}var f=a("./astar");g.prototype=new f,g.prototype.constructor=g,b.exports=g}),a.define("/finders/breadth_first.js",function(a,b,c,d,e){function g(a){f.call(this,a)}var f=a("./base");g.prototype=new f,g.prototype.constructor=g,g.prototype._find=function(){var a=[],b,c,d,e,f,g=this.startX,h=this.startY,i=this.endX,j=this.endY,k=this.grid;this.openList=a,a.push([g,h]),k.setAttributeAt(g,h,"opened",!0);while(a.length){b=a.shift(),c=b[0],d=b[1],k.setAttributeAt(c,d,"closed",!0);if(c==i&&d==j)return this._constructPath();this._inspectSurround(c,d)}return[]},g.prototype._inspectNodeAt=function(a,b,c,d){var e=this.grid,f=e.getNodeAt(a,b);if(f.get("closed")||f.get("opened"))return;this.openList.push([a,b]),e.setAttributeAt(a,b,"opened",!0),e.setAttributeAt(a,b,"parent",[c,d])},g.prototype._inspectSurround=function(a,b){var c=f.xOffsets,d=f.yOffsets,e=this.grid,g,h,i;for(g=0;g<c.length;++g)h=a+c[g],i=b+d[g],e.isInside(h,i)&&e.isWalkableAt(h,i)&&this._inspectNodeAt(h,i,a,b)},g.prototype._inspectSurroundDiagonal=function(a,b){var c=f.xOffsets,d=f.yOffsets,e=f.xDiagonalOffsets,g=f.yDiagonalOffsets,h=this.grid,i,j,k,l=[];for(i=0;i<c.length;++i)j=a+c[i],k=b+d[i],h.isInside(j,k)&&h.isWalkableAt(j,k)&&(this._inspectNodeAt(j,k,a,b),l.push(i));for(i=0;i<l.length;++i)j=a+e[l[i]],k=b+g[l[i]],h.isInside(j,k)&&h.isWalkableAt(j,k)&&this._inspectNodeAt(j,k,a,b)},b.exports=g}),a.define("/finders/dijkstra.js",function(a,b,c,d,e){function g(a){f.call(this,a),this.heuristic=function(a,b){return 0}}var f=a("./astar");g.prototype=new f,g.prototype.constructor=g,b.exports=g}),a.define("/finders/bi_astar.js",function(a,b,c,d,e){function i(a){g.call(this,a)}var f=a("./base"),g=a("./astar"),h=a("../core/heap");i.prototype=new g,i.prototype.constructor=i,i.prototype._find=function(){var a,b,c=this.startX,d=this.startY,e=this.endX,f=this.endY,g=this.grid,i,j=function(a,b){var c=g.getAttributeAt(a[0],a[1],"f"),d=g.getAttributeAt(b[0],b[1],"f");return c!=d?c<d:g.getAttributeAt(a[0],a[1],"h")<g.getAttributeAt(b[0],b[1],"h")},k=new h(j),l=new h(j);this.sourceOpenList=k,this.targetOpenList=l,k.push([c,d]),i=g.getNodeAt(c,d),i.set("g",0),i.set("f",0),i.set("opened",!0),i.set("by","source"),l.push([e,f]),i=g.getNodeAt(e,f),i.set("g",0),i.set("f",0),i.set("opened",!0),i.set("by","target");while(!k.isEmpty()&&!l.isEmpty())if(this._expandSource()||this._expandTarget())return this.path;return[]},i.prototype._expandSource=function(){return this._expand("source")},i.prototype._expandTarget=function(){return this._expand("target")},i.prototype._expand=function(a){var b,c,d,e;return e=this.grid,b=this[a+"OpenList"].pop(),c=b[0],d=b[1],e.setAttributeAt(c,d,"closed",!0),e.setAttributeAt(c,d,"by",a),this._inspectSurround(c,d,a)},i.prototype._inspectSurround=function(a,b,c){var d=f.xOffsets,e=f.yOffsets,g=this.grid,h,i,j;for(h=0;h<d.length;++h){i=a+d[h],j=b+e[h];if(g.isInside(i,j)&&g.isWalkableAt(i,j)&&this._inspectNodeAt(i,j,a,b,!1,c))return!0}return!1},i.prototype._inspectSurroundDiagonal=function(a,b,c){var d=f.xOffsets,e=f.yOffsets,g=f.xDiagonalOffsets,h=f.yDiagonalOffsets,i=this.grid,j,k,l,m=[];for(j=0;j<d.length;++j){k=a+d[j],l=b+e[j];if(i.isInside(k,l)&&i.isWalkableAt(k,l)){if(this._inspectNodeAt(k,l,a,b,!1,c))return!0;m.push(j)}}for(j=0;j<m.length;++j){k=a+g[m[j]],l=b+h[m[j]];if(i.isInside(k,l)&&i.isWalkableAt(k,l)&&this._inspectNodeAt(k,l,a,b,!0,c))return!0}return!1},i.prototype._inspectNodeAt=function(a,b,c,d,e,f){var g=this.grid,h=this[f+"OpenList"],i=g.getNodeAt(a,b);if(i.get("closed"))return!1;if(i.get("opened")){if(i.get("by")!=f)return this.pathFound=!0,this.path=this._constructPath(a,b,c,d,f),!0;this._tryUpdate(a,b,c,d,e,f)&&h.heapify()}else i.set("opened",!0),i.set("by",f),this._tryUpdate(a,b,c,d,e,f),h.push([a,b]);return!1},i.prototype._tryUpdate=function(a,b,c,d,e,f){var g=this.grid,h=g.getNodeAt(c,d),i=h.get("g")+(e?1.4142:1),j=g.getNodeAt(a,b);return j.get("g")===undefined||i<j.get("g")?(j.set("parent",[c,d]),j.set("g",i),j.set("h",this._calculateH(a,b,f)),j.set("f",j.get("g")+j.get("h")),!0):!1},i.prototype._constructPath=function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n;h=this.startX,i=this.startY,j=this.endX,k=this.endY,l=this.grid,e=="source"?(m=[[c,d]],n=[[a,b]]):(m=[[a,b]],n=[[c,d]]);for(;;){f=m[0][0],g=m[0][1];if(f==h&&g==i)break;m.unshift(l.getAttributeAt(f,g,"parent"))}for(;;){f=n[0][0],g=n[0][1];if(f==j&&g==k)break;n.unshift(l.getAttributeAt(f,g,"parent"))}return n.reverse(),m.concat(n)},i.prototype._calculateH=function(a,b,c){if(c=="source"){var d=Math.abs(a-this.endX),e=Math.abs(b-this.endY);return this.heuristic(d,e)}var d=Math.abs(a-this.startX),e=Math.abs(b-this.startY);return this.heuristic(d,e)},b.exports=i}),a.define("/finders/bi_best_first.js",function(a,b,c,d,e){function g(a){f.call(this,a);var b=this.heuristic;this.heuristic=function(a,c){return b(a,c)*1e6}}var f=a("./bi_astar");g.prototype=new f,g.prototype.constructor=g,b.exports=g}),a.define("/finders/bi_breadth_first.js",function(a,b,c,d,e){function h(a){g.call(this,a)}var f=a("./base"),g=a("./breadth_first");h.prototype=new g,h.prototype.constructor=h,h.prototype._find=function(){var a,b,c,d,e,f=this.startX,g=this.startY,h=this.endX,i=this.endY,j=this.grid,k=[],l=[];this.sourceOpenList=k,this.targetOpenList=l,k.push([f,g]),j.setAttributeAt(f,g,"opened",!0),j.setAttributeAt(f,g,"by","source"),l.push([h,i]),j.setAttributeAt(h,i,"opened",!0),j.setAttributeAt(h,i,"by","target");while(k.length&&l.length)if(this._expandSource()||this._expandTarget())return this.path;return[]},h.prototype._expandSource=function(){return this._expand("source")},h.prototype._expandTarget=function(){return this._expand("target")},h.prototype._expand=function(a){var b,c,d,e=this.grid;return b=this[a+"OpenList"].shift(),c=b[0],d=b[1],e.setAttributeAt(c,d,"closed",!0),e.setAttributeAt(c,d,"by",a),this._inspectSurround(c,d,a)},h.prototype._inspectSurround=function(a,b,c){var d=f.xOffsets,e=f.yOffsets,g=this.grid,h,i,j;for(h=0;h<d.length;++h){i=a+d[h],j=b+e[h];if(g.isInside(i,j)&&g.isWalkableAt(i,j)&&this._inspectNodeAt(i,j,a,b,c))return!0}return!1},h.prototype._inspectSurroundDiagonal=function(a,b,c){var d=f.xOffsets,e=f.yOffsets,g=f.xDiagonalOffsets,h=f.yDiagonalOffsets,i=this.grid,j,k,l,m=[];for(j=0;j<d.length;++j){k=a+d[j],l=b+e[j];if(i.isInside(k,l)&&i.isWalkableAt(k,l)){if(this._inspectNodeAt(k,l,a,b,c))return!0;m.push(j)}}for(j=0;j<m.length;++j){k=a+g[m[j]],l=b+h[m[j]];if(i.isInside(k,l)&&i.isWalkableAt(k,l)&&this._inspectNodeAt(k,l,a,b,c))return!0}return!1},h.prototype._inspectNodeAt=function(a,b,c,d,e){var f=this.grid,g=f.getNodeAt(a,b);return g.get("closed")?!1:g.get("opened")?g.get("by")!=e?(this.path=this._constructPath(a,b,c,d,e),!0):!1:(this[e+"OpenList"].push([a,b]),f.setAttributeAt(a,b,"opened",!0),f.setAttributeAt(a,b,"parent",[c,d]),f.setAttributeAt(a,b,"by",e),!1)},h.prototype._constructPath=function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n;h=this.startX,i=this.startY,j=this.endX,k=this.endY,l=this.grid,e=="source"?(m=[[c,d]],n=[[a,b]]):(m=[[a,b]],n=[[c,d]]);for(;;){f=m[0][0],g=m[0][1];if(f==h&&g==i)break;m.unshift(l.getAttributeAt(f,g,"parent"))}for(;;){f=n[0][0],g=n[0][1];if(f==j&&g==k)break;n.unshift(l.getAttributeAt(f,g,"parent"))}return n.reverse(),m.concat(n)},b.exports=h}),a.define("/finders/bi_dijkstra.js",function(a,b,c,d,e){function g(a){f.call(this,a),this.heuristic=function(a,b){return 0}}var f=a("./bi_astar");g.prototype=new f,g.prototype.constructor=g,b.exports=g}),a.define("/finders/jump_point.js",function(a,b,c,d,e){function i(a){a=a||{},f.call(this,a),this.heuristic=a.heuristic||g.manhattan}var f=a("./base"),g=a("../core/heuristic"),h=a("../core/heap");i.prototype=new f,i.prototype.constructor=i,i.prototype._find=function(){var a,b,c=this.startX,d=this.startY,e=this.endX,f=this.endY,g=this.grid,i=new h(function(a,b){var c=g.getAttributeAt(a[0],a[1],"f"),d=g.getAttributeAt(b[0],b[1],"f");return c!=d?c<d:g.getAttributeAt(a[0],a[1],"h")<g.getAttributeAt(b[0],b[1],"h")}),j,k;this.openList=i,k=g.getNodeAt(c,d),k.set("g",0),k.set("f",0),i.push([c,d]),k.set("opened",!0);while(!i.isEmpty()){j=i.pop(),a=j[0],b=j[1],g.setAttributeAt(a,b,"closed",!0);if(a==e&&b==f)return this._constructPath();this._identifySuccessors(a,b)}return[]},i.prototype._identifySuccessors=function(a,b){var c=this._findNeighbors(a,b),d,e,f,g,h,i;for(i=0;i<c.length;i++)e=c[i][0],f=c[i][1],g=e-a,h=f-b,d=this._jump(e,f,a,b),d&&this._inspectNodeAt(d[0],d[1],a,b)},i.prototype._jump=function(a,b,c,d){var e=this.grid,f=a-c,g=b-d,h,i;if(!this._isOpen(a,b))return null;if(a==this.endX&&b==this.endY)return[a,b];if(f!==0&&g!==0){if(this._isOpen(a-f,b+g)&&!this._isOpen(a-f,b)||this._isOpen(a+f,b-g)&&!this._isOpen(a,b-g))return[a,b]}else if(f!==0){if(this._isOpen(a+f,b+1)&&!this._isOpen(a,b+1)||this._isOpen(a+f,b-1)&&!this._isOpen(a,b-1))return[a,b]}else if(this._isOpen(a+1,b+g)&&!this._isOpen(a+1,b)||this._isOpen(a-1,b+g)&&!this._isOpen(a-1,b))return[a,b];if(f!==0&&g!==0){h=this._jump(a+f,b,a,b),i=this._jump(a,b+g,a,b);if(h||i)return[a,b]}return this._isOpen(a+f,b)||this._isOpen(a,b+g)?this._jump(a+f,b+g,a,b):null},i.prototype._isOpen=function(a,b){var c=this.grid;return c.isInside(a,b)&&c.isWalkableAt(a,b)},i.prototype._findNeighbors=function(a,b){var c=this.grid,d=c.getNodeAt(a,b),e=d.get("parent"),g=f.xOffsets,h=f.yOffsets,i=f.xDiagonalOffsets,j=f.yDiagonalOffsets,k,l,m,n,o,p,q,r=[];if(e)p=e[0],q=e[1],n=(a-p)/Math.max(Math.abs(a-p),1),o=(b-q)/Math.max(Math.abs(b-q),1),n!==0&&o!==0?(this._isOpen(a,b+o)&&r.push([a,b+o]),this._isOpen(a+n,b)&&r.push([a+n,b]),(this._isOpen(a,b+o)||this._isOpen(a+n,b))&&r.push([a+n,b+o]),!this._isOpen(a-n,b)&&this._isOpen(a,b+o)&&r.push([a-n,b+o]),!this._isOpen(a,b-o)&&this._isOpen(a+n,b)&&r.push([a+n,b-o])):n===0?this._isOpen(a,b+o)&&(this._isOpen(a,b+o)&&r.push([a,b+o]),this._isOpen(a+1,b)||r.push([a+1,b+o]),this._isOpen(a-1,b)||r.push([a-1,b+o])):this._isOpen(a+n,b)&&(this._isOpen(a+n,b)&&r.push([a+n,b]),this._isOpen(a,b+1)||r.push([a+n,b+1]),this._isOpen(a,b-1)||r.push([a+n,b-1]));else for(k=0;k<g.length;++k)l=a+g[k],m=b+h[k],this._isOpen(l,m)&&r.push([l,m]),n=i[k],o=j[k],this._isOpen(a+n,b+o)&&(this._isOpen(a+n,b)||this._isOpen(a,b+o))&&r.push([a+n,b+o]);return r},i.prototype._inspectNodeAt=function(a,b,c,d){var e=this.grid,f=this.openList,g=e.getNodeAt(a,b),h=c-a!==0&&d-b!==0;if(g.get("closed"))return;g.get("opened")?this._tryUpdate(a,b,c,d,h)&&f.heapify():(g.set("opened",!0),this._tryUpdate(a,b,c,d,h),f.push([a,b]))},i.prototype._tryUpdate=function(a,b,c,d,e){var f=this.grid,g=f.getNodeAt(c,d),h=Math.max(Math.abs(a-c),Math.abs(b-d)),i=g.get("g")+(e?1.4142:1)*h,j=f.getNodeAt(a,b);return j.get("g")===undefined||i<j.get("g")?(j.set("parent",[c,d]),j.set("g",i),j.set("h",this._calculateH(a,b)),j.set("f",j.get("g")+j.get("h")),!0):!1},i.prototype._calculateH=function(a,b){var c=Math.abs(a-this.endX),d=Math.abs(b-this.endY);return this.heuristic(c,d)},b.exports=i}),a.define("/pathfinding.js",function(a,b,c,d,e){b.exports={Node:a("./core/node"),Grid:a("./core/grid"),Heap:a("./core/heap"),Heuristic:a("./core/heuristic"),BaseFinder:a("./finders/base"),AStarFinder:a("./finders/astar"),BestFirstFinder:a("./finders/best_first"),BreadthFirstFinder:a("./finders/breadth_first"),DijkstraFinder:a("./finders/dijkstra"),BiAStarFinder:a("./finders/bi_astar"),BiBestFirstFinder:a("./finders/bi_best_first"),BiBreadthFirstFinder:a("./finders/bi_breadth_first"),BiDijkstraFinder:a("./finders/bi_dijkstra"),JumpPointFinder:a("./finders/jump_point")}}),a("/pathfinding.js"),a("/pathfinding")}()
View
360 src/finders/jump_point.js
@@ -0,0 +1,360 @@
+var BaseFinder = require('./base');
+var Heuristic = require('../core/heuristic');
+var Heap = require('../core/heap');
+
+/**
+ * Path finder using the Jump Point Search algorithm
+ * @constructor
+ * @extends BaseFinder
+ * @requires Heap
+ * @requires Heuristic
+ * @param {boolean} opt -
+ * [opt.heuristic]: Heuristic function being used to estimate the distance
+ * (defaults to manhattan).
+ */
+function JumpPointFinder(opt) {
+ opt = opt || {};
+ BaseFinder.call(this, opt);
+ this.heuristic = opt.heuristic || Heuristic.manhattan;
+}
+
+
+/**
+ * Extends the BaseFinder
+ */
+JumpPointFinder.prototype = new BaseFinder();
+
+
+/**
+ * The constructor of the instance.
+ */
+JumpPointFinder.prototype.constructor = JumpPointFinder;
+
+
+/**
+ * Find and return the the path.
+ * @protected
+ * @return {Array.<[number, number]>} The path, including both start and
+ * end positions.
+ */
+JumpPointFinder.prototype._find = function() {
+ var x, y, // current x, y
+ sx = this.startX,
+ sy = this.startY,
+ ex = this.endX,
+ ey = this.endY,
+ grid = this.grid,
+ openList = new Heap(function(posA, posB) {
+ var fa = grid.getAttributeAt(posA[0], posA[1], 'f'),
+ fb = grid.getAttributeAt(posB[0], posB[1], 'f');
+ if (fa != fb) {
+ return fa < fb;
+ } else {
+ return grid.getAttributeAt(posA[0], posA[1], 'h') <
+ grid.getAttributeAt(posB[0], posB[1], 'h');
+ }
+ }),
+ pos,
+ node;
+
+ this.openList = openList;
+
+ // set the `g` and `f` value of the start node to be 0
+ node = grid.getNodeAt(sx, sy);
+ node.set('g', 0);
+ node.set('f', 0);
+
+ // push the start node into the open list
+ openList.push([sx, sy]);
+ node.set('opened', true);
+
+ // while the open list is not empty
+ while (!openList.isEmpty()) {
+ // pop the position of node which has the minimum `f` value.
+ pos = openList.pop();
+ x = pos[0];
+ y = pos[1];
+ grid.setAttributeAt(x, y, 'closed', true);
+
+ // if reached the end position, construct the path and return it
+ if (x == ex && y == ey) {
+ return this._constructPath();
+ }
+
+ this._identifySuccessors(x,y);
+ }
+
+ // fail to find the path
+ return [];
+};
+
+/**
+ * Identify successors for the given node. Runs a jump point search in the
+ * direction of each available neighbor, adding any points found to the open
+ * list.
+ * @protected
+ * @param {number} x - The x coordinate of the position.
+ * @param {number} y - The y coordinate of the position.
+ */
+JumpPointFinder.prototype._identifySuccessors = function(x,y) {
+ var neighbors = this._findNeighbors(x,y),
+ jumpPoint, nx, ny, dx, dy, i;
+ for(i = 0; i < neighbors.length; i++) {
+ nx = neighbors[i][0];
+ ny = neighbors[i][1];
+ dx = nx-x;
+ dy = ny-y;
+ jumpPoint = this._jump(nx, ny, x, y);
+ if (jumpPoint) {
+ this._inspectNodeAt(jumpPoint[0], jumpPoint[1], x, y);
+ }
+ }
+};
+
+/**
+ Search recursively in the direction (parent -> child), stopping only when a
+ * jump point is found.
+ * @protected
+ * @param {number} x - The x coordinate of the position.
+ * @param {number} y - The y coordinate of the position.
+ * @param {number} px - The x coordinate of the parent position.
+ * @param {number} py - The y coordinate of the parent position.
+ * @return {Array.<[number, number]>} The x, y coordinate of the jump point
+ * found, or null if not found
+ */
+JumpPointFinder.prototype._jump = function(x,y,px,py) {
+ var grid = this.grid,
+ dx = x - px, dy = y - py, jx, jy;
+ if (!this._isOpen(x,y)) {
+ return null;
+ }
+ else if (x == this.endX && y == this.endY) {
+ return [x,y];
+ }
+
+ // check for forced neighbors
+ // along the diagonal
+ if (dx !== 0 && dy !== 0) {
+ if ((this._isOpen(x - dx, y + dy) && !this._isOpen(x - dx, y)) ||
+ (this._isOpen(x + dx, y - dy) && !this._isOpen(x, y - dy))) {
+ return [x,y];
+ }
+ }
+ // horizontally/vertically
+ else {
+ if( dx !== 0 ) { // moving along x
+ if((this._isOpen(x + dx, y + 1) && !this._isOpen(x, y + 1)) ||
+ (this._isOpen(x + dx, y - 1) && !this._isOpen(x, y - 1))) {
+ return [x,y];
+ }
+ }
+ else {
+ if((this._isOpen(x + 1, y + dy) && !this._isOpen(x + 1, y)) ||
+ (this._isOpen(x - 1, y + dy) && !this._isOpen(x - 1, y))) {
+ return [x,y];
+ }
+ }
+ }
+
+ // when moving diagonally, must check for vertical/horizontal jump points
+ if (dx !== 0 && dy !== 0) {
+ jx = this._jump(x + dx, y, x, y);
+ jy = this._jump(x, y + dy, x, y);
+ if (jx || jy) {
+ return [x,y];
+ }
+ }
+
+ // moving diagonally, must make sure one of the vertical/horizontal
+ // neighbors is open to allow the path
+ if (this._isOpen(x + dx, y) || this._isOpen(x, y + dy)) {
+ return this._jump(x + dx, y + dy, x, y);
+ }
+ else {
+ return null;
+ }
+};
+
+/**
+ * Check if a point on the grid is present and walkable.
+ * @protected
+ * @param {number} x - The x coordinate of the position.
+ * @param {number} y - The y coordinate of the position.
+ * @return {boolean} True if the point is available and walkable.
+ */
+JumpPointFinder.prototype._isOpen = function(x, y) {
+ var grid = this.grid;
+ return grid.isInside(x,y) && grid.isWalkableAt(x,y);
+};
+
+/**
+ * Find the neighbors for the given node. If the node has a parent,
+ * prune the neighbors based on the jump point search algorithm, otherwise
+ * return all available neighbors.
+ * @protected
+ * @param {number} x - The x coordinate of the position.
+ * @param {number} y - The y coordinate of the position.
+ * @return {Array.<[number, number]>} The neighbors found.
+ */
+JumpPointFinder.prototype._findNeighbors = function(x, y) {
+ var grid = this.grid,
+ node = grid.getNodeAt(x, y),
+ parent = node.get('parent'),
+ xOffsets = BaseFinder.xOffsets,
+ yOffsets = BaseFinder.yOffsets,
+ xDiagonalOffsets = BaseFinder.xDiagonalOffsets,
+ yDiagonalOffsets = BaseFinder.yDiagonalOffsets,
+ i, nx, ny, dx, dy, px, py,
+ neighbors = [];
+
+ // directed pruning: can ignore most neighbors, unless forced.
+ if (parent) {
+ px = parent[0];
+ py = parent[1];
+ // get the normalized direction of travel
+ dx = (x - px) / Math.max(Math.abs(x - px), 1);
+ dy = (y - py) / Math.max(Math.abs(y - py), 1);
+
+ // search diagonally
+ if ( dx !== 0 && dy !== 0) {
+ if (this._isOpen(x, y + dy)) {
+ neighbors.push([x, y + dy]);
+ }
+ if (this._isOpen(x + dx, y)) {
+ neighbors.push([x + dx, y]);
+ }
+ if (this._isOpen(x, y + dy) || this._isOpen(x + dx, y)) {
+ neighbors.push([x + dx, y + dy]);
+ }
+ if (!this._isOpen(x - dx, y) && this._isOpen(x, y + dy)) {
+ neighbors.push([x - dx, y + dy]);
+ }
+ if (!this._isOpen(x, y - dy) && this._isOpen(x + dx, y)) {
+ neighbors.push([x + dx, y - dy]);
+ }
+ }
+ // search horizontally/vertically
+ else {
+ if(dx === 0) {
+ if (this._isOpen(x, y + dy)) {
+ if (this._isOpen(x, y + dy)) {
+ neighbors.push([x, y + dy]);
+ }
+ if (!this._isOpen(x + 1, y)) {
+ neighbors.push([x + 1, y + dy]);
+ }
+ if (!this._isOpen(x - 1, y)) {
+ neighbors.push([x - 1, y + dy]);
+ }
+ }
+ }
+ else {
+ if (this._isOpen(x + dx, y)) {
+ if (this._isOpen(x + dx, y)) {
+ neighbors.push([x + dx, y]);
+ }
+ if (!this._isOpen(x, y + 1)) {
+ neighbors.push([x + dx, y + 1]);
+ }
+ if (!this._isOpen(x, y - 1)) {
+ neighbors.push([x + dx, y - 1]);
+ }
+ }
+ }
+ }
+ }
+ // return all open neighbors
+ else {
+ for (i = 0; i < xOffsets.length; ++i) {
+ nx = x + xOffsets[i];
+ ny = y + yOffsets[i];
+ if (this._isOpen(nx,ny)) {
+ neighbors.push([nx,ny]);
+ }
+ dx = xDiagonalOffsets[i];
+ dy = yDiagonalOffsets[i];
+ if (this._isOpen(x + dx, y + dy) &&
+ (this._isOpen(x + dx, y) || this._isOpen(x, y + dy))) {
+ neighbors.push([x + dx, y + dy]);
+ }
+ }
+ }
+
+ return neighbors;
+};
+
+/**
+ * Push the position into the open list if this position is not in the list.
+ * Otherwise, if the position can be accessed with a lower cost from the given
+ * parent position, then update its parent and cost.
+ * @protected
+ * @param {number} x - The x coordinate of the position.
+ * @param {number} y - The y coordinate of the position.
+ * @param {number} px - The x coordinate of the parent position.
+ * @param {number} py - The y coordinate of the parent position.
+ */
+JumpPointFinder.prototype._inspectNodeAt = function(x, y, px, py) {
+ var grid = this.grid,
+ openList = this.openList,
+ node = grid.getNodeAt(x, y),
+ isDiagonal = (px-x) !== 0 && (py-y) !== 0;
+ if (node.get('closed')) {
+ return;
+ }
+ if (node.get('opened')) {
+ if (this._tryUpdate(x, y, px, py, isDiagonal)) {
+ openList.heapify();
+ }
+ } else {
+ node.set('opened', true);
+ this._tryUpdate(x, y, px, py, isDiagonal);
+ openList.push([x, y]);
+ }
+};
+
+
+/**
+ * Try to update the position's info with the given parent.
+ * If this position can be accessed from the given parent with lower
+ * `g` cost, then this position's parent, `g` and `f` values will be updated.
+ * @protected
+ * @param {number} x - The x coordinate of the position.
+ * @param {number} y - The y coordinate of the position.
+ * @param {number} px - The x coordinate of the parent position.
+ * @param {number} py - The y coordinate of the parent position.
+ * @param {boolean} isDiagonal - Whether [x, y] and [px, py] is diagonal
+ * @return {boolean} Whether this position's info has been updated.
+ */
+JumpPointFinder.prototype._tryUpdate = function(x, y, px, py, isDiagonal) {
+ var grid = this.grid,
+ pNode = grid.getNodeAt(px, py), // parent node
+ // include distance, as parent may not be immediately adjacent:
+ d = Math.max(Math.abs(x-px), Math.abs(y-py)),
+ ng = pNode.get('g') + (isDiagonal ? 1.4142 : 1) * d, // next `g` value
+ node = grid.getNodeAt(x, y);
+
+ if (node.get('g') === undefined || ng < node.get('g')) {
+ node.set('parent', [px, py]);
+ node.set('g', ng);
+ node.set('h', this._calculateH(x, y));
+ node.set('f', node.get('g') + node.get('h'));
+ return true;
+ }
+ return false;
+};
+
+
+/**
+ * Calculate the `h` value of a given position.
+ * @protected
+ * @param {number} x - The x coordinate of the position.
+ * @param {number} y - The y coordinate of the position.
+ * @return {number}
+ */
+JumpPointFinder.prototype._calculateH = function(x, y) {
+ var dx = Math.abs(x - this.endX),
+ dy = Math.abs(y - this.endY);
+ return this.heuristic(dx, dy);
+};
+
+module.exports = JumpPointFinder;
View
3 src/pathfinding.js
@@ -11,5 +11,6 @@ module.exports = {
'BiAStarFinder' : require('./finders/bi_astar'),
'BiBestFirstFinder' : require('./finders/bi_best_first'),
'BiBreadthFirstFinder' : require('./finders/bi_breadth_first'),
- 'BiDijkstraFinder' : require('./finders/bi_dijkstra')
+ 'BiDijkstraFinder' : require('./finders/bi_dijkstra'),
+ 'JumpPointFinder' : require('./finders/jump_point')
};
View
2 test/path_test.js
@@ -60,7 +60,9 @@ pathTest('BiDijkstra', new BiDijkstraFinder(), STRICT);
var BestFirstFinder = require('../src/finders/best_first.js');
var BiBestFirstFinder = require('../src/finders/bi_best_first.js');
var BiAStarFinder = require('../src/finders/bi_astar.js');
+var JumpPointFinder = require('../src/finders/jump_point');
pathTest('BestFirst', new BestFirstFinder(), NON_STRICT);
pathTest('BiBestFirst', new BiBestFirstFinder(), NON_STRICT);
pathTest('BiAStar', new BiAStarFinder(), NON_STRICT);
+pathTest('JumpPoint', new JumpPointFinder(), NON_STRICT);
View
15 visual/index.html
@@ -120,6 +120,21 @@ <h3 id="dijkstra_header"><a href="#">Dijkstra</a></h3>
<label class="option_label">Bi-directional</label> <br>
</div>
</div>
+
+ <h3 id="jump_point_header"><a href="#">Jump Point Search</a></h3>
+ <div id="jump_point_section" class="finder_section">
+ <header class="option_header">
+ <h3>Heuristic</h3>
+ </header>
+ <div id="jump_point_heuristic" class="sub_options">
+ <input type="radio" name="jump_point_heuristic" value="manhattan" checked />
+ <label class="option_label">Manhattan</label> <br>
+ <input type="radio" name="jump_point_heuristic" value="euclidean"/>
+ <label class="option_label">Euclidean</label> <br>
+ <input type="radio" name="jump_point_heuristic" value="chebyshev"/>
+ <label class="option_label">Chebyshev</label> <br>
+ </div>
+ </div>
</div>
</div>
View
8 visual/panel.js
@@ -121,6 +121,14 @@ window.Panel = {
finder = new PF.DijkstraFinder({allowDiagonal: allowDiagonal});
}
break;
+
+ case 'jump_point_header':
+ heuristic = $('input[name=jump_point_heuristic]:checked').val();
+ finder = new PF.JumpPointFinder({
+ heuristic: PF.Heuristic[heuristic]
+ });
+ break;
+
}
return finder;
Something went wrong with that request. Please try again.