阿望教你用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 相关文章推荐
用JavaScript和注册表脚本实现右键收藏Web页选中文本
Jan 28 Javascript
JavaScript在IE中“意外地调用了方法或属性访问”
Nov 19 Javascript
让你的CSS像Jquery一样做筛选的实现方法
Jul 10 Javascript
jquery插件珍藏(图片局部放大/信息提示框)
Jan 08 Javascript
判断浏览器的内核及版本号方法汇总
Jan 05 Javascript
聊一聊Vue.js过渡效果
Sep 07 Javascript
完美解决jQuery fancybox ie 无法显示关闭按钮的问题
Nov 29 Javascript
微信小程序 页面跳转如何实现传值
Apr 05 Javascript
基于Vue实现关键词实时搜索高亮显示关键词
Jul 21 Javascript
微信小程序实现留言板功能
Nov 02 Javascript
ionic使用angularjs表单验证(模板验证)
Dec 12 Javascript
JavaScript模板引擎原理与用法详解
Dec 24 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制作图型计数器的例子
2006/10/09 PHP
批量获取memcache值并按key的顺序返回的实现代码
2011/06/14 PHP
php中获得视频时间总长度的另一种方法
2011/09/15 PHP
解析argc argv在php中的应用
2013/06/24 PHP
php实现mysql备份恢复分卷处理的方法
2014/12/26 PHP
PHP中Laravel 关联查询返回错误id的解决方法
2017/04/01 PHP
PHP实现将多个文件压缩成zip格式并下载到本地的方法示例
2018/05/23 PHP
PHP实现数组转JSon和JSon转数组的方法示例
2018/06/14 PHP
javascript cookies操作集合
2010/04/12 Javascript
web前端开发也需要日志
2010/12/09 Javascript
jquery中常用的函数和属性详细解析
2014/03/07 Javascript
Nodejs极简入门教程(二):定时器
2014/10/25 NodeJs
JS修改iframe页面背景颜色的方法
2015/04/01 Javascript
JavaScript简单判断复选框是否选中及取出值的方法
2015/08/13 Javascript
探讨JavaScript语句的执行过程
2016/01/28 Javascript
JS实现瀑布流布局
2017/10/21 Javascript
node.js 模块和其下载资源的镜像设置的方法
2018/09/06 Javascript
JavaScript实现通讯录功能
2020/12/27 Javascript
详解Django框架中用context来解析模板的方法
2015/07/20 Python
将Python的Django框架与认证系统整合的方法
2015/07/24 Python
基于python内置函数与匿名函数详解
2018/01/09 Python
python smtplib模块自动收发邮件功能(二)
2018/05/22 Python
基于python OpenCV实现动态人脸检测
2018/05/25 Python
Django 实现admin后台显示图片缩略图的例子
2019/07/28 Python
图解python全局变量与局部变量相关知识
2019/11/02 Python
adidas美国官网:adidas US
2016/09/21 全球购物
Michael Kors美国官网:美式奢侈生活风格的代表
2016/11/25 全球购物
策划主管的工作职责
2013/11/24 职场文书
大学生咖啡店创业计划书
2014/01/21 职场文书
永不妥协观后感
2015/06/10 职场文书
2016年10月份红领巾广播稿
2015/12/21 职场文书
承诺书怎么写 ?
2019/04/16 职场文书
关于感恩的素材句子(38句)
2019/11/11 职场文书
一篇文章弄懂MySQL查询语句的执行过程
2021/05/07 MySQL
使用pipenv管理python虚拟环境的全过程
2021/09/25 Python
PyTorch device与cuda.device用法
2022/04/03 Python