阿望教你用vue写扫雷小游戏


Posted in Javascript onJanuary 20, 2020

前言

话说阿望还在大学时,某一天寝室突然停网了,于是和室友两人不约而同地打开了扫雷,比相同难度下谁更快找出全部的雷,玩得不亦乐乎,就这样,扫雷伴我们度过了断网的一周,是整整一周啊,不用上课的那种,可想而知我们是有多无聊了。

这两天临近过年了,该放假的已经放假了,不该放假的已经请假了,公交不打挤了,地铁口不堵了,公司也去了大半部分的人了,就留阿望这种不得不留下来值班的人守着空荡荡的办公室了,于是,多年前那种无所事事的断网心态再次袭来,于是,想玩扫雷的心再次蹦?出来,于是,点开了电脑的附件,于是,发现我的电脑上并没有扫雷【手动微笑.jpg】。

怎么想起要写扫雷的阿望就不多废话了,直接开干。

扫雷游戏规则

想当年阿望还是在大学才参透扫雷的游戏规则的,初高中的时候都是靠运气瞎点,从没赢过,当然了,境界提升以后,追求的自然就不是赢了,而是速度。

来,看规则:

阿望教你用vue写扫雷小游戏

大白话游戏规则,计算逻辑如下:

游戏开始,选择难度 鼠标左键随机点开一个格子A(你想用右键标雷也可以) 左键点击后一共有三种情况 1) 是数字,假设是2,表示以该格子A为中心的九宫格除了该格子以外的8个格子内有2个雷 2)是空白,表示以该格子A为中心的九宫格除了该格子以外的8个格子内没有雷,并且系统计算分别以这8个格子为中心九宫格除了该格子以外的8个格子是否是空白 a. 是空白,继续计算以这8个格子为中心九宫格除了该格子以外的8个格子是否是空白,循环扩散 b. 是格子边界,停止边界扩散 c. 是数字,数字格子翻出来显示,停止扩散 3)是雷,game over 了。。。 如果点第一次只翻了一个数字是比较难开始游戏的,如果第一次翻了一大片出来,恭喜你,可以开始秀你的智商了,在确认是雷点的格子点右键,即可标记地雷格,表示你认定这个格子是地雷 如果数字周围的雷被全部标记出来了,但数字周围还有没被翻开的格子的话,双击该格子,自动将其他格子翻开

好,这下规则我们了解了,接下来,摩拳擦掌,开始写代码咯,本次代码用vue来写,没有原因,习惯了而已。

扫雷代码逻辑

看着游戏规则,我们先来理一理,要如何完成这个功能,我们以最简单的,最能使人理解的步骤,来完成这次功能。

  1. 难度选择
  2. 根据选择的游戏难度绘制游戏主界面,就是格盘
  3. 根据选择的游戏难度随机生成地雷放到盘中
  4. 计算每个格子周围的雷数
  5. 完成用户交互(左键点击翻格子,右键点击放雷)
  6. 空白格扩散翻格子
  7. 计算游戏结束

生成项目

避免vue新手不知如何下手,那就从建项目开始吧,环境安装我就不讲了,脚手架也是要有的

第一步,初始化lovesweeping工程(阿望不喜欢地雷,喜欢小桃心)

项目很小,不需要路由,不需要vuex,即可完成,只带了sass,连eslint都可以不要,看官们可以根据自己的喜好建项目

阿望教你用vue写扫雷小游戏

项目生成之后,helloworld就可以删掉了,它的存在并没有什么意义

文件切分

这一步主要是构建工程结构,简单画一下主要的几个文件

- src
 - components
  - SelectLevel.vue [新增,难度选择组件]
  - LoveSweeping.vue [新增,游戏界面组件]
 - App.vue [父组件,负责组件间的切换和某些数据传递]
 - main.js
- package.json

好,初步认识了项目结构以后,咱把该新建的建好,可以不加东西,不报错就行

然后就是把组件间的切换代码写好,再来一步步的填充代码

阿望教你用vue写扫雷小游戏

难度选择

这一步很简单,首先画好难度选择的页面,难度可以自己设置,你觉得合理就行,我这里的数据格式是这样的,用一个对象表示一个难度等级,对象中包含了难度描述,以及难度设置,设置中包含了格子横排数,格子纵排数,雷数

