基于Vue.js实现数字拼图游戏


Posted in Javascript onAugust 02, 2016

先来看看效果图:

基于Vue.js实现数字拼图游戏

功能分析

当然玩归玩,作为一名Vue爱好者,我们理应深入游戏内部,一探代码的实现。接下来我们就先来分析一下要完成这样的一个游戏,主要需要实现哪些功能。下面我就直接将此实例的功能点罗列在下了:

    1.随机生成1~15的数字格子,每一个数字都必须出现且仅出现一次

    2.点击一个数字方块后,如其上下左右有一处为空,则两者交换位置

    3.格子每移动一步,我们都需要校验其是否闯关成功

    4.点击重置游戏按钮后需对拼图进行重新排序

以上便是本实例的主要功能点,可见游戏功能并不复杂,我们只需一个个攻破就OK了,接下来我就来展示一下各个功能点的Vue代码。

构建游戏面板

作为一款以数据驱动的JS框架,Vue的HTML模板很多时候都应该绑定数据的,比如此游戏的方块格子,我们这里肯定是不能写死的,代码如下:

<template>
  <div class="box">
    <ul class="puzzle-wrap">
      <li 
        :class="{'puzzle': true, 'puzzle-empty': !puzzle}" 
        v-for="puzzle in puzzles" 
        v-text="puzzle"
      ></li>
    </ul>
  </div>
</template>

<script>
export default {
  data () {
    return {
      puzzles: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
    }
  }
}
</script>

这里我省略了css样式部分,大家可以先不用关心。以上代码我们将1~15的数字写死在了一个数组中,这显然不是随机排序的,那么我们就来实现随机排序的功能。

随机排序数字

<template>
  <div class="box">
    <ul class="puzzle-wrap">
      <li 
        :class="{'puzzle': true, 'puzzle-empty': !puzzle}" 
        v-for="puzzle in puzzles" 
        v-text="puzzle"
      ></li>
    </ul>
  </div>
</template>

<script>
export default {
  data () {
    return {
      puzzles: []
    }
  },
  methods: {

    // 重置渲染
    render () {
      let puzzleArr = [],
        i = 1

      // 生成包含1 ~ 15数字的数组
      for (i; i < 16; i++) {
        puzzleArr.push(i)
      }

      // 随机打乱数组
      puzzleArr = puzzleArr.sort(() => {
        return Math.random() - 0.5
      });

      // 页面显示
      this.puzzles = puzzleArr
      this.puzzles.push('')
    },
  },
  ready () {
    this.render()
  }
}

以上代码,我们利用for循环生成了一个1~15的有序数组,之后我们又利用原生JS的sort方法随机打乱数字,这里还包含了一个知识点就是Math.random()方法。

利用sort()方法进行自定义排序,我们需要提供一个比较函数,然后返回一个用于说明这两个值的相对顺序的数字,其返回值如下:

    1.返回一个小于 0 的值,说明 a 小于 b

    2.返回 0,说明 a 等于 b

    3.返回一个大于 0 的值,说明 a 大于 b

这里利用Math.random()生成一个 0 ~ 1 之间的随机数,再减去0.5,这样就会有一半概率返回一个小于 0 的值, 一半概率返回一个大于 0 的值,就保证了生成数组的随机性,实现了动态随机生成数字格子的功能。

需要注意的是,我们还在数组最后插了一个空字符串,用来生成唯一的空白格子。

交换方块位置

<template>
  <div class="box">
    <ul class="puzzle-wrap">
      <li 
        :class="{'puzzle': true, 'puzzle-empty': !puzzle}" 
        v-for="puzzle in puzzles" 
        v-text="puzzle"
        @click="moveFn($index)"
      ></li>
    </ul>
  </div>
</template>

