新西兰服务器

如何利用js+canvas实现扫雷游戏


如何利用js+canvas实现扫雷游戏

发布时间:2022-06-08 14:14:41 来源:高防服务器网 阅读:54 作者:iii 栏目:开发技术

这篇文章主要介绍“如何利用js+canvas实现扫雷游戏”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“如何利用js+canvas实现扫雷游戏”文章能帮助大家解决问题。

代码如下

<body>         胜利条件,找出所有地雷并标记      <form action="javaScript:createContent()">          <div id="message" >地雷数量必须小于地图大小xy的平方</div>          <br />           地图大小xy :<input id="xyNum" type="number" required="true" name="points" min="1" max="50"  />           booNum:<input id="booNum" type="number" required="true" name="points" min="1" max="2500"/>          <input type="submit" value="OK" : />          <br /> 1. 输入宽度 <br />2. 输入地雷数(地雷数小于宽*宽) <br /> 3. 单击确定  <br />          鼠标右键:<br />          第一次:标记您的猜测<br />          第二次: 取消标签<br />      </form>      <div id= 'game'>         </div>      <script src="./js/MarkObs.js"></script>      <script src="./js/Isboo.js"></script>      <script src="./js/lei.js"></script>      <script>      let xy = document.getElementById('xyNum');      let boo = document.getElementById('booNum');      let meg = document.getElementById("message");      let div = document.getElementById('game');         //获取输入的宽高和地雷数      createContent = function (){              // console.log(xy.value);              // console.log(boo.value);              let xyNum = xy.value;               let booNum = boo.value;               // console.log(Math.pow(xyNum,2));                            //判断输入是否合法              if(Math.pow(xyNum,2)<boo.value){                  meg.style.display = 'block';              }              else {//绘制地图                  div.innerHTML = '';//清除上次div里的地图                  let game = new Game('game',xyNum,booNum);              }          }      </script>  </body>

lei.js