// 难度
 level: [
  {
   text: '青铜', // 难度描述
   value: [9, 9, 10] // 格子横排数,格子纵排数,雷数
  }, {
   text: '黄金',
   value: [12, 9, 20] 
 ]

然后模板中直接渲染列表,这样做的好处是,想要增加难度直接在数组中添加数据即可

<li v-for="(item, index) in level" :key="index" @click="handleChoseLevel(item.value)"></li>

该组件中只有一个方法:选择难度之后,跳转到游戏主界面上去,因为项目没有用路由,直接使用组件间的切换,所以,这个方法只负责告诉父组件,我已经选择好难度了,可以开始游戏了

// 选择难度
 handleChoseLevel(level) {
  this.$emit("chose-level", level);
 }

代码如下:

阿望教你用vue写扫雷小游戏

界面长这样,当然,你要觉得难看自己换个样式也行

阿望教你用vue写扫雷小游戏

游戏界面

画格盘

通过游戏难度选择来决定游戏格盘的大小,组件间已通过App将游戏难度传至界面组件中,我们用props把数据接收到,消化成自己的数据,画格盘需要的数据有:横排格子数,纵排格子数

画格子:我们将格子的索引暴露出来,后续可以帮助我们试错。整个格局有两种方式来表示格盘,坐标式和索引式,比如横9纵9的格子,[0, 0]代表第1个格子,[2, 3]代表第三行第四列也就是第20个格子。此次使用索引式来标志格盘

<div 
  v-for="col in cols" :key="Math.random() + col"
  class="game-content-row">
  <span 
   v-for="row in rows" :key="Math.random() + row"
   class="game-block">
   <span></span>
  </span>
 </div>

阿望教你用vue写扫雷小游戏

随机生成地雷

首先data中添加一个minePosition属性,用来记录雷点位置

随机生成地雷比较简单,主要注意,生成的地雷点数在格盘个数范围内,那么就可以写出随机生成的地雷了。界面组件已收集到横排格子数、纵排格子数、雷数,那么就能得到格子总数,假设横9纵9,10个雷,那么就是生成10个81以内的随机数(如果索引从0开始,即80以内)。

// 随机获取雷点位置
 getMinePosition() {
  // 定义一个数组装不重复的格点
  let mineArr = [];
  // 循环雷数生成不重复的雷点
  for (let n = 0; n < this.gameInfo[2]; n++) {
   const random = Math.floor(Math.random() * this.latticeNum);
   if (mineArr.indexOf(random) === -1) {
    mineArr.push(random);
   } else {
    n--;
   }
  }
  this.minePosition = mineArr;
 },

把地雷位置暴露出来

阿望教你用vue写扫雷小游戏

格子周围的雷数

确认了雷点位置,接下来要做的就是确认每一个非雷点位置周围的雷的数量,我们用对象来描述一个格子,这个对象主要包含以下几个属性

// 格子属性
 lattice: [{
  index: 0, // 格子索引
  mineNum: 0, // 周围雷数
  isMine: false, // 是否是雷
  isOpen: false, // 是否已经被点开
  isMark: false, // 是否被标记
 }],

这里我们主要用到index, isMine, mineNum属性,这一步,主要是计算每个格子元素的mineNum值,依赖于以下两个方法,个人觉得扫雷游戏最难理解的,最难捋清的逻辑,其中一个就是获取非雷点位置周围8个位置索引的方法getLatticeIndex(另一个是点击空白格扩散)

// 获取格子周围的雷数,
 getMineNumAroundLattice(lattice, index) {
  // 先获取格子周围的有效索引
  const latticeIndexArr = this.getLatticeIndex(index);
  // 循环索引,索引值在雷点数组中的,即为雷,当前格子的雷点数加1
  latticeIndexArr.forEach(i => {
   if (this.minePosition.indexOf(i) > -1) {
    lattice.mineNum ++;
   }
  });
 },
 // 获取格子周围的有效索引
 getLatticeIndex(index) {
  // 存索引值的变量
  let latticeIndexArr = [];
  // 当前格子位于第几行
  const latticeRow = Math.ceil(index / this.rows);
  // 当前格子位于第几列(求余为0说明是最右边一列)
  const latticeCol = Math.ceil(index % this.rows) || this.rows;
  // 第一行没有上一行,不需要计算减1的行值,最后一行没有下一行,不需要计算加1的行值
  for (let i = (latticeRow === 1 ? 0 : -1); i < (latticeRow === this.cols ? 1 : 2); i++) {
   // 第一列没有左列,不需要计算减1的列值,最后一列没有右列,不需要计算加1的列值
   for (let j = (latticeCol === 1 ? 0 : -1); j < (latticeCol === this.rows ? 1 : 2); j++) {
    // 索引值 = (当前行值 + (上一行【-1】/当前行【0】/下一行【+1】) - 1【1是索引从0开始,所以需要减去】) * 每行格子数 + 当前列值 + (上一列【-1】/当前列【0】/下一列【+1】)
    const latticeIndex = (latticeRow + i - 1) * this.rows + (latticeCol + j);
    latticeIndexArr.push(latticeIndex);
   }
  }
  return latticeIndexArr;
 },

有了这两个方法,咱成功地获取到了每个非雷点格子周围的雷的数量,来,展示出来,这样展示的好处是,我们一眼就可以看出算法是否正确

阿望教你用vue写扫雷小游戏

没问题了,来,接着往下走,格盘数据基本都设置好了,那我们接下来要做的就是,点开格子操作

点击交互

这一步先做简单点,有个明显的区别就可以了,点雷我们先不管,先看点数字和空白的情况,首先得明确,到时候格子的可见属性是全部要被隐藏的,点击了才会显示出来,这就用到了我们上一步提到的isOpen属性,默认肯定全是不可见的,点击之后,非雷翻开

点数字

点数字很简单,直接翻开,将isOpen属性设置为true

来点不一样的,isOpen === true 的时候字体变红色,走你┏ (?ω?)=☞

@click.left="handleClickLattice(lattice[(col - 1) * rows + row - 1])"
// 点了格子
 handleClickLattice(lattice) {
  // 是数字
  if (lattice.mineNum) {
   if (!lattice.isOpen && !lattice.isMark) {
    lattice.isOpen = true;
   }
  } 
 },

点空白

第二个难点来咯,点空白格需要注意以下几点:1、空白格表示周围8格都没有雷 2、扩散周围8格,判断雷数,循环往复 3、遇边界停止扩散,遇数字停止扩散

假设横9纵9的格盘,第二排第三列格为空白格即第12格,那么点了该空白格之后,首先将其与周围8格(2,3,4,11,12,13,20,21,22)一起,isOpen置为true,然后分别以周围8格为中心,判断该格子是数字,停止扩散,是空白格,继续扩散

这一步写完,

// 代码把下半部分补齐
 handleClickLattice(lattice) {
  ... else {
    // 是空白
    const latticeIndexArr = this.getLatticeIndex(lattice.index);
    this.showWhiteAround(lattice, latticeIndexArr);
   }
 },
 // 展示周围的空白标记,直至边缘(格子边缘或者数字)
 showWhiteAround(lattice, latticeIndexArr) {
  // 避免有重复的数据停不下来,去个重
  latticeIndexArr = [...new Set(latticeIndexArr)];
  for (let i = 0; i < latticeIndexArr.length; i++) {
   const item = latticeIndexArr[i];
   latticeIndexArr.splice(i, 1);
   i--;
   if (this.lattice[item].isOpen) {
    continue;
   }
   this.lattice[item].isOpen = true;
   if (!this.lattice[item].mineNum) {
    const arr = this.getLatticeIndex(this.lattice[item].index);
    this.showWhiteAround(this.lattice[item], latticeIndexArr.concat(arr));
   } 
  }
 },

基本明面上的扫雷步骤就已经完成了,handleClickLattice方法再加一步判断,如果是雷,游戏结束

阿望教你用vue写扫雷小游戏

右键标记雷点

这个就很简单了,写个右击事件,修改一下格子的isMark和isOpen属性,这一步的基本逻辑就是

右击格子 格子本身已经被打开 1)是:格子已经被标记为地雷? a. 是:取消标记(isMark和isOpen取反),剩余地雷数+1 b. 否:说明是右击了已经被点开的数字格,不做任何操作 2)否:isMark和isOpen取反,记录该格子已经被标记为地雷,格子处于打开状态,剩余地雷数-1,判断是否结束

