Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Bruno Windels committed Feb 10, 2012
0 parents commit f2b6e84
Show file tree
Hide file tree
Showing 3 changed files with 362 additions and 0 deletions.
166 changes: 166 additions & 0 deletions astar-graph.js
@@ -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;
})();
35 changes: 35 additions & 0 deletions astar.html
@@ -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>
161 changes: 161 additions & 0 deletions astar.js
@@ -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;
})();

0 comments on commit f2b6e84

Please sign in to comment.