<script>
export default {
  data () {
    return {
      puzzles: []
    }
  },
  methods: {

    // 重置渲染
    render () {
      let puzzleArr = [],
        i = 1

      // 生成包含1 ~ 15数字的数组
      for (i; i < 16; i++) {
        puzzleArr.push(i)
      }

      // 随机打乱数组
      puzzleArr = puzzleArr.sort(() => {
        return Math.random() - 0.5
      });

      // 页面显示
      this.puzzles = puzzleArr
      this.puzzles.push('')
    },

    // 点击方块
    moveFn (index) {

      // 获取点击位置及其上下左右的值
      let curNum = this.puzzles[index],
        leftNum = this.puzzles[index - 1],
        rightNum = this.puzzles[index + 1],
        topNum = this.puzzles[index - 4],
        bottomNum = this.puzzles[index + 4]

      // 和为空的位置交换数值
      if (leftNum === '') {
        this.puzzles.$set(index - 1, curNum)
        this.puzzles.$set(index, '')
      } else if (rightNum === '') {
        this.puzzles.$set(index + 1, curNum)
        this.puzzles.$set(index, '')
      } else if (topNum === '') {
        this.puzzles.$set(index - 4, curNum)
        this.puzzles.$set(index, '')
      } else if (bottomNum === '') {
        this.puzzles.$set(index + 4, curNum)
        this.puzzles.$set(index, '')
      }
    }
  },
  ready () {
    this.render()
  }
}
</script>

    1.这里我们首先在每个格子的li上添加了点击事件@click="moveFn($index)",通过$index参数获取点击方块在数组中的位置

    2.其次获取其上下左右的数字在数组中的index值依次为index - 4、index + 4、index - 1、index + 1

    3.当我们找到上下左右有一处为空的时候我们将空的位置赋值上当前点击格子的数字,将当前点击的位置置为空

备注:我们为什么要使用$set方法,而不直接用等号赋值呢,这里包含了Vue响应式原理的知识点。

// 因为 JavaScript 的限制,Vue.js 不能检测到下面数组变化:

// 1.直接用索引设置元素,如 vm.items[0] = {};
// 2.修改数据的长度,如 vm.items.length = 0。
// 为了解决问题 (1),Vue.js 扩展了观察数组,为它添加了一个 $set() 方法:

// 与 `example1.items[0] = ...` 相同,但是能触发视图更新
example1.items.$set(0, { childMsg: 'Changed!'})

检测是否闯关成功

<template>
  <div class="box">
    <ul class="puzzle-wrap">
      <li 
        :class="{'puzzle': true, 'puzzle-empty': !puzzle}" 
        v-for="puzzle in puzzles" 
        v-text="puzzle"
        @click="moveFn($index)"
      ></li>
    </ul>
  </div>
</template>

<script>
export default {
  data () {
    return {
      puzzles: []
    }
  },
  methods: {

    // 重置渲染
    render () {
      let puzzleArr = [],
        i = 1

      // 生成包含1 ~ 15数字的数组
      for (i; i < 16; i++) {
        puzzleArr.push(i)
      }

      // 随机打乱数组
      puzzleArr = puzzleArr.sort(() => {
        return Math.random() - 0.5
      });

      // 页面显示
      this.puzzles = puzzleArr
      this.puzzles.push('')
    },

    // 点击方块
    moveFn (index) {

      // 获取点击位置及其上下左右的值
      let curNum = this.puzzles[index],
        leftNum = this.puzzles[index - 1],
        rightNum = this.puzzles[index + 1],
        topNum = this.puzzles[index - 4],
        bottomNum = this.puzzles[index + 4]

      // 和为空的位置交换数值
      if (leftNum === '') {
        this.puzzles.$set(index - 1, curNum)
        this.puzzles.$set(index, '')
      } else if (rightNum === '') {
        this.puzzles.$set(index + 1, curNum)
        this.puzzles.$set(index, '')
      } else if (topNum === '') {
        this.puzzles.$set(index - 4, curNum)
        this.puzzles.$set(index, '')
      } else if (bottomNum === '') {
        this.puzzles.$set(index + 4, curNum)
        this.puzzles.$set(index, '')
      }

      this.passFn()
    },

    // 校验是否过关
    passFn () {
      if (this.puzzles[15] === '') {
        const newPuzzles = this.puzzles.slice(0, 15)

        const isPass = newPuzzles.every((e, i) => e === i + 1)

        if (isPass) {
          alert ('恭喜,闯关成功!')
        }
      }
    }
  },
  ready () {
    this.render()
  }
}
</script>

