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 相关文章推荐
用脚本调用样式的几种方法
Dec 09 Javascript
js下弹出窗口的变通
Apr 18 Javascript
javascript延时重复执行函数 lLoopRun.js
Jun 29 Javascript
一个简单的JavaScript 日期计算算法
Sep 11 Javascript
12个非常有创意的JavaScript小游戏
Mar 18 Javascript
页面加载完后自动执行一个方法的js代码
Sep 06 Javascript
JS折半插入排序算法实例
Dec 02 Javascript
JavaScript原生节点操作小结
Jan 17 Javascript
微信小程序实战之自定义模态弹窗(8)
Apr 18 Javascript
解决layer弹层遮罩挡住窗体的问题
Aug 17 Javascript
详解js加减乘除精确计算
Mar 19 Javascript
Vue 实现前进刷新后退不刷新的效果
Jun 14 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
简单的cookie计数器实现源码
2013/06/07 PHP
Swoole 5将移除自动添加Event::wait()特性详解
2019/07/10 PHP
从阿里妈妈发现的几个不错的表单验证函数
2007/09/21 Javascript
JavaScript中把数字转换为字符串的程序代码
2013/06/19 Javascript
js实现选中复选框文字变色的方法
2015/08/14 Javascript
jQuery超简单选项卡完整实例
2015/09/26 Javascript
angularjs实现多张图片上传并预览功能
2017/02/24 Javascript
jQuery实现广告条滚动效果
2017/08/22 jQuery
vue-router实现组件间的跳转(参数传递)
2017/11/07 Javascript
bootstrap响应式导航条模板使用详解(含下拉菜单,弹出框)
2017/11/17 Javascript
React中常见的动画实现的几种方式
2018/01/10 Javascript
浅谈Node.js 子进程与应用场景
2018/01/24 Javascript
详解微信小程序调用支付接口支付
2019/04/28 Javascript
详细教你微信公众号正文页SVG交互开发技巧
2019/07/25 Javascript
0基础学习前端开发的一些建议
2020/07/14 Javascript
在vue中封装方法以及多处引用该方法详解
2020/08/14 Javascript
[01:17]炒鸡美酒第四天TA暴走
2018/06/05 DOTA
python对象及面向对象技术详解
2016/07/19 Python
python爬取拉勾网职位数据的方法
2018/01/24 Python
利用pandas读取中文数据集的方法
2018/07/25 Python
Python 闭包,函数分隔作用域,nonlocal声明非局部变量操作示例
2019/10/14 Python
Python 实现训练集、测试集随机划分
2020/01/08 Python
对python中list的五种查找方法说明
2020/07/13 Python
Django中如何用xlwt生成表格的方法步骤
2021/01/31 Python
HTML5 新旧语法标记对我们有什么好处
2012/12/13 HTML / CSS
美的官方商城:Midea
2016/09/14 全球购物
布鲁明戴尔百货店:Bloomingdale’s
2016/12/21 全球购物
美国购买韩国护肤和美容产品网站:Althea Korea
2020/11/16 全球购物
市场营销专业个人自荐信格式
2013/09/21 职场文书
银行实习生的自我评价
2013/12/09 职场文书
行政专员工作职责
2013/12/22 职场文书
小学生评语集锦
2014/04/18 职场文书
新党章的学习心得体会
2014/11/07 职场文书
培根随笔读书笔记
2015/07/01 职场文书
2015年秋季小学开学标语
2015/07/16 职场文书
Java 中的 Unsafe 魔法类的作用大全
2021/06/26 Java/Android