// 右键确认是雷点
  handleSureMinePoint(lattice) {
   if (!lattice.isOpen) {
    lattice.isMark = true;
    lattice.isOpen = true;
    this.minePosition.splice(this.minePosition.indexOf(lattice.index), 1);
    this.judgeIsOver();
   } else {
    if (lattice.isMark) {
     lattice.isMark = false;
     lattice.isOpen = false;
     this.minePosition.push(lattice.index);
    }
   }
  },

游戏是否结束

游戏结束一共有三种情况:1、点到雷了,直接结束 2、雷被标记完了(有可能失败了,标错了) 3、翻开的格子数 + 雷数 = 总格子数

完成功能

接下来要做的就是把格子属性隐藏起来,假装不知道,再假装鼠标一点,格子就翻过来了。这就用到之前提到的格子属性isMark和isOpen,本身元素处于隐藏状态,当被标记或者被打开的时候设置相应的属性使其可见就行了,如此,便完成了扫雷的基本功能,有兴趣的小朋友可以自己融合多种功能试一试

阿望教你用vue写扫雷小游戏

当然,这只是其中一种实现方式,把所有的计算全部放在玩游戏之前了,爱动脑筋的朋友们也可以想想,如果放在每一次点击时做计算该如何组织代码

阿望的源代码中还集合了【重开一局】、【改变难度】、【游戏计时】等功能,样式兼容手机和PC在线玩,在手机上玩的时候我在纠结手机如何模仿PC端的右键点击标雷操作,没有好的想法,不想用双击,于是多加了一个状态,点击页面【标记】按钮,即表示标记雷点,再点击一次表示还原,正常点开数字格,坐火车无聊的小朋友可以玩一玩哟

