上午女儿跟我去逛超市,在文具区看到一本书,总共有10幅图都是小迷宫游戏,图什么的都挺漂亮,就是有点贵应该是纸比较好,要30多块钱,我就觉得划不来(典型的铁公鸡),我就跟女儿说家里有,买了其他东西就回来了,然后网上查了一下,主要用到的是一个算法,于是吃完午饭就开始写了,这就学马老师来一波回首掏!
有人可能会说你这人真抠门,这点钱都舍不得掏。
我会说:这是钱的问题吗?这是专业,我们程序员的钱有那么好赚吗?我待会就跟我老婆要30块钱,说我买了个迷宫游戏书,我们程序员的钱不就是敲代码来的吗,变现有问题?
刷新就可以换一个通道,不比书香,可以一直玩,一直玩一直爽。
? ? ? ? ? ? 1.将起点作为当前迷宫单元并标记为已访问
?? ??? ??? ?2.当还存在未标记的迷宫单元,进行循环
?? ??? ??? ??? ?1).如果当前迷宫单元有未被访问过的的相邻的迷宫单元
?? ??? ??? ??? ??? ?(1).随机选择一个未访问的相邻迷宫单元
?? ??? ??? ??? ??? ?(2).将当前迷宫单元入栈
?? ??? ??? ??? ??? ?(3).移除当前迷宫单元与相邻迷宫单元的墙
?? ??? ??? ??? ??? ?(4).标记相邻迷宫单元并用它作为当前迷宫单元
?? ??? ??? ??? ?2).如果当前迷宫单元不存在未访问的相邻迷宫单元,并且栈不空
?? ??? ??? ??? ??? ?(1).栈顶的迷宫单元出栈
?? ??? ??? ??? ??? ?(2).令其成为当前迷宫单元
这个算法叫做“深度优先”,简单来说,就是从起点开始走,寻找它的上下左右4个邻居,然后随机一个走,到走不通的时候就返回上一步继续走,直到全部单元都走完。
1.创建格子单元对象。
2.通过算法将这些格子打通,绘制出迷宫的形状。
3.绘制入口与终点的格子。
4.添加键盘的上、下、左、右移动事件,写好对应的函数,到达终点提示胜利。
1.每个单元的墙,分为上墙、右墙、下墙、左墙,把这些墙用长度为4的数组表示,元素的值为true则表示墙存在,否则墙不存在,代码里数组的下标方式来确定墙是否存在。
2.单元是根据行列来创建的,会用到双循环,类似表格,比如第二行用 i 来表示的话就是 1,第3列用 j 来表示就是2,那第二行第3列的元素组合起来就是(1,2)
3.那同理它的上邻居就是(0,2),右邻居(1,3),下邻居(2,2),左邻居(1,1),也就是上下邻居是 i 减加1,左右邻居是 j 减加1。
4.正方形4个点的坐标分别为(x1,y1)(x2,y2)(x3,y3)(x4,y4),计算坐标的公式为:
//左上角坐标
x1=j*w;
y1=i*w;
//右上角坐标
x2=(j+1)*w;
y2=i*w;
//右下角坐标
x3=(j+1)*w;
y3=(i+1)*w;
//左下角坐标
x4=j*w;
y4=(i+1)*w;
计算坐标,假如每个正方形的宽高都是40,那么(1,2)这个单元的坐标如下图:
5.墙的处理,之前说到墙是以一个4个元素的数组来表示的,比如数组为:[true,true,true,true],则图为:
如果数组为[false,true,true,true],则图为:
6.如果要联通右边的邻居要怎么做呢?当前单元去除右墙,右边单元去除左墙,这样就联通了。
去除后就这样,以此类推
此构造函数不是直接利用Rect来绘制方形的,而是自己以绘制4条直线的方式来绘制的,既方形的上、右、下、左4条直线。
1.计算坐标,这个上面已经提过。
2.根据墙数组的值来确定是否绘制这条直线,[true,true,true,true]就绘制完整的方形,[false,true,true,true]的话,上边就会缺失。
//用4条直线画方形的构造函数
function LineRect(o){
this.x=0,//x坐标
this.y=0,//y坐标
this.init(o);
this.axis(this.i,this.j);
}
LineRect.prototype.init=function(o){
for(var key in o){
this[key]=o[key];
}
//上右下左4面墙 true就表示要绘制
this.walls=[true,true,true,true];
}
//根据i,j计算出坐标
LineRect.prototype.axis=function(i,j){
var w = this.maze.dis;
//i代表行 j代表列
//左上角坐标
this.x1=j*w;
this.y1=i*w;
//右上角坐标
this.x2=(j+1)*w;
this.y2=i*w;
//右下角坐标
this.x3=(j+1)*w;
this.y3=(i+1)*w;
//左下角坐标
this.x4=j*w;
this.y4=(i+1)*w;
}
//绘制函数
LineRect.prototype.render=function(context){
this.ctx=context;
innerRender(this);
function innerRender(obj){
var ctx=obj.ctx;
ctx.save()
ctx.beginPath();
ctx.translate(obj.x,obj.y);
if(obj.lineWidth){
ctx.lineWidth=obj.lineWidth;
}
//判断上、右、下、左 的墙,true的话墙就会有,否则墙就没有
var top = obj.walls[0];
var right = obj.walls[1];
var bottom = obj.walls[2];
var left = obj.walls[3];
if(top){
ctx.moveTo(obj.x1,obj.y1);
ctx.lineTo(obj.x2,obj.y2);
}
if(right){
ctx.moveTo(obj.x2,obj.y2);
ctx.lineTo(obj.x3,obj.y3);
}
if(bottom){
ctx.moveTo(obj.x3,obj.y3);
ctx.lineTo(obj.x4,obj.y4);
}
if(left){
ctx.moveTo(obj.x4,obj.y4);
ctx.lineTo(obj.x1,obj.y1);
}
obj.strokeStyle?(ctx.strokeStyle=obj.strokeStyle):null;
ctx.stroke();
ctx.restore();
}
return this;
}
Maze.prototype.drawGrid=function(){
this.rows = Math.floor(this.h/this.dis);
this.cols = Math.floor(this.w/this.dis);
//根据行数、列数来创建格子
for(var i=0;i<this.rows;i++){
for(var j=0;j<this.cols;j++){
var cell = this.buildCell(i,j);
this.renderArr.push(cell);
}
}
}
//创建格子
Maze.prototype.buildCell=function(i,j){
var param={i:i,j:j,lineWidth:1,maze:this};
//创建格子对象
var cell = new LineRect(param);
return cell;
}
//查找当前单元是否有未被访问的邻居单元
LineRect.prototype.findNeighbors=function(){
//邻居分为上下左右
var maze = this.maze ;
this.arr = maze.renderArr;
var res=[];//返回的数组
var top = this.getNeighbor('0');
var right = this.getNeighbor('1');
var bottom = this.getNeighbor('2');
var left = this.getNeighbor('3');
if(top){
res.push(top);
}
if(right){
res.push(right);
}
if(bottom){
res.push(bottom);
}
if(left){
res.push(left);
}
return res;//返回邻居数组
}
//查找邻居
LineRect.prototype.getNeighbor=function(type,lost_visited){
var key,neighbor;
if(type=='0'){
key = this.assemKey(this.i-1,this.j);
}else if(type=='1'){
key = this.assemKey(this.i,this.j+1);
}else if(type=='2'){
key = this.assemKey(this.i+1,this.j);
}else if(type=='3'){
key = this.assemKey(this.i,this.j-1);
}
if(key){
neighbor = this.arr[key];//首先找到这个邻居
if(neighbor.visited && !lost_visited){//判断是否被访问,如果被访问了返回undefined lost_visited表示是否忽略访问的情况
neighbor = undefined;
}
}
return neighbor;
}
//根据i,j计算数组单元在数组中的下标值
LineRect.prototype.assemKey=function(i,j){
if(i<0 || j<0 || i>=this.maze.rows || j>=this.maze.cols){//超出边界了
return undefined;
}
return i*this.maze.cols+j;//计算出i,j位置单元在数组中的下标
}
跟着算法来写的代码,唯一要注意的是我设置了一个值unVisitedCount,初始值为所有单元的数量,每当一个单元被标记为已访问后,这个值就递减1,当值为0后就终止循环,结束算法。
Maze.prototype.computed=function(){
/*
1.将起点作为当前迷宫单元并标记为已访问
2.当还存在未标记的迷宫单元,进行循环
1).如果当前迷宫单元有未被访问过的的相邻的迷宫单元
(1).随机选择一个未访问的相邻迷宫单元
(2).将当前迷宫单元入栈
(3).移除当前迷宫单元与相邻迷宫单元的墙
(4).标记相邻迷宫单元并用它作为当前迷宫单元
2).如果当前迷宫单元不存在未访问的相邻迷宫单元,并且栈不空
(1).栈顶的迷宫单元出栈
(2).令其成为当前迷宫单元
*/
var stack = this.stack ; //栈
var arr = this.renderArr;
var current = arr[0];//取第一个为当前单元
this.pathArr.push(current);
current.visited=true;//标记为已访问
var unVisitedCount=arr.length-1;//因为第一个已经设置为访问了
var neighbors ;
while(unVisitedCount>0){
neighbors = current.findNeighbors();//查找邻居集合(未被访问的)
if(neighbors.length>0){//如果当前迷宫单元有未被访问过的的相邻的迷宫单元
//随机选择一个未访问的相邻迷宫单元
var index = _.getRandom(0,neighbors.length);
var next = neighbors[index];
//将当前迷宫单元入栈
stack.push(current);
//移除当前迷宫单元与相邻迷宫单元的墙
this.removeWall(current,next);
//标记相邻迷宫单元并用它作为当前迷宫单元
next.visited=true;
//标记一个为访问,则计数器递减1
unVisitedCount--;//递减
current = next;
}else if(stack.length>0){//如果当前迷宫单元不存在未访问的相邻迷宫单元,并且栈不空
/*
1.栈顶的迷宫单元出栈
2.令其成为当前迷宫单元
*/
var cell = stack.pop();
current = cell;
}
//推入路线数组
this.pathArr.push(current);
}
}
//移除当前迷宫单元与相邻迷宫单元的墙
Maze.prototype.removeWall=function(a,b){
if(a.i==b.i){//横向邻居
if(a.j>b.j){//匹配到的是左边邻居
//左边邻居的话,要移除自己的左墙和邻居的右墙
a.walls[3]=false;
b.walls[1]=false;
}else{//匹配到的是右边邻居
//右边邻居的话,要移除自己的右墙和邻居的左墙
a.walls[1]=false;
b.walls[3]=false;
}
}else if(a.j==b.j){//纵向邻居
if(a.i>b.i){//匹配到的是上边邻居
//上边邻居的话,要移除自己的上墙和邻居的下墙
a.walls[0]=false;
b.walls[2]=false;
}else{//匹配到的是下边邻居
//下边邻居的话,要移除自己的下墙和邻居的上墙
a.walls[2]=false;
b.walls[0]=false;
}
}
}
//创建起点和终点格子
Maze.prototype.drawRunCell=function(i,j){
var end = new _.Rect({
x:(this.cols-1)*this.dis+this.dis2,
y:(this.rows-1)*this.dis+this.dis2,
width:this.dis-2*this.dis2,
height:this.dis-2*this.dis2,
fill:true,
fillStyle:'red'
});
end.i=this.rows-1,end.j=this.cols-1;//设定i,j值,判断是否终点
this.renderArr2.push(end);
var start = new _.Rect({
x:0+this.dis2,
y:0+this.dis2,
width:this.dis-2*this.dis2,
height:this.dis-2*this.dis2,
fill:true,
fillStyle:'blue'
});
start.i=0,start.j=0;//设定i,j值,控制移动
this.renderArr2.push(start);
}
//按键的控制
Maze.prototype.control=function(){
var that=this;
global.addEventListener('keydown',function(e){
console.log(that.endFlag)
if(that.endFlag) return ;
var dir;
switch (e.keyCode){
case 87://w
case 38://上
dir=0;//上移动
break;
case 83://s
case 40://下
dir=2;//下移动
break;
case 65://a
case 37://左
dir=3;//左移动
break;
case 68://d
case 39://右
dir=1;//右移动
break;
}
that.move(dir);
//测试用,记得删除
that.render();
});
}
//移动
Maze.prototype.move=function(dir){
var cur = this.renderArr2[1];//当前移动的方块
var key = this.assemKey(cur);//根据移动方块的i,j计算出key
var cell = this.renderArr[key];//得到移动方块对应的单元
var wall = cell.walls[dir];//得到对应的那面墙
if(!wall){//表示是没有墙能移动
var neighbor = cell.getNeighbor(dir,1);
if(!neighbor){
return ;
}
cur.x=neighbor.x1+this.dis2;
cur.y=neighbor.y1+this.dis2;
cur.i=neighbor.i;
cur.j=neighbor.j;
}
var end = this.renderArr2[0];
if(cur.i==end.i && cur.j==end.j ){
this.endFlag=true;
console.log('完成');
this.endShow();
}
}
Maze.prototype.assemKey=function(e){
return e.i*this.cols+e.j;//计算出i,j位置单元在数组中的下标
}
//展示结束的图片(胜利)
Maze.prototype.endShow=function(){
var image,img,sx=0,sy=0,sWidth=225,sHeight=108,dx=this.w/2-110,dy=this.h/2-100,dWidth=225,dHeight=108;
image = this.imgObj['suc'];
img = new _.ImageDraw({image:image,sx:sx,sy:sy,sWidth:sWidth,sHeight:sHeight, dx:dx, dy:dy ,dWidth:dWidth,dHeight:dHeight});
this.renderArr2.push(img);
this.render();
}
写出来也花了不少脑细胞,能看到这里的都是大佬,我去找老婆提现去了。
方式1:少量积分,下载代码
方式2:关注下方公众号,回复?130?下载代码
?
?
苹果今天发布了 macOS 11 Big Sur 的第一个公测 Beta,允许非开发人员在今年秋天...
正则表达式分组: 如果想匹配3个数字,正则表达式可以用如下写法: \d{3} 以上代码...
本文实例讲述了laravel框架中间件简单使用方法。分享给大家供大家参考,具体如下...
文章目录 本文认证鉴权思路方案 一. 认证服务器 1. 需要依赖 2. 编写认证服务 3....
!--METADATA TYPE="typelib" UUID="00000205-0000-0010-8000-00AA006D2EA4" NAME...
看了这个的一些帖子,学到了很多东西,现在和大家一起分享 flash现在只是一个显示...
分库分表是什么 下边以电商系统中的例子来说明,下图是电商系统卖家模块的表结构...
content相当于你例子中的FCKeditor1。 复制代码 代码如下: //获取格式化的编辑器...
相关的题外话: 一、操作系统 window系统内部都是unicode的。文件夹名,文件名等...
75 份速查表,由 vikas 收集整理,包括:jQuery、HTML、HTML5、CSS、CSS3、JavaS...