基于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 attr(&quot;checked&quot;) 返回checked或undefined 获取选中失效
Oct 10 Javascript
js学习之----深入理解闭包
Nov 21 Javascript
简单实现Vue的observer和watcher
Dec 21 Javascript
详解JS中定时器setInterval和setTImeout的this指向问题
Jan 06 Javascript
Jquery鼠标放上去显示全名的实现方法
Feb 06 Javascript
js中删除数组中的某一元素实例(无下标时)
Feb 28 Javascript
JS仿Base.js实现的继承示例
Apr 07 Javascript
解决jquery appaend元素中id绑定事件失效的问题
Sep 12 jQuery
angularjs实现猜数字大小功能
May 20 Javascript
微信小程序使用image组件显示图片的方法【附源码下载】
Dec 08 Javascript
js技巧之十几行的代码实现vue.watch代码
Jun 09 Javascript
Javascript中window.name属性详解
Nov 19 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执行速度全攻略(上)
2006/10/09 PHP
简单的页面缓冲技术
2006/10/09 PHP
php中使用redis队列操作实例代码
2013/02/07 PHP
PHP实现的迪科斯彻(Dijkstra)最短路径算法实例
2017/09/16 PHP
Laravel框架实现简单的学生信息管理平台案例
2019/05/07 PHP
laravel框架路由分组,中间件,命名空间,子域名,路由前缀实例分析
2020/02/18 PHP
javascript:void(0)是什么意思示例介绍
2013/11/17 Javascript
jQuery瀑布流插件Wookmark使用实例
2014/04/02 Javascript
纯Javascript实现ping功能的方法
2015/03/20 Javascript
jquery性能优化高级技巧
2015/08/24 Javascript
javascript实现很浪漫的气泡冒出特效
2020/09/05 Javascript
详解layui中的树形关于取值传值问题
2018/01/16 Javascript
快速搭建Node.js(Express)用户注册、登录以及授权的方法
2019/05/09 Javascript
jQuery Ajax async=&gt;false异步改为同步时,解决导致浏览器假死的问题
2019/07/22 jQuery
webpack常用构建优化策略小结
2019/11/21 Javascript
浅谈vue中document.getElementById()拿到的是原值的问题
2020/07/26 Javascript
python使用reportlab画图示例(含中文汉字)
2013/12/03 Python
使用python绘制人人网好友关系图示例
2014/04/01 Python
Python正则抓取新闻标题和链接的方法示例
2017/04/24 Python
一个月入门Python爬虫学习,轻松爬取大规模数据
2018/01/03 Python
python装饰器-限制函数调用次数的方法(10s调用一次)
2018/04/21 Python
解决python 自动安装缺少模块的问题
2018/10/22 Python
python制作图片缩略图
2019/04/30 Python
浅谈python元素如何去重,去重后如何保持原来元素的顺序不变
2020/02/28 Python
利用Python函数实现一个万历表完整示例
2021/01/23 Python
Linux如何为某个操作添加别名
2013/03/01 面试题
个人自我评价范文
2014/02/05 职场文书
幼儿教师研修感言
2014/02/12 职场文书
乡镇三项教育实施方案
2014/03/30 职场文书
店面出租协议书范本
2014/11/28 职场文书
小学教师工作总结2015
2015/04/07 职场文书
法律讲堂观后感
2015/06/11 职场文书
2016寒假社会实践心得体会范文
2015/10/09 职场文书
导游词之山东红叶谷
2019/10/31 职场文书
详解nodejs内置模块
2021/05/06 NodeJs
浅析Python实现DFA算法
2021/06/26 Python