我们在moveFn方法里调用了passFn方法来进行检测,而passFn方法里又涉及了两个知识点:

(1)slice方法

通过slice方法我们截取数组的前15个元素生成一个新的数组,当然前提了数组随后一个元素为空

(2)every方法

通过every方法我们来循环截取后数组的每一个元素是否等于其index+1值,如果全部等于则返回true,只要有一个不等于则返回false

如果闯关成功那么isPass的值为true,就会alert "恭喜,闯关成功!"提示窗,如果没有则不提示。

重置游戏

重置游戏其实很简单,只需添加重置按钮并在其上调用render方法就行了:

<template>
  <div class="box">
    <ul class="puzzle-wrap">
      <li 
        :class="{'puzzle': true, 'puzzle-empty': !puzzle}" 
        v-for="puzzle in puzzles" 
        v-text="puzzle"
        @click="moveFn($index)"
      ></li>
    </ul>
    <button class="btn btn-warning btn-block btn-reset" @click="render">重置游戏</button>
  </div>
</template>

<script>
export default {
  data () {
    return {
      puzzles: []
    }
  },
  methods: {

    // 重置渲染
    render () {
      let puzzleArr = [],
        i = 1

      // 生成包含1 ~ 15数字的数组
      for (i; i < 16; i++) {
        puzzleArr.push(i)
      }

      // 随机打乱数组
      puzzleArr = puzzleArr.sort(() => {
        return Math.random() - 0.5
      });

      // 页面显示
      this.puzzles = puzzleArr
      this.puzzles.push('')
    },

    // 点击方块
    moveFn (index) {

      // 获取点击位置及其上下左右的值
      let curNum = this.puzzles[index],
        leftNum = this.puzzles[index - 1],
        rightNum = this.puzzles[index + 1],
        topNum = this.puzzles[index - 4],
        bottomNum = this.puzzles[index + 4]

      // 和为空的位置交换数值
      if (leftNum === '') {
        this.puzzles.$set(index - 1, curNum)
        this.puzzles.$set(index, '')
      } else if (rightNum === '') {
        this.puzzles.$set(index + 1, curNum)
        this.puzzles.$set(index, '')
      } else if (topNum === '') {
        this.puzzles.$set(index - 4, curNum)
        this.puzzles.$set(index, '')
      } else if (bottomNum === '') {
        this.puzzles.$set(index + 4, curNum)
        this.puzzles.$set(index, '')
      }

      this.passFn()
    },

    // 校验是否过关
    passFn () {
      if (this.puzzles[15] === '') {
        const newPuzzles = this.puzzles.slice(0, 15)

        const isPass = newPuzzles.every((e, i) => e === i + 1)

        if (isPass) {
          alert ('恭喜,闯关成功!')
        }
      }
    }
  },
  ready () {
    this.render()
  }
}
</script>

<style>
@import url('./assets/css/bootstrap.min.css');

body {
  font-family: Arial, "Microsoft YaHei"; 
}

.box {
  width: 400px;
  margin: 50px auto 0;
}

.puzzle-wrap {
  width: 400px;
  height: 400px;
  margin-bottom: 40px;
  padding: 0;
  background: #ccc;
  list-style: none;
}

.puzzle {
  float: left;
  width: 100px;
  height: 100px;
  font-size: 20px;
  background: #f90;
  text-align: center;
  line-height: 100px;
  border: 1px solid #ccc;
  box-shadow: 1px 1px 4px;
  text-shadow: 1px 1px 1px #B9B4B4;
  cursor: pointer;
}

.puzzle-empty {
  background: #ccc;
  box-shadow: inset 2px 2px 18px;
}

.btn-reset {
  box-shadow: inset 2px 2px 18px;
}
</style>

这里我一并加上了css代码。

总结

以上就是本文的全部内容,其实本游戏的代码量不多,功能点也不是很复杂,不过通过Vue来写这样的游戏,有助于我们了解Vue以数据驱动的响应式原理,在简化代码量的同时也增加了代码的可读性。希望本文对大家学些Vue有所帮助。

