游戏开发基于vue开发的扫雷小游戏系统介绍

链接地址:

游戏开发基于vue开发的扫雷小游戏系统源码.zip资源-CSDN文库https://download.csdn.net/download/2402_83140078/89323534

前言

以前一直使用 jquery 开发,到现在,接触 vue 也有一年多时间了,期间不少朋友都问过我,从 jquery 转到 vue 容易吗? 怎么快速入门? 终于,最近比较闲,就准备写一点东西,希望能够对有这方面想法的朋友提供一丁点的帮助。

在这些文章中,并没有囊括 Vue 的所有内容,甚至没有大部分内容,可以说仅仅是冰山一角,权作抛砖引玉吧。

这个系列的文章,适用于已经熟悉前端开发,但未接触过 VUE 这样的架构的朋友,当然,了解或者熟悉 nodejs 开发会更好。如果你不懂 js,甚至没有接触过,这些可能就不适合你了。同样,这些文章仅用于入门,不适用于深入学习。

大纲

  • 搭建环境
    • NodeJS
    • 了解 npm 命令
    • Vue-CLI
    • 开发工具 vscode
    • 开发插件 vue-devtools
  • 创建第一个 Vue 应用
    • 创建项目
    • 目录结构说明
  • 了解组件
    • 什么是组件?存在的意义
    • 组件的结构以及使用
  • 编写第一个组件 面板 Board.vue
    • 准备数据结构
    • 构建布局/样式
    • 生成扫雷数据
  • 编写第二个组件 雷区 Square.vue
    • Square.vue
    • 翻开雷区
    • 插旗标记
  • 游戏统计以及结果
    • 组件的事件传递

搭建 Vue 开发环境

Vue 的开发利用了 NodeJS 的便利性,通过 npm 可以获取很多第三方的代码,同时,npm 也是开发时的命令工具入口。

注:以后将 NodeJS 均称作 node

安装 node

去搜索 nodejs ,然后下载系统版本对应的安装包,然后安装。

npm 基本用法

npm 即 node Package Manager,其实这是一个 javascript 包的管理工具,并不限用 node 的包。

在 node 安装成功后,会自动安装上 npm ,此时的 npm 可能不是最新的版本,你可以通过命令

npm i npm@latest -g

来安装最新的 npm

  • npm init <project> 创建一个名为 project 的 node 项目
  • npm install <package>[@<version>] [--save|--save-dev] 向当前项目安装包 package
    • install 表示安装命令,可以缩写成 i
    • @<version> 是要安装的版本号,留空表示安装最新版本
    • --save 是指要安装的包记录写入 package.json 文件中的依赖节点(dependencies)
    • --save-dev 功能同 --save,但会将记录写入开发依赖节点(devDependencies),表示此包是开发时使用的,并不是产品上使用的
    • 平时在安装包时,若没有特别需要,只需要 npm i <package> 就可以了
  • npm run <command> 执行 package.json 中 scripts 节点下的命令
    • 在使用 Vue 的时候,会常常用到 npm run dev 或 npm run serve

当然,也可以使用 yarn 作为包管理工具(这也是 Vue 官方推荐的工具),其用法此处略过

安装 Vue-CLI

Vue cli 是 Vue 官方提供的一整套脚手架,用于创建和管理 Vue 项目。通过以下命令来安装

npm i @vue/cli -g

安装后,可以通过以下命令测试安装结果

vue --version

此时应该会显示当前的 vue cli 版本号。

安装开发工具 VSCODE

本质上,Vue 并不挑开发工具,即使是轻量如 nodepad++ 也能胜任。但是,使用 IDE 能得到极大的便利,包括静态错误检查以及智能提示。、

WebStorm 本应该是首选的 IDE,不过是收费的,而且安装包也很大,相比之下 VS Code 就更加平民化了。

好吧,去搜索 vs code ,然后安装吧。安装好后,还需要安装 Vetur 插件以支持 Vue 语法,以及 ESLint 插件,以支持静态检查。

代码格式化快捷键: Shift+Alt+F ,后面会用到的

开发插件 vue-devtools

Vue 提供了浏览器上的开发插件,用于审查 Vue 的组件,数据,事件以及性能等。

