做个小项目~纯原生JS手把手逐句解释写一个扫雷小游戏(附源码)
话不多说,开整!
1.规划
先打开现有的扫雷小游戏分析一下,都有哪些功能
有一堆格子,要可以选择难度,要按照难度生成地雷,地雷周围的格子要变现出周围有多少雷,格子就直接控制table好了
还有一些小细节,比如计时等等,可以最后在加上
那么就分析完了,开始整活!
2.html结构
html嘛,没什么特别需要介绍的
<div id="main"> <table id="landmine"></table> <div id="operation"> <div class="tip"> 剩余雷数:<span id="landMineCount">0</span> 个 </div> <div class="tip"> 持续时间: <span id="costTime">0</span> 秒 </div> <fieldset> <legend>难度选择:</legend> <input type="radio" name="level" id="llevel" checked="checked" value="10" /><label for="llevel">初级(10*10)</label><br /> <input type="radio" name="level" id="mlevel" value="15" /><label for="mlevel">中级(15*15)</label><br /> <input type="radio" name="level" id="hlevel" value="20" /><label for="hlevel">高级(20*20)</label><br /> </fieldset> <input type="button" id="begin" value="开始游戏" /><br />
大概就是这样子,非常的简洁美观
那么再下来就是画格子了
3.画格子
先初始化一下,构建一下绘制小格子的方法draw()
这个方法需要四个参数:
- rowCount:行数
- colCount:列数
- minLandMineCount:最少雷数
- maxLandMineCount:最多雷数
var Mine=function(rowCount, colCount, minLandMineCount, maxLandMineCount ){ this.table=document.getElementById("landmine");//获取那个表格 this.cells = document.getElementsByTagName('td'); //获取所有小格子 this.rowCount = rowCount || 10; //格子行数 this.colCount = colCount || 10; //格子列数 this.landMineCount = 0; //地雷个数 this.markLandMineCount = 0; //标记的地雷个数 this.minLandMineCount = minLandMineCount || 10; //地雷最少个数 this.maxLandMineCount = maxLandMineCount || 20; //地雷最多个数}}
这样就做好了画格子的数据准备了
接下来咱们需要想想
还有什么是这个游戏没有实现的?
- 第一,我们要记录玩家标记了哪些格子,所以我们需要一个数组
- 第二,玩家每次标记雷,需要一个方法,更新上面的数组和雷的数量
- 第三,当踩到雷的时候,需要一个方法,告诉玩家他挂了
- 第四,要记录玩家标记雷次数和游戏时间
第二、第三两条,方法以后再考虑如何实现,先用null代替
于是在构造器在再增加下面的属性
this.arrs = []; this.beginTime = null; //游戏开始时间 this.endTime = null; //游戏结束时间 this.currentSetpCount = 0; //标记雷次数 this.trueCount = 0; //标记正确的雷的次数 this.endCallBack = null; //踩到雷的方法 this.landMineCallBack = null; //标记为地雷时的方法
然后,开始画格子!
我们在Mine上追加一个原型方法draw,用来画格子:
在追加单元格的同时,一并生成地雷,需要一个数组,记录地雷的位置
Mine.prototype={draw:function(){var gz="";for(var i=0;i<this.rowCount;i++){gz+="";for(var j=0;j<this.colCount;j++){gz+=`<td id="mine${i+"_"+j}">`;}gz+=" ";}document.getElementById("landmine").innerHTML=gz;}}
目前,初始化的js是这样:
var Mine=function(rowCount, colCount, minLandMineCount, maxLandMineCount ){ this.table=document.getElementById("landmine");//获取那个表格 this.cells = document.getElementsByTagName('td'); //获取所有小格子 this.rowCount = rowCount || 10; //格子行数 this.colCount = colCount || 10; //格子列数 this.landMineCount = 0; //地雷个数 this.markLandMineCount = 0; //标记的地雷个数 this.minLandMineCount = minLandMineCount || 10; //地雷最少个数 this.maxLandMineCount = maxLandMineCount || 20; //地雷最多个数 this.arrs = []; //后面会用,地雷地图用的 this.beginTime = null; //游戏开始时间 this.endTime = null; //游戏结束时间 this.currentSetpCount = 0; //标记雷次数 this.trueCount = 0; //标记正确的雷的次数 this.endCallBack = null; //踩到雷的方法 this.landMineCallBack = null; //标记为地雷时的方法}Mine.prototype={draw:function(){var gz="";for(var i=0;i<this.rowCount;i++){gz+="";for(var j=0;j<this.colCount;j++){gz+=`<td id="mine${i+"_"+j}">`;}gz+=" ";}document.getElementById("landmine").innerHTML=gz;}}
现在,加一加css,然后看看效果:
td{background-color:green;width:15px;height: 15px}
👆执行以下,得到👇
似乎问题不大,把这一个文件命名为Main.js,引用一下
然后,就要进行事件绑定了
我们把调用的js放另外一个文件里,叫main.js吧
调用的js
首先,再思考一下,我们需要什么
我们要获取三个单选框,还有那个按钮的dom,然后把用户选择的难度传给开始游戏的方法,并绑定到开始游戏的按钮上
好办
var radios=document.getElementsByName("level");for(var i=0;i<3;i++){radios[i].onclick=function(){var value=this.value;document.getElementById("begin").onclick=function(){//这里}}}
然后,我们要肯定要记录一下我们创建的Main对象,先用null代替,所以在全局声明一个
var lei=null;
然后我们需要一个开始游戏的方法start,用这个方法调用上面的Main方法,所以这个方法需要什么参数呢?需要游戏的难度,需要行、列、雷数量等等等等
雷的数量按照如下计算
最多:(长宽)/5
最少:(长宽)/5-长
然后绑定在开始游戏的按钮上
那我们就对上面的代码加点东西:
var lei=null;var radios=document.getElementsByName("level");for(var i=0;i<3;i++){radios[i].onclick=function(){var value=this.value;document.getElementById("begin").onclick=function(){start(value,value,value*value/5,value*value/5 -value)}}}function start(rowCount, colCount, minLandMineCount, maxLandMineCount){//这里还需要一些别的,以后再加lei=new Mine(rowCount, colCount, minLandMineCount, maxLandMineCount)lei.draw()}
这样,点击单选框就会改变格子的样子了。
接着,我们需要做一些判断
比如,点击单选框后,会创建刺激的新游戏,万一是误触怎么办呢?
所以,在执行start()前,先判断lei是不是null,不是的话,最后确认一下:
把start()改成:
function start(rowCount, colCount, minLandMineCount, maxLandMineCount){//这里还需要一些别的,以后再加if(lei!=null){var jg=confirm("开始新游戏?")if(jg){lei=new Mine(rowCount, colCount, minLandMineCount, maxLandMineCount)lei.draw()}}else{lei=new Mine(rowCount, colCount, minLandMineCount, maxLandMineCount)lei.draw()}}
汇总一下开始游戏的代码(也就是main.js):
var lei=null;var radios=document.getElementsByName("level");for(var i=0;i<3;i++){radios[i].onclick=function(){var value=this.value;document.getElementById("begin").onclick=function(){start(value,value,value*value/5,value*value/5 -value)}}}function start(rowCount, colCount, minLandMineCount, maxLandMineCount){//这里还需要一些别的,以后再加if(lei!=null){var jg=confirm("开始新游戏?")if(jg){lei=new Mine(rowCount, colCount, minLandMineCount, maxLandMineCount)lei.draw()}}else{lei=new Mine(rowCount, colCount, minLandMineCount, maxLandMineCount)lei.draw()}}
到此为止,我们就实现了地雷地图的绘制
然后,我们要进行的是生成地雷
生成地雷
我们把所有格子绘制成一个数组,初始值用0表示,再最随机生成地雷,用9代替
其他的则填入为格子附近的雷的数量
数组中值的意义
0:周围每一个雷
9:我自己是雷
1-8:周围雷的个数
那么,我们在Mine的prototype中再加入一个方法:
DrawMine:function(){for(var i=0;i<this.rowCount;i++){this.arrs[i] = [];//arr再前面Mine的构造里已经声明过了for(var j=0;j<this.colCount;j++){this.arrs[i][j] = 0;}}}
然后就要构思构思了,雷的随机生成应该怎么搞?
在前面,我们已经声明过了,最小的雷数和最大的雷数minLandMineCount, maxLandMineCount,毫无疑问要传给这个方法,因为后面还要用到随机生成雷,所以这里可以封装一个随机数的函数
那么就可以这么写:
SJCount: function (FirstValue, LastValue) { var Choices = LastValue - FirstValue + 1;//确保最大值能取到 return Math.floor(Math.random() * Choices + FirstValue);},
然后,让雷的数量的值等于这个
this.landMineCount = this.SJCount(this.minLandMineCount, this.maxLandMineCount);
接下来,咱们思考一下,怎么生成这些雷呢?
我们要先知道总共有多少格子,也就是arr数组的长度
然后,为了保证随机生成的雷位置不同,我们还要加一个去重操作
那么:
landMine:function(){var all=this.rowCount*this.colCount;var MineList=[];for(var i = 0; i < this.landMineCount; i++){var randomNum = this.SJCount(0, allCount-1);var row= parseInt(randomNum / this.colCount),col= randomNum % this.colCount;//从0到格子总数-1随机出来一个数作为雷,然后算出来这个数对应的行和列分别是多少if (randomNum in MineList) { i--; continue; }//如果随机出来的数已经在列表里面了,那么往回退,重新生成 this.arrs[row][col] = 9; MineList[randomNum] = randomNum; }}
如此,我们就随机生成了若干个雷,并且记录到了数组里。
现在,数组只剩最后一种,也就是表示附近雷的个数的值
那么只用遍历一遍所有格子,每个格子周围的值是9的个数
注意,要跳过边界的情况,否则会数组越界
所以,我们继续给Mine里添加方法,用于计算周围雷的数量:
everyCount: function () { for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { if (this.arrs[i][j] == 9) continue; if (i > 0 && j > 0) { if (this.arrs[i - 1][j - 1] == 9) this.arrs[i][j]++; } if (i > 0) { if (this.arrs[i - 1][j] == 9) this.arrs[i][j]++; } if (i > 0 && j < this.colCount - 1) { if (this.arrs[i - 1][j + 1] == 9) this.arrs[i][j]++; } if (j > 0) { if (this.arrs[i][j - 1] == 9) this.arrs[i][j]++; } if (j < this.colCount - 1) { if (this.arrs[i][j + 1] == 9) this.arrs[i][j]++; } if (i < this.rowCount - 1 && j > 0) { if (this.arrs[i + 1][j - 1] == 9) this.arrs[i][j]++; } if (i < this.rowCount - 1) { if (this.arrs[i + 1][j] == 9) this.arrs[i][j]++; } if (i < this.rowCount - 1 && j < this.colCount - 1) { if (this.arrs[i + 1][j + 1] == 9) this.arrs[i][j]++; } } }}
好耶!汇总一下Main.js
var Mine=function(rowCount, colCount, minLandMineCount, maxLandMineCount ){ this.table=document.getElementById("landmine");//获取那个表格 this.cells = document.getElementsByTagName('td'); //获取所有小格子 this.rowCount = rowCount || 10; //格子行数 this.colCount = colCount || 10; //格子列数 this.landMineCount = this.SJCount(this.minLandMineCount, this.maxLandMineCount); //地雷个数 this.markLandMineCount = 0; //标记的地雷个数 this.minLandMineCount = minLandMineCount || 10; //地雷最少个数 this.maxLandMineCount = maxLandMineCount || 20; //地雷最多个数 this.arrs = []; //后面会用,地雷地图用的 this.beginTime = null; //游戏开始时间 this.endTime = null; //游戏结束时间 this.currentSetpCount = 0; //标记雷次数 this.trueCount = 0; //标记正确的雷的次数 this.endCallBack = null; //踩到雷的方法 this.landMineCallBack = null; //标记为地雷时的方法}Mine.prototype={draw:function(){var gz="";for(var i=0;i<this.rowCount;i++){gz+="";for(var j=0;j<this.colCount;j++){gz+=`<td id="mine${i+"_"+j}">`;}gz+=" ";}document.getElementById("landmine").innerHTML=gz;},DrawMine:function(){for(var i=0;i<this.rowCount;i++){this.arrs[i] = [];//arr再前面Mine的构造里已经声明过了for(var j=0;j<this.colCount;j++){this.arrs[i][j] = 0;}}},SJCount: function (FirstValue, LastValue) { var Choices = LastValue - FirstValue + 1;//确保最大值能取到 return Math.floor(Math.random() * Choices + FirstValue);},landMine:function(){var all=this.rowCount*this.colCount;var MineList=[];for(var i = 0; i < this.landMineCount; i++){var randomNum = this.SJCount(0, allCount-1);var row= parseInt(randomNum / this.colCount),col= randomNum % this.colCount;//从0到格子总数-1随机出来一个数作为雷,然后算出来这个数对应的行和列分别是多少if (randomNum in MineList) { i--; continue; }//如果随机出来的数已经在列表里面了,那么往回退,重新生成 this.arrs[row][col] = 9; MineList[randomNum] = randomNum; }},everyCount: function () { for (let i = 0; i < this.rowCount; i++) { for (let j = 0; j < this.colCount; j++) { if (this.arrs[i][j] == 9) continue; if (i > 0 && j > 0) { if (this.arrs[i - 1][j - 1] == 9) this.arrs[i][j]++; } if (i > 0) { if (this.arrs[i - 1][j] == 9) this.arrs[i][j]++; } if (i > 0 && j < this.colCount - 1) { if (this.arrs[i - 1][j + 1] == 9) this.arrs[i][j]++; } if (j > 0) { if (this.arrs[i][j - 1] == 9) this.arrs[i][j]++; } if (j < this.colCount - 1) { if (this.arrs[i][j + 1] == 9) this.arrs[i][j]++; } if (i < this.rowCount - 1 && j > 0) { if (this.arrs[i + 1][j - 1] == 9) this.arrs[i][j]++; } if (i < this.rowCount - 1) { if (this.arrs[i + 1][j] == 9) this.arrs[i][j]++; } if (i < this.rowCount - 1 && j < this.colCount - 1) { if (this.arrs[i + 1][j + 1] == 9) this.arrs[i][j]++; } } }}}
再往后,就是给每个格子添加点击事件啦。
格子的点击事件
我们先构思一下,每当我点击一个格子,会有几种情况?
左键:
第一,是数字为0的格子,一下子展开紧邻的所有为0的格子
第二,数字是1-8的格子,就展开这一个格子
第三,雷,游戏结束
右键:标记雷
好耶!思路清晰了!
我们先把左右键给格子绑定上:
我们先把标记成雷的css设置成
.flag{background-color:red;}
$: function (id) { return document.getElementById(id);},//用于获取元素bingMine:function(){ for (let i = 0; i < this.rowCount; i++) { for (let j = 0; j < this.colCount; j++) { this.$("mine" + i + "_" + j).onmousedown= function(e){e = e || window.event; var mouseNum = e.button;//获取是按得那个键 var className = this.className; if (mouseNum == 2) { if (className == "flag") {this.className = "";self.markLandMineCount--; } else {this.className = "flag";self.markLandMineCount++; } if (self.landMineCallBack) { self.landMineCallBack(self.landMineCount - self.markLandMineCount); } if (self.trueCount == self.landMineCount ) { self.success(); } } else if (mouseNum == 1||className != "flag") { //打开相连的所有数为0的格子的方法 } };}}}
这时我们发现,右键已经可以标红格子了
不过!会弹出浏览器菜单!这可不行!
我们要加入这一句,禁用右键:
加在Main的构造函数里
document.oncontextmenu = function () { //禁用右键菜单 return false; };
然后我们就可以用lei.bingMain()调用一下了
var lei=null;var radios=document.getElementsByName("level");for(var i=0;i<3;i++){radios[i].onclick=function(){var value=this.value;document.getElementById("begin").onclick=function(){start(value,value,value*value/5,value*value/5 -value)}}}function start(rowCount, colCount, minLandMineCount, maxLandMineCount){//这里还需要一些别的,以后再加if(lei!=null){var jg=confirm("开始新游戏?")if(jg){lei=new Mine(rowCount, colCount, minLandMineCount, maxLandMineCount)lei.draw()}else{return;}}else{lei=new Mine(rowCount, colCount, minLandMineCount, maxLandMineCount)lei.draw();}lei.DrawMine()lei.landMine()lei.everyCount()lei.bingMine()}
(右键点击格子就会变红了)
我们打开控制台,走一下lei.arrs,看看数组怎么样:
看上去,一点问题都没有嘛
然后我们就可以写一下展开地图了
思路也很简单
如果左键点击的这个格子对应的不是9,那么显示这个数字
如果展开
也很简单嘛
open: function (obj, x, y) { if (this.arrs[x][y] != 9) { this.currentSetpCount++; obj.innerHTML = this.arrs[x][y]; obj.className = "clear"; obj.onmousedown = null; } else { this.failed(); }},
然后如果我们点击到0,会点开所有的0,以及其周围不是雷的格子
怎么实现呢?
我们现在open进行判定,如果是0的话,执行一个方法,接收坐标信息,然后判断3*3内有没有0,如果有的话,再判断是否被标记为雷,如果也没有,就调用open,实现递归展开(并需要跳过自身)
所以open变成
open: function (obj, x, y) { if (this.arrs[x][y] != 9) { this.currentSetpCount++; obj.innerHTML = this.arrs[x][y]; obj.className = "clear"; obj.onmousedown = null; if (this.arrs[x][y] == 0) { this.showNoLandMine.call(this, x, y); } } else { this.failed(); }},
然后showNoLandMine就这么写:
showNoLandMine: function (x, y) { for (var i = x - 1; i < x + 2; i++) for (var j = y - 1; j < y + 2; j++) { if (!(i == x && j == y)) { var ele = this.$("mine" + i + "_" + j); if (ele && ele.className == "") { this.open.call(this, ele, i, j); } } }},
目前为止,扫雷就是这个样子
(中间没有数字的就是雷了)
然后,我们在里面添加失败的方法和成功的方法:
failed:function(){alert("你踩雷了") for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { if (this.arrs[i][j] == 9) this.$("mine" + i + "_" + j).innerHTML="雷" else{this.$("mine" + i + "_" + j).innerHTML=this.arrs[i][j]} }}},success:function(){ alert("你赢了") for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { if (this.arrs[i][j] == 9) this.$("mine" + i + "_" + j).innerHTML="雷" else{this.$("mine" + i + "_" + j).innerHTML=this.arrs[i][j]} }}}
这两个方法的核心就是显示出所有的块块
(这就是是最后的样子)
然后我们完善一下其他的内容,比如显示标记的雷的数量,以及时间等等
比如在最开始,声明变量的地方加入
document.querySelector("#landMineCount").innerHTML=this.landMineCount;
以及在点击时添加一句
document.querySelector("#landMineCount").innerHTML=self.landMineCount-self.markLandMineCount
然后时间上,也就是一个简简单单的计数器:
this.ts=0;this.jsq=setInterval(()=>{this.beginTime=Date.parse(new Date());this.ts++;document.querySelector("#costTime").innerHTML=this.ts;},1000)
然后在赢或者死了的时候,停止计时:
failed:function(){ for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { if (this.arrs[i][j] == 9) this.$("mine" + i + "_" + j).innerHTML="雷" else{this.$("mine" + i + "_" + j).innerHTML=this.arrs[i][j]} }clearInterval(this.jsq)}},success:function(){ alert("win") for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { if (this.arrs[i][j] == 9) this.$("mine" + i + "_" + j).innerHTML="雷" else{this.$("mine" + i + "_" + j).innerHTML=this.arrs[i][j]} }clearInterval(this.jsq)}}
那么现在,几乎全部功能就实现了,我们在稍微修改修改CSS
(毕竟太丑了啊喂!)
现在这里送上没有修改css的版本,方便大家理解:
(js css html写到一个文件里了)
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><style>td{background-color:green;width:15px;height: 15px}.flag{background-color:red;}</style><script>var Mine=function(rowCount, colCount, minLandMineCount, maxLandMineCount ){ this.table=document.getElementById("landmine");//获取那个表格 this.cells = document.getElementsByTagName('td'); //获取所有小格子 this.rowCount = rowCount || 10; //格子行数 this.colCount = colCount || 10; //格子列数 this.markLandMineCount = 0; //标记的地雷个数 this.minLandMineCount = minLandMineCount || 10; //地雷最少个数 this.maxLandMineCount = maxLandMineCount || 20; //地雷最多个数 this.landMineCount = this.SJCount(this.minLandMineCount, this.maxLandMineCount); //地雷个数 this.arrs = []; //后面会用,地雷地图用的 this.beginTime = null; //游戏开始时间 this.endTime = null; //游戏结束时间 this.currentSetpCount = 0; //标记雷次数 this.trueCount = 0; //标记正确的雷的次数 this.endCallBack = null; //踩到雷的方法 this.landMineCallBack = null; //标记为地雷时的方法this.ts=0;this.jsq=setInterval(()=>{this.beginTime=Date.parse(new Date());this.ts++;document.querySelector("#costTime").innerHTML=this.ts;},1000);document.querySelector("#landMineCount").innerHTML=this.landMineCount; document.oncontextmenu = function () { //禁用右键菜单 return false; };}Mine.prototype={draw:function(){var gz="";for(var i=0;i<this.rowCount;i++){gz+="";for(var j=0;j<this.colCount;j++){gz+=`<td id="mine${i+"_"+j}">`;}gz+=" ";}document.getElementById("landmine").innerHTML=gz;},DrawMine:function(){for(var i=0;i<this.rowCount;i++){this.arrs[i] = [];//arr再前面Mine的构造里已经声明过了for(var j=0;j<this.colCount;j++){this.arrs[i][j] = 0;}}},SJCount: function (FirstValue, LastValue) { var Choices = LastValue - FirstValue + 1;//确保最大值能取到 return Math.floor(Math.random() * Choices + FirstValue);},landMine:function(){var all=this.rowCount*this.colCount;var MineList=[];for(var i = 0; i < this.landMineCount; i++){var randomNum = this.SJCount(0, all-1);var row= parseInt(randomNum / this.colCount),col= randomNum % this.colCount;//从0到格子总数-1随机出来一个数作为雷,然后算出来这个数对应的行和列分别是多少if (randomNum in MineList) { i--; continue; }//如果随机出来的数已经在列表里面了,那么往回退,重新生成 this.arrs[row][col] = 9; MineList[randomNum] = randomNum; }},everyCount: function () { for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { if (this.arrs[i][j] == 9) continue; if (i > 0 && j > 0) { if (this.arrs[i - 1][j - 1] == 9) this.arrs[i][j]++; } if (i > 0) { if (this.arrs[i - 1][j] == 9) this.arrs[i][j]++; } if (i > 0 && j < this.colCount - 1) { if (this.arrs[i - 1][j + 1] == 9) this.arrs[i][j]++; } if (j > 0) { if (this.arrs[i][j - 1] == 9) this.arrs[i][j]++; } if (j < this.colCount - 1) { if (this.arrs[i][j + 1] == 9) this.arrs[i][j]++; } if (i < this.rowCount - 1 && j > 0) { if (this.arrs[i + 1][j - 1] == 9) this.arrs[i][j]++; } if (i < this.rowCount - 1) { if (this.arrs[i + 1][j] == 9) this.arrs[i][j]++; } if (i < this.rowCount - 1 && j < this.colCount - 1) { if (this.arrs[i + 1][j + 1] == 9) this.arrs[i][j]++; } } }},$: function (id) { return document.getElementById(id);},//用于获取元素bingMine:function(){var self=this; for (let i = 0; i < this.rowCount; i++) { for (let j = 0; j < this.colCount; j++) { self.$("mine" + i + "_" + j).onmousedown = function (e) { e = e || window.event; var mouseNum = e.button; var className = this.className; if (mouseNum == 2) { if (className == "flag") {this.className = "";self.markLandMineCount--; } else {this.className = "flag";self.markLandMineCount++;if(self.arrs[i][j]==9){ self.trueCount++;} }document.querySelector("#landMineCount").innerHTML=self.landMineCount-self.markLandMineCount if (self.landMineCallBack) {self.landMineCallBack(self.landMineCount - self.markLandMineCount); } if (self.trueCount == self.landMineCount ) { self.success(); } } else if (mouseNum == 1||className != "flag") { self.open.call(self,this, i, j); } };}}},showNoLandMine: function (x, y) { for (var i = x - 1; i < x + 2; i++) for (var j = y - 1; j < y + 2; j++) { if (!(i == x && j == y)) { var ele = this.$("mine" + i + "_" + j); if (ele && ele.className == "") { this.open.call(this, ele, i, j); } } }},open: function (obj, x, y) { if (this.arrs[x][y] != 9) { this.currentSetpCount++; obj.innerHTML = this.arrs[x][y]; obj.className = "clear"; // alert( this.trueCount) obj.onmousedown = null; if (this.arrs[x][y] == 0) { this.showNoLandMine.call(this, x, y); } } else { this.failed(); }},failed:function(){ for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { if (this.arrs[i][j] == 9) this.$("mine" + i + "_" + j).innerHTML="雷" else{this.$("mine" + i + "_" + j).innerHTML=this.arrs[i][j]} }clearInterval(this.jsq)}},success:function(){ alert("win") for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { if (this.arrs[i][j] == 9) this.$("mine" + i + "_" + j).innerHTML="雷" else{this.$("mine" + i + "_" + j).innerHTML=this.arrs[i][j]} }clearInterval(this.jsq)}}}</script><title>Document</title></head><body><div id="main"> <table id="landmine"></table> <div id="operation"> <div class="tip"> 剩余雷数:<span id="landMineCount">0</span> 个 </div> <div class="tip"> 持续时间: <span id="costTime">0</span> 秒 </div> <fieldset> <legend>难度选择:</legend> <input type="radio" name="level" id="llevel" value="10" /><label for="llevel">初级(10*10)</label><br /> <input type="radio" name="level" id="mlevel" value="15" /><label for="mlevel">中级(15*15)</label><br /> <input type="radio" name="level" id="hlevel" value="20" /><label for="hlevel">高级(20*20)</label><br /> </fieldset> <input type="button" id="begin" value="开始游戏" /><br /> <script>var lei=null;var radios=document.getElementsByName("level");for(var i=0;i<3;i++){radios[i].onclick=function(){var value=this.value;document.getElementById("begin").onclick=function(){start(value,value,value*value/5,value*value/5 -value)}}}function start(rowCount, colCount, minLandMineCount, maxLandMineCount){//这里还需要一些别的,以后再加if(lei!=null){var jg=confirm("开始新游戏?")if(jg){lei=new Mine(rowCount, colCount, minLandMineCount, maxLandMineCount)lei.draw()}else{return;}}else{lei=new Mine(rowCount, colCount, minLandMineCount, maxLandMineCount)lei.draw();}lei.DrawMine()lei.landMine()lei.everyCount()lei.bingMine()} </script></body></html>
以及修改CSS等等后的版本:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><style>td{background-color:rgb(191,191,191);border-left:3px white solid;border-top:3px white solid;border-right:3px rgb(126,126,124) solid;border-bottom:3px rgb(126,126,124) solid;width:20px;height: 20px;text-align: center;line-height: 20px;}.clear{background-color: rgb(193,193,193);border:2px #585858 solid}.flag{background:url(https://pic.imgdb.cn/item/61b72ab72ab3f51d9163640f.png);background-color: rgb(193,193,193);border:2px #585858 solid}</style><script>var Mine=function(rowCount, colCount, minLandMineCount, maxLandMineCount ){ this.table=document.getElementById("landmine");//获取那个表格 this.cells = document.getElementsByTagName('td'); //获取所有小格子 this.rowCount = rowCount || 10; //格子行数 this.colCount = colCount || 10; //格子列数 this.markLandMineCount = 0; //标记的地雷个数 this.minLandMineCount = minLandMineCount || 10; //地雷最少个数 this.maxLandMineCount = maxLandMineCount || 20; //地雷最多个数 this.landMineCount = this.SJCount(this.minLandMineCount, this.maxLandMineCount); //地雷个数 this.arrs = []; //后面会用,地雷地图用的 this.beginTime = null; //游戏开始时间 this.endTime = null; //游戏结束时间 this.currentSetpCount = 0; //标记雷次数 this.trueCount = 0; //标记正确的雷的次数 this.endCallBack = null; //踩到雷的方法 this.landMineCallBack = null; //标记为地雷时的方法this.ts=0;this.jsq=setInterval(()=>{this.beginTime=Date.parse(new Date());this.ts++;document.querySelector("#costTime").innerHTML=this.ts;},1000);document.querySelector("#landMineCount").innerHTML=this.landMineCount; document.oncontextmenu = function () { //禁用右键菜单 return false; };}Mine.prototype={draw:function(){var gz="";for(var i=0;i<this.rowCount;i++){gz+="";for(var j=0;j<this.colCount;j++){gz+=`<td id="mine${i+"_"+j}">`;}gz+=" ";}document.getElementById("landmine").innerHTML=gz;},DrawMine:function(){for(var i=0;i<this.rowCount;i++){this.arrs[i] = [];//arr再前面Mine的构造里已经声明过了for(var j=0;j<this.colCount;j++){this.arrs[i][j] = 0;}}},SJCount: function (FirstValue, LastValue) { var Choices = LastValue - FirstValue + 1;//确保最大值能取到 return Math.floor(Math.random() * Choices + FirstValue);},landMine:function(){var all=this.rowCount*this.colCount;var MineList=[];for(var i = 0; i < this.landMineCount; i++){var randomNum = this.SJCount(0, all-1);var row= parseInt(randomNum / this.colCount),col= randomNum % this.colCount;//从0到格子总数-1随机出来一个数作为雷,然后算出来这个数对应的行和列分别是多少if (randomNum in MineList) { i--; continue; }//如果随机出来的数已经在列表里面了,那么往回退,重新生成 this.arrs[row][col] = 9; MineList[randomNum] = randomNum; }},everyCount: function () { for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { if (this.arrs[i][j] == 9) continue; if (i > 0 && j > 0) { if (this.arrs[i - 1][j - 1] == 9) this.arrs[i][j]++; } if (i > 0) { if (this.arrs[i - 1][j] == 9) this.arrs[i][j]++; } if (i > 0 && j < this.colCount - 1) { if (this.arrs[i - 1][j + 1] == 9) this.arrs[i][j]++; } if (j > 0) { if (this.arrs[i][j - 1] == 9) this.arrs[i][j]++; } if (j < this.colCount - 1) { if (this.arrs[i][j + 1] == 9) this.arrs[i][j]++; } if (i < this.rowCount - 1 && j > 0) { if (this.arrs[i + 1][j - 1] == 9) this.arrs[i][j]++; } if (i < this.rowCount - 1) { if (this.arrs[i + 1][j] == 9) this.arrs[i][j]++; } if (i < this.rowCount - 1 && j < this.colCount - 1) { if (this.arrs[i + 1][j + 1] == 9) this.arrs[i][j]++; } } }},$: function (id) { return document.getElementById(id);},//用于获取元素bingMine:function(){var self=this; for (let i = 0; i < this.rowCount; i++) { for (let j = 0; j < this.colCount; j++) { self.$("mine" + i + "_" + j).onmousedown = function (e) { e = e || window.event; var mouseNum = e.button; var className = this.className; if (mouseNum == 2) { if (className == "flag") {this.className = "";self.markLandMineCount--; } else {if(self.landMineCount-self.markLandMineCount<=0)return;this.className = "flag";self.markLandMineCount++;if(self.arrs[i][j]==9){ self.trueCount++;} }document.querySelector("#landMineCount").innerHTML=self.landMineCount-self.markLandMineCount if (self.landMineCallBack) {self.landMineCallBack(self.landMineCount - self.markLandMineCount); } if (self.trueCount == self.landMineCount ) { self.success(); } } else if (mouseNum == 1||className != "flag") { self.open.call(self,this, i, j); } };}}},showNoLandMine: function (x, y) { for (var i = x - 1; i < x + 2; i++) for (var j = y - 1; j < y + 2; j++) { if (!(i == x && j == y)) { var ele = this.$("mine" + i + "_" + j); if (ele && ele.className == "") { this.open.call(this, ele, i, j); } } }},open: function (obj, x, y) { if (this.arrs[x][y] != 9) { this.currentSetpCount++; obj.innerHTML = this.arrs[x][y]==0?"":this.arrs[x][y]; obj.className = "clear"; // alert( this.trueCount) obj.onmousedown = null; if (this.arrs[x][y] == 0) { this.showNoLandMine.call(this, x, y); } } else { this.failed(); }},failed:function(){ for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { if (this.arrs[i][j] == 9) this.$("mine" + i + "_" + j).innerHTML="
" else{this.$("mine" + i + "_" + j).innerHTML=this.arrs[i][j]} }clearInterval(this.jsq)}},success:function(){ alert("win") for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { if (this.arrs[i][j] == 9) this.$("mine" + i + "_" + j).innerHTML="
" else{this.$("mine" + i + "_" + j).innerHTML=this.arrs[i][j]} }clearInterval(this.jsq)}}}</script><title>Document</title></head><body><fieldset style="width: 45%;float:left"> <legend>难度选择:</legend> <input type="radio" name="level" id="llevel" value="10" /><label for="llevel">初级(10*10)</label><br /> <input type="radio" name="level" id="mlevel" value="15" /><label for="mlevel">中级(15*15)</label><br /> <input type="radio" name="level" id="hlevel" value="20" /><label for="hlevel">高级(20*20)</label><br /><input type="button" id="begin" value="开始游戏" /><br /> </fieldset><div id="main"> <fieldset style="width: 45%;float:right"><legend>游戏数据:</legend><br> <div id="operation"> <div class="tip"> 剩余雷数:<span id="landMineCount">0</span> 个 </div> <div class="tip"> 持续时间: <span id="costTime">0</span> 秒 </div><br></fieldset><div style="clear: both;"></div><table id="landmine" cellpadding="0" cellspacing="0"></table> <script>var lei=null;var radios=document.getElementsByName("level");for(var i=0;i<3;i++){radios[i].onclick=function(){var value=this.value;document.getElementById("begin").onclick=function(){start(value,value,value*value/5,value*value/5 -value)}}}function start(rowCount, colCount, minLandMineCount, maxLandMineCount){//这里还需要一些别的,以后再加if(lei!=null){var jg=confirm("开始新游戏?")if(jg){lei=new Mine(rowCount, colCount, minLandMineCount, maxLandMineCount)lei.draw()}else{return;}}else{lei=new Mine(rowCount, colCount, minLandMineCount, maxLandMineCount)lei.draw();}lei.DrawMine()lei.landMine()lei.everyCount()lei.bingMine()} </script></body></html>
这是最后美化一下下的样子