js实现扫雷小程序的示例代码


Posted in Javascript onSeptember 27, 2017

初学javascript,写了一个扫雷程序练练手!

扫雷规则及功能

扫雷想必大家都不陌生,就是windows上点击排雷的小游戏,它的主要规则有

1.左键点击显示当前格子是否为雷,如果为雷的话,GameOver啦,如果不是雷的话,这个格子会显示周围八个格子内的雷数量。
2.鼠标右键标记,标记可能的雷,标记了之后取消需要再次右键点击该格子,左键无效果。
3.鼠标中键(滚轮)按下后,快捷扫雷(如果周围雷数和未被标记且未被翻开的格子相等,会将这些格子一并翻开)

主要功能基本完全复刻了windows7扫雷的功能

扫雷github地址:扫雷github地址

扫雷算法

1.首先我定义了一个构造函数,里面是一系列的属性:

var mineCraft = function(num1,num2,mine_num,obj,type){
    this.num1 = num1;            //整个扫雷的行数
    this.num2 = num2;            //整个扫雷的列数 
    this.mine_num = mine_num;        //雷的个数
    this.tiles = [];             //数组里面存放的是每个小格子
    this.obj = obj;             //扫雷放置的对象
    this.flag = true;            
    this.arr = [];
    this.arr_2 = [];
    this.time_dsq = null;     
    this.time_dc ='';
    this.time_arr = [[],[],[]];       //时间统计信息
    this.details = [[],[],[]];       // 游戏统计详情
    this.type = type;           //游戏类型:初级/中级/高级/自定义
    this.buildTiles();           //创建游戏函数
  };

2.在页面上创建扫雷的界面 函数buildTiles

buildTiles:function(){
      this.obj.style.width = 51*this.num1+'px'; //在传进来的对象上画整体格子,每个小格子51px大小,总大小就为个数*单个大小
      this.obj.style.height = 51*this.num2+'px';
      var indexOfdiv = 0;           
      for(var i = 0;i<this.num2;i++){
        for(var j = 0;j<this.num1;j++){
          var tile = document.createElement('div');
          tile.className = 'tile';     //定义小格子class
          tile.index = indexOfdiv;     //为每个小格子添加索引
          this.tiles[indexOfdiv] = tile;  //将小格子存入数组中
          indexOfdiv++;
          this.obj.appendChild(tile);    //将小格子插入到整个扫雷界面中 
        }
      }
      this.obj.oncontextmenu = function(){  //取消浏览器的默认右键菜单事件
        return false;
      }
      this.event();            //点击事件
    },

3.绑事件函数:

event : function(){
      var _this = this;
      this.obj.onmouseover = function(e){    //鼠标悬停事件---
        if(e.target.className == 'tile'){
          e.target.className = 'tile current';
        }
      }
      this.obj.onmouseout = function(e){    //鼠标移出事件--
        if(e.target.className == 'tile current'){
          e.target.className = 'tile';
        }
      }
      this.obj.onmousedown = function(e){    //鼠标按下事件
        var index = e.target.index;
        if(e.button == 1){          //e.button属性 左键0/中键1/右键2
          event.preventDefault();    //取消默认
        }
        _this.changeStyle(e.button,e.target,index);
      }
      this.obj.onmouseup = function(e){   //鼠标弹起事件
        if(e.button == 1){
          _this.changeStyle(3,e.target);
        }
      }
    },

4.点击调用的函数:

changeStyle:function(num1,obj,num_index){     
      if(num1 == 0){         //是左键的话
        if(this.flag){       //this.flag 是之前定义的用于判断是否为第一次点击
          this.store(num_index);     //store函数,存放被点击的格子周围的8个格子
          this.setMineCraft(this.mine_num,this.arr,num_index); //如果是第一次点击 即调用布雷函数 更改flag状态
          this.flag = false;
          this.detail_statistics(0,false);   //开始信息统计函数
        }        
        if(obj.className != 'tile'&&obj.className !='tile current'){//如果不是第一次点击,被点击的格子不是未点击状态,无效
          return false;
        }
        if(obj.getAttribute('val') == 0){  //如果不是雷。改为翻开状态
          obj.className = "showed";    
          obj.innerHTML = obj.getAttribute('value') == 0?'':obj.getAttribute('value');          //显示周围雷数
          this.showAll(obj.index);   //递归函数判断周围格子的情况,就是扫雷游戏上一点开会出现一片的那种
        }
        if(this.over(obj)){       //判断游戏是否结束
          this.last();     
        }
      }
      if(num1 == 2){            //右键标记事件
        if(obj.className == 'biaoji'){
          obj.className = 'tile';
        }else if(obj.className !='biaoji'&&obj.className != 'showed'){
          obj.className = 'biaoji';
        }
      }
      if(num1 == 1){           // 中键事件
        if(obj.className =="showed"){
          this.show_zj1(obj.index);
        }
      }
      if(num1 == 3){          //鼠标弹起事件
        
        if (obj.className == "showed") {
          var flag1 = this.show_zj2(obj.index,0);
        }else{
          this.show_zj2(obj.index,1)
          return false;
        }

        if(flag1&&this.over()){     //弹起判断是否结束
          this.last();
        }
      }
    },