目前此插件支持 Chrome, Firefox, Electron.

  1. 安装方法一: 上 github 搜索 vue-devtools, 自动通过源码编译。
    • 使用源码编译时,先移除 package.json 中的测试相关项,这样能避免编译失败。
      • 移除 release 项中的 npm run test命令
      • 移除 所有 test 开头的命令
      • 移除 devDependencies 节点下的依赖包 cypress
    • 生成插件,执行命令 npm run build
  2. 安装方法二: 上应用商店搜索安装

创建第一个 Vue 应用

上一节已经搭建好了基础环境,现在就可以开始干了。

创建项目

创建项目时,不需要特意去创建一个空的目录,vue-cli 会自动创建新目录作为项目目录(与项目名称相同)

执行命令创建项目 mine-sweeper

vue create mine-sweeper

创建过程中,会有一些选项,此时需要选择:

cli

此时选择 default (babel, eslint),直接按 回车 即可。

此选项表示使用 babel 处理 ES6 以及 ES-next 等新功能,同时使用 eslint 执行代码检查。

注: 使用默认选仅为个人喜好,有兴趣也可以看看手动配置项

选择后会自动使用 yarn 开始安装依赖的插件。

cli

当然,也可以选择新的 UI 方式创建 (我还没有使用过)

vue ui

安装好后,执行命令

cd mine-sweeper
yarn serve

便可以启动项目了

cli

此时,到浏览器打开地址 http://localhost:8080/ 便能看到 HelloWord 界面了。

cli

目录结构说明

  • .git git 仓库目录
  • node_modules node 包存放目录
  • public 不经过编译的静态文件存放目录
    • favicon.ico
    • index.html 入口html文件
  • src 源码目录 (也就是我们的工作目录)
    • assets 需要被编译的静态文件存放目录,比如 图片 样式 脚本
    • components 组件放到此目录下
    • App.vue 入口组件,被 main.js 调用
    • main.js 编译入口文件
  • .gitignore
  • babel.config.js babel 配置文件
  • package.json 项目配置文件,提供给npm使用的命令,以及项目依赖存放
  • README.md
  • yarn.lock

为了方便后期调试,可以编辑 package.json 文件的 eslintConfig.rules 节点,添加以下内容以允许 console 和 debugger 语句:

"rules": {
    "no-console": "off",
    "no-debugger": "off"
}

组件

组件的结构

组件一般包含以下几部分

  • 模板声明
  • 组件声明
  • 样式
<template>
  // 你的html写在这里
</template>

<script>
  export default {
    name: 'ComponentName'
    // ...你的组件声明写在这里
  }
</script>
<style scoped>
  // 你的组件内样式写在这里
</style>
<style>
  // 你的全局样式写在这里
</style>

模板声明

模板是一种与 html 相同的写法的片段,用于描述文档结构。

<template>
  <div>
    内容
  </div>
</template>

模板必需使用 template 标签为根,并且根只能拥有一个直接子元素(不限标签类型),比如:

<template>
  <div>
    内容1
  </div>
  <div>内容2</div>
</template>

这样的写法是有问题的。

模板中可以使用所有的 html 标签,也可以引用其它的组件:

<template>
  <div>
    <custom-component />
  </div>
</template>

标签上也可以如原生的 html 一样,写属性,事件,样式,类等。

<template>
  <div style="color: red;" class="class-name">
    内容
  </div>
</template>

组件声明

组件的声明是通过 js 实现的,需要注意的是,在 Vue 中,默认使用了 ES6 的语法,特别是其模块的导入导出。

ES6 模块的导入导出

将函数作为模块导出

haha.js

function haha() {
  console.log('message from haha')
}
function hehe() {
  console.log('message from haha')
}
export default haha
export { hehe }

这里使用了两种导出方式,一个默认的导出 haha,另一个具名(没有研究过到底叫啥名,自己编的名字)的导出 hehe

在其它位置导入此模块

hehe.js

import haha from './relativePath/to/haha.js'
import { hehe } from './relativePath/to/haha.js'
haha() // 输出: message from haha
hehe() // 输出: message from hehe

注意,两种导出方式对应的导入也是不同的


好吧,再回到组件的声明。

Vue 的组件声明,就使用了默认导出的方式 export default {}

