Vue.js实现拼图游戏

Vue.js实现拼图游戏

之前写过一篇《基于Vue.js的表格分页组件》的文章,主要介绍了Vue组件的编写方法,有兴趣的可以访问这里进行阅读:http://www.cnblogs.com/luozhihao/p/5516065.html

前言

为了进一步让大家了解Vue.js的神奇魅力,了解Vue.js的一种以数据为驱动的理念,本文主要利用Vue实现了一个数字拼图游戏,其原理并不是很复杂,效果图如下:

demo展示地址为:https://luozhihao.github.io/vue-puzzle/index.html#!/

有能力的可以玩玩,拼出来有赏哦~~

功能分析

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

  • 随机生成1~15的数字格子,每一个数字都必须出现且仅出现一次
  • 点击一个数字方块后,如其上下左右有一处为空,则两者交换位置
  • 格子每移动一步,我们都需要校验其是否闯关成功
  • 点击重置游戏按钮后需对拼图进行重新排序

以上便是本实例的主要功能点,可见游戏功能并不复杂,我们只需一个个攻破就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()方法进行自定义排序,我们需要提供一个比较函数,然后返回一个用于说明这两个值的相对顺序的数字,其返回值如下:

  • 返回一个小于 0 的值,说明 a 小于 b
  • 返回 0,说明 a 等于 b
  • 返回一个大于 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!‘})

详见:http://cn.vuejs.org/guide/list.html#问题

检测是否闯关成功

<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以数据驱动的响应式原理,在简化代码量的同时也增加了代码的可读性。

本实例的所有源码我已经上传至我的github,地址为https://github.com/luozhihao/vue-puzzle 需要的童鞋可以自行下载运行。

原创文章,转载请注明来自一个萝卜一个坑 -博客园[http://www.cnblogs.com/luozhihao]

本文地址:http://www.cnblogs.com/luozhihao/p/5726661.html

本文同步发表于:https://segmentfault.com/a/1190000006137236

时间: 2024-08-03 11:13:49

Vue.js实现拼图游戏的相关文章

利用Vue.js实现拼图游戏

之前写过一篇<基于Vue.js的表格分页组件>的文章,主要介绍了Vue组件的编写方法,有兴趣的可以访问这里进行阅读:http://www.cnblogs.com/luozhihao/p/5516065.html 前言 为了进一步让大家了解Vue.js的神奇魅力,了解Vue.js的一种以数据为驱动的理念,本文主要利用Vue实现了一个数字拼图游戏,其原理并不是很复杂,效果图如下: demo展示地址为:https://luozhihao.github.io/vue-puzzle/index.html

走进Vue.js

走进Vue.js Vue.js作为目前最热门最具前景的前端框架之一,其提供了一种帮助我们快速构建并开发前端项目的新的思维模式.本文旨在帮助大家认识Vue.js,了解Vue.js的开发流程,并进一步理解如何通过Vue.js来构建一个中大型的前端项目,同时做好相应的部署与优化工作. 文章将以PPT图片附加文字介绍的形式展开,不会涉及知识点的具体代码,点到为止.有兴趣的同学可以查看相应的文档进行了解. Vue.js简介 从上图的介绍中我们不难发现Vue.js是一款轻量级的以数据驱动的前端JS框架,其和

前端开发之走进Vue.js

Vue.js作为目前最热门最具前景的前端框架之一,其提供了一种帮助我们快速构建并开发前端项目的新的思维模式.本文旨在帮助大家认识Vue.js,了解Vue.js的开发流程,并进一步理解如何通过Vue.js来构建一个中大型的前端项目,同时做好相应的部署与优化工作. 文章将以PPT图片附加文字介绍的形式展开,不会涉及知识点的具体代码,点到为止.有兴趣的同学可以查看相应的文档进行了解. Vue.js简介 从上图的介绍中我们不难发现Vue.js是一款轻量级的以数据驱动的前端JS框架,其和jQuery最大的

vue.js学习笔记1

Vue.js作为目前最热门最具前景的前端框架之一,其提供了一种帮助我们快速构建并开发前端项目的新的思维模式.本文旨在帮助大家认识Vue.js,了解Vue.js的开发流程,并进一步理解如何通过Vue.js来构建一个中大型的前端项目,同时做好相应的部署与优化工作. 文章将以PPT图片附加文字介绍的形式展开,不会涉及知识点的具体代码,点到为止.有兴趣的同学可以查看相应的文档进行了解. Vue.js简介 从上图的介绍中我们不难发现Vue.js是一款轻量级的以数据驱动的前端JS框架,其和jQuery最大的

一款html拼图游戏详解

本文是爱编程原创翻译,转载请看清文末的转载要求,谢谢合作! 游戏介绍 这篇文章是献给web游戏开发者用简单的开发工具开发一款游戏.此文介绍了用html.css.javascript只需简单和几个步骤开发一款2d游戏.在这里,我要呈现给大家如何创建一款拼图游戏,在这个游戏中你可以拖动小图块来拼成完整的大图. 点击这里查看游戏效果. 游戏规则 游戏规则非常简单,你只要拖动被切碎的图片块来组成完整的大图.需要用正确的方法才能最终拼成完整的图片.在游戏中拖动小图片的次数将被统计下来.所以.应该尽量用最少

从Vue.js窥探前端行业

近年来前端开发趋势 1.旧浏览器逐渐淘汰,移动端需求增加: 旧浏览器主要指的是IE6-IE8,它是不支持ES5特性的:IE9+.chrome.sarafi.firefox对ES5是完全支持的,移动端大部分浏览器是基于webkit内核,所以ES5在移动端也是全面支持的,因此vue可以在移动端以及现代浏览器中大显身手. 2.前端交互越来越多,功能越来越复杂: 现在的前端可谓是包罗万象,比如高大上的技术库和框架.酷炫的运营活动页面.H5小游戏,当然前端技术的应用在更多具有商业价值的应用上,比如下图.

vue.js基础知识篇(2):指令详解

第三章:指令 1.语法 指令以v-打头,它的值限定为绑定表达式,它负责的是按照表达式的值应用某些行为到DOM上. 内部指令有v-show,v-else,v-model,v-repeat,v-for,v-text,v-el,v-html,v-on,v-bind,v-ref,v-pre,v-cloak,v-if. 2.内部指令 (1)控制元素的显示与否:v-if,v-show.v-else v-if是真实的条件渲染,根据表达式的true/false在DOM中生成或移除一个元素. 第一,这个指令是惰性

JavaScript写一个拼图游戏

拼图游戏的代码400行, 有点多了, 在线DEMO的地址是:打开: 因为使用canvas,所以某些浏览器是不支持的: you know: 为什么要用canvas(⊙o⊙)?  因为图片是一整张jpg或者png, 我们要用把图片导入到canvas画布, 然后再调用上下文context的getImageData方法, 把图片处理成小图, 这些小图就作为拼图的基本单位: 如何判断游戏是否结束, 或者说如何判断用户拼成的大图是正确的? 我们就要在刚刚生成的小图上面添加自定义属性, 后期在小图被移动后再一

JS经典拼板游戏

原文链接:http://www.guimigame.com/thread-49-1-1.html,对于代码有什么不明白的地方,可以到这里给我发问哦! 效果演示 废话不多说了,直接上代码! <!doctype html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>JS重现