在游戏开发中,又一个很常见的需求,就是让一角色从A点走到B点,而我们期望所走的路是最短的,最容易想到的就是两点之间直线最短,我们可以通过勾股定理来求出两点之间的距离,但这个情况只能用于两点之间没有障碍物的情况,如果两点之间有很多不可避免无法穿过的障碍物的时候,怎么办呢?因此,我们的需求就是:计算出两点之间的最短路径,而且能够避开所有的障碍物

A-star javascript实现

第一种情况:

在这种情况下,所走路径方向朝上

第二种情况:

当我们把上方的障碍物增多的时候,选择走的路径就往下走

第三种情况:

在障碍物中间打开一个口子,那么所选择的路径就会之间从中间穿过

前置知识,百度百科:

  启发式搜索:启发式搜索(Heuristically Search)又称为有信息搜索(Informed Search),它是利用问题拥有的启发信息来引导搜索,达到减少搜索范围、降低问题复杂度的目的,这种利用启发信息的搜索过程称为启发式搜索。

   A-Star(A*)算法的核心:将游戏背景分成一个又一个的格子,每个格子计算出一个估值,然后遍历起点的格子去找格子周围估值最小的节点作为下一步要走的路径,然后递归遍历,直到找到目标点

实现算法的关键点

  1 将游戏背景划分成大小一样的正方形格子 ,我们把这些格子作为算法的基本单元,大小自定义

  2 创建open队列(也就是一个数组),这个队列里面放可能会走的单元格

  3 创建close队列(也是一个数组),这个队列里面放不能走的单元格,这里会包括已经走过的单元格和所有的障碍物

  4  对所有的单元格进行估值

  5  找到当前单元格周围的单元格,并且从这些单元格种找到估值最小的单元格

  6  给每个走过的单元格加指针,记录所走过的路径,以便于打印最终路线

估价函数

  在A*算法种用到的就是启发式搜索,而启发式搜索是利用拥有问题的启发信息来引导搜索的,所以我们需要把当前单元格周围的点都找出来进行估值,这个估值表示当前点到目标点的实际代价,如果到目标点的实际代价越高,那么说明要走的路就越长,因此,我们需要找到估值最低的作为下一步要走的路径

  估价函数公式:fn(n) = g(n)+h(n)

  其中:fn(n)  表示节点n的估值,g(n) 表示初始点到当前节点的实际代价,h(n)表示当前节点到目标点的实际代价,这里的实际代价就是这他们之间的最短距离

  

具体代码实现:

1 完成ui布局,这一步我们动态创建出一个 20 * 20 的正方形地图,每个格子大小也为20

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
#ul1{
margin: 30px auto;
border: 1px solid black;
border-bottom: none;
border-right:none ;
padding: 0;
height: auto;
overflow: hidden;
}
#ul1 li{
list-style: none;
border: 1px solid black;
border-top:none ;
border-left:none ;
float: left;
}
#ul1 li.style1{
background-color: red;
}
#ul1 li.style2{
background-color: black;
}
#ul1 li.style3{
background-color: orange;
}
#btn{
position: absolute;
left: 50%;
margin-left: -50px;
}
#btn:hover{
background-color: #E21918;
color: white;
border-radius: 4px;
}
</style>
</head>
<body>
<ul id="ul1"> </ul>
<input id="btn" type="button" value="开始寻路"/>
<script type="text/javascript">
//1 找对象
var oUl = document.getElementById("ul1");
var aLi = document.getElementsByTagName("li");
var oBtn = document.getElementById("btn")
//创建一个数组来表示地图,这个地图 20 * 20 其中 值为1 表示起始节点 值为2 表示障碍物 值为3 表示目标节点
var map = [
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,2,2,2,2,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,
0,0,0,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
] /*
* 定义初始化函数
*/
function init(){
//在这个函数中需要动态创建出li 通过调用封装好的函数实现
createMap()
}
//调用初始化函数
init();
/*
* 定义创建地图函数
*/
function createMap(){
//定义li的大小
var liSize = 20;
for(var i=0;i<map.length;i++){
//创建元素
var oLi = document.createElement("li");
//设置宽高
oLi.style.width = liSize +"px";
oLi.style.height = liSize + "px";
//添加到li中
oUl.appendChild(oLi);
//判断map元素的值来设置起始点以及障碍物
if(map[i]==1){
//如果map值为1 设置样式1 背景为红色 表示起始点
oLi.className = "style1";
}else if(map[i]==2){
//如果map值为2 设置样式2 背景为黑色 表示障碍物
oLi.className = "style2";
}else if(map[i]==3){
//如果map值为3 设置样式3 背景为橙色 表示结束点
oLi.className = "style3"
}
}
oUl.style.width = 20*(liSize+1)+1+"px"
}
</script>
</body>
</html>

