基于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 相关文章推荐
jQuery 页面载入进度条实现代码
Feb 08 Javascript
Js实现无刷新删除内容
Apr 29 Javascript
javascript中checkbox使用方法实例演示
Nov 19 Javascript
ES6中如何使用Set和WeakSet
Mar 10 Javascript
利用fecha进行JS日期处理
Nov 21 Javascript
JavaScript表单验证完美代码
Mar 02 Javascript
JavaScript的继承实现小结
May 07 Javascript
JavaScript模拟文件拖选框样式v1.0的实例
Aug 04 Javascript
JS实现的数组去除重复数据算法小结
Nov 17 Javascript
JS倒计时两种实现方式代码实例
Jul 27 Javascript
Javascript生成器(Generator)的介绍与使用
Jan 31 Javascript
JavaScript实现外溢动态爱心的效果的示例代码
Mar 21 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
Ajax PHP分页演示
2007/01/02 PHP
深入PHP中的HashTable结构详解
2013/06/13 PHP
PHP统计二维数组元素个数的方法
2013/11/12 PHP
laravel 4安装及入门图文教程
2014/10/29 PHP
快速解决PHP调用Word组件DCOM权限的问题
2017/12/27 PHP
php连接mysql之mysql_connect()与mysqli_connect()的区别
2020/07/19 PHP
扩展String功能方法
2006/09/22 Javascript
JS实现拖动示例代码
2013/11/01 Javascript
jQuery源码解读之hasClass()方法分析
2015/02/20 Javascript
jQuery中next方法用法实例
2015/04/24 Javascript
快速掌握Node.js模块封装及使用
2016/03/21 Javascript
BootStrap的弹出框(Popover)支持鼠标移到弹出层上弹窗层不隐藏的原因及解决办法
2016/04/03 Javascript
AngularJs 动态加载模块和依赖
2016/09/15 Javascript
JavaScript 判断一个对象{}是否为空对象的简单方法
2016/10/09 Javascript
jQuery的 $.ajax防止重复提交的两种方法(推荐)
2016/10/14 Javascript
bootstrap+jQuery 实现下拉菜单中复选框全选和全不选效果
2017/06/12 jQuery
基于Vue的移动端图片裁剪组件功能
2017/11/28 Javascript
es6中reduce的基本使用方法
2019/09/10 Javascript
Vue循环中多个input绑定指定v-model实例
2020/08/31 Javascript
javascript实现电商放大镜效果
2020/11/23 Javascript
JavaScript如何实现防止重复的网络请求的示例
2021/01/28 Javascript
[05:09]DOTA2-DPC中国联赛2月22日Recap集锦
2021/03/11 DOTA
python中的hashlib和base64加密模块使用实例
2014/09/02 Python
python求列表交集的方法汇总
2014/11/10 Python
python爬虫使用cookie登录详解
2017/12/27 Python
PyQt实现界面翻转切换效果
2018/04/20 Python
tensorflow实现简单的卷积网络
2018/05/24 Python
python pandas实现excel转为html格式的方法
2018/10/23 Python
利用Python实现微信找房机器人实例教程
2019/03/10 Python
Python如何批量获取文件夹的大小并保存
2020/03/31 Python
python获取系统内存占用信息的实例方法
2020/07/17 Python
Python析构函数__del__定义原理解析
2020/11/20 Python
python3实现简单飞机大战
2020/11/29 Python
django使用多个数据库的方法实例
2021/03/04 Python
化工专业应届生求职信
2013/11/08 职场文书
数学教育专业求职信
2014/07/22 职场文书