5.布雷:我之前的布雷是在页面加载在buildTiles()的时候布雷的,但是这样会导致有可能你电机的第一个格子就是雷(游戏性不强),后来修改到第一次点击完成之后布雷(确保第一下点的不是雷),避开直接炸死的现象.所以把调用放在后面的event后触发的changeStyle函数中

setMineCraft:function(num,arr_first,num_first){ //雷的个数、最开始被点击的格子周围的八个、被点击的那个格子
      var arr_index = [];          
      for(var i = 0;i<arr_first.length;i++){
        arr_index.push(arr_first[i].index);
      }
      var length = this.tiles.length;
      for (var i = 0; i < length; i++) {
        this.tiles[i].setAttribute("val", 0);
      }
      for (var i = 0; i < num; i++) {       
        var index_Mine = Math.floor(Math.random() * this.tiles.length);
        if(index_Mine == num_first||arr_index.lastIndexOf(index_Mine)>-1){//如果是属于第一次点击的周围的直接跳过在该位置布雷
          num++;
          continue;
        }
        
        if (this.tiles[index_Mine].getAttribute("val") == 0) {
          this.tiles[index_Mine].setAttribute("val", 1);
        }else {
          num++;
        }
      }
      this.showValue();
      this.event()
    },

6.存储周围格子的函数:

store : function(num) {  //传入格子的index.
      var tiles_2d = [];
      var indexs = 0;
      for(var i = 0;i<this.num2;i++){
        tiles_2d.push([]);
        for(var j = 0;j<this.num1;j++){
          tiles_2d[i].push(this.tiles[indexs]);
          indexs++;
        } 
      }
      var j = num % this.num1;
      var i = (num - j) / this.num1;
      this.arr = [];
        //左上
      if (i - 1 >= 0 && j - 1 >= 0) {
        this.arr.push(tiles_2d[i - 1][j - 1]);
      }
        //正上
      if (i - 1 >= 0) {
        this.arr.push(tiles_2d[i - 1][j]);
      }
        //右上
      if (i - 1 >= 0 && j + 1 <= this.num1-1) {
        this.arr.push(tiles_2d[i - 1][j + 1]);
      }
        //左边
      if (j - 1 >= 0) {
        this.arr.push(tiles_2d[i][j - 1]);
      }
        //右边
      if (j + 1 <= this.num1-1) {
        this.arr.push(tiles_2d[i][j + 1]);
      }
        //左下
      if (i + 1 <= this.num2-1 && j - 1 >= 0) {
        this.arr.push(tiles_2d[i + 1][j - 1]);
      }
        //正下
      if (i + 1 <= this.num2-1) {
        this.arr.push(tiles_2d[i + 1][j]);
      }
        //右下
      if (i + 1 <= this.num2-1 && j + 1 <= this.num1-1) {
        this.arr.push(tiles_2d[i + 1][j + 1]);
      }
    },

7.showAll函数:作用是如果该格子周围没有雷,自动翻开周围8个格子,然后再判断周围八个格子的周围8隔格子是否有雷,利用了递归的方法
 

showAll:function(num){
      if (this.tiles[num].className == "showed" && this.tiles[num].getAttribute("value") == 0){
        this.store(this.tiles[num].index);
        var arr2 = new Array();
        arr2 = this.arr;
        for (var i = 0; i < arr2.length; i++) {
          if (arr2[i].className != "showed"&&arr2[i].className !='biaoji') {
            if (arr2[i].getAttribute("value") == 0) {
              arr2[i].className = "showed";
              this.showAll(arr2[i].index);
            } else {
              arr2[i].className = "showed";
              arr2[i].innerHTML = arr2[i].getAttribute("value");
            }
          }
        }
      }
    },

8.show_zj函数:主要是中键按钮的作用中键点击后的函数,这里的show_zj1是鼠标键按下后的显示效果,
show_zj2函数就是