2  估价函数封装

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
#ul1{
margin: 30px auto;
border: 1px solid black;
border-bottom: none;
border-right:none ;
padding: 0;
height: auto;
overflow: hidden;
}
#ul1 li{
list-style: none;
border: 1px solid black;
border-top:none ;
border-left:none ;
float: left;
}
#ul1 li.style1{
background-color: red;
}
#ul1 li.style2{
background-color: black;
}
#ul1 li.style3{
background-color: orange;
}
#btn{
position: absolute;
left: 50%;
margin-left: -50px;
}
#btn:hover{
background-color: #E21918;
color: white;
border-radius: 4px;
}
</style>
</head>
<body>
<ul id="ul1"> </ul>
<input id="btn" type="button" value="开始寻路"/>
<script type="text/javascript">
//1 找对象
var oUl = document.getElementById("ul1");
var aLi = document.getElementsByTagName("li");
var oBtn = document.getElementById("btn");
//找到起始点,因为我们给起始点设置了不同的样式 所以可以通过样式可以找到起始点
var beginLi = document.getElementsByClassName("style1");
var endLi = document.getElementsByClassName("style3");
//创建一个数组来表示地图,这个地图 20 * 20 其中 值为1 表示起始节点 值为2 表示障碍物 值为3 表示目标节点
var map = [
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,2,2,2,2,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,
0,0,0,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
] /*
* 定义初始化函数
*/
function init(){
//在这个函数中需要动态创建出li 通过调用封装好的函数实现
createMap()
}
//调用初始化函数
init();
/*
* 定义创建地图函数
*/
function createMap(){
//定义li的大小
var liSize = 20;
for(var i=0;i<map.length;i++){
//创建元素
var oLi = document.createElement("li");
//设置宽高
oLi.style.width = liSize +"px";
oLi.style.height = liSize + "px";
//添加到li中
oUl.appendChild(oLi);
//判断map元素的值来设置起始点以及障碍物
if(map[i]==1){
//如果map值为1 设置样式1 背景为红色 表示起始点
oLi.className = "style1";
}else if(map[i]==2){
//如果map值为2 设置样式2 背景为黑色 表示障碍物
oLi.className = "style2";
}else if(map[i]==3){
//如果map值为3 设置样式3 背景为橙色 表示结束点
oLi.className = "style3"
}
} //ul的宽带等于 ul的左边 1 + 20个节点的宽带 20*(liSize+1) 其中 liSize+1 是因为 节点有1个像素的右边框
oUl.style.width = 20*(liSize+1)+1+"px"
}
/**
* 估价函数封装,传入一个节点给他一个估值
*/ function fn(nowLi){
return g(nowLi)+h(nowLi)
}
//初始点到当前节点的实际代价
function g(nowLi){
//勾股定理
//横坐标的差值 beginLi[0] 开始节点 nowLi表示当前正在被估值的节点
var a = nowLi.offsetLeft-beginLi[0].offsetLeft;
//纵坐标的差值
var b = nowLi.offsetTop - beginLi[0].offsetTop;
//勾股定理 开方得到两点之间最短距离
return Math.sqrt(a*a+b*b)
}
//当前节点到目标点的实际代价
function h(nowLi){
//勾股定理,这里和g函数一样的原理 endLi[0] 结束节点 nowLi表示当前正在被估值的节点
var a = nowLi.offsetLeft-endLi[0].offsetLeft;
var b = nowLi.offsetTop - endLi[0].offsetTop;
return Math.sqrt(a*a+b*b)
}
</script>
</body>
</html>

