js实现扫雷源代码


Posted in Javascript onNovember 27, 2020

经过一段时间学习,对javascript有了一个初步的了解自己制作了一个扫雷,源代码+详细注释放在后面,先看下效果图。

初始化界面:

js实现扫雷源代码

游戏界面:

js实现扫雷源代码

难易程度切换:

js实现扫雷源代码

游戏结束:

js实现扫雷源代码

思路

采用构造函数的形式进行全局开发

生成游戏棋盘

  • 利用双层for循环创建设定的棋盘大小
  • 为每个单元格的dom元素创建一个属性,该属性用于保存单元格的所有信息,如x,y坐标,value,是否为雷等

随机生成炸弹

  • 利用随机数,随机生成炸弹x,y坐标,并将符合该坐标信息的单元格的属性更改为雷
  • 炸弹是在用户第一次点击的时候生成,防止用户第一次点击到炸弹
  • 将生成的每个炸弹信息都保存到一个this变量中,方便后续使用
  • 遍历每个炸弹周围的非炸弹方格,每遍历一次value值+1

鼠标左键点击

  • 点击的时候需要考虑该单元格是否有被标记小旗子(isFlag属性),如果有则无法点击
  • 判断是雷还是数字,雷的话则游戏结束,数字则继续判断是否等于0,等于0则使用递归显示空白区域
  • 每次打开一个单元格,需要更改该单元格的isOpen属性,表示单元格被打开

鼠标右键点击

  • 点击时需要考虑该单元格的isOpen属性是否被打开,打开的话则无法点击
  • 当该单元格没有标记旗帜时标记,如果有标记旗帜则取消标记
  • 每标记一个方格,剩余炸弹数量-1,取消标记则+1

游戏结束

  • 当左键点击到炸弹的时候游戏结束。失败
  • 剩余炸弹数量为0时。判断旗帜标记是否正确,正确游戏胜利,标记有误则失败

HTML代码

超短的HTML代码

<!DOCTYPE html>
<html lang="en">

<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Document</title>
 <link rel="stylesheet" href="css/index.css" >
</head>

<body>
 <div class="main">
  <header class="header">
   <button>初级</button>
   <button>中级</button>
   <button>高级</button>
  </header>
  <div class="gameBox" id="gameBox"></div>
  <footer class="footer">剩余雷数量:<span id="surplusMine"></span>
  </footer>
 </div>
</body>
<script src="js/index.js"></script>

</html>

CSS代码

.main .header {
 text-align: center;
 margin: 20px auto;
}

.main .gameBox table {
 border-spacing: 1px;
 background-color: rgb(170, 170, 170);
 text-align: center;
 margin: 20px auto;
}

.main .gameBox table td.mine {
 /* 游戏结束时显示 */
 border: none;
 background: url(./../img/mine.png) no-repeat;
 background-size: 90% 90%;
 background-color: #e9e6e6;
 background-position: 2px 0;
}

.main .gameBox table td.targetMine {
 /* 游戏结束时显示,触发雷的单元格 */
 border: none;
 background: url(./../img/mine.png) no-repeat;
 background-size: 90% 90%;
 background-color: #ff4b4b;
 background-position: 2px 0;
}

.main .gameBox table td.targetFlag {
 /* 右键标记方格时显示 */
 background: url(./../img/flag.png) no-repeat;
 background-size: 90% 90%;
 background-position: 2px 0;
 background-color: #e9e6e6;
}

.main .gameBox table td {
 /* 单元格初始样式 */
 width: 20px;
 height: 20px;
 box-sizing: border-box;
 border: 2px solid;
 border-color: #eee #ccc #ccc #eee;
 background-color: #e9e6e6;
 font-size: 1px;
 font-weight: 800;
}

.gameBox table td.zero,
.gameBox table td.one,
.gameBox table td.two,
.gameBox table td.three,
.gameBox table td.four,
.gameBox table td.five,
.gameBox table td.six,
.gameBox table td.seven,
.gameBox table td.eight,
.gameBox table td.nine {
 border: none;
 background-color: rgb(211, 200, 200);
}