show_zj1:function(num){
      this.store(this.tiles[num].index);
      for (var i = 0; i < this.arr.length; i++) {
        if (this.arr[i].className == "tile") {
          this.arr_2.push(this.arr[i]);
          this.arr[i].className = "showed";
          // this.arr[i].className = "test";
        }
      }
    },
    show_zj2:function(num,zt){
      
      var count = 0;
      this.store(this.tiles[num].index);      
      
      for(var i = 0,len = this.arr_2.length;i<len;i++){
        this.arr_2[i].className = 'tile';     //按下效果恢复原状
      }

      this.arr_2.length = 0;
      for(var i = 0;i<this.arr.length;i++){
        this.arr[i].className == 'biaoji'&&count++;
      }
      if(zt == 1){
        return false;
      }
      var numofmines = this.tiles[num].getAttribute("value");
      if(numofmines == count){               //如果周围雷数和周围被标记数相等就翻开周围的格子
          var arr = new Array(this.arr.length);
          for(var i = 0;i<this.arr.length;i++){
            arr[i] = this.arr[i];
          }
          for (var i = 0,length = arr.length; i < length; i++) { 
            if (arr[i].className == "tile" && arr[i].getAttribute("val") != 1) {//如果周围格子无雷则继续。
              arr[i].className = "showed";
              arr[i].innerHTML = arr[i].getAttribute("value") == 0?"":arr[i].getAttribute("value");
              this.showAll(arr[i].index);
            } else if (arr[i].className == "tile" && arr[i].getAttribute("val") == 1) {  //如果周围格子有雷,游戏结束
              this.over(arr[i]);
              this.last();
              return false;
            }
          }
      }
      return true;
    },

9.结束判断:

over:function(obj){
      var flag = false;
      var showed = document.getElementsByClassName('showed');  
      var num = this.tiles.length - this.mine_num;     
      if(showed.length == num){           //如果被排出来的格子数等于总格子数-雷数,这游戏成功结束  
        this.detail_statistics(1,true);      //游戏统计 ,true代表胜,false,代表失败
        alert('恭喜你获得成功');
        flag = true;
      }else if(obj&&obj.getAttribute('val') == 1){   //如果被点击的是雷,则炸死
        this.detail_statistics(1,false);
        alert('被炸死!');
        flag = true;

      }
      return flag;
    },

10.结束后的显示函数:

last:function(){   
      var len = this.tiles.length;
      for(var i = 0;i<len;i++){
        this.tiles[i].className = this.tiles[i].getAttribute('val') == 1?'boom':'showed';
        if(this.tiles[i].className != 'boom'){  //
          this.tiles[i].innerHTML = this.tiles[i].getAttribute('value') == 0?'':this.tiles[i].getAttribute('value');
        }
      }
      this.obj.onclick = null;
      this.obj.oncontextmenu = null;
    },

11 统计信息:还是比较全的和windows7扫雷版的判断项目是一样的,使用的是每次结束游戏后将数据存入localStorage中,

//已玩游戏,已胜游戏,胜率,最多连胜,最多连败,当前连局;
    detail_statistics:function(num,zt){
      var time_pay = 1;
      var _this = this;
      if(num == 0){
        this.time_dsq = setInterval(function(){
          $('#time_need').text(time_pay);
          _this.time_dc =time_pay;
          time_pay++;
         },1000);
    
      }
      else if(num == 1){
        clearInterval(this.time_dsq);
        if(this.type == 4){return false;}
        if(localStorage.details == undefined){          
          localStorage.details = JSON.stringify([[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]]); //这里存放的就是上面注释中的六项数据
        }
        if(JSON.parse(localStorage.details) instanceof Array){
          this.details = JSON.parse(localStorage.details);       
        }
        this.details[this.type][0] += 1;
        if(zt == false){
          if(this.details[this.type][5]>=0){
            this.details[this.type][5] = -1;
          }else{
            this.details[this.type][5] -= 1;
          }  
          if(this.details[this.type][5]<this.details[this.type][4]){
            this.details[this.type][4] = this.details[this.type][5];
          }
          this.details[this.type][2] = this.toPercent(this.details[this.type][2]/this.details[this.type][0]);        
          localStorage.details = JSON.stringify(this.details);
          return false;
        }

        if(this.details[this.type][5]>=0){
          this.details[this.type][5] += 1;
        }else{
          this.details[this.type][5] = 1;
        }
        if(this.details[this.type][5]>this.details[this.type][3]){
          this.details[this.type][3] = this.details[this.type][5];
        }
        this.details[this.type][3] += 1;
        this.details[this.type][2] = this.toPercent(this.details[this.type][4]/this.details[this.type][0]);
        localStorage.details = JSON.stringify(this.details);
        
        var time1 = new Date();        
        var time_str = time1.getFullYear()+'/'+time1.getMonth()+'/'+time1.getDate()+' '+time1.getHours()+':'+time1.getMinutes();
        if(localStorage.time == undefined){
          localStorage.time = JSON.stringify([[],[],[]]);
        }
        if(JSON.parse(localStorage.time) instanceof Array){
          this.time_arr = JSON.parse(localStorage.time);
        }

        this.time_arr[this.type].push([this.time_dc,time_str]);
        this.time_arr[this.type].sort(function(a,b){
          return a[0]-b[0];
        });
        if(this.time_arr[this.type].length>5){
          this.time_arr[this.type].pop();
        }
        localStorage.time = JSON.stringify(this.time_arr);
      
      }
    },

