A*算法学习 附Javascript 算法实现练习
因工作需要,学习和了解一些路径算法,正好用Javascript 练练手。
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>A*寻路算法演示 [http://mapig.cnblogs.com/]</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js " type="text/javascript"></script>
<script type="text/javascript">
/*----------------------------------------------------------------
// http://mapig.cnblogs.com/
// 文件名:A*寻路算法实现
// 文件功能描述:A*寻路算法实现
// 修改标识:
// 修改描述:2009/07/06 v0.1 , 按照基本算法简单实现,未做如任何优化
//----------------------------------------------------------------*/
Function.prototype.bind = function(object) {
var method = this;
return function() {
method.apply(object, arguments);
}
}
//状态枚举
var status = {
//网格
grid: 0,
//起点
start: 1,
//终点
end: 2,
//路径
path:3,
//障碍
barrier: -1
}
//基本配置
var config = {
//网格数目x
grid_x_num: 30,
//网格数目y
grid_y_num: 20,
//障碍数目
barrier_num: 150,
//网格尺寸宽度
grid_size_width: 20,
//网格尺寸高度
grid_size_height: 20
}
//A*路径算法
function APathFind(canvas) {
//状态数组
this.aryGridInfo = new Array();
//查询任务
this.aryTask = new Array();
//画布设置
this.sizeX = config.grid_x_num;
this.sizeY = config.grid_y_num;
this.sizeWidth = config.grid_size_width;
this.sizeHeight = config.grid_size_height;
this.barrierNum = config.barrier_num;
//绘制画板
this.canvas = canvas;
this.canvas.css({ position: 'relative' });
//Grid模板
templeteGrid = $("<div/>").css({
position: 'absolute',
width: this.sizeWidth + 'px',
height: this.sizeHeight + 'px',
borderStyle: 'solid',
borderWidth: '1px',
borderColor: '#afafaf'
});
templeteGrid.click(this.select.bind(this));
for (var i = 0; i < this.sizeX; i++) {
for (var j = 0; j < this.sizeY; j++) {
var left = this.sizeWidth * i;
var top = this.sizeHeight * j;
var jGrid = templeteGrid.clone(true);
jGrid.css({ left: left + 'px', top: top + 'px' });
jGrid.attr('id', 'grid_' + i + '_' + j).attr('x', i).attr('y', j);
this.canvas.append(jGrid);
}
};
//初始化
this.init();
//绘制
this.render();
};
//初始化方法
APathFind.prototype.init = function() {
//开始点
this.bx = -1;
this.by = -1;
//当前点
this.cx = -1;
this.cy = -1;
//结束点
this.ex = -1;
this.ey = -1;
//初始化二维数组
for (var i = 0; i < this.sizeX; i++) {
this.aryGridInfo[i] = new Array();
for (var j = 0; j < this.sizeY; j++) {
this.aryGridInfo[i][j] = {
status: status.grid,
f: Number.MAX_VALUE,
g: Number.MAX_VALUE,
h: Number.MAX_VALUE,
px: -1,
py: -1,
close: false
};
}
}
//在数据中标注障碍 标记-1
for (var k = 0; k < this.barrierNum; k++) {
var rx = Math.floor(Math.random() * this.sizeX);
var ry = Math.floor(Math.random() * this.sizeY);
this.aryGridInfo[rx][ry].status = status.barrier;
}
}
//呈现方法
APathFind.prototype.render = function() {
var grids = this.canvas.children();
for (var i = 0; i < grids.length; i++) {
var jGrid = $(grids[i]);
var x = jGrid.attr("x");
var y = jGrid.attr("y");
var gridInfo = this.aryGridInfo[x][y];
if (gridInfo.status == status.grid) {
jGrid.css({ backgroundColor: '#efefef' });
} else if (gridInfo.status == status.barrier) {
jGrid.css({ backgroundColor: '#0000ff' });
} else if (gridInfo.status == status.start) {
jGrid.css({ backgroundColor: '#ff0000' });
} else if (gridInfo.status == status.end) {
jGrid.css({ backgroundColor: '#00ff00' });
} else if (gridInfo.status == status.path) {
jGrid.css({ backgroundColor: '#00ffff' });
}
}
}
//选择方法
APathFind.prototype.select = function(e) {
var jGrid = $(e.currentTarget);
var x = jGrid.attr("x");
var y = jGrid.attr("y");
//有效性检测
if (this.aryGridInfo[x][y].status != status.grid) {
alert("位置选择错误!");
return;
}
//状态检测
if (this.bx == -1 && this.by == -1) {//起点未选择
this.bx = x;
this.by = y;
this.aryGridInfo[x][y].status = status.start;
} else if (this.ex == -1 && this.ey == -1) {//终点未选择
this.ex = x;
this.ey = y;
this.aryGridInfo[x][y].status = status.end;
//路径寻找
this.find();
} else {
alert("已完成路径寻找,请刷新后再试!");
}
this.render();
}
//路径搜索
APathFind.prototype.find = function() {
this.cx = this.bx;
this.cy = this.by;
this.cGrid = this.aryGridInfo[this.cx][this.cy];
//关闭起点
this.cGrid.close = true;
//距离起点的权重 水平垂直+10 斜角+14
this.cGrid.g = 0;
//距离终点的权重 曼哈顿数 横坐标网格差*10 + 纵坐标网格差 * 10
this.cGrid.h = Math.abs(this.ey - this.cy) * 10 + Math.abs(this.ex - this.cx) * 10;
//整体权重 = 距离起点的权重 + 距离终点的权重
this.cGrid.f = this.cGrid.g + this.cGrid.h;
//是否移动到末尾
var isMoveEnd = false;
try {
do {
var cmp = { f: Number.MAX_VALUE, x: -1, y: -1 };
this.cx = parseInt(this.cx);
this.cy = parseInt(this.cy);
//检测左网格
var lGrid = this.checkGrid(this.cx - 1, this.cy, 10);
cmp = this.CmpGrid(lGrid, cmp, this.cx - 1, this.cy);
//检测右网格
var rGrid = this.checkGrid(this.cx + 1, this.cy, 10);
cmp = this.CmpGrid(rGrid, cmp, this.cx + 1, this.cy);
//检测上网格
var uGrid = this.checkGrid(this.cx, this.cy - 1, 10);
cmp = this.CmpGrid(uGrid, cmp, this.cx, this.cy - 1);
//检测下网格
var dGrid = this.checkGrid(this.cx, this.cy + 1, 10);
cmp = this.CmpGrid(dGrid, cmp, this.cx, this.cy + 1);
//如果左网格和上网格没有障碍 左上网格
if (lGrid != null && uGrid != null) {
this.checkGrid(this.cx - 1, this.cy - 1, 14);
}
//如果左网格和下网格没有障碍 左下网格
if (lGrid != null && dGrid != null) {
this.checkGrid(this.cx - 1, this.cy + 1, 14);
}
//如果右网格和上网格没有障碍 右上网格
if (rGrid != null && uGrid != null) {
this.checkGrid(this.cx + 1, this.cy - 1, 14);
}
//如果右网格和下网格没有障碍 右下网格
if (rGrid != null && dGrid != null) {
this.checkGrid(this.cx + 1, this.cy + 1, 14);
}
if (cmp.f < Number.MAX_VALUE) {
this.cx = cmp.x;
this.cy = cmp.y;
} else {
var grid = this.aryTask.pop();
if (grid) {
this.cx = grid.x;
this.cy = grid.y;
} else {
isMoveEnd = true;
}
}
this.cGrid = this.aryGridInfo[this.cx][this.cy];
this.aryGridInfo[this.cx][this.cy].close = true;
} while (isMoveEnd == false);
} catch (e) { var a = 1; };
//绘制返回路径
var eGrid = this.aryGridInfo[this.ex][this.ey];
if (eGrid.px != -1 && eGrid.py != -1) {
var x = eGrid.px;
var y = eGrid.py;
while (this.bx != x || this.by != y) {
var grid = this.aryGridInfo[x][y];
grid.status = status.path;
x = grid.px;
y = grid.py;
}
} else {
alert("没有发现合适路径!");
this.ex = -1;
this.ey = -1;
}
// for (var i = 0; i < this.sizeX; i++) {
// for (var j = 0; j < this.sizeY; j++) {
// var grid = this.aryGridInfo[i][j];
// var info = "x:" + i;
// info += " y:" + j;
// info += "<br/>f:";
// info += (grid.f == Number.MAX_VALUE) ? "Max" : grid.f;
// info += "<br/>g:";
// info += (grid.g == Number.MAX_VALUE) ? "Max" : grid.g;
// info += "<br/>h:";
// info += (grid.h == Number.MAX_VALUE) ? "Max" : grid.h;
// info += "<br/>status:" + grid.status;
// info += " px:" + grid.px;
// info += " py:" + grid.py;
// $('#grid_' + i + '_' + j).html(info);
// }
// }
}
APathFind.prototype.checkGrid = function(x, y, power) {
if (x >= 0 && x < this.sizeX && y >= 0 && y < this.sizeY) {
var grid = this.aryGridInfo[x][y];
if (grid.status == status.grid) {//空白网格
//检测权重是否更低 (距离是否更近)
if (this.cGrid.g + power < grid.g) {
grid.g = this.cGrid.g + power;
grid.h = Math.abs(this.ex - x) * 10 + Math.abs(this.ey - y) * 10;
grid.f = grid.g + grid.h;
grid.px = this.cx;
grid.py = this.cy;
//如果网格不是关闭的,加入任务列表
if (grid.close != true) {
this.aryTask.push({ x: x, y: y });
}
}
return grid;
} else if (grid.status == status.end) {//遇到终点
grid.g = this.cGrid.g + power;
grid.h = Math.abs(this.ex - x) * 10 + Math.abs(this.ey - y) * 10;
grid.f = this.cGrid.g + this.cGrid.h;
grid.px = this.cx;
grid.py = this.cy;
throw "finish";
}
}
return null;
}
//比较网格 [寻找周边下一步网格]
APathFind.prototype.CmpGrid = function(gird, cmp, x, y) {
if (gird != null) {
if (gird.f < cmp.f && gird.close != true) {
cmp.f = gird.f;
cmp.x = x;
cmp.y = y;
}
}
return cmp;
}
$(document).ready(function() {
//创建画板
var canvas = $("#canvas");
//创建对象
var pathfind = new APathFind(canvas);
$("#btnInit").click(function() { pathfind.init(); pathfind.render(); });
});
</script>
</head>
<body>
<div>
<span>通过鼠标选取一个起点和一个终点</span>
<input id="btnInit" type="button" value="刷新" />
</div>
<div id="canvas"></div>
</body>
</html>