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 相关文章推荐
Javascript 获取链接(url)参数的方法
Feb 15 Javascript
jquery操作select option 的代码小结
Jun 21 Javascript
Javascript 键盘事件的组合使用实现代码
May 04 Javascript
jquery Mobile入门—外部链接切换示例代码
Jan 08 Javascript
Flex通过JS获取客户端IP和计算机名的实例代码
Nov 21 Javascript
JS读写CSS样式的方法汇总
Aug 16 Javascript
ES6 迭代器(Iterator)和 for.of循环使用方法学习(总结)
Feb 08 Javascript
AngularJS中的作用域实例分析
May 16 Javascript
微信小程序异步API为Promise简化异步编程的操作方法
Aug 14 Javascript
微信小程序map组件结合高德地图API实现wx.chooseLocation功能示例
Jan 23 Javascript
vue3使用vuedraggable实现拖拽功能
Apr 06 Vue.js
前端框架ECharts dataset对数据可视化的高级管理
Dec 24 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
神族 PROTOSS 概述
2020/03/14 星际争霸
php结合飞信 免费天气预报短信
2009/05/07 PHP
php引用地址改变变量值的问题
2012/03/23 PHP
Php中使用Select 查询语句的实例
2014/02/19 PHP
PHP使用微信开发模式实现搜索已发送图文及匹配关键字回复的方法
2017/09/13 PHP
javascript下判断一个元素是否存在的代码
2010/03/05 Javascript
js实现简单的星级选择器提交效果适用于评论等
2013/10/18 Javascript
浅谈js中变量初始化
2015/02/03 Javascript
nodejs mysql 实现分页的方法
2017/06/06 NodeJs
用jquery获取select标签中选中的option值及文本的示例
2018/01/25 jQuery
React如何避免重渲染
2018/04/10 Javascript
JS实现简单的点赞与踩功能示例
2018/12/05 Javascript
vue动态添加路由addRoutes之不能将动态路由存入缓存的解决
2019/02/19 Javascript
前端js中的事件循环eventloop机制详解
2019/05/15 Javascript
js实现AI五子棋人机大战
2020/05/28 Javascript
Vue基于iview table展示图片实现点击放大
2020/08/05 Javascript
vue打开子组件弹窗都刷新功能的实现
2020/09/21 Javascript
[01:09:24]Ti4开幕式
2014/07/19 DOTA
使用python开发vim插件及心得分享
2014/11/04 Python
详解python3中socket套接字的编码问题解决
2017/07/01 Python
Python数据结构与算法之图的最短路径(Dijkstra算法)完整实例
2017/12/12 Python
使用python实现ANN
2017/12/20 Python
python实现把两个二维array叠加成三维array示例
2019/11/29 Python
PyCharm下载和安装详细步骤
2019/12/17 Python
python与mysql数据库交互的实现
2020/01/06 Python
使用Python发现隐藏的wifi
2020/03/04 Python
销售业务实习自我鉴定
2013/09/23 职场文书
小型女装店的创业计划书
2014/01/09 职场文书
怎么写自荐书范文
2014/02/12 职场文书
公司活动方案范文
2014/03/06 职场文书
趣味运动会广播稿
2014/09/13 职场文书
2014年医药代表工作总结
2014/11/22 职场文书
毕业生爱心捐书倡议书
2015/04/27 职场文书
CSS基础详解
2021/10/16 HTML / CSS
利用Python实现Picgo图床工具
2021/11/23 Python
Python+Pillow+Pytesseract实现验证码识别
2022/05/11 Python