js实现扫雷游戏
javascript作为前端常用的开发语言,越来越多的人都在学习它,今天就教大家利用js来实现基本的扫雷游戏。由于本篇文章主要面向于初学者,因此在思想上我会尽最大努力让它简单化,若有读者看完有不明白请多多反馈。
预备知识:Javascript语言的基础知识,HTML、CSS(这里用的不多,了解一点即可),数据结构
首先,我先简单的介绍一下扫雷游戏的基本规则(了解规则的跳过即可)
1.初始状态,玩家只能看到若干个方格,方格可以被点击
2.若点击的方格是雷,则显示棋盘上的所有雷,游戏结束,当前被点击的雷块的背景颜色发生变化,并且这时点击任何方格将不会有任何反应。
3.若点击的部分不是雷,并且该块的四周有雷,则该块显示周围雷的数量。
4.若点击的部分不是雷并且该块的四周没有雷,该块显示空白,并且周围无雷区域会自动打开。
5.右键点击,点击后可以标记玩家认为是雷的方格。(如图红色的方格)
主要的规则就这些,接下来整理一下整体思路(以9*9的棋盘为例)
1.创建棋盘边界的div
<h1>共<span></span>颗雷</h1> <div id="father"></div>
#father{ width: 468px; height: 468px; border: 3px solid black; }
2.初始化棋盘,即创建若干个方格div,并且将每个div给予一个id值,保证每个id是相互独立的,之后依次添加到边界div中即可
var square = document.getElementsByTagName("div"); var span = document.getElementsByTagName("span");
function init(n) { for(var i=1;i<=n;i++){ var check = document.createElement("div"); check.id = i; check.setAttribute("displayName",0); check.className="a"; check.innerHTML = i; check.style.width = "50px"; check.style.height = "50px"; check.style.border = "1px solid red"; check.style.backgroundColor = "#5ad7d7"; check.style.float = "left"; square[0].appendChild(check); } }
以9*9棋盘为例,因此传入n=81
当然,在真正的游戏中是不显示方格对应id的值,因此后续需要将check.innerHTML = i;注释掉
3.构建图的数据结构
主要思路:用js中的字典来存储,我们知道字典中有键值对,那么每一个id值就可以充当字典的键,而该键对应的值就是四周可以检测到的范围
如图所示:
中间的大圆圈部分都有8个周边方格,
如34,周边的方格有24,25,26,33,35,42,43,44 设当前id值为num,周边方格可总结为num-1,num+1,num-8,num+8,num-9,num+9,num-10,num+10
四个角比较特殊,只有三个周边方格,如9,周边方格有8,17,18,由于格数较少,这里特殊处理
最后就是四周的方格了,分上下左右四个方向来考虑
上(id值大于等于2小于等于8):比如5,周边方格有4,6,13,14,15 设当前id值为num,周边方格可总结为num-1,num+1,num+8,num+9,num+10
下(id值大于等于74小于等于80):比如78,周边方格有,68,69,70,77,79 设当前id值为num,周边方格可总结为num-1,num+1,num-10,num-9,num-8
左(id值除以9余数为1):比如37,周边方格有28,29,38,46,47 设当前id值为num,周边方格可总结为num-9,num+9,num+1,num-8,num+10
右(id值除以9余数为0):比如54,周边方格有44,45,62,63,53 设当前id值为num,周边方格可总结为um-1,num-9,num+9,num-10,num+8
具体代码如下所示(只适合9*9的棋盘,读者熟悉之后可自行修改成适用多行列的):
function sweeper(num){ var arr = []; if(num==1){ arr.push(2,10,11) }else if(num==9){ arr.push(8,17,18) }else if(num==73){ arr.push(64,65,74) }else if(num==81){ arr.push(71,72,80) }else if((num>=2)&&(num<=8)){ arr.push(num-1,num+1,num+8,num+9,num+10) }else if((num>=74)&&(num<=80)){ arr.push(num-1,num+1,num-10,num-9,num-8) }else if(num%9==1){ arr.push(num-9,num+9,num+1,num-8,num+10) }else if(num%9==0){ arr.push(num-1,num-9,num+9,num-10,num+8) } else{ arr.push(num-1,num+1,num-8,num+8,num-9,num+9,num-10,num+10) } return arr; } function sweeper_graph(n){ dict = {} for(i=1;i<=n;i++){ arr = sweeper(i); dict[i]=arr; } return dict; }
在真正的开发中,不建议写死,这里为了让大家看的更明白,先写成死数据,待读者熟悉后可自行修改,灵活运用。
上述还是以9*9为例,调用sweeper_graph函数,用变量graph来接收其返回值,并且可打印变量进行查看
上述为图的部分展示结果,可对应上边棋盘进行对比,形象的展示了每个方格周边方格的id值,即节点的邻接点。之后检测雷的工作都可以利用这个字典即可。
4.随机生成雷
function radom_thunder(spread_num,n){ var arr = []; for (var i = 1; i <=n; i++) { arr.push(i); } arr.sort( function () { return 0.5 - Math.random(); }); arr.length = spread_num; //console.log(arr) for(var i=0;i<arr.length;i++){ square[arr[i]].className="b"; } }
同样还是9*9的棋盘,以18颗雷为例,调用radom_thunder(18,81),类似的可以看到square的打印结果
如上图,class为b的方格就是雷区。
5.计算每个方格周围的雷
function cal_spread(graph){ spread_num_arr = []; //为了展示效果,单纯开发不需要这个 for(var key in dict){ var count = 0; if(document.getElementById(key).getAttribute("class")!="b"){ //当前方格不是雷的时候,计算当前方格周围的雷数 for(var i=0;i<dict[key].length;i++){ if(document.getElementById(dict[key][i]).getAttribute("class")==="b"){ count++; } } document.getElementById(key).setAttribute("displayName",count); //为方格div创建一个新属性displayName,赋值为当前方格周围雷的数 temp = document.getElementById(key).getAttribute("displayName") // 提取属性displayName值,为了展示效果,单纯开发不需要这行 } else{ //当前方格是雷的时候, document.getElementById(key).setAttribute("displayName",-1); //为方格div创建一个新属性displayName 赋值为-1. temp = document.getElementById(key).getAttribute("displayName") // 属性displayName值,为了展示效果,单纯开发不需要这行 } spread_num_arr.push(temp);//为了展示效果,单纯开发不需要这行 } return spread_num_arr; //为了展示效果,单纯开发不需要这个行 }
arr = cal_spread(graph); //为了展示效果,单纯开发不需要这行
for(i=1;i<square.length;i++){ //为了展示效果,单纯开发不需要这行
square[i].innerHTML = arr[i-1]; //为了展示效果,单纯开发不需要这行
}
如上图, -1代表雷,其他数字代表当前方格周围雷的数量,当然在游戏中不必显示出该结果,因此上边说明需要注释的代码不写也可以,检测代码对错的时候可以这样检测。
6.检测雷
//检测雷 function open_test(x){ //传入方格的id值 num = document.getElementById(x).getAttribute("displayName") if(num<0){ //当前方格为雷 document.getElementById(x).style.backgroundColor ="white"; //当前方格背景颜色变为白色 for(var k=0; k<square.length; k++){ square[k].onclick = null; //所有方格点击后无任何响应 if(square[k].className==="b"){ //显示棋盘上的所有雷 square[k].innerHTML = "雷"; square[k].style.color = "red"; square[k].style.fontSize = "32px"; square[k].style.textAlign = "center"; } } alert("踩到雷了,再试一次吧!"); //游戏失败结束弹窗 }else if(num>0){ //当前方格没有雷,并且周围有雷, if((seen.includes(x)==false)){ //该方格未被点击过 seen中存放点击过的方格 等会在入口函数中定义 seen.push(x); //将该方格对应id放到seen中 } document.getElementById(x).innerHTML=num; //当前方格显示周边雷的数量 document.getElementById(x).style.backgroundColor ="white"; //当前方格背景颜色变为白色 document.getElementById(x).style.textAlign = "center"; //当前方格文字居中 return; }else{ //当前方格没有雷,并且周围也没有雷, document.getElementById(x).innerHTML=""; //显示空白 document.getElementById(x).style.backgroundColor ="white"; //当前方格背景颜色变为白色 for(var i=0;i<graph[x].length;i++){ if((seen.includes(graph[x][i])==false)){ //若周边的方格没有被点击过 递归的去检测周边的所有方格,直到周围有雷为止跳出递归 seen.push(graph[x][i]); open_test(graph[x][i]); } } } }
7.入口函数:上边一系列函数的调用(完全面向过程的思维方式)
function main(){ check_num = 81; init(check_num); //初始化棋盘 spread_num = 18; //设置雷的数量 span[0].innerHTML = spread_num; //显示雷的数量 seen=[]; //存放被点击过的方格id var left = 0; //鼠标右键监听的两个变量 var right = 0; graph = sweeper_graph(check_num); //构建图结构 radom_thunder(spread_num,check_num); //随机生成雷 cal_spread(graph); //计算非雷方格周边雷的数量 //左键点击扫雷 for(var i=1; i<square.length; i++){ square[i].onclick = function(){ open_test(this.id); //检测雷 if(seen.length>=(check_num-spread_num)){ alert("恭喜您躲过了所有雷"); //游戏成功弹窗提示 } } } //右键点击标记 for(var l = 1; l<square.length; l++){ square[l].oncontextmenu = function () { if(!this.left){ if(!right){ this.style.backgroundColor = "red"; right = 1; }else{ this.style.backgroundColor = "#5ad7d7"; right = 0; } } else if(this.left){ return true; } return false;//返回值为false,避免鼠标原有的右击事件。 }; } }
运行main()函数完成就可以玩扫雷游戏了。
本次思想主要偏于面向过程,有兴趣的读者可以利用面向对象的思维来实现该游戏,体会二者之间的优点与不足。