查看阿望的源码: mineSweeping

在线试玩:mine-sweeping-online

希望各位看官不吝右上角赐个小星星哦,阿望这厢有礼啦,新年快乐啦 ★,° :.☆( ̄? ̄)/$: .°★

总结

以上所述是小编给大家介绍的阿望教你用vue写扫雷小游戏,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Javascript 相关文章推荐
JQuery的一些小应用收集
Mar 27 Javascript
读jQuery之七 判断点击了鼠标哪个键的代码
Jun 21 Javascript
JS中怎样判断undefined(比较不错的方法)
Mar 27 Javascript
javascript转换静态图片,增加粒子动画效果
May 28 Javascript
详解Bootstrap glyphicons字体图标
Jan 04 Javascript
javascript Promise简单学习使用方法小结
May 17 Javascript
AngularJS中实现动画效果的方法
Jul 28 Javascript
只要1K 纯JS脚本送你一朵3D红色玫瑰
Aug 09 Javascript
jQuery动态改变多行文本框高度的方法
Sep 07 Javascript
JS匹配日期和时间的正则表达式示例
May 12 Javascript
vue2.0$nextTick监听数据渲染完成之后的回调函数方法
Sep 11 Javascript
vue中的v-model原理,与组件自定义v-model详解
Aug 04 Javascript
JavaScript Window窗口对象属性和使用方法
Jan 19 #Javascript
浅谈webpack和webpack-cli模块源码分析
Jan 19 #Javascript
js原生map实现的方法总结
Jan 19 #Javascript
Node.js操作MongoDB数据库实例分析
Jan 19 #Javascript
JS运算符简单用法示例
Jan 19 #Javascript
Vue 微信端扫描二维码苹果端却只能保存图片问题(解决方法)
Jan 19 #Javascript
JS基础之逻辑结构与循环操作示例
Jan 19 #Javascript
You might like
PHP定时自动生成静态HTML的实现代码
2010/06/20 PHP
PHP实用函数分享之去除多余的0
2015/02/06 PHP
PHP生成随机数的方法总结
2018/03/01 PHP
PHP使Laravel为JSON REST API返回自定义错误的问题
2018/10/16 PHP
YII框架模块化处理操作示例
2019/04/26 PHP
Laravel关系模型指定条件查询方法
2019/10/10 PHP
Javascript 八进制转义字符(8进制)
2011/04/08 Javascript
jQuery 关于伪类选择符的使用说明
2013/04/24 Javascript
解决css和js的{}与smarty定界符冲突问题的两种方法
2013/09/10 Javascript
div模拟滚动条效果示例代码
2013/10/16 Javascript
原生javascript实现无间缝滚动示例
2014/01/28 Javascript
javascript中sort排序实例详解
2016/07/24 Javascript
bootstrap选项卡使用方法解析
2017/01/11 Javascript
Node.js 使用命令行工具检查更新
2017/06/08 Javascript
关于vue的语法规则检测报错问题的解决
2018/05/21 Javascript
使用Object.defineProperty如何巧妙找到修改某个变量的准确代码位置
2018/11/02 Javascript
Vue.js的动态组件模板的实现
2018/11/26 Javascript
在vue-cli 3中给stylus、sass样式传入共享的全局变量
2019/08/12 Javascript
一起来了解一下JavaScript的预编译(小结)
2021/03/01 Javascript
Python3实现的腾讯微博自动发帖小工具
2013/11/11 Python
python实现批量转换文件编码(批转换编码示例)
2014/01/23 Python
Python break语句详解
2014/03/11 Python
Python写入数据到MP3文件中的方法
2015/07/10 Python
Python判断文本中消息重复次数的方法
2016/04/27 Python
使用Python的Flask框架来搭建第一个Web应用程序
2016/06/04 Python
Python 判断是否为质数或素数的实例
2017/10/30 Python
Python中按键来获取指定的值
2019/03/02 Python
Python matplotlib 绘制双Y轴曲线图的示例代码
2020/06/12 Python
css3 利用transform打造走动的2D时钟
2020/10/20 HTML / CSS
儿媳婚宴答谢词
2014/01/14 职场文书
国际贸易专业个人求职信格式
2014/02/02 职场文书
活动总结报告格式
2014/05/09 职场文书
纪念九一八事变演讲稿:牢记历史,捍卫主权
2014/09/14 职场文书
2014向国旗敬礼网上签名活动总结
2014/09/27 职场文书
mybatis-plus模糊查询指定字段
2022/04/28 Java/Android
Python如何让字典保持有序排列
2022/04/29 Python