export default {
  name: '组件名称',
  // 声明此组件的属性(由父组件调用时传入),可以理解为函数的参数
  props: {
    // 属性名称
    propName: {
      // 属性的类型,多个类型时,使用数组: [String, Number]
      type: String,
      default: '默认值',
      // 属性是否是必需的
      required: false
    }
    // 更多的属性
  },
  // 引用的组件集合
  components: {},
  // 混入列表 [1]
  mixins: [],
  // 过滤器,提供给组件模板使用的管道过滤器,用于对数据的简单处理 [2]
  filters: {},
  // 拥有的数据,数据始终应该使用 return {} 的方式提供,以避免组件复用导致数据被共享
  data() {
    return {
      data1: 1
    }
  },
  // 方法集合,这些方法可以在组件内通过 this 访问,也能在模板内直接访问
  methods: {},
  // 组件的计算属性 [3]
  computed: {
    // 一个示例,用于计算 data1 的平方
    hehe() {
      return this.data1 ** 2
    }
  },
  // 监视数据变化的集合,当指定的数据变化时,此处的方法会被调用
  watch: {
    // 监视数据 data1,在此方法内,可以实现在 data1 变化时执行代码
    data1(newValue) {}
  },
  // 生命周期钩子: 组件创建后调用,此时组件还没挂载到 DOM 树上
  created() {},
  // 生命周期钩子: 组件挂载到 DOM 树上后调用,一般在这里面执行一些初始化代码
  mounted() {},
  // 生命周期钩子: 组件更新后调用,要注意的是,不能在这个组件内执行会更新组件的操作,否则可能会出现死循环 [4]
  updated() {},
  // 生命周期钩子: 组件销毁前调用,这里一般执行一些回收操作,如:数据重置,移除事件绑定,取消 setTimeout 或 setInterval 等
  beforeDestroy() {}
}

[1] 混入为 Vue 中组件重用的一种方式,旨在多个组件中共用相同的声明,类似面向对象中的接口或虚类

[2] 过滤器类似数据格式化函数,在使用时,类似 linux shell 中的管道用法

[3] 计算属性用于执行自动计算,当其中使用到的数变量发生变化时,会自动重新计算其值。在使用计算属性时,需要当作一个属性,而不是一个函数,如: this.hehe 而不是 this.hehe()。计算属性应该始终返回一个值

[4] 也不是绝对不可以执行更新代码,但是必须要有条件判断,以终止执行

这里并没有把全部结构都列出来,若有需要请移步官方文档

在模板中使用数据与逻辑

<template>
  <div>
    <ul>
      <li v-for="item in data" :key="item">{{item}}</li>
    </ul>
    <div v-show="visible" @click="onClick">点击后隐藏这个元素</div>
  </div>
