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 相关文章推荐
jQuery Pagination Ajax分页插件(分页切换时无刷新与延迟)中文翻译版
Jan 11 Javascript
jquery实现弹出窗口效果的实例代码
Nov 28 Javascript
Javascript中匿名函数的调用与写法实例详解(多种)
Jan 26 Javascript
再谈JavaScript异步编程
Jan 27 Javascript
jQuery实现鼠标经过购物车出现下拉框代码(推荐)
Jul 21 Javascript
解决BootStrap Fileinput手机图片上传显示旋转问题
Jun 01 Javascript
vue引入jq插件的实例讲解
Sep 12 Javascript
zTree树形菜单交互选项卡效果的实现方法
Dec 25 Javascript
vue-cli下的vuex的简单Demo图解(实现加1减1操作)
Feb 26 Javascript
vue基于element-ui的三级CheckBox复选框功能的实现代码
Oct 15 Javascript
解决vue 界面在苹果手机上滑动点击事件等卡顿问题
Nov 27 Javascript
TS 类型收窄教程示例详解
Sep 23 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
NOD32 v2.70.32 简体中文封装版 提供下载了
2007/02/27 PHP
PHP插入排序实现代码
2013/04/04 PHP
PHP基于Redis消息队列实现发布微博的方法
2017/05/03 PHP
php 多继承的几种常见实现方法示例
2019/11/18 PHP
javascript 建设银行登陆键盘
2008/06/10 Javascript
日期 时间js控件
2009/05/07 Javascript
jquery 得到当前页面高度和宽度的两个函数
2010/02/21 Javascript
js substr支持中文截取函数代码(中文是双字节)
2013/04/17 Javascript
jQuery遍历Table应用示例
2014/04/09 Javascript
Javascript实现Array和String互转换的方法
2015/12/21 Javascript
jQuery的实例及必知重要的jQuery选择器详解
2016/05/20 Javascript
Ext JS框架程序中阻止键盘触发回退或者刷新页面的代码分享
2016/06/07 Javascript
js实现自定义路由
2017/02/04 Javascript
微信小程序如何获知用户运行小程序的场景教程
2017/05/17 Javascript
Vue.js学习笔记之修饰符详解
2017/07/25 Javascript
微信小程序 swiper组件构建轮播图的实例
2017/09/20 Javascript
ui-router中使用ocLazyLoad和resolve的具体方法
2017/10/18 Javascript
vue.js开发实现全局调用的MessageBox组件实例代码
2017/11/22 Javascript
[02:17]2016完美“圣”典风云人物:Sccc专访
2016/12/03 DOTA
py2exe 编译ico图标的代码
2013/03/08 Python
用Python和MD5实现网站挂马检测程序
2014/03/13 Python
python常用web框架简单性能测试结果分享(包含django、flask、bottle、tornado)
2014/08/25 Python
简介Python设计模式中的代理模式与模板方法模式编程
2016/02/02 Python
python实现txt文件格式转换为arff格式
2018/05/31 Python
Python对象与引用的介绍
2019/01/24 Python
djang常用查询SQL语句的使用代码
2019/02/15 Python
对Python生成器、装饰器、递归的使用详解
2019/07/19 Python
python实现抠图给证件照换背景源码
2019/08/20 Python
python-Web-flask-视图内容和模板知识点西宁街
2019/08/23 Python
浅谈JupyterNotebook导出pdf解决中文的问题
2020/04/22 Python
以设计师精品品质提供快速时尚:Mostata
2019/05/10 全球购物
培训科主任岗位职责
2014/08/08 职场文书
三好学生主要事迹怎么写
2015/11/03 职场文书
SQL Server基本使用和简单的CRUD操作
2021/04/05 SQL Server
详解CSS故障艺术
2021/05/25 HTML / CSS
V Rising 服务器搭建图文教程
2022/06/16 Servers