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 相关文章推荐
又一个图片自动缩小的JS代码
Mar 10 Javascript
javascript 在firebug调试时用console.log的方法
May 10 Javascript
node.js中的fs.fchown方法使用说明
Dec 16 Javascript
js实现select跳转菜单新窗口效果代码分享(超简单)
Aug 21 Javascript
js实现的tab标签切换效果代码分享
Aug 25 Javascript
详解JS面向对象编程
Jan 24 Javascript
JS实现的适合做faq或menu滑动效果示例
Nov 17 Javascript
详解JS去重及字符串奇数位小写转大写
Dec 29 Javascript
JS回调函数基本定义与用法实例分析
May 24 Javascript
自己动手封装一个React Native多级联动
Sep 19 Javascript
如何在Angular应用中创建包含组件方法示例
Mar 23 Javascript
layui表格分页 记录勾选的实例
Sep 02 Javascript
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获取网页内容方法总结
2008/12/04 PHP
PHP中调用ASP.NET的WebService的代码
2011/04/22 PHP
浏览器预览PHP文件时顶部出现空白影响布局分析原因及解决办法
2013/01/11 PHP
php查找任何页面上的所有链接的方法
2013/12/03 PHP
php实现的IMEI限制的短信验证码发送类
2015/05/05 PHP
JavaScript 学习笔记 Black.Caffeine 09.11.28
2009/11/30 Javascript
两个JavaScript jsFiddle JSBin在线调试器
2010/03/14 Javascript
JavaScript高级程序设计(第3版)学习笔记12 js正则表达式
2012/10/11 Javascript
nullJavascript中创建对象的五种方法实例
2013/05/07 Javascript
用JavaScript计算在UTF-8下存储字符串占用字节数
2013/08/08 Javascript
JS实现向表格行添加新单元格的方法
2015/03/30 Javascript
JavaScript简单实现弹出拖拽窗口(二)
2016/06/17 Javascript
springMVC结合AjaxForm上传文件
2016/07/12 Javascript
javascript验证手机号和实现星号(*)代替实例
2016/08/16 Javascript
JavaScript中子对象访问父对象的方式详解
2016/09/01 Javascript
js手动播放图片实现图片轮播效果
2016/09/17 Javascript
jQuery仿写百度百科的目录树
2017/01/03 Javascript
Node.js使用NodeMailer发送邮件实例代码
2017/03/06 Javascript
从零开始学习Node.js系列教程一:http get和post用法分析
2017/04/13 Javascript
react-native之ART绘图方法详解
2017/08/08 Javascript
Vue中在新窗口打开页面及Vue-router的使用
2018/06/13 Javascript
Vue2.5学习笔记之如何在项目中使用和配置Vue
2018/09/26 Javascript
Node.js创建一个Express服务的方法详解
2020/01/06 Javascript
[01:17]炒鸡美酒第四天TA暴走
2018/06/05 DOTA
Python中那些 Pythonic的写法详解
2019/07/02 Python
Python递归函数特点及原理解析
2020/03/04 Python
快速创建python 虚拟环境
2020/11/28 Python
加拿大在线隐形眼镜和眼镜店:VisionPros
2019/10/06 全球购物
秘书专业自荐信范文
2013/12/26 职场文书
银行先进个人事迹材料
2014/05/11 职场文书
设计大赛策划方案
2014/06/13 职场文书
企业廉洁教育心得体会
2016/01/20 职场文书
《蓝鲸的眼睛》读后感5篇
2020/01/15 职场文书
Python 实现定积分与二重定积分的操作
2021/05/26 Python
GoFrame框架数据校验之校验结果Error接口对象
2022/06/21 Golang
js 实现Material UI点击涟漪效果示例
2022/09/23 Javascript