.gameBox table td.zero {}

.gameBox table td.one {
 color: blue;
}

.gameBox table td.two {
 color: rgb(5, 93, 5);
}

.gameBox table td.three {
 color: #008c8c;
}

.gameBox table td.four {
 color: crimson;
}

.gameBox table td.five {
 color: rgb(228, 91, 0);
}

.gameBox table td.six {
 color: darkorange;
}

.gameBox table td.seven {
 color: rgb(193, 196, 50);
}

.gameBox table td.eight {
 color: pink;
}

.main .footer {
 text-align: center;
}

javaScript代码

核心代码

function Game(tr, td, mineNum) {
  this.td = td;
  this.tr = tr;
  this.mineNum = mineNum; //存储预设或设定的炸弹总数,用于后续判断是否胜利使用
  this.surplusMine = 0; //剩余雷数
  this.mineInfo = []; //用于接收随机生成的雷的信息
  this.tdsArr = [] //存放单元格的信息
  this.isPlay = false; //是否开始玩
  this.openClass = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
  this.gameBox = document.getElementById("gameBox");
  this.table = document.createElement("table"); //生成table标签
  this.footerNum = document.getElementById("surplusMine"); //剩余炸弹数量显示框

 }

 Game.prototype.creatDom = function() { //创建游戏区域,在玩家第一次点击游戏区域的时候执行
  this.table.oncontextmenu = function() { return false }; //清除默认右键单机事件
  for (var i = 0; i < this.gameBox.children.length; i++) { //为防止重新开始游戏时,重复生成多个table,在添加之前先删除之前的
   var childNod = this.gameBox.children[i];
   this.gameBox.removeChild(childNod);
  }

  for (var i = 0; i < this.tr; i++) {
   var tr = document.createElement("tr");
   this.tdsArr[i] = []; //为每一行生成一个数组

   for (var j = 0; j < this.td; j++) {
    var td = document.createElement("td");
    tr.appendChild(td); //将生成的td插入到tr中
    this.tdsArr[i][j] = td;
    td.info = { //info属性包括了单元格的所有信息,很重要
     type: "number", //格子类型,用于判断是否时炸弹
     x: i, //行
     y: j, //列
     value: 0, //当该格子周围有炸弹时显示该数值,生成炸弹的时候会++
     isOpen: false, //判断该单元格是否被打开
     isFlag: false //判断是否有标记flag
    }
   }
   this.table.appendChild(tr); //见tr插入到table中
  }
  this.gameBox.appendChild(this.table);
 }

 Game.prototype.creatMine = function(event, target) { //生成炸弹,该方法会在用户第一次点击棋盘的时候执行一次

  var This = this;
  for (var i = 0; true; i++) { //随机生成炸弹,生成扎当数与设定扎当书mineNum相同时终止循环
   var randomX = Math.floor(Math.random() * this.tr), //随机生成炸弹的行数
    randomY = Math.floor(Math.random() * this.td); //随机生成炸弹的列数
   // console.log(randomX + " " + randomY)
   if (target.info.x != randomX || target.info.y != randomY) { //保证第一次点击的时候不是炸弹
    if (this.tdsArr[randomX][randomY].info.type != "mine") { //保证每次生成的雷的位置不重复

     this.tdsArr[randomX][randomY].info.type = "mine"; //单元格更改属性为雷
     this.surplusMine++; //生成雷的数量+1
     this.mineInfo.push(this.tdsArr[randomX][randomY]); //将生成的雷的信息存放到this变量中,方便后续使用

    }
    if (this.surplusMine >= this.mineNum) { //当生成的炸弹数量等于设定的数量后跳出循环
     break;
    }
   }
  }

  //为每个炸弹周围的方格添加数字
  for (var i = 0; i < this.mineInfo.length; i++) {
   var around = this.getAround(this.mineInfo[i], This); //获取每个炸弹的周围方格
   // console.log(this.getAround(this.mineInfo[i], This))

   for (var j = 0; j < around.length; j++) { //将周围每个方格的value++
    around[j].info.value += 1;
   }
  }


 }

 Game.prototype.getAround = function(thisCell, This) { //获取某个方格的周围非炸弹方格,需要传递一个单元格dom元素,Game的this
  var x = thisCell.info.x, //行
   y = thisCell.info.y, //列
   result = [];
  // x-1,y-1  x-1,y  x-1,y+1
  // x,y-1   x,y   x,y+1
  // x+1,y-1  x+1y  x+1,y+1
  for (var j = x - 1; j <= x + 1; j++) {
   for (var k = y - 1; k <= y + 1; k++) {
    if ( //游戏区域的边界,行数x和列数y不能为负数,且不能超过设定的行数和列数
     j < 0 ||
     k < 0 ||
     j > (This.tr - 1) ||
     k > (This.td - 1) ||
     //同时跳过自身和周边是雷的方格
     This.tdsArr[j][k].info.type == "mine" ||
     (j == x && k == y)

    ) {
     continue; //满足上述条件是则跳过当此循环;
    } else {
     result.push(This.tdsArr[j][k]) //将符合的单元格push到result中返回
    }
   }
  }
  return result;
 }

 Game.prototype.lifeMouse = function(event, target) { //左键点击事件
  var This = this; //用变量的方式将Game的this传递到函数中
  var noOpen = 0; //没有被打开的格子数量
  if (!target.info.isFlag) { //表示该必须没有被右键标记才能鼠标左击
   if (target.info.type == "number") { //是数字时,则可视化

    function getAllZero(target, This) { //递归函数
     // console.log(target.info)
     if (target.info.isFlag) { //当这个单元格之前有被标记过flag时,则将剩余炸弹数+1
      This.surplusMine += 1;
      target.info.isFlag = false; //单元格被打开后初始化flag
     }
     if (target.info.value == 0) { //等于格子的value等于0的时候

      target.className = This.openClass[target.info.value]; //可视化

      target.info.isOpen = true; //表示该单元格被打开

      var thisAround = This.getAround(target, This); //获取该单元格周围的格子信息

      for (var i = 0; i < thisAround.length; i++) {
       // console.log(thisAround[i].info.isOpen)
       if (!thisAround[i].info.isOpen) { //递归的条件,当格子的open为true时不执行

        getAllZero(thisAround[i], This) //执行递归
       }

      }

     } else {
      target.innerHTML = target.info.value;
      target.className = This.openClass[target.info.value]; //可视化
      target.info.isOpen = true; //表示单元格被打开
      target.info.isFlag = false; //单元格被打开后初始化flag

     }
    }

    getAllZero(target, This); //首次执行

    //每次鼠标左键点击的时候都需要检查一下没有被打开的方格数量,每有一个则noOpen++
    for (var i = 0; i < this.tr; i++) {
     for (var j = 0; j < this.tr; j++) {
      if (this.tdsArr[i][j].info.isOpen == false) {
       noOpen++;
      }
     }

    }
    //当noOpen的数量与炸弹数量相同时,说明剩余的方格全是雷,游戏通过
    if (noOpen == this.mineNum) {
     console.log(noOpen)
     this.gameWin();
    }

   } else { //点击到了炸弹,游戏结束
    this.gameOver(target)
   }
  }



 }

 Game.prototype.rightMouse = function(target) { //鼠标右键点击执行
  if (!target.info.isOpen) {
   if (!target.info.isFlag) { //标记
    target.className = "targetFlag"; //显示旗帜
    target.info.isFlag = true; //表示该方格已经被标记
    this.surplusMine -= 1; //每标记一个方格,剩余炸弹数量-=1
    // console.log(this.surplusMine)
   } else { //取消标记
    target.className = ""; //去掉旗帜
    target.info.isFlag = false;
    this.surplusMine += 1;
    // console.log(this.surplusMine)

   }

   var isWin = true;
   if (this.surplusMine == 0) { //标记完所有flag时,遍历所有单元格
    // console.log(this.mineInfo.length)
    for (var i = 0; i < this.mineInfo.length; i++) {
     console.log(this.mineInfo[i].info.isFlag)
     if (!this.mineInfo[i].info.isFlag) { //检查每个雷的isFlag属性是否被标记,只要有一个为false则输掉游戏
      isWin = false;
      this.gameOver(target, 1);
      break;
     }
    }
    isWin ? this.gameWin(1) : 0; //三目运算符号
   }


   // if (this.surplusMine == 0) { //标记完所有flag时,遍历所有单元格
   //  for (var i; i < this.tr; i++) {
   //   for (var j; j < this.td; j++) {
   //    if()
   //   }

   //  }
   // }
  }
 }

 Game.prototype.gameOver = function(target, code) { //游戏结束,code为触发代码,当旗用完了时为1,点击到炸弹为0
  // console.log(this.mineInfo)
  var mineInfoLen = this.mineInfo.length;
  for (var i = 0; i < mineInfoLen; i++) { //显示每个雷的位置
   this.mineInfo[i].className = "mine";
  }
  this.table.onmousedown = false; //取消鼠标事件

  if (code) {
   alert("旗帜用完了,没有排除所有雷,游戏结束")
  } else {
   target.className = "targetMine"; //触发雷标红色
   alert("你被炸弹炸死了,游戏结束")
  }

 }

 Game.prototype.gameWin = function(code) { //游戏胜利
  if (code) {
   alert("你成功标记所有地雷,游戏通过")
  } else {
   alert("你找到了所有安全区域,游戏通过")
  }
  this.table.onmousedown = false;


 }

 Game.prototype.play = function() {
  var This = this; //需要将this传递到事件函数中使用
  this.table.onmousedown = function(event) {
   event = event || window.event; //兼容IE
   target = event.target || event.srcElement //兼容IE

   if (!this.isPlay) { //首次点击初始化棋盘,随机生成炸弹
    this.isPlay = true;
    This.creatMine(event, target);
   }

   if (event.button == 0) { //鼠标左键点击时执行
    This.lifeMouse(event, target);

   } else if (event.button == 2) { //右键点击执行
    This.rightMouse(target)
   }
   This.footerNum.innerHTML = This.surplusMine; //每次点击右键,刷新页面下方的剩余雷数
  }
 }

 Game.prototype.tablePos = function() { //将table居中显示
  var width = this.table.offsetWidth,
   height = this.table.offsetHeight;
  // console.log(this.table.offsetWidth)
  this.table.style.width = width + "px ";
  this.table.style.height = height + "px "


 }


 function addEvent(elem, type, handle) { //添加事件函数
  if (elem.addEventListener) { //w3c标准
   elem.addEventListener(type, handle, false);
  } else if (elem.attachEvent) { //IE9及以下
   elem.attachEvent("on" + type, function() {
    handle.call(elem);
   })
  } else { //其他情况
   elem["on" + type] = handle;
  }
 }

 Game.prototype.setDegree = function() { //调整难度
  var button = document.getElementsByTagName("button");

  addEvent(button[0], "click", function() { //简单
   var game = new Game(10, 10, 10);
   game.creatDom();
   game.play();
   game.tablePos();
  });

  addEvent(button[1], "click", function() { //一般
   var game = new Game(16, 16, 50);
   game.creatDom();
   game.play();
   game.tablePos();
  });

  addEvent(button[2], "click", function() { //困难
   var game = new Game(30, 30, 125);
   game.creatDom();
   game.play();
   game.tablePos();
  });


 }

 // 默认棋盘
 var game = new Game(10, 10, 10);
 game.creatDom();
 game.play();
 game.tablePos();
 game.setDegree()

