引言:
上午女儿跟我去逛超市,在文具区看到一本书,总共有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 下载代码
转载:https://blog.csdn.net/dkm123456/article/details/116095303