3 定义开始队列和结束队列数组 并且封装函数实现节点添加

            /*
* 封装函数 实现开始队列中元素的添加
*/ function openFn(){
//1 需要把openArr中的第一个元素删除 并且返回给 nodeLi变量
var nodeLi = openArr.shift();
//2 把openArr中删除的这个li节点 添加到closeArr中 这里调用closeFn函数实现
closeFn(nodeLi); }
/*
* 封装函数 实现结束队列中元素的添加
*
*/
function closeFn(nodeLi){
//open队列中删除的元素 被 push到close队列中
closeArr.push(nodeLi);
}

疑问?1openFn 什么时候执行?  openArr 中什么时候有数据? 所以需要添加以下代码:

1) 在初始化函数中添加按钮的点击事件
           //初始化函数
function init(){
createMap()
//点击按钮的时候 需要去收集可能走的路线
oBtn.onclick = function(){
openFn();
}
}
2) 在创建地图的时候,就要区分出 起始点和障碍物 并且把起点放在open队列数组中,把障碍物和放在close队列数组中
           function createMap(){
//定义li的大小
var liSize = 20;
for(var i=0;i<map.length;i++){
//创建元素
var oLi = document.createElement("li");
//设置宽高
oLi.style.width = liSize +"px";
oLi.style.height = liSize + "px";
//添加到li中
oUl.appendChild(oLi);
//判断map元素的值来设置起始点以及障碍物
if(map[i]==1){
//如果map值为1 设置样式1 背景为红色 表示起始点
oLi.className = "style1";
//当元素刚开始创建的时候,open队列中的元素只有 起始节点 也就是说将红色点都放到open队列中 并且 刚开始的时候 起始点只有一个
openArr.push(oLi); //这里是新增加的代码
}else if(map[i]==2){
//如果map值为2 设置样式2 背景为黑色 表示障碍物
oLi.className = "style2";
//把黑色的点都放到close队列中 这些作为障碍物 是不会走的
closeArr.push(oLi); //这里是新增加的代码
}else if(map[i]==3){
//如果map值为3 设置样式3 背景为橙色 表示结束点
oLi.className = "style3"
}
}
//ul的宽带等于 ul的左边 1 + 20个节点的宽带 20*(liSize+1) 其中 liSize+1 是因为 节点有1个像素的右边框
oUl.style.width = 20*(liSize+1)+1+"px"
}