总结一下,该游戏个人觉得难点有4个:

  • 没有思路,在bilibili看了一个教学视频,但是比较难理解,在原有基础上增加了自己的一些想法
  • 递归
  • 获取某一个方格周围的八个方格
  • 多层的if嵌套和循环

缺点:

  • 性能不佳,存在大量for循环,且没有优化
  • 某些时候界面显示的剩余炸弹数量不准确(已修复)
  • 代码冗余较多

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
OfflineSave离线保存代码再次发布使用说明
May 23 Javascript
用JavaScript将从数据库中读取出来的日期型格式化为想要的类型。
Aug 15 Javascript
javascript学习笔记(五) Array 数组类型介绍
Jun 19 Javascript
事件冒泡是什么如何用jquery阻止事件冒泡
Mar 20 Javascript
利用jQuery及AJAX技术定时更新GridView的某一列数据
Dec 04 Javascript
基于Node.js的强大爬虫 能直接发布抓取的文章哦
Jan 10 Javascript
JS弹出窗口插件zDialog简单用法示例
Jun 12 Javascript
window.open打开窗口被拦截的快速解决方法
Aug 04 Javascript
js 单引号替换成双引号,双引号替换成单引号的实现方法
Feb 16 Javascript
微信小程序 五星评分的实现实例
Aug 04 Javascript
微信小程序自定义对话框弹出和隐藏动画
Jul 19 Javascript
vue浏览器返回监听的具体步骤
Feb 03 Vue.js
JavaScript字符串转数字的简单实现方法
Nov 27 #Javascript
使用vue编写h5公众号跳转小程序的实现代码
Nov 27 #Vue.js
JavaScript中的Proxy对象
Nov 27 #Javascript
详解JavaScript中的链式调用
Nov 27 #Javascript
在Vue中使用CSS3实现内容无缝滚动的示例代码
Nov 27 #Vue.js
vuex的数据渲染与修改浅析
Nov 26 #Vue.js
vue动态合并单元格并添加小计合计功能示例
Nov 26 #Vue.js
You might like
php 字符串函数收集
2010/03/29 PHP
php中3des加密代码(完全与.net中的兼容)
2012/08/02 PHP
php中OR与|| AND与&amp;&amp;的区别总结
2013/10/26 PHP
php数组合并的二种方法
2014/03/21 PHP
PHP类型约束用法示例
2016/09/28 PHP
js以对象为索引的关联数组
2010/07/04 Javascript
33个优秀的 jQuery 图片展示插件分享
2012/03/14 Javascript
window.open()详解及浏览器兼容性问题示例探讨
2014/05/29 Javascript
jQuery防止重复绑定事件的解决方法
2016/05/14 Javascript
深入理解JavaScript 函数
2016/06/06 Javascript
JavaScript中的对象和原型(一)
2016/08/12 Javascript
js enter键激发事件实例代码
2016/08/17 Javascript
分享一个原生的JavaScript拖动方法
2016/09/25 Javascript
清除输入框内的空格
2016/12/21 Javascript
bootstrap paginator分页前后台用法示例
2017/06/17 Javascript
View.post() 不靠谱的地方你知道多少
2017/08/29 Javascript
javascript读取本地文件和目录方法详解
2020/08/06 Javascript
python调用短信猫控件实现发短信功能实例
2014/07/04 Python
Python实现过滤单个Android程序日志脚本分享
2015/01/16 Python
利用Python实现Shp格式向GeoJSON的转换方法
2019/07/09 Python
python3.6编写的单元测试示例
2019/08/17 Python
numpy.linalg.eig() 计算矩阵特征向量方式
2019/11/29 Python
html通过canvas转成base64的方法
2019/07/18 HTML / CSS
HTML5中判断横屏竖屏的方法(移动端)
2016/08/04 HTML / CSS
牵手50新加坡:专为黄金岁月的单身人士而设的交友网站
2020/08/16 全球购物
在Java开发中如何选择使用哪种集合类
2016/08/09 面试题
linux面试题参考答案(3)
2012/09/13 面试题
采购员岗位职责
2013/11/15 职场文书
《玩具柜台前的孩子》教学反思
2014/02/13 职场文书
幼儿园小班个人工作总结
2015/02/12 职场文书
会计求职简历自我评价
2015/03/10 职场文书
2015年班组建设工作总结
2015/05/13 职场文书
2015年度酒店客房部工作总结
2015/05/25 职场文书
信息技术研修心得体会
2016/01/08 职场文书
Java设计模式之享元模式示例详解
2022/03/03 Java/Android
分享几个实用的CSS代码块
2022/06/10 HTML / CSS