先来看看效果图:

功能分析
当然玩归玩,作为一名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>
</div>
这里我省略了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()
  }
}
</div>
以上代码,我们利用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>
</div>
    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!'})
</div>
检测是否闯关成功
<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)
        t
  
 