扫雷的主要部分就是这些了,还有一些小功能包括没写来,要看完整的可以看gitHub

之前看书学习总觉得学了就忘,感觉懂了公式却不知道怎么用,写完扫雷小程序觉得收获了很多

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

Javascript 相关文章推荐
JavaScript:Div层拖动效果实例代码
Aug 06 Javascript
对象转换为原始值的实现方法
Jun 06 Javascript
js 获取元素所有兄弟节点的实现方法
Sep 06 Javascript
Javascript数组中push方法用法分析
Oct 31 Javascript
基于javascript实现数字英文验证码
Jan 25 Javascript
JQuery.dataTables表格插件添加跳转到指定页
Jun 09 jQuery
bootstrapvalidator之API学习教程
Jun 29 Javascript
jQuery中.attr()和.data()的区别分析
Sep 03 jQuery
javascript获取指定区间范围随机数的方法
Sep 08 Javascript
jQuery实现高级检索功能
May 28 jQuery
jQuery实现的图片点击放大缩小功能案例
Jan 02 jQuery
file-loader打包图片文件时路径错误输出为[object-module]的解决方法
Jan 03 Javascript
Three.js如何实现雾化效果示例代码
Sep 27 #Javascript
浅谈Angular4中常用管道
Sep 27 #Javascript
深入理解Vue.js源码之事件机制
Sep 27 #Javascript
js截取字符串功能的实现方法
Sep 27 #Javascript
详解node+express+ejs+bootstrap构建项目
Sep 27 #Javascript
Three.js基础学习之场景对象
Sep 27 #Javascript
vue父组件中获取子组件中的数据(实例讲解)
Sep 27 #Javascript
You might like
dedecms 制作模板中使用的全局标记图文教程
2007/03/11 PHP
php面向对象全攻略 (九)访问类型
2009/09/30 PHP
用PHP实现递归循环每一个目录
2010/08/08 PHP
php 模拟GMAIL,HOTMAIL(MSN),YAHOO,163,126邮箱登录的详细介绍
2013/06/18 PHP
php注册和登录界面的实现案例(推荐)
2016/10/24 PHP
php实现通过stomp协议连接ActiveMQ操作示例
2020/02/23 PHP
javascript 动态添加表格行
2006/06/22 Javascript
JQuery获取元素文档大小、偏移和位置和滚动条位置的方法集合
2010/01/12 Javascript
javascript 控制input只允许输入的各种指定内容
2014/06/19 Javascript
微信小程序获取手机号授权用户登录功能
2017/11/09 Javascript
jQuery简单实现的HTML页面文本框模糊匹配查询功能完整示例
2018/05/09 jQuery
Vue自定义弹窗指令的实现代码
2018/08/13 Javascript
Node.js中读取TXT文件内容fs.readFile()用法
2018/10/10 Javascript
微信小程序云开发之云函数详解
2019/05/16 Javascript
AI小程序之语音听写来了,十分钟掌握百度大脑语音听写全攻略
2020/03/13 Javascript
python正则表达式判断字符串是否是全部小写示例
2013/12/25 Python
python实现给字典添加条目的方法
2014/09/25 Python
简介Django中内置的一些中间件
2015/07/24 Python
Python编写一个闹钟功能
2017/07/11 Python
理解python中生成器用法
2017/12/20 Python
Python操作MongoDB数据库的方法示例
2018/01/04 Python
解决seaborn在pycharm中绘图不出图的问题
2018/05/24 Python
Django实现微信小程序的登录验证功能并维护登录态
2019/07/04 Python
从列表或字典创建Pandas的DataFrame对象的方法
2019/07/06 Python
python3中替换python2中cmp函数的实现
2019/08/20 Python
关于初始种子自动选取的区域生长实例(python+opencv)
2020/01/16 Python
pytorch下的unsqueeze和squeeze的用法说明
2021/02/06 Python
linux面试题参考答案(4)
2014/09/21 面试题
文员个人的求职信范文
2013/09/26 职场文书
酒店前厅员工辞职信
2014/01/08 职场文书
小学清明节活动总结
2014/07/04 职场文书
学生意外伤害赔偿协议书
2014/09/17 职场文书
《微笑着面对生活》优秀演讲稿范文
2014/09/23 职场文书
未婚证明格式
2015/06/15 职场文书
Python爬取科目四考试题库的方法实现
2021/03/30 Python
JavaScript实现两个数组的交集
2022/03/25 Javascript