Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Bruno Windels
committed
Feb 10, 2012
0 parents
commit f2b6e84
Showing
3 changed files
with
362 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
var PathFindr = PathFindr || {}; | ||
PathFindr.drawMap = (function() { | ||
var ctx; | ||
var openList, closedList, pathMap, obstacles, map, options, canvas; | ||
|
||
function prepareForDrawing() { | ||
obstacles = createObstacleMap(); | ||
var astar = new PathFindr.AStar(map.startPos, map.endPos, isPassable); | ||
path = createPathMap(astar.calculatePath(options && options.debug)); | ||
openList = astar.openList; | ||
closedList = astar.closedList; | ||
} | ||
|
||
function isPath(x, y) { | ||
return !!path[x + '_' + y]; | ||
} | ||
|
||
function isObstacle(x, y) { | ||
return !!obstacles[x + '_' + y]; | ||
} | ||
|
||
function withinBounds(x, y) { | ||
if(x < 0 || y < 0) { | ||
return false; | ||
} | ||
if(x >= map.width || y >= map.height) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
function isPassable(x, y) { | ||
return withinBounds(x, y) && !isObstacle(x, y); | ||
} | ||
|
||
function createObstacleMap() { | ||
var obstacleMap = {}, x, y; | ||
map.obstacles.forEach(function(o) { | ||
for(x = o.x; x < o.x + (o.width || 1); ++x) { | ||
for(y = o.y; y < o.y + (o.height || 1); ++y) { | ||
obstacleMap[x + '_' + y] = true; | ||
} | ||
} | ||
}); | ||
return obstacleMap; | ||
} | ||
|
||
function createPathMap(path) { | ||
var pathMap = {}; | ||
if(path) { | ||
path.forEach(function(n) { | ||
pathMap[n.x + '_' + n.y] = true; | ||
}); | ||
} | ||
return pathMap; | ||
} | ||
|
||
function tilePixelPos(x, y) { | ||
return { | ||
x: x * (map.pixelsPerTile + map.borderWidth) + map.borderWidth/2, | ||
y: y * (map.pixelsPerTile + map.borderWidth) + map.borderWidth/2 | ||
}; | ||
} | ||
|
||
function drawGrid() { | ||
var w, y; | ||
ctx.strokeStyle = '#804000'; | ||
ctx.lineWidth = map.borderWidth; | ||
ctx.beginPath(); | ||
for(w = 0; w < canvas.width; w+= map.pixelsPerTile + map.borderWidth) { | ||
ctx.moveTo(w, 0); | ||
ctx.lineTo(w, canvas.height); | ||
} | ||
for(h = 0; h < canvas.height; h+= map.pixelsPerTile + map.borderWidth) { | ||
ctx.moveTo(0, h); | ||
ctx.lineTo(canvas.width, h); | ||
} | ||
ctx.stroke(); | ||
} | ||
|
||
function drawTiles() { | ||
var x, y, color, pos; | ||
for(x = 0; x < map.width; ++x) { | ||
for(y = 0; y < map.height; ++y) { | ||
color = getTileColor(x, y); | ||
if(color) { | ||
ctx.fillStyle = color; | ||
pos = tilePixelPos(x, y); | ||
ctx.fillRect( | ||
pos.x, | ||
pos.y, | ||
map.pixelsPerTile + map.borderWidth, | ||
map.pixelsPerTile + map.borderWidth | ||
); | ||
} | ||
} | ||
} | ||
} | ||
|
||
function getTileColor(x, y) { | ||
if(x === map.startPos.x && y === map.startPos.y) { | ||
return '#6dd34f'; | ||
} | ||
if(x === map.endPos.x && y === map.endPos.y) { | ||
return '#214210'; | ||
} | ||
if(isObstacle(x, y)) { | ||
return '#804000'; | ||
} | ||
if(isPath(x, y)) { | ||
return 'orange'; | ||
} | ||
return null; | ||
} | ||
|
||
function drawOpenAndClosedList() { | ||
function drawNode(color, node) { | ||
var pos = tilePixelPos(node.x, node.y); | ||
var center = { | ||
x: pos.x + map.pixelsPerTile/2, | ||
y: pos.y + map.pixelsPerTile/2 | ||
}; | ||
var parentOffset = node.parentOffset(); | ||
var pointerPos = { | ||
x: center.x + ((map.pixelsPerTile/2) * parentOffset.x), | ||
y: center.y + ((map.pixelsPerTile/2) * parentOffset.y) | ||
}; | ||
var radius = map.pixelsPerTile / 4; | ||
ctx.fillStyle = color; | ||
ctx.beginPath(); | ||
ctx.arc(center.x, center.y, radius, 0, Math.PI*2, true); | ||
ctx.fill(); | ||
ctx.lineWidth = map.borderWidth*2; | ||
ctx.strokeStyle = color; | ||
ctx.beginPath(); | ||
ctx.moveTo(center.x, center.y); | ||
ctx.lineTo(pointerPos.x , pointerPos.y); | ||
ctx.stroke(); | ||
} | ||
openList.forEach(drawNode.bind(null, '#1514ff')); | ||
closedList.forEach(drawNode.bind(null, '#c800ff')); | ||
} | ||
|
||
function draw() { | ||
drawTiles(); | ||
if(options.debug) { | ||
drawOpenAndClosedList(); | ||
} | ||
drawGrid(); | ||
} | ||
|
||
function drawMap(c, m, o) { | ||
map = m; | ||
options = o; | ||
canvas = c; | ||
canvas.height = map.height * map.pixelsPerTile + map.height + map.borderWidth; | ||
canvas.width = map.width * map.pixelsPerTile + map.width + map.borderWidth; | ||
ctx = canvas.getContext('2d'); | ||
ctx.fillStyle = 'white'; | ||
ctx.fillRect(0, 0, canvas.width, canvas.height); | ||
|
||
prepareForDrawing(); | ||
draw(); | ||
} | ||
return drawMap; | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head></head> | ||
<body> | ||
<canvas id="map"></canvas> | ||
<script src="astar.js"></script> | ||
<script src="astar-graph.js"></script> | ||
<script> | ||
(function() { | ||
var map = { | ||
width: 25, | ||
height: 25, | ||
pixelsPerTile: 20, | ||
borderWidth: 1, | ||
endPos: {x: 1, y: 10}, | ||
startPos: {x: 1, y: 2}, | ||
obstacles: [ | ||
{x: 4, y: 0, height: 5}, | ||
{x: 1, y: 5, width: 4}, | ||
{x: 0, y: 7, width: 19, height: 2}, | ||
{x: 8, y: 2, height: 5}, | ||
{x: 12, y: 0, height: 5}, | ||
{x: 16, y: 2, height: 5}, | ||
{x: 12, y: 8, height: 5}, | ||
{x: 8, y: 10, height: 11}, | ||
{x: 8, y: 18, width: 11}, | ||
{x: 8, y: 18, width: 11} | ||
] | ||
}; | ||
var canvas = document.getElementById('map'); | ||
PathFindr.drawMap(canvas, map, {debug: true}); | ||
})(); | ||
</script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
var PathFindr = PathFindr || {}; | ||
PathFindr.AStar = (function() { | ||
function find(array, fn) { | ||
var index; | ||
var found = array.some(function(a, i) { | ||
index = i; | ||
return fn(a); | ||
}); | ||
return found ? index : -1; | ||
} | ||
|
||
function AStar(startPos, endPos, isPassable) { | ||
this.startPos = startPos; | ||
this.endPos = endPos; | ||
this.isPassable = isPassable; | ||
} | ||
|
||
AStar.prototype.calculatePath = function(debug) { | ||
var openList = [new PathNode(this.startPos.x, this.startPos.y)]; | ||
var closedList = []; | ||
var endPos = this.endPos; | ||
var currentNode, adjecents; | ||
|
||
function totalCost(n) { | ||
return n.costFromStart() + n.estimatedCostTo(endPos); | ||
} | ||
|
||
function compareByTotalCost(a, b) { | ||
return totalCost(a) - totalCost(b); | ||
} | ||
|
||
function notInClosedList(a) { | ||
return !closedList.some(function(b) { | ||
return a.equal(b); | ||
}); | ||
} | ||
|
||
function updateOrAppendNode(node) { | ||
var index = find(openList, function(a) {return a.equal(node);}); | ||
if(index === -1) { | ||
openList.push(node); | ||
} else if(node.costFromStart() < openList[index].costFromStart()) { | ||
openList[index] = node; | ||
} | ||
} | ||
|
||
while(openList.length) { | ||
openList.sort(compareByTotalCost); | ||
currentNode = openList[0]; | ||
//remove first element | ||
openList = openList.slice(1); | ||
closedList.push(currentNode); | ||
if(currentNode.equal(endPos)) { | ||
break; | ||
} | ||
adjecents = currentNode.adjacentNodes(this.isPassable); | ||
//filter out nodes present in the closed list | ||
adjecents = adjecents.filter(notInClosedList); | ||
//find out if these have duplicates in the open list and which ones are better | ||
adjecents.forEach(updateOrAppendNode); | ||
} | ||
|
||
if(debug) { | ||
this.openList = openList; | ||
this.closedList = closedList; | ||
} | ||
|
||
if(currentNode.equal(endPos)) { | ||
return currentNode.path(); | ||
} else { | ||
return null; //no path found | ||
} | ||
}; | ||
|
||
function PathNode(x, y, parent) { | ||
this.x = x; | ||
this.y = y; | ||
this.parent = parent; | ||
} | ||
|
||
PathNode.prototype.cost = function() { | ||
if(!this.parent) { | ||
return 0; | ||
} | ||
if(this.x !== this.parent.x && this.y !== this.parent.y) { | ||
return 1.41; | ||
} | ||
return 1; | ||
}; | ||
|
||
PathNode.prototype.costFromStart = function() { | ||
var cost = 0; | ||
if(this.parent) { | ||
cost = this.parent.costFromStart(); | ||
} | ||
cost += this.cost(); | ||
return cost; | ||
}; | ||
|
||
PathNode.prototype.estimatedCostTo = function(p) { | ||
return Math.abs(p.x - this.x) + Math.abs(p.y - this.y); | ||
}; | ||
|
||
PathNode.prototype.path = function() { | ||
var path; | ||
if(this.parent) { | ||
path = this.parent.path(); | ||
} else { | ||
path = []; | ||
} | ||
path.push(this); | ||
return path; | ||
}; | ||
|
||
PathNode.prototype.equal = function(p) { | ||
return p.x === this.x && p.y === this.y; | ||
}; | ||
|
||
PathNode.prototype.parentOffset = function(p) { | ||
if(!this.parent) { | ||
return {x: 0, y: 0}; | ||
} | ||
return { | ||
x: this.parent.x - this.x, | ||
y: this.parent.y - this.y | ||
}; | ||
}; | ||
|
||
PathNode.prototype.adjacentNodes = function(isPassable) { | ||
var nodes = [], self = this; | ||
function addNode(relX, relY) { | ||
var x = self.x + relX; | ||
var y = self.y + relY; | ||
if(!isPassable(x, y)) { | ||
return false; | ||
} | ||
nodes.push(new PathNode(x, y, self)); | ||
return true; | ||
} | ||
var topPassable = addNode(0, -1); | ||
var bottomPassable = addNode(0, 1); | ||
var leftPassable = addNode(-1, 0); | ||
var rightPassable = addNode(1, 0); | ||
//add diagonal neighbours if non-diagonal neighbours are passable | ||
if(topPassable && leftPassable) { | ||
addNode(-1, -1); | ||
} | ||
if(topPassable && rightPassable) { | ||
addNode(1, -1); | ||
} | ||
if(bottomPassable && leftPassable) { | ||
addNode(-1, 1); | ||
} | ||
if(bottomPassable && rightPassable) { | ||
addNode(1, 1); | ||
} | ||
return nodes; | ||
}; | ||
|
||
return AStar; | ||
})(); |