</template>
<script>
  export default {
      name: 'Tt',
      data(){
          return {
            visible: true,
            data: ['hehe', 'haha', 'hihi', 'hoho]
          }
      },
      methods: {
          onClick(e) {
              this.visible = false
          }
      }
  }
</script>

解释一下上面的代码吧

  • v-for 这是用来搞循环的,可以遍历 data 中的每一项 item
  • :key 这是 Vue 中对循环的约束,要求循环必须设置一个唯一的 key 值。另外,这个写法是 v-bind:key 的简写,表示给属性 key 绑定变量 item。所有的属性(组件属性,以及 html 属性均可使用此写法),如:<div :style="styleObject"></div>
  • {{item}} 这是模板取值的表达式,此时 item 的值会被填充到 li 元素中
  • v-show 控制元素的显示/隐藏(通过设置样式 display 实现);另一种相似的写法为v-if,不同之处在于,v-if 为 false 时是将元素从 DOM 树移除,而 v-show 为 false 时是设置 display: none
  • @click="onClick" 为元素绑定点击事件 onClick,这是 v-on:click的简写,所有事件都能使用这样的方式来简写,比如: @mouseover@keydown,自定义事件也如此
  • 在 onClick 方法中,有这么一句 this.visible = false ,这里的 this 表示的是当前的组件实例,可以通过 this 访问组件的所有数据

样式

组件内样式 (含作用域),仅对当前组件生效

<style scoped>
  div {
    color: red;
  }
</style>

这个样式将组件内的所有 div 的文字设置为 red

全局样式,对项目内所有元素生效

<style>
  div {
    color: red;
  }
</style>

这个样式将项目内的所有 div 的文字设置为 red

也还支持导入外部样式

<style>
  import "./external.css"
</style>

预编译的Less/Sass也是支持的

<style lang="less" scoped>
  div {
    color: red;
    > span {
      color: black;
    }
  }
</style>

接下来的扫雷项目就会使用 less 编写样式。

组件的引用

首先,有这么一个组件 Sample.vue

<template>
  <div>{{textContent}}</div>
</template>
<script>
  export default {
    name: 'Sample',
    props: {
      textContent: {
        type: String
      }
    }
  }
</script>

此组件的声明中,包含一个名叫 textContent 的属性,通过另一个组件去引用组件 Sample.vue (引用时可以通过属性指定 textContent 的值)

<template>
  <div>
    <sample-component :text-content="text" />
  </div>
</template>
<script>
  // 导入组件模块 Sample
  import Sample from './Sample.vue'
  export default {
    name: 'Sample',
    components: {
      // 将组件 Sample 重命名为 SampleComponent
      SampleComponent: Sample
    },
    data() {
      return {
        text: '文本1'
      }
    },
    mounted() {
      // 5秒后改变 text 的值,此时组件  Sample.vue 中的显示应该同步变化
      setTimeout(() => {
        // 对 text 赋值会导致界面更新,因为数据的变化被监听了
        this.text = '改变后的文本'
      }, 5000)
    }
  }
</script>

注意: 在命名组件时,一般使用 帕斯卡命名法(Pascal) ,在引入组件时,可以保留原组件名称,也可以指定新的名称,如:

export default {
  component: {
    // 保留原名称
    Sample: Sample,
    // 指定新名称
    SampleComponent: Sample
  }
}

按 W3C 标准,组件标签应该始终为小写,多个词时使用短横线 - 分隔(同样地,此规则也适用于属性名称),如:

<template>
  <div>
    <sample :text-content="text" />
    <sample-component :text-content="text" />
  </div>
</template>

全局引用

如果一个组件需要大范围地使用,那么像前面这样每次都引用就是一个笨办法了。Vue 提供了全局的组件注册

import Vue from 'vue'

import Sample from './components/Sample.vue'

Vue.component(Sample.name, Sample)

这样就能在项目的任意组件内使用 sample 组件了

这些代码一般会写在 main.js 中

编写第一个组件 面板

我们要开始写扫雷游戏了,此时需要删掉自动创建的一些文件,加上我们自己创建的新文件。

在这之前,可以看看 src/App.vue 以及 src/components/HelloWord.vue ,结合前一节说到的组件结构和用法,加深一些了解。

好了,开始吧,先使用 vs code 打开项目目录吧。

不要忘记通过命令 npm run serve 启动服务器

一些准备工作

到 src/components 目录创建文件 Board.vue,并填上以下代码

<template>
  <div class="board"></div>
</template>

<script>
  export default {
    name: 'Board'
  }
</script>

此时,创建了一个名叫 Board 的单文件 Vue 组件。

board,表示扫雷游戏的整个区域。

打开文件 src/App.vue,长这样的,将原来的 HelloWord.vue 组件的引用,替换成 Board.vue 的引用

<template>
  <div id="app">
    <board />
  </div>
</template>

<script>
  import Board from './components/Board.vue'

  export default {
    name: 'app',
    components: {
      Board
    }
  }
</script>

后面,我们整个游戏的开发都会在 Board.vue 组件中进行。

准备数据结构

首先定义一下数据结构,扫雷的行列数据,最简单就是使用二维数组来存放了,所以?那就创建一个二维数组 data 来存放吧。第一维表示雷区的行,第二维表示雷区的列就行了。

然后,一般的扫雷游戏都有个难度等级吧,我们就用 level 来表示好了,这是一个数值,值越大表示难度越大,设置个默认难度为 1

默认难度为 1 了,那么再默认一个雷的数量 mineCount 吧,就设置为 9 好了

再想想,还需要定义雷区的尺寸,也就是需要几行几列 size (使行列数相同),就都设置为 9 吧

于是,得到了下面的组件数据声明:

export default {
  data() {
    return {
      // 使用 data 作为扫雷的数据存储
      data: [],
      level: 1,
      mineCount: 9,
      size: 9
  }
}

构建布局/样式

既然是扫雷游戏,那么总得有个区域吧,看起来每个雷区都是一个格子,这样的布局,不正是表格擅长的吗?

Board.vue 模板如下

<template>
  <div class="board">
    <table>
      <tr v-for="i in size" :key="i">
        <td v-for="j in size" :key="j">
          {{i}}-{{j}}
        </td>
      </tr>
    </table>
  </div>
</template>

在此,使用 {{i}}-{{j}} 将行和列的值都给显示在了界面上。

不晓得你有没有注意到,size 是个数值,在使用 i in size这样的写法时,得到的 i 是从 1 开始的,其结果也包含了 size 的最大值 9

保存后,界面会自动更新,显示如图

此时会发现,生成的表格没有边框,很难分清单元格的边界在哪里。所以,写点样式吧

<style scoped>
  table {
    border: 1px solid #aaaaaa;
    border-collapse: collapse;
  }
  td {
    border: 1px solid #aaaaaa;
    padding: 5px;
  }
</style>

显示效果如下

生成扫雷数据

生成扫雷数据的详细算法以及过程参见源码 src/component/Board.vue 中的方法

生成后的数据,被填充到了 data 中,现在,改造一下模板,使其能直接显示这些数据

<template>
  <div class="board">
    <table>
      <tr v-for="(row, rowIndex) in data" :key="rowIndex">
        <td v-for="cell in row" :key="cell.col">
          <span v-if="cell.mine">雷</span>
          <span v-else>{{cell.count}}</span>
        </td>
      </tr>
    </table>
  </div>
</template>

显示效果如下

编写第二个组件 雷区

上一节已经画出了雷区,并且按位置放置好了雷,那么这一节,就要开始交互处理了。

但是,细细一想,在扫雷游戏中,每个雷区都会有遮罩(mask)、雷、旗、问号等显示,并且需要处理鼠标的左/右键的点击事件,相对来说,这算一个比较复杂的功能了。既然如此,就新写一个组件 Square.vue 吧。

Square.vue

Square.vue

<template>
  <div class="square" :class="computedClass" @click="onClick">
    <span class="text" v-if="clipped && square.mine">雷</span>
    <span class="text" v-if="clipped && !square.mine">{{square.count}}</span>
  </div>
</template>

<script>
export default {
  name: "Square",
  props: {
    data: {
      type: Object,
      required: true
    }
  },
  data() {
    return {
      // 标记此区是否已经被翻开
      clipped: false
    };
  },
  computed: {
    // 自动计算样式
    computedClass() {
      return {
        clipped: this.clipped
      };
    },
    square() {
      return this.data || {};
    }
  },
  methods: {
    onClick() {
      this.clipped = true;
    }
  }
};
</script>
<style lang="less" scoped>
.square {
  display: block;
  height: 30px;
  line-height: 30px;
  width: 30px;
  text-align: center;
  background-color: burlywood;
  /* 设置鼠标动作交互 */
  &:hover {
    background-color: lightblue;
  }
}

.clipped,
.clipped:hover {
  background-color: #ffffff;
}
</style>

此时,我使用了 Less 样式,需要先安装 Less 支持 yarn add less less-loader -D,对应的 npm 命令为 npm install less less-loader --save-dev

效果如下

现在来说明一下此组件中用到的东西:

  • 设置了一个名为 data 的属性(prop),以接收从父组件 Board传过来的雷区数据。
  • 设置数据 clipped 用来标记雷区是否已经翻开
  • 计算属性 computedClass 用于在 clipped 变化时,自动切换样式类 clipped
  • 计算属性 square 用于获取属性数据 data,并在其为空时使用一个空对象代替
  • 方法 onClick 接收此组件被点击的事件
  • :class="computedClass" @click="onClick" 分别设置样式 clipped 和绑定事件 onClick
  • 使用了 v-if 语法,在不同的条件下显示不同的内容
  • {{square.count}} 输出文本到界面上

在 Board.vue 中引用:

<template>
  <div class="board">
    <table>
      <tr v-for="(row, rowIndex) in data" :key="rowIndex">
        <td v-for="cell in row" :key="cell.col">
          <square :data="cell" />
        </td>
      </tr>
    </table>
  </div>
</template>

<script>
import Square from './Square'
export default {
  // 此处省略了之前已经写好的代码
  components: { Square }
}
</script>

到现在,就有了简单的鼠标交互了,此时点击雷区就能翻开指定块了。

插旗标记

回想一下扫雷这个游戏,还需要通过鼠标右键来将某个区标记为雷(插旗),所以我们需要再给组件加上鼠标的右键事件,此时,就可以对组件进行一些改造使其能同时响应鼠标的左右键事件,模板如下:

Square.vue

<template>
  <div
    class="square"
    :class="computedClass"
    @click.left="onLeftClick"
    @click.right.prevent="onRightClick"
  >
    <span class="text" v-if="clipped && square.mine">雷</span>
    <span class="text" v-if="clipped && !square.mine">{{countText}}</span>
  </div>
</template>

<script>
// 省略了已经写好的部分
export default {
  data() {
    return {
      // 标记是否已经插上了旗
      flagged: false
    };
  },
  computed: {
    countText() {
      return this.data.count ? this.data.count : "";
    }
  },
  methods: {
    onLeftClick() {
      if (this.clipped || this.flagged) {
        return;
      }
      this.clipped = true;
    },
    onRightClick() {
      this.flagged = !this.flagged;
    }
  }
};
</script>
<style lang="less" scoped>
.flagged {
  &:after {
    content: "旗";
    background-color: red;
    color: white;
    padding: 1px 3px;
    font-size: 12px;
  }
}
</style>

效果如图

继续解释一下新增加的部分:

  • @click.left 和 @click.right.prevent 分别绑定鼠标的左/右键点击事件,leftright 以及 prevent 都是 Vue 提供了事件修饰符, 其中的 prevent 即e.preventDefault()
  • flagged 新添加的数据,用于标记雷区是否使用旗标记
  • countText 这个计算属性,用于在周围的雷数量为 0 时,在界面上显示为空白

到此,扫雷的基本功能就完成了。

游戏统计以及结果

前一节完成了扫雷的基本功能:翻开雷区以及标记为雷。在这一节,就到获取游戏结果的时候了。

有个问题,我们之前的翻开雷区以及标记为雷都是在 Square 组件中实现的,但是游戏结果需要 Board 组件来统计,咋整呢? 这时候就需要用到事件传递了。

组件的事件传递

在 Vue 中,父子组件中经常会传递事件;在此,也仅仅会涉及到父组件接收子组件传递的事件。

若欲了解更多事件相关信息,请查阅文档的事件以及事件总线

子组件通过 this.$emit('event-name', payload, ...) 向父组件发事件,父组件通过在调用子组件时的 @event-name="eventHandler" 来接收并处理事件。

其中,event-name 是事件的名称,payload 是事件的负载(可以是任何类型的数据), $emit 方法在事件名后,可以传递多个参数,此时,在 eventHandler 函数上会接收到相同数量的参数。

所以,对 Square 组件进行一些改动,以将翻开与标雷事件传递给 Board 组件:

发出事件

Square.vue

export default {methods: {
    onLeftClick() {
      if (this.clipped || this.marked) {
        return;
      }
      this.clipped = true;
      if (this.data.mine) {
          // 当前是雷时,触发爆炸事件
        this.$emit("exploded");
      } else {
          // 当前不是雷时触发翻开事件
        this.$emit("clipped");
      }
    },
    onRightClick() {
        // 切换插旗
      this.marked = !this.marked;
      this.$emit("marked", this.marked);
    }
  }
}

此时,一共触发了三个事件:

  • exploded 当翻开雷区时遇到雷触发
  • clipped 当翻开雷区时未遇到雷触发
  • marked 当插上旗后触发

接收事件

Board.vue

<template>
  <div class="board">
    <div>雷数量: {{mineCount}}, 已标记: {{markCount}},剩下: {{mineCount - markCount}}</div>
    <table>
      <tr v-for="(row, rowIndex) in data" :key="rowIndex">
        <td v-for="cell in row" :key="cell.col">
          <square :data="cell" @clipped="onClipped" @exploded="onExploded" @marked="onMarked" />
        </td>
      </tr>
    </table>
    <div class="result" v-if="result">{{result}}</div>
  </div>
</template>

<script>
// 省略已经写好的部分
export default {
  data() {
    return {
      // 标雷数量
      markCount: 0,
      // 翻开数量
      clipCount: 0,
      // 游戏结果
      result: null
    };
  },
  watch: {
    remainSquares(v) {
      if (v === 0) {
        this.result = "通关";
      }
    }
  },
  computed: {
    remainSquares() {
      return this.size ** 2 - this.markCount - this.clipCount;
    }
  },
  methods: {
    onClipped() {
      this.clipCount++;
    },
    onExploded() {
      this.result = "背时,搞爆了";
    },
    onMarked(marked) {
      if (marked) {
        this.markCount++;
      } else {
        this.markCount--;
      }
    }
  }
};
</script>

在 Board 组件中,接收了三个事件 @clipped="onClipped" @exploded="onExploded" @marked="onMarked"。另外添加了一个 div.result 用于显示游戏结果。

在游戏面板上方,添加了雷数量统计信息的展示。

新增加了一个计算属性 remainSquares,用于计算剩下未翻开并且未插旗的雷区,当此值为0时,表示游戏通关。 this.size ** 2 表示雷区总数量。

另外,新增了一个监视器 remainSquares,以监视其值的变化,在变化时,判断游戏是否通关。

结果来源:

  • 当翻开雷时,会触发 exploded 事件,此时会设置 result 的值,此时为游戏失败
  • 当已经翻开和标记块之和等于总数时,,此时为游戏通过

失败

通过

总结

回顾这个小游戏的开发,已经介绍了以下内容:

  • 使用 Vue cli 创建项目
  • Vue 组件的结构
  • Vue 模板以及语法
  • Vue 事件传递
  • Vue 计算属性
  • Vue 监视器

而 Vue 远不只是如此,还有很多有意思的东西,等你去玩

  • 34
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,下面是一个基于 Vue.js 的简单扫雷游戏实现: HTML 代码: ```html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Vue.js 扫雷游戏</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="./minesweeper.js"></script> </head> <body> <div id="app"> <h1>Vue.js 扫雷游戏</h1> <table> <tbody> <tr v-for="(row, rowIndex) in board" :key="rowIndex"> <td v-for="(cell, colIndex) in row" :key="colIndex" @click="clickCell(rowIndex, colIndex)" :class="{ 'cell': true, 'revealed': cell.revealed, 'mine': cell.mine }"> {{ cell.content }} </td> </tr> </tbody> </table> <div v-if="gameOver"> <p>游戏结束!</p> <button @click="restart">重新开始</button> </div> <div v-else> <p>剩余雷数:{{ minesLeft }}</p> <button @click="restart">重新开始</button> </div> </div> </body> </html> ``` JavaScript 代码(minesweeper.js): ```javascript // 初始化游戏棋盘 function initBoard(row, col, numMines) { const board = []; for (let i = 0; i < row; i++) { board.push([]); for (let j = 0; j < col; j++) { board[i].push({ content: '', revealed: false, mine: false }); } } // 添加地雷 let count = 0; while (count < numMines) { const i = Math.floor(Math.random() * row); const j = Math.floor(Math.random() * col); if (!board[i][j].mine) { board[i][j].mine = true; count++; // 更新其它方格的数字 for (let r = Math.max(0, i - 1); r <= Math.min(row - 1, i + 1); r++) { for (let c = Math.max(0, j - 1); c <= Math.min(col - 1, j + 1); c++) { if (r !== i || c !== j) { board[r][c].content += 1; } } } } } return board; } // Vue.js 实例 const app = new Vue({ el: '#app', data: { row: 10, col: 10, numMines: 10, board: [], minesLeft: 0, gameOver: false }, methods: { clickCell(row, col) { const cell = this.board[row][col]; if (!cell.revealed) { cell.revealed = true; if (cell.mine) { this.gameOver = true; } else if (cell.content === '') { // 递归展开周围的空白方格 for (let r = Math.max(0, row - 1); r <= Math.min(this.row - 1, row + 1); r++) { for (let c = Math.max(0, col - 1); c <= Math.min(this.col - 1, col + 1); c++) { if (r !== row || c !== col) { this.clickCell(r, c); } } } } } // 检查游戏是否结束 this.gameOver = this.board.some(row => row.some(cell => cell.mine && cell.revealed)); }, restart() { this.board = initBoard(this.row, this.col, this.numMines); this.minesLeft = this.numMines; this.gameOver = false; this.board.forEach(row => row.forEach(cell => cell.revealed = false)); } }, created() { this.board = initBoard(this.row, this.col, this.numMines); this.minesLeft = this.numMines; }, computed: { minesLeft() { return this.numMines - this.board.reduce((count, row) => { return count + row.filter(cell => cell.revealed && cell.mine).length; }, 0); } } }); ``` 在浏览器中打开 HTML 文件,即可开始游戏

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

熬夜写代码的平头哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值