Javascript 相关文章推荐
javascript之可拖动的iframe效果代码
Aug 01 Javascript
JavaScript实现16进制颜色值转RGB的方法
Feb 09 Javascript
jquery分析文本里url或邮件地址为真实链接的方法
Jun 20 Javascript
AngularJS转换响应内容
Jan 27 Javascript
详解React开发中使用require.ensure()按需加载ES6组件
May 12 Javascript
Javascript 一些需要注意的细节(必看篇)
Jul 08 Javascript
微信小程序picker组件下拉框选择input输入框的实例
Sep 20 Javascript
详解JavaScript中的六种错误类型
Sep 21 Javascript
Vue.js 2.0和Cordova开发webApp环境搭建方法
Feb 26 Javascript
p5.js入门教程之键盘交互
Mar 19 Javascript
ES6的Fetch异步请求的实现方法
Dec 07 Javascript
vue刷新页面时去闪烁提升用户体验效果的实现方法
Dec 10 Javascript
js 获取范围内的随机数实例代码
Aug 02 #Javascript
url传递的参数值中包含&amp;时,url自动截断问题的解决方法
Aug 02 #Javascript
AngularJS基础 ng-include 指令示例讲解
Aug 01 #Javascript
基于jQuery实现表格的查看修改删除
Aug 01 #Javascript
jQuery自制提示框tooltip改进版
Aug 01 #Javascript
Three.js学习之文字形状及自定义形状
Aug 01 #Javascript
jQuery实现的省市县三级联动菜单效果完整实例
Aug 01 #Javascript
You might like
乱谈我对耳机、音箱的感受
2021/03/02 无线电
PHP面向对象概念
2011/11/06 PHP
PHP遍历并打印指定目录下所有文件实例
2014/02/10 PHP
ThinkPHP自动填充实现无限级分类的方法
2014/08/22 PHP
Yii框架Session与Cookie使用方法示例
2019/10/14 PHP
javascript 操作cookies及正确使用cookies的属性
2009/10/15 Javascript
jquery的Tooltip插件 qtip使用详细说明
2010/09/08 Javascript
js获取触发事件元素在整个网页中的绝对坐标(示例代码)
2013/12/13 Javascript
JS阻止冒泡事件以及默认事件发生的简单方法
2014/01/17 Javascript
jQuery遍历Table应用示例
2014/04/09 Javascript
jQuery遍历对象、数组、集合实例
2014/11/08 Javascript
再谈javascript原型继承
2014/11/10 Javascript
详解Javascript动态操作CSS
2014/12/08 Javascript
javascript表单控件实例讲解
2016/09/13 Javascript
Jquery给当前页或者跳转后页面的导航栏添加选中后样式的实例
2016/12/08 Javascript
js实现继承的方法及优缺点总结
2019/05/08 Javascript
javascript实现的时间格式加8小时功能示例
2019/06/13 Javascript
Javascript摸拟自由落体与上抛运动原理与实现方法详解
2020/04/08 Javascript
[05:35]DOTA2英雄梦之声_第13期_拉比克
2014/06/21 DOTA
Python实现针对给定字符串寻找最长非重复子串的方法
2018/04/21 Python
pycharm 实现显示project 选项卡的方法
2019/01/17 Python
Python实现html转换为pdf报告(生成pdf报告)功能示例
2019/05/04 Python
Python语言进阶知识点总结
2019/05/28 Python
python+selenium 简易地疫情信息自动打卡签到功能的实现代码
2020/08/22 Python
粉红色的鲸鱼:Vineyard Vines
2018/02/17 全球购物
英国的一家创新礼品和小工具零售商:Menkind
2019/08/24 全球购物
什么是事务?事务有哪些性质?
2012/03/11 面试题
5.1手机促销活动
2014/01/17 职场文书
希特勒经典演讲稿
2014/05/19 职场文书
十佳中学生事迹材料
2014/06/02 职场文书
预备党员转正材料
2014/12/19 职场文书
2015年库房工作总结
2015/04/30 职场文书
2015年毕业实习工作总结
2015/05/29 职场文书
Python Django框架介绍之模板标签及模板的继承
2021/05/27 Python
Java工作中实用的代码优化技巧分享
2022/04/21 Java/Android
安装Windows Server 2012 R2企业版操作系统并设置好相关参数
2022/04/29 Servers