经过上面两步,完整代码如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
#ul1{
margin: 30px auto;
border: 1px solid black;
border-bottom: none;
border-right:none ;
padding: 0;
height: auto;
overflow: hidden;
}
#ul1 li{
list-style: none;
border: 1px solid black;
border-top:none ;
border-left:none ;
float: left;
}
#ul1 li.style1{
background-color: red;
}
#ul1 li.style2{
background-color: black;
}
#ul1 li.style3{
background-color: orange;
}
#btn{
position: absolute;
left: 50%;
margin-left: -50px;
}
#btn:hover{
background-color: #E21918;
color: white;
border-radius: 4px;
}
</style>
</head>
<body>
<ul id="ul1"> </ul>
<input id="btn" type="button" value="开始寻路"/>
<script type="text/javascript">
//1 找对象
var oUl = document.getElementById("ul1");
var aLi = document.getElementsByTagName("li");
var oBtn = document.getElementById("btn");
//找到起始点,因为我们给起始点设置了不同的样式 所以可以通过样式可以找到起始点
var beginLi = document.getElementsByClassName("style1");
var endLi = document.getElementsByClassName("style3");
//定义开始队列数组 open队列: 收集可能会需要走的路线 要走的路线放在open队列中
var openArr = []
//定义结束队列数组 close队列: 排除掉不能走的路线 不走的路线放在close队列中
var closeArr = []
//创建一个数组来表示地图,这个地图 20 * 20 其中 值为1 表示起始节点 值为2 表示障碍物 值为3 表示目标节点
var map = [
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,2,2,2,2,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,
0,0,0,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
] /*
* 定义初始化函数
*/
function init(){
//在这个函数中需要动态创建出li 通过调用封装好的函数实现
createMap()
}
//调用初始化函数
init();
/*
* 定义创建地图函数
*/
function createMap(){
//定义li的大小
var liSize = 20;
for(var i=0;i<map.length;i++){
//创建元素
var oLi = document.createElement("li");
//设置宽高
oLi.style.width = liSize +"px";
oLi.style.height = liSize + "px";
//添加到li中
oUl.appendChild(oLi);
//判断map元素的值来设置起始点以及障碍物
if(map[i]==1){
//如果map值为1 设置样式1 背景为红色 表示起始点
oLi.className = "style1";
//当元素刚开始创建的时候,open队列中的元素只有 起始节点 也就是说将红色点都放到open队列中 并且 刚开始的时候 起始点只有一个
openArr.push(oLi);
}else if(map[i]==2){
//如果map值为2 设置样式2 背景为黑色 表示障碍物
oLi.className = "style2";
//把黑色的点都放到close队列中 这些作为障碍物 是不会走的
closeArr.push(oLi);
}else if(map[i]==3){
//如果map值为3 设置样式3 背景为橙色 表示结束点
oLi.className = "style3"
}
}
oUl.style.width = 20*(liSize+1)+1+"px"
}
/**
* 估价函数封装,传入一个节点给他一个估值
*/ function fn(nowLi){
return g(nowLi)+h(nowLi)
}
//初始点到当前节点的实际代价
function g(nowLi){
//勾股定理
//横坐标的差值 beginLi[0] 开始节点 nowLi表示当前正在被估值的节点
var a = nowLi.offsetLeft-beginLi[0].offsetLeft;
//纵坐标的差值
var b = nowLi.offsetTop - beginLi[0].offsetTop;
//勾股定理 开方得到两点之间最短距离
return Math.sqrt(a*a+b*b)
}
//当前节点到目标点的实际代价
function h(nowLi){
//勾股定理,这里和g函数一样的原理 endLi[0] 结束节点 nowLi表示当前正在被估值的节点
var a = nowLi.offsetLeft-endLi[0].offsetLeft;
var b = nowLi.offsetTop - endLi[0].offsetTop;
return Math.sqrt(a*a+b*b)
} /*
* 封装函数 实现开始队列中元素的添加
*/ function openFn(){
//1 需要把openArr中的第一个元素删除 并且返回给 nodeLi变量
var nodeLi = openArr.shift();
//2 把openArr中删除的这个li节点 添加到closeArr中 这里调用closeFn函数实现
closeFn(nodeLi); }
/*
* 封装函数 实现结束队列中元素的添加
*
*/
function closeFn(nodeLi){
//open队列中删除的元素 被 push到close队列中
closeArr.push(nodeLi);
}
</script>
</body>
</html>

4 上面的步骤已经把  开始节点放到了open队列中   把障碍物放到了 close队列中,接下来我们要做的就是 寻找下一个要走的节点,并且把这个节点添加到close队列中

1) 封装函数查找周围的节点
             /**
* 封装函数查找某个节点周围的节点
*/
function findLi(nodeLi){
//创建一个结果数组 把查找到的结果放到这个数组中
var result = [];
//循环所有的li节点 进行查找
for(var i=0;i<aLi.length;i++){
//如果经过过滤 返回的是true 表示 这个节点不是障碍物 那么需要添加到result结果数组中
if(filter(aLi[i])){
result.push(aLi[i]);
}
}
//接下来需要在没有障碍物的结果中去找 和 当前节点相邻的节点
//判断条件是 他们的横纵坐标的差值需要小于 等于 网格大小
for(var i=0;i<result.length;i++){
if(Math.abs(nodeLi.offsetLeft - result[i].offsetLeft)<=21 && Math.abs(nodeLi.offsetTop - result[i].offsetTop)<=20+1 ){
//这里的result[i]就是当前目标点相邻的节点 把这些节点传入到估价函数就能得到他们的估值,并且要把这些估值挂载到他们自身的一个自定义属性上
result[i].num = fn(result[i]);
//把已经经过估值的li添加到openArr中
openArr.push(result[i]);
}
}
}
/**
* 封装函数 实现过滤功能
* 这个函数的功能就是 接收到一个li 判断是否是障碍物 如果是 就返回false 如果不是就返回true
*/
function filter(nodeLi){
//循环close队列中的所有元素 与传过来的节点进行比对 如果比对成功 返回false
for(var i=0;i<closeArr.length;i++){
if(nodeLi == closeArr[i]){
return false;
}
}
for(var i=0;i<openArr.length;i++){
if(nodeLi == openArr[i]){
return false;
}
}
//如果循环完都没有匹配上 那么证明当前传过来的 li节点 并不是障碍物
return true;
}
2) 上面函数封装好以后 需要在openFn函数中进行调用
            function openFn(){
//nodeLi 表示 当前open队列中的元素 也就是说 先去除第一个起始节点
//shift 方法的作用: 把数组中的第一个元素删除,并且返回这个被删除的元素
var nodeLi = openArr.shift();
//如果nodeLi 和 endLi 一样了 那么证明已经走到目标点了 ,这个时候需要停止调用
if(nodeLi == endLi[0]){ return;
}
//把open队列中删除的元素 添加到 close队列中
closeFn(nodeLi)
//接下来 需要找到 nodeLi 周围的节点
findLi(nodeLi); }
3) 经过上面的函数调用 在openArr数组中就已经 添加了 当前路径 周围的8个点,我们要从这8个点中去寻找一个估值最小的作为下一步要走的点

4) 接下来需要对openArr中的点进行估值排序
          function openFn(){
//nodeLi 表示 当前open队列中的元素 也就是说 先去除第一个起始节点
//shift 方法的作用: 把数组中的第一个元素删除,并且返回这个被删除的元素
var nodeLi = openArr.shift();
//如果nodeLi 和 endLi 一样了 那么证明已经走到目标点了 ,这个时候需要停止调用
if(nodeLi == endLi[0]){
showPath();
return;
}
//把open队列中删除的元素 添加到 close队列中
closeFn(nodeLi)
//接下来 需要找到 nodeLi 周围的节点,并且对这些点进行估值
findLi(nodeLi); //经过上面的步骤 已经能够找到相邻的元素了 接下来需要对这些元素的估值进行排序
openArr.sort(function(li1,li2){
return li1.num - li2.num
}) }
5) 经过上面的步骤 就已经能够确定下一步要走的点了  接下来要做的就是同样的操作 因此需要 递归调用openFn函数,在找到目标点的时候停止
           function openFn(){
//nodeLi 表示 当前open队列中的元素 也就是说 先去除第一个起始节点
//shift 方法的作用: 把数组中的第一个元素删除,并且返回这个被删除的元素
var nodeLi = openArr.shift();
//如果nodeLi 和 endLi 一样了 那么证明已经走到目标点了 ,这个时候需要停止调用
if(nodeLi == endLi[0]){
showPath();
return;
}
//把open队列中删除的元素 添加到 close队列中
closeFn(nodeLi)
//接下来 需要找到 nodeLi 周围的节点
findLi(nodeLi); //经过上面的步骤 已经能够找到相邻的元素了 接下来需要对这些元素的估值进行排序
openArr.sort(function(li1,li2){
return li1.num - li2.num
}) //进行递归操作 找下一步需要走的节点 在这个过程中,也需要执行相同的步
// 那就是查找相邻的节点 但是查找出来的结果可能和上一次的重复,也就是说上一次动作已经把这个元素添加到open队列中了
//那么就没有必要再进行push操作了 所以还需要在过滤函数中加一段代码
openFn();
}

5 打印出所走的路径

1) 给所走过的路径设置一个父指针,例如: 当前的节点应该有一个属性来存上一个节点,这样我们就可以从最后一个节点倒推出上面所有节点

在找到下一个要走的点的时候,把上一个已经走过的点挂载到下一个要走的点身上

             /**
* 封装函数查找某个节点周围的节点
*/
function findLi(nodeLi){
//创建一个结果数组 把查找到的结果放到这个数组中
var result = [];
//循环所有的li节点 进行查找
for(var i=0;i<aLi.length;i++){
//如果经过过滤 返回的是true 表示 这个节点不是障碍物 那么需要添加到result结果数组中
if(filter(aLi[i])){
result.push(aLi[i]);
}
}
//接下来需要在没有障碍物的结果中去找 和 当前节点相邻的节点
//判断条件是 他们的横纵坐标的差值需要小于 等于 网格大小
for(var i=0;i<result.length;i++){
if(Math.abs(nodeLi.offsetLeft - result[i].offsetLeft)<=21 && Math.abs(nodeLi.offsetTop - result[i].offsetTop)<=20+1 ){
//这里的result[i]就是当前目标点相邻的节点 把这些节点传入到估价函数就能得到他们的估值,并且要把这些估值挂载到他们自身的一个自定义属性上
result[i].num = fn(result[i]);
//nodeLi 是当前的位置 result[i] 是当前位置相邻的点 下一次要走的位置就在这几个点中,所以给result[i]定义一个parent属性
//来存上一次的路径 ,最终把这些路径联系起来就是完整的路径
result[i].parent = nodeLi;
//把已经经过估值的li添加到openArr中
openArr.push(result[i]);
}
}
}
2)  封装一个函数来找到所有已经走过的点
           //最终线路数组
