基于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 相关文章推荐
爱恋千雪-US-AscII加密解密工具(网页加密)下载
Jun 06 Javascript
提高网站信任度的技巧
Oct 17 Javascript
jQuery ReferenceError: $ is not defined 错误的处理办法
May 10 Javascript
js实现拖拽效果
Feb 12 Javascript
JavaScript+html5 canvas制作的百花齐放效果完整实例
Jan 26 Javascript
详解微信小程序 登录获取unionid
Jun 27 Javascript
node中koa中间件机制详解
Aug 22 Javascript
详解webpack4多入口、多页面项目构建案例
May 25 Javascript
vuejs前后端数据交互之从后端请求数据的实例
Aug 11 Javascript
Node.js assert断言原理与用法分析
Jan 04 Javascript
详解如何给React-Router添加路由页面切换时的过渡动画
Apr 25 Javascript
微信小程序wx.request拦截器使用详解
Jul 09 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
《PHP编程最快明白》第五讲:php目录、文件操作
2010/11/01 PHP
php动态实现表格跨行跨列实现代码
2012/11/06 PHP
php使用explode()函数将字符串拆分成数组的方法
2015/02/17 PHP
php自定义urlencode,urldecode函数实例
2015/03/24 PHP
PHP输出九九乘法表代码实例
2015/03/27 PHP
php实现转换html格式为文本格式的方法
2016/05/16 PHP
PHP实现的折半查询算法示例
2017/10/09 PHP
javascript下查找父节点的简单方法
2007/08/13 Javascript
js中的escape及unescape函数的php实现代码
2007/09/04 Javascript
IE6下focus与blur错乱的解决方案
2011/07/31 Javascript
百度地图api应用标注地理位置信息(js版)
2013/02/01 Javascript
超链接的禁用属性Disabled使用示例
2014/07/31 Javascript
node.js解决获取图片真实文件类型的问题
2014/12/20 Javascript
浅析javascript的return语句
2015/12/15 Javascript
深入学习jQuery Validate表单验证
2016/01/18 Javascript
jQuery Easyui实现左右布局
2016/01/26 Javascript
JS获取一个未知DIV高度的方法
2016/08/09 Javascript
Jquery实现上下移动和排序代码
2016/10/17 Javascript
JavaScript自定义函数实现查找两个字符串最长公共子串的方法
2016/11/24 Javascript
jquery插件锦集【推荐】
2016/12/16 Javascript
详解vue-cli与webpack结合如何处理静态资源
2017/09/19 Javascript
javascript数组定义的几种方法
2017/10/06 Javascript
vue中锚点的三种方法
2018/07/06 Javascript
使用C语言来扩展Python程序和Zope服务器的教程
2015/04/14 Python
python实现textrank关键词提取
2018/06/22 Python
python保存数据到本地文件的方法
2018/06/23 Python
Python常用模块sys,os,time,random功能与用法实例分析
2020/01/07 Python
Python bisect模块原理及常见实例
2020/06/17 Python
瑞贝卡·明可弗包包官网:Rebecca Minkoff
2016/07/21 全球购物
沙龙级头发造型工具:FOXYBAE
2018/07/01 全球购物
大学生军训感想
2014/02/16 职场文书
党员廉洁自律承诺书
2014/05/26 职场文书
golang在GRPC中设置client的超时时间
2021/04/27 Golang
基于python定位棋子位置及识别棋子颜色
2021/07/26 Python
python库sklearn常用操作
2021/08/23 Python
德生BCL3000抢先使用感受和评价
2022/04/07 无线电