内容简介:这个系列分为两部分,第一部分为迷宫的生成及操作,第二部分为自动寻路算法。我们先看效果:我们直入正题,先说一说生成迷宫的思路。
这个系列分为两部分,第一部分为迷宫的生成及操作,第二部分为自动寻路算法。
我们先看效果: 点击查看
我们直入正题,先说一说生成迷宫的思路。
整个思路十分简单:
首先我们将迷宫视为一个m行n列的单元格组合,每一个单元格便可以表示为 maze[i][j] 。接下来迷宫与m*n单元格的区别是什么呢?对,迷宫就是相当于不同单元格以某种规律相互连通,也就相当于我们把相邻的两个单元格之间的重合线给去掉,然后按照某种规律循环,便可生成一个迷宫。
我们假定从左上角开始出发,遍历每一个单元格,如果该单元格未被访问过,则查看其相邻元素(上,下,左,右)是否有未访问的单元格,如果有则随机取出一个相邻元素并打通他们之间的重合线,如果没有则回退到上一个单元格。
上代码:
首先我们创建一个构造函数:
function Maze(obj,col,row){ this.col = col || 10; this.row = row || 10; this.canvas = obj.getContext('2d'); this.init(); }
在这个构造函数中,我们接收三个参数,分别为canvas元素,迷宫的行数与列数,并直接调用Maze的init方法。
init : function(){ this.cell = (width - 2) / this.col; for(var i = 0 ; i < this.row ; i++){ maze_cells[i] = []; for(var j = 0; j < this.col ; j++){ maze_cells[i].push({ 'x' : j, 'y' : i, 'top' : false, 'bottom' : false, 'left' : false, 'right' : false, 'isVisited' : false, 'g' : 0, 'h' : 0, 'f' : 0 }) } } start_cell = {'x' : 0, 'y' : 0 }; start_row = start_cell.x; start_col = start_cell.y; visitRooms.push(start_cell) roomsLine.push(start_cell) maze_cells[0][0].isVisited = true; maze_cells[0][0].top = true; maze_cells[this.row-1][this.col-1].bottom = true; this.calcCells(0,0,maze_cells); this.drawCells(); maze_cells[0][0].top = false; maze_cells[this.row-1][this.col-1].bottom = false; this.drawRect(start_col,start_row); this.bindEvent(); },
在init方法中,我们首先根据传入的列数col来计算单元格的宽度,然后构建一个maze_cells对象,其中每一行为一个数组,每个单元格包含的值分别代表x,y坐标,上下左右4个方向是否可以通行,是否访问过,还有该单元格的g,h,f值。我们假定迷宫的开口位于整个迷宫的左上角,出口位于右下角。visitRooms用来储存我们已访问过的单元格,roomLine则记录我们的访问路径。我们将迷宫的入口处和出口处的top,bottom分别设为true后再设置为false是为了在绘制的过程中不出现边框,绘制完成后保证不能向上(下)移动。
ps:canvas绘制线条是居中于我们坐标的,即在(1,1)处绘制宽度为2的线条起始是从(0,1)开始的,所以我们用整个canvas的宽度减去了线条的宽度2,当然这里也可以设置为变量更方便修改。
接下来我们需要遍历每一个单元格,如下通过递归的形式访问每一个单元格,当某一个单元格的相邻元素全部被访问过并且roomLine数组为空时就意味着我们已经访问了所有的单元格,具体原因自行脑补。
calcCells : function(x,y,arr){ var neighbors = []; if(x-1 >=0 && !maze_cells[x-1][y].isVisited){ neighbors.push({'x' : x-1 ,'y' : y}) } if(x+1 < this.row && !maze_cells[x+1][y].isVisited){ neighbors.push({'x' : x+1 ,'y' : y}) } if(y-1 >=0 && !maze_cells[x][y-1].isVisited){ neighbors.push({'x' : x ,'y' : y-1}) } if(y+1 <this.col && !maze_cells[x][y+1].isVisited){ neighbors.push({'x' : x ,'y' : y+1}) } if(neighbors.length>0){ //相邻房间有未访问房间 var current = {'x' : x , 'y' : y}; var next = neighbors[Math.floor(Math.random() * neighbors.length)]; maze_cells[next.x][next.y].isVisited = true; visitRooms.push({'x' : next.x , 'y' : next.y}) roomsLine.push({'x' : next.x , 'y' : next.y}); this.breakWall(current,next); this.calcCells(next.x,next.y,arr) }else{ var next = roomsLine.pop(); if(next != null){ this.calcCells(next.x,next.y,arr) } } },
我们看到如果当前单元格的相邻单元格有未访问的,则执行breakWall方法,即打通当前单元格与相邻单元格中间的墙,当然我们应该随机选择一个未访问的相邻单元格。我们通过将单元格的top,bottom,left,right属性设置为true或false来标识这个方向是否应该有边框,同时该方向是否可走。
breakWall : function(cur,next){ if(cur.x < next.x){ maze_cells[cur.x][cur.y].bottom = true; maze_cells[next.x][next.y].top = true; } if(cur.x > next.x){ maze_cells[cur.x][cur.y].top = true; maze_cells[next.x][next.y].bottom = true; } if(cur.y < next.y){ maze_cells[cur.x][cur.y].right = true; maze_cells[next.x][next.y].left = true; } if(cur.y > next.y){ maze_cells[cur.x][cur.y].left = true; maze_cells[next.x][next.y].right = true; } },
进行完上面的两步,我们的一个完整数组已经构成了,接下来便可以开始绘制了,top,left,right,bottom为false时则有边框,true时无边框。这一步比较简单,我们在结尾调用了一个drawOffset方法,该方法将创建一个离屏对象,这样我们在动态修改迷宫的时候可以直接将离屏的图像绘制到当前画布中。
drawCells : function(){ var ctx = this.canvas, //canvas对象 w = this.cell; ctx.clearRect(0,0,$('canvas').width,$('canvas').height) ctx.beginPath(); ctx.save(); ctx.translate(1,1) ctx.strokeStyle = '#000000'; ctx.lineWidth = 2; for(var i in maze_cells){ //i 为 row var len = maze_cells[i].length; for( var j = 0; j < len; j++){ var cell = maze_cells[i][j]; i = parseInt(i); if(!cell.top){ ctx.moveTo(j*w,i*w); ctx.lineTo((j+1)*w ,i*w); } if(!cell.bottom){ ctx.moveTo(j*w,(i+1)*w); ctx.lineTo((j+1)*w ,(i+1)*w) } if(!cell.left){ ctx.moveTo(j*w,i*w); ctx.lineTo(j*w,(i+1)*w ) } if(!cell.right){ ctx.moveTo((j+1)*w,i*w); ctx.lineTo((j+1)*w,(i+1)*w) } } } ctx.stroke(); ctx.restore(); this.drawOffset(); },
drawOffset : function(){ var offsetCanvas = document.createElement('canvas'); offsetCanvas.id = 'offset'; document.body.appendChild(offsetCanvas); offsetCanvas.width = $('canvas').width; offsetCanvas.height = $('canvas').height; var offset = $('offset').getContext('2d'); offset.clearRect(0,0,$('canvas').width,$('canvas').height) offset.drawImage($('canvas'),0,0,offsetCanvas.width,offsetCanvas.height); $('offset').style.display ='none' },
绑定事件比较简单,我们为window监听keydown事件,根据不同的keyCode来判断我们应该行走的方向。
var _self = this; window.addEventListener('keydown',function(event){ switch (event.keyCode) { case 37 : event.preventDefault(); if(maze_cells[start_row][start_col].left){ start_col --; } break; case 38 : event.preventDefault(); if(maze_cells[start_row][start_col].top){ start_row --; } break; case 39 : event.preventDefault(); if(maze_cells[start_row][start_col].right){ start_col ++ } break; case 40 : event.preventDefault(); if(maze_cells[start_row][start_col].bottom){ start_row ++; } break; } _self.drawRect(start_col,start_row); if(start_col == (_self.col - 1) && start_row == ( _self.row - 1)){ alert('到达终点了') } });
drawRect便是我们移动的目标。
drawRect : function(col,row){ var ctx = this.canvas; ctx.save(); ctx.clearRect(0,0,canvas.width,canvas.height); ctx.drawImage($('offset'),0,0) ctx.translate(2,2) ctx.fillStyle = '#ff0000'; ctx.fillRect(col*this.cell,row*this.cell,this.cell-2,this.cell-2); ctx.restore(); },
到这里我们的迷宫便完成了。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 如何走出混网管理迷宫
- 使用 PICO-8 开发迷宫小游戏
- 数据结构与算法(七):迷宫回溯和八皇后问题
- 利用UPnP协议在物联网设备的迷宫中隐藏自己
- 用Q-learning算法实现自动走迷宫机器人
- 锐捷RG-DDP动态防御系统问世 为网络病毒建一所“迷宫”
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
解构产品经理:互联网产品策划入门宝典
电子工业出版社 / 2018-1 / 65
《解构产品经理:互联网产品策划入门宝典》以作者丰富的职业背景及著名互联网公司的工作经验为基础,从基本概念、方法论和工具的解构入手,配合大量正面或负面的案例,完整、详细、生动地讲述了一个互联网产品经理入门所需的基础知识。同时,在此基础上,将这些知识拓展出互联网产品策划的领域,融入日常工作生活中,以求职、沟通等场景为例,引导读者将知识升华为思维方式。 《解构产品经理:互联网产品策划入门宝典》适合......一起来看看 《解构产品经理:互联网产品策划入门宝典》 这本书的介绍吧!