var resultParent = [];
/**
* 定义一个函数来找到上一次走过的节点
*/ function findParent(li){
resultParent.unshift(li);
if(li.parent == beginLi[0]){
return;
}
findParent(li.parent);
}
3) 封装函数来打印  路径
             /**
* 打印出所走过的路径
*/
function showPath(){
//closeArr中最后一个 就是 找到目标点的前一个位置 因为走过的位置都会被存放在closeArr中
var lastLi = closeArr.pop();
var iNow = 0;
//调用findParent函数 来找上一个节点
findParent(lastLi) var timer = setInterval(function(){
resultParent[iNow].style.background = "red";
iNow++;
if(iNow == resultParent.length){
clearInterval(timer);
}
},500)
}

4) 在找到最终目标点的时候 调用打印路径函数

             function openFn(){
//nodeLi 表示 当前open队列中的元素 也就是说 先去除第一个起始节点
//shift 方法的作用: 把数组中的第一个元素删除,并且返回这个被删除的元素
var nodeLi = openArr.shift();
//如果nodeLi 和 endLi 一样了 那么证明已经走到目标点了 ,这个时候需要停止调用
if(nodeLi == endLi[0]){
showPath();
return;
}
//把open队列中删除的元素 添加到 close队列中
closeFn(nodeLi)
//接下来 需要找到 nodeLi 周围的节点
findLi(nodeLi); //经过上面的步骤 已经能够找到相邻的元素了 接下来需要对这些元素的估值进行排序
openArr.sort(function(li1,li2){
return li1.num - li2.num
}) //进行递归操作 找下一步需要走的节点 在这个过程中,也需要执行相同的步骤 那就是查找相邻的节点
//但是查找出来的结果可能和上一次的重复,也就是说上一次动作已经把这个元素添加到open队列中了
//那么就没有必要再进行push操作了 所以还需要在过滤函数中加一段代码
openFn();
}

走到这里 我们的算法已经基本实现

完整代码地址:https://github.com/dadifeihong/algorithm

javascript 实现 A-star 寻路算法的更多相关文章

  1. javascript的Astar版 寻路算法

    去年做一个模仿保卫萝卜的塔防游戏的时候,自己写的,游戏框架用的是coco2d-html5 实现原理可以参考 http://www.cnblogs.com/technology/archive/2011 ...

  2. A*(也叫A star, A星)寻路算法Java版

    寻路算法有非常多种,A*寻路算法被公觉得最好的寻路算法. 首先要理解什么是A*寻路算法,能够參考这三篇文章: http://www.gamedev.net/page/resources/_/techn ...

  3. 算法:Astar寻路算法改进

    早前写了一篇<RCP:gef智能寻路算法(A star)> 出现了一点问题. 在AStar算法中,默认寻路起点和终点都是N x N的方格,但如果用在路由上,就会出现问题. 如果,需要连线的 ...

  4. 无递归 A星寻路算法

    整理硬盘的时候,发现我早些年写的A星寻路算法.特放上来,待有缘人拿去,无递归噢,性能那是杠杠的. 码上伺候 public class Node { public int X { get; set; } ...

  5. 不再依赖A*,利用C++编写全新寻路算法

    一,说在前面的话 大概在半年前,看见一到信息竞赛题:在任意方格阵中设置障碍物,确定起始点后,求这两点之间路径.当时觉得蛮有意思的,但是没有时间去做,今天花了两个小时来实现它.据说有一个更高级的寻路算法 ...

  6. 一种高效的寻路算法 - B*寻路算法

    在此把这个算法称作B* 寻路算法(Branch Star 分支寻路算法,且与A*对应),本算法适用于游戏中怪物的自动寻路,其效率远远超过A*算法,经过测试,效率是普通A*算法的几十上百倍. 通过引入该 ...

  7. PHP树生成迷宫及A*自己主动寻路算法

    PHP树生成迷宫及A*自己主动寻路算法 迷宫算法是採用树的深度遍历原理.这样生成的迷宫相当的细,并且死胡同数量相对较少! 随意两点之间都存在唯一的一条通路. 至于A*寻路算法是最大众化的一全自己主动寻 ...

  8. 数据结构和算法总结(三):A* 寻路算法

    前言 复习下寻路相关的东西,而且A star寻路在游戏开发中应用挺多的,故记录下. 正文 迪杰斯特拉算法 说起A*得先谈谈Dijkstra算法,它是在BFS基础上的一种带权值的两点最短寻路贪心算法. ...

  9. A星寻路算法介绍

    你是否在做一款游戏的时候想创造一些怪兽或者游戏主角,让它们移动到特定的位置,避开墙壁和障碍物呢? 如果是的话,请看这篇教程,我们会展示如何使用A星寻路算法来实现它! 在网上已经有很多篇关于A星寻路算法 ...

  10. A*寻路算法探究

    A*寻路算法探究 A*算法常用在游戏的寻路,是一种静态网路中求解最短路径的搜索方法,也是解决很多搜索问题的算法.相对于Dijkstra,BFS这些算法在复杂的搜索更有效率.本文在U3D中进行代码的测试 ...

随机推荐

  1. Scrum立会报告+燃尽图(Beta阶段第二次)

    此作业要求参见:https://edu.cnblogs.com/campus/nenu/2018fall/homework/2384 项目地址:https://coding.net/u/wuyy694 ...

  2. CS小分队第二阶段冲刺站立会议(5月29日)

    昨日成果:昨天在为主界面设计自主添加应用快捷方式功能,连续遇到困难. 遇到的困难:1.string字符串数组无法在单击事件中使用,提示string无法在eventargs中检索,尝试了各种方式都不行 ...

  3. C++ Primer Plus学习:第十章

    过程性编程和面向对象编程 面向对象编程(OOP)的特性: 抽象 封装和数据隐藏 多态 继承 代码的可重用性 抽象和类 类是一种将抽象转化为用户定义类型的C++工具,它将数据表示和操纵数据的方法合成一个 ...

  4. vsftpd 安全性能工具

    vsftpd实战(服务端192.168.23.12,客户端192.168.23.11) 1:安装vsftpdyum install -y vsftpd 2:客户端安装lftpyum install - ...

  5. 结对编程学习fault、error、failure三种状态

    点滴成就 学习时间 新编写代码行数 博客量(篇) 学习知识点 第一周 10小时 0 0 了解软件工程 第二周 10小时 0 1 项目开题 第三周 15小时 0 1 开通博客.开展项目调查 第四周 20 ...

  6. Matlab中TCP通讯-实现外部程序提供优化目标函数解

    版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:Matlab中TCP通讯-实现外部程序提供优化目标函数解     本文地址:http://te ...

  7. 第六周PSP &进度条

    团队项目PSP 一.表格:     C类型 C内容 S开始时间 E结束时间 I时间间隔 T净时间(mins) 预计花费时间(mins) 讨论 讨论alpha完成情况并总结 9:40 11:20 17 ...

  8. 【leetcode】215. Kth Largest Element in an Array

    Find the kth largest element in an unsorted array. Note that it is the kth largest element in the so ...

  9. CentOS系统iptables防火墙的启动、停止以及开启关闭端口的操作

    CentOS 配置防火墙操作实例(启.停.开.闭端口):注:防火墙的基本操作命令:查询防火墙状态:[root@localhost ~]# service   iptables status停止防火墙: ...

  10. Python @retry装饰器的使用与实现案例(requests请求失败并重复请求)

    在爬虫代码的编写中,requests请求网页的时候常常请求失败或错误,一般的操作是各种判断状态和超时,需要多次重试请求,这种情况下,如果想优雅的实现功能,可以学习下retrying包下的retry装饰 ...