/* 一个自定义原型数组方法  可以放到html里  二维数组查找  arr:要找数组第一第二项 找到返回下标,没有返回-1  PS:只要this数组和arr数组第一第二项的值相等,即为找到。  */  Array.prototype.myindexOf = function(arr){      for(let i=0;i<this.length;i++){                    if((this[i][0] == arr[0]) &&(this[i][1]==arr[1])){              return i;          }      }      return -1;  }  /*  初始化地雷图  id:传入绘制地图的容器id  xyNum:长||宽的格子数(地图固定正方形)  booNum:地雷数  */   class Game {      constructor(id,xyNum,booNum){          this.xyNum = xyNum;          this.booNum = booNum;          this.id = id;             this.booArrs = [];//保存雷的位置          this.boox = -1;//地雷在x轴第几个块          this.booy = -1;//地雷在x轴第几个块             this.numArrs = [];//保存提醒数字的位置以及数字          this.num = 0;//保存找到的提醒数字的个数             this.markArrs = [];//保存标记位置的数组             //单个块的宽高          this.divw = 20;             // 初始化画布          this.initCanvas(xyNum);          // 初始化地雷位置(地雷用-1代替,图片绘制麻烦)          this.initBooxy(xyNum,booNum);          // 初始化遮挡物          this.initObs(xyNum);             //判断是否胜利          this.win();               }      /*初始化画布(包括网格)      xyNum:传入需要绘制的宽格子数      */       initCanvas(xyNum){          this.canvas = document.createElement('canvas');          this.ctx = this.canvas.getContext('2d');                    //1为border          this.canvas.width = (this.divw+1)*xyNum;          this.canvas.height = (this.divw+1)*xyNum;             // 绘制网格坐标                  // 获取canvas的宽高;                  let w = this.canvas.width;                  let h = this.canvas.height;                  // 绘制水平线                  for (let i = 1; i < h / 21; i++) {                      this.ctx.beginPath();                      this.ctx.lineTo(0,  21 * i) //起点                      this.ctx.lineTo(w, 21 * i); //重点                      this.ctx.stroke();                            }                  // h绘制垂直线                  for (let i = 1; i < w / 21; i++) {                      this.ctx.beginPath();                      this.ctx.lineTo(21 * i,0) //起点                      this.ctx.lineTo(21 * i,h); //重点                      this.ctx.stroke();                            }                  // ctx.stroke();             // 放入容器          this.div = document.getElementById(this.id);          this.div.appendChild(this.canvas);                    // 绑定点击事件!!!          this.canvas.addEventListener('mousedown',this.mouseDown.bind(this))//!!!!注意需要更改this指向,用bind                    // 清除鼠标右键的默认事件 “contextmenu“          this.canvas.addEventListener("contextmenu",function(event){              event.preventDefault()          })      }         /*初始化地雷(包括提醒数字)      xyNum:传入地图的宽的格子数      booNum:传入地雷数      */       initBooxy (xyNum,booNum){             // 随机地雷位置 并保存起来          for(let i=0;i<booNum;i++){                 // x,y为地雷所在格子坐标,从0开始              this.boox = parseInt(Math.random()*xyNum);              this.booy = parseInt(Math.random()*xyNum);                 //避免雷的位置重复              while(this.booArrs.myindexOf([this.boox,this.booy])!=-1){                  this.boox = parseInt(Math.random()*xyNum);                  this.booy = parseInt(Math.random()*xyNum);              }                 this.booArrs.push([this.boox,this.booy])//!!!保存地雷的位置              console.log(i,'x:'+this.boox,'y:'+this.booy);                 //绘制地雷              this.ctx.beginPath();//不清楚可不可以删              this.ctx.rect(this.boox*21,this.booy*21,20,20);              this.ctx.fillStyle = 'red';              this.ctx.fill();          }             // 绘制地雷位置周围提醒数字              // 这里的逻辑可以优化,不提前绘制数字,在点击清除障碍物后再判断绘制。               /*              想法一:在每个雷周围添加数字1,如果在多个雷交集处累加              想法二:所有块依次判断周围是否有雷,有几个雷,就fillText()多少              想法三:(一二结合)先找每个雷,该雷周围的8个块依次 判断周围有几个雷          */               // 这里为法二         for(let i=0;i<xyNum;i++){             for(let j=0;j<xyNum;j++){                 let num = 0;//提醒数字 ,每次重置为0                                if(this.booArrs.myindexOf([i-1,j-1]) !=-1){                      num++;                  }                  if(this.booArrs.myindexOf([i-1,j]) !=-1){                      num++;                  }                  if(this.booArrs.myindexOf([i-1,j+1]) !=-1){                      num++;                  }                  if(this.booArrs.myindexOf([i,j-1]) !=-1){                      num++;                  }                  if(this.booArrs.myindexOf([i,j+1]) !=-1){                      num++;                  }                  if(this.booArrs.myindexOf([i+1,j-1]) !=-1){                      num++;                  }                  if(this.booArrs.myindexOf([i+1,j]) !=-1){                      num++;                  }                  if(this.booArrs.myindexOf([i+1,j+1]) !=-1){                      num++;                  }                                   //绘制提醒数字                    if(num!=0 && (this.booArrs.myindexOf([i,j]) ==-1 )){//(this.booArrs.myindexOf([i,j]) ==-1)地雷不标注提示数字若。要标注需要+1(本身)                     this.ctx.font = '18px fasdg'                  this.ctx.fillStyle = '#000'                  this.ctx.fillText(num,i*(this.divw+1)+2,(j+1)*(this.divw+1)-2);//加1和j+1为测试结果,-+2是为了文本在格子里居中//y为文本中线坐标                                    this.numArrs.push([i,j,num]);//i,j为提醒数字的块坐标,num为装数组里的值(myindexOf来判断)                  }                  // this.NUM = num;             }         }                             }         /*初始化遮挡物       xyNum:传入地图的宽的格子数      */      initObs(xyNum){      for(let i=0;i<xyNum;i++){          for(let j=0;j<xyNum;j++){                 this.ctx.beginPath();              this.ctx.rect(i*21,j*21,20,20);              // this.ctx.fillStyle = 'rgb(155,25,205,0.7)';//设置障碍物透明度可以方便查看雷的位置              this.ctx.fillStyle = 'rgb(155,25,205,1)';//正常游戏时透明度为'1‘              this.ctx.fill();          }      }     }        /*点击事件在initCanvas中绑定*/       mouseDown(){         //这里使用preventDefault,默认事件被没有消除,是因为触发鼠标右键的默认事件的事件类型不是mousedown,是contextmenu      // event.preventDefault(); //ie9以下不兼容             this.clix = Math.floor(event.layerX/( this.divw+1));//this.divw为20是块的宽      this.cliy = Math.floor(event.layerY/( this.divw+1));          // 鼠标左键      if(event.button==0){          this.clearObs(this.clix,this.cliy);            }            // 鼠标右键      else if(event.button==2){                              this.markObs(this.clix,this.cliy);      }          }           /*扫雷*/  //这里的代码可以封装一下 为了方便此处没有封装     clearObs(x,y){      // console.log(x,y);点击坐标         this.ctx.clearRect(x*21,y*21,20,20);//清除指定块            // 点击到标记,点击到提醒数字,点击到地雷,点击到空白,      if(this.markArrs.myindexOf([x,y])!=-1){  //点击到标记,重新覆盖          this.ctx.rect(x*21,y*21,20,20);          this.ctx.fillStyle = 'rgb(155,25,205,1)';          this.ctx.fill();                    this.ctx.beginPath();          this.ctx.fillStyle = 'red';          this.ctx.fillText('?',x*(this.divw+1)+2,(y+1)*(this.divw+1)-2);          this.ctx.fill();         }      else if(this.numArrs.myindexOf([x,y])!=-1){//点击到提醒数字          let index = this.numArrs.myindexOf([x,y]);//下标          let num = this.numArrs[index][2];//提醒数字          this.ctx.fillText(num,x*(this.divw+1)+2,(y+1)*(this.divw+1)-2);//加1和j+1为测试结果,-+2是为了文本在格子里居中//y为文本中线坐标          this.num++;      }      else if(this.booArrs.myindexOf([x,y])!=-1){//,点击到地雷,全部绘制          console.log(this.booArrs.myindexOf([x,y]));              //绘制全图              // 绘制提醒数字              for(let i=0;i<this.xyNum;i++){                  for(let j=0;j<this.xyNum;j++){                      let num = 0;//提醒数字 ,每次重置为0                       // if(booArrs.indexof([i-1,j-1]) != -1){//数组是对象这样永远-1                       this.ctx.clearRect(i*21,j*21,20,20);                       if(this.booArrs.myindexOf([i-1,j-1]) !=-1){                           num++;                       }                       if(this.booArrs.myindexOf([i-1,j]) !=-1){                           num++;                       }                       if(this.booArrs.myindexOf([i-1,j+1]) !=-1){                           num++;                       }                       if(this.booArrs.myindexOf([i,j-1]) !=-1){                           num++;                       }                       if(this.booArrs.myindexOf([i,j+1]) !=-1){                           num++;                       }                       if(this.booArrs.myindexOf([i+1,j-1]) !=-1){                           num++;                       }                       if(this.booArrs.myindexOf([i+1,j]) !=-1){                           num++;                       }                       if(this.booArrs.myindexOf([i+1,j+1]) !=-1){                           num++;                       }                                                                           //绘制提醒数字                       if(num!=0 && (this.booArrs.myindexOf([i,j]) ==-1 )){//(this.booArrs.myindexOf([i,j]) ==-1)地雷不标注提示数字若要标注需要+1(本身)                              this.ctx.font = '18px fasdg'                       this.ctx.fillStyle = '#000'                       this.ctx.fillText(num,i*(this.divw+1)+2,(j+1)*(this.divw+1)-2);//加1和j+1为测试结果,-+2是为了文本在格子里居中//y为文本中线坐标                                              this.numArrs.push([i,j,num]);//i,j为提醒数字的块坐标,num为装数组里的值(myindexOf来判断)                       }                       // this.NUM = num;                  }              }              // 绘制地雷              for(let i=0;i<this.booArrs.length;i++){                  this.ctx.fillStyle = 'red';                  this.ctx.rect(this.booArrs[i][0]*21,this.booArrs[i][1]*21,20,20);                  this.ctx.fill();               }              this.ctx.clearRect((this.xyNum-1)*21,(this.xyNum-1)*21,20,20);//每次最后一个都会变红,不知道原因,此处专门删除。                           alert('你惊动了雷雷');                       }         else {             this.isboo(this.ctx,x,y,this.booArrs,this.numArrs,this.markArrs,this.xyNum);            }      }        win (){//标记数组==地雷数组      this.tim = setInterval(()=>{          if(this.booArrs.length ==this.markArrs.length){              for(let i=0;i<this.booNum;i++){                                    if( true == this.booArrs.some(()=>{                      return this.markArrs.myindexOf(this.booArrs[i])!=-1;                  })){                     this.booNum--;                  }                  if(this.booNum==0){                      clearInterval(this.tim);                      alert('you are win');                      }              }          }      },10)        }     isboo(ctx,x,y,booArrs,numArrs,markArrs,xyNum){         new Isboo(ctx,x,y,booArrs,numArrs,markArrs,xyNum);     }            /*标记       */     markObs(x,y){           console.log(x,y);      new MarkObs(this.ctx,x,y,this.booArrs,this.divw,this.markArrs);              }          }

isboo.js

Array.prototype.myindexOf = function(arr){      for(let i=0;i<this.length;i++){                    if((this[i][0] == arr[0]) &&(this[i][1]==arr[1])){              return i;          }      }      return -1;  }  /*  这里解决点击到空白格子时,把周围的空白格一起显示。此处的逻辑可以再优化.  ctx:布局  x,点击位置  y,点击位置  booArrs:炸弹的位置数组  numArrs:提示数的位置  markArrs:标记的位置  */   class Isboo {      constructor(ctx,x,y,booArrs,numArrs,markArrs,xyNum){          this.x = x;          this.y = y;                    // 判断有没有提醒数字          this.isbool(ctx,x,y,booArrs,numArrs,markArrs,xyNum);          this.isboor(ctx,x,y,booArrs,numArrs,markArrs,xyNum);          this.isboot(ctx,x,y,booArrs,numArrs,markArrs,xyNum);          this.isboob(ctx,x,y,booArrs,numArrs,markArrs,xyNum);      }      isbool(ctx,x,y,booArrs,numArrs,markArrs,xyNum){          if((numArrs.myindexOf([x,y])==-1)&&(x<xyNum)&&(markArrs.myindexOf([x,y])==-1)){              ctx.clearRect(x*21,y*21,20,20);              x+=1;              this.isbool(ctx,x,y,booArrs,numArrs,markArrs,xyNum);              // this.isboor(ctx,x,y,booArrs,numArrs,markArrs,xyNum);              this.isboot(ctx,x,y,booArrs,numArrs,markArrs,xyNum);              this.isboob(ctx,x,y,booArrs,numArrs,markArrs,xyNum);          }else {              return ;          }      }      isboor(ctx,x,y,booArrs,numArrs,markArrs,xyNum){          if((numArrs.myindexOf([x,y])==-1)&&(x>=0)&&(markArrs.myindexOf([x,y])==-1)){              ctx.clearRect(x*21,y*21,20,20);              x-=1;              // this.isbool(ctx,x,y,booArrs,numArrs,markArrs,xyNum);              this.isboor(ctx,x,y,booArrs,numArrs,markArrs,xyNum);              this.isboot(ctx,x,y,booArrs,numArrs,markArrs,xyNum);              this.isboob(ctx,x,y,booArrs,numArrs,markArrs,xyNum);          }else {              return ;          }      }      isboot(ctx,x,y,booArrs,numArrs,markArrs,xyNum){          if((numArrs.myindexOf([x,y])==-1)&&(y<xyNum)&&(markArrs.myindexOf([x,y])==-1)){              ctx.clearRect(x*21,y*21,20,20);              y+=1;              // this.isbool(ctx,x,y,booArrs,numArrs,markArrs,xyNum);              // this.isboor(ctx,x,y,booArrs,numArrs,markArrs,xyNum);              this.isboot(ctx,x,y,booArrs,numArrs,markArrs,xyNum);              // this.isboob(ctx,x,y,booArrs,numArrs,markArrs,xyNum);             }else {              return ;          }      }      isboob(ctx,x,y,booArrs,numArrs,markArrs,xyNum){          if((numArrs.myindexOf([x,y])==-1)&&(y>=0)&&(markArrs.myindexOf([x,y])==-1)){              ctx.clearRect(x*21,y*21,20,20);              y-=1;              // this.isbool(ctx,x,y,booArrs,numArrs,markArrs,xyNum);              // this.isboor(ctx,x,y,booArrs,numArrs,markArrs,xyNum);              // this.isboot(ctx,x,y,booArrs,numArrs,markArrs,xyNum);              this.isboob(ctx,x,y,booArrs,numArrs,markArrs,xyNum);          }else {              return ;          }      }     }

MarkObs.js

Array.prototype.myindexOf = function(arr){      for(let i=0;i<this.length;i++){                    if((this[i][0] == arr[0]) &&(this[i][1]==arr[1])){              return i;          }      }      return -1;  }  /*  ctx:布局  x,点击位置  y,点击位置  booArrs:炸弹的位置数组  divw:各自宽度  markarrs:标记数组  */   class MarkObs{      constructor(ctx,x,y,booArrs,divw,markarrs){          this.markObs(ctx,x,y,booArrs,divw,markarrs);      }         markObs(ctx,x,y,booArrs,divw,markarrs){             if(markarrs.myindexOf([x,y])==-1){//如果标记数组里没有该地址,则标记,并添加进数组          ctx.beginPath();          ctx.fillStyle = 'red';          ctx.fillText('?',x*(divw+1)+2,(y+1)*(divw+1)-2);          markarrs.push([x,y]);          }else {//如果标记数组里有该地址,则取消标记,并从数组中删除              ctx.clearRect(x*(divw+1),y*(divw+1),divw,divw);              ctx.beginPath();              ctx.rect(x*21,y*21,20,20);              ctx.fillStyle = 'rgb(155,25,205,1)';              ctx.fill();              markarrs.splice((markarrs.myindexOf([x,y])),1);          }      }     }

页面效果

初始化障碍物设置了透明度时

正常游戏时

这里点击右键标记后忘了把填充颜色设置回来。所以后面变红。

关于“如何利用js+canvas实现扫雷游戏”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注高防服务器网行业资讯频道,小编每天都会为大家更新不同的知识点。

[微信提示:高防服务器能助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。

[图文来源于网络,不代表本站立场,如有侵权,请联系高防服务器网删除]
[