直接上代码,里面的注释很详细,只是简单的实现了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>走动步数</title>
</head>
<body >
<canvas id="canvas" style="border:1px solid #d3d3d3;">
你的浏览器不支持h5
</canvas>
<script>
/**
* 构思:首先,用深搜便利出行动力所能达到的所有点 ,在显示出来
*
*/
//人物的初始坐标 和 人物的行动力
var menx = 10,meny = 10,stap = 6;
//大地图的长款
var n = 50,m = 50;
//人物行走时的坐标
var el={x:0,y:0};
//所有行走路线的数组
var xingZouluxian= [];
function $(id){
return document.getElementById(id);
}
var canvas = $("canvas");
var ctx = canvas.getContext("2d");
canvas.width = 500;//画布的宽
canvas.height = 500;//画布的高
ctx.fillRect(menx*10,meny*10,10,10);//实心小方块 人物显示的地方
canvas.addEventListener("click",function(e){
var x = e.offsetX;
var y = e.offsetY;
el.x = Math.floor(x/10);
el.y = Math.floor(y/10);
//通过搜索 计算人物在行动力范围类 可以到达的地方
dfs(menx,meny,stap);
canvas.height = canvas.height;//重绘画布
a[menx][meny] = 1;//人物坐标给初始化为0,避免下面重绘给覆盖掉
if(x>menx*10&&x<menx*10+10&&y>meny*10&&y<meny*10+10){//判断点击的是否在人物身体范围
ctx.save();//记住画布状态
ctx.fillStyle = "#4A4AFF";//颜色
//绘制行动路线
for(var i =0;i< a.length;i++){
for(var j =0;j<a[i].length;j++){
if(a[i][j]==1){
ctx.fillRect(i*10,j*10,10,10);
}
}
}
ctx.restore();//回到画布上一个状态
ctx.fillRect(menx*10,meny*10,10,10);//绘制人物 当前位置
}else{
if(que(x,y)){//如果点击超过行动界限 取消这次的行动并清除掉行动图
canvas.height = canvas.height;//重绘画布
ctx.fillRect(menx*10,meny*10,10,10);//重画人物
}else{//如果没超出行动力范围 且没点击人物
canvas.height = canvas.height;
ctx.fillRect(menx*10,meny*10,10,10);
//计算路线
bfs();
//最短路线数组
var luxian = [];
//初始化fq 让他等于xingZouluxian数组的最后一条序号
var fq = tail-1;
litui(xingZouluxian);
function litui(xingZouluxian){
//用于接受的临时数组 方便放入luxian的二位数组中
var sss = [];
//从最后一点开始取坐标
sss.push(xingZouluxian[fq].x) ;
sss.push(xingZouluxian[fq].y);
luxian.push(sss);
// f存的是这一条的父路径序号
fq = xingZouluxian[fq].f;
//放在返回上面 否则fq等于0 后没有父路径会报错
if(fq==0){
return luxian;
}
//必须返回整个数组
return litui(xingZouluxian);
}
//给蛇头预定的值
if(luxian.length>0){
var num = luxian.length-index;
menx = luxian[num][0];
meny = luxian[num][1];
}
ctx.save();//记住画布状态
ctx.fillStyle = "red";//颜色
//绘制行动路线
console.log(luxian);
luxian = luxian.reverse();
for(var i =0;i< luxian.length;i++){
ctx.fillRect(luxian[i][0]*10,luxian[i][1]*10,10,10);
}
ctx.restore();//回到画布上一个状态
//动态的画人物的行走
runRenwu(luxian);
}
}
})
//记录 初始化记录数组
var book = [];
for(var i =0;i<m;i++){
var now = [];
for(var j = 0;j<n;j++){
now[j] = 0;
}
book[i] = now;
}
//地图 记录行动力所能达到的点
var a = [];
for(var i =0;i<n;i++){
var now1 = [];
for(var j = 0;j<n;j++){
now1[j] = 0;
}
a[i] = now1;
}
//这是计算行动力所能达到的点的坐标 深搜
function dfs(x,y,num){//参数分别为开始位置的x,y 和 行动力
//方向
var next = [
[0,1],//右
[1,0],//下
[0,-1],//左
[-1,0]//上
]
var tx,ty;//下一步可以去的点
if(num==0){
return;
}
for(var k =0;k<=3;k++){
tx = x + next[k][0];
ty = y + next[k][1];
if(tx<0||tx>=50||ty<0||ty>=50){
continue;//直接跳过本次循环 (brack是跳过整个循环)
}
if(book[tx][ty]==0){
book[tx][ty]=1;//记录这个点已经走过
num--;//走过后行动力就减一(不同地形可以减不同的行动力)
a[tx][ty] = 1;//这个点可以到达那么让其值等于1
dfs(tx,ty,num);//在检测这个点相邻可以走的点
book[tx][ty]=0;//下一个点不能走后返回到这一步,去掉标记,让这个点可以走
num++;//既然退回了一步,那么行动力就还回去(减多少还多少)
}
}
return;//如果这个点不能走了,就返回到上一个能走的点尝试(剩下的能走的点是否可以行动)
}
//这里是计算路线的方法 广搜
function bfs(){
//构建地图
//自定义类
function note(x,y,s,f){
this.x = x;//横坐标
this.y = y;//纵坐标
this.s = s;//步数
this.f = f;//父亲在队列中的编号
}
//用来存储地图
var a = [];
for(var i =1;i<=n;i++){
var now = [];
for(var j =1;j<=m;j++){
now[j] = 0;
}
a[i] = now;
}
//记录是否走过的队列
var book_2 = [];
for(var i =1;i<=n;i++){
var now = [];
for(var j =1;j<=m;j++){
now[j] = 0;
}
book_2[i] = now;
}
//定义一个用于表示走的方向的数组
var next = [[0,1],[1,0],[0,-1],[-1,0]];//右 下 左 上
//迷宫开始地点
startx = menx;
starty = meny;
index = 1;
//终点
p = el.x;
q = el.y;
//队列初始化
head = 1;
tail = 1;
xingZouluxian[tail] = new note(startx,starty,0,0);
tail++;
//打印所有走过的点
//console.log(xingZouluxian);
//打印迷宫
book_2[startx][starty] = 0;
flag = 0;//用来标记是否到达终点,0表示暂时没到达,1表示到达
//当前队列不为空时循环
while(head<tail){
//枚举四个方向
for(k =0 ;k<=3;k++){
//计算下一个点的坐标
tx = xingZouluxian[head].x + next[k][0];
ty = xingZouluxian[head].y + next[k][1];
//判断是否越界
if(tx<1||tx>n||ty<1||ty>m){
continue;
}
//判断是否是障碍物或者已经在路径中
if(a[tx][ty]==0&&book_2[tx][ty]==0){
//把这个点标记为已经走过
//注意宽搜只入队一次,所以和深搜不同,不需要将book_2数组还原
book_2[tx][ty] =1;
//插入新的点到队列中
xingZouluxian[tail] = new note(tx,ty,xingZouluxian[head].s+1,head);//因为这个点是从head扩展来的,所以他的父节点
//是head,f代表路径 s代表步数是父节点的步数加1
tail++;
}
//如果到目标点了,停止扩展,任务结束,退出循环
if(tx==p&&ty==q){
//注意下面的两句话位置不要写反了
flag = 1;
break;
}
}
if(flag==1){
break;
}
head++;//注意这个地方千万不要忘记,当一个或站点结束后,head++才能对后面的点在进行扩展
}
return xingZouluxian;
}
function que(x,y){//判断点击处是否在行动范围内
for(var i =0;i< a.length;i++){
for(var j =0;j<a[i].length;j++){
if(a[i][j]==1){
if(x>i*10&&x<i*10+10&&y>j*10&&y<j*10+10){
return false;
}
}
}
}
return true;
}
function runRenwu(a){
var luxian = a.concat();
setTimeout(function(){
canvas.height = canvas.height;
ctx.save();//记住画布状态
ctx.fillStyle = "red";//颜色
//绘制行动路线
var nextLuxian = luxian.shift();
console.log(nextLuxian);
for(var i =0;i< luxian.length;i++){
ctx.fillRect(luxian[i][0]*10,luxian[i][1]*10,10,10);
}
ctx.restore();//回到画布上一个状态
if(luxian.length==0){//行动完毕
menx =nextLuxian[0];
meny =nextLuxian[1];
ctx.fillRect(nextLuxian[0]*10,nextLuxian[1]*10,10,10);
console.log(menx+','+meny);
aClear();
return;
}
ctx.fillRect(nextLuxian[0]*10,nextLuxian[1]*10,10,10);
runRenwu(luxian);
},200)
}
function aClear(){//清除记录可到达范围数组a
for(var i=0;i< a.length;i++){
for(var j = 0;j< a.length;j++){
a[i][j]=0;
}
}
}
</script>
</body>
</html>