利用Vue.js實(shí)現(xiàn)拼圖游戲

來(lái)源:微信公眾號(hào)—前端呼啦圈(Love-FED)
作者:勞卜

之前寫(xiě)過(guò)一篇《基于Vue.js的表格分頁(yè)組件》的文章,主要介紹了Vue組件的編寫(xiě)方法,有興趣的可以訪問(wèn)這里進(jìn)行閱讀:基于Vue.js的表格分頁(yè)組件 - 勞卜 - 博客園**

前言

為了進(jìn)一步讓大家了解Vue.js的神奇魅力,了解Vue.js的一種以數(shù)據(jù)為驅(qū)動(dòng)的理念,本文主要利用Vue實(shí)現(xiàn)了一個(gè)數(shù)字拼圖游戲,其原理并不是很復(fù)雜,效果圖如下:

游戲效果圖
游戲效果圖

demo展示地址為:vue-puzzle

有能力的可以玩玩,拼出來(lái)有賞(勞卜大神的獎(jiǎng)賞)哦~

功能分析

當(dāng)然玩歸玩,作為一名Vue愛(ài)好者,我們理應(yīng)深入游戲內(nèi)部,一探代碼的實(shí)現(xiàn)。接下來(lái)我們就先來(lái)分析一下要完成這樣的一個(gè)游戲,主要需要實(shí)現(xiàn)哪些功能。下面我就直接將此實(shí)例的功能點(diǎn)羅列在下了:

  • 隨機(jī)生成1~15的數(shù)字格子,每一個(gè)數(shù)字都必須出現(xiàn)且僅出現(xiàn)一次
  • 點(diǎn)擊一個(gè)數(shù)字方塊后,如其上下左右有一處為空,則兩者交換位置
  • 格子每移動(dòng)一步,我們都需要校驗(yàn)其是否闖關(guān)成功
  • 點(diǎn)擊重置游戲按鈕后需對(duì)拼圖進(jìn)行重新排序

以上便是本實(shí)例的主要功能點(diǎn),可見(jiàn)游戲功能并不復(fù)雜,我們只需一個(gè)個(gè)攻破就OK了,接下來(lái)我就來(lái)展示一下各個(gè)功能點(diǎn)的Vue代碼。

構(gòu)建游戲面板

作為一款以數(shù)據(jù)驅(qū)動(dòng)的JS框架,Vue的HTML模板很多時(shí)候都應(yīng)該綁定數(shù)據(jù)的,比如此游戲的方塊格子,我們這里肯定是不能寫(xiě)死的,代碼如下:

<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樣式部分,大家可以先不用關(guān)心。以上代碼我們將1~15的數(shù)字寫(xiě)死在了一個(gè)數(shù)組中,這顯然不是隨機(jī)排序的,那么我們就來(lái)實(shí)現(xiàn)隨機(jī)排序的功能。

隨機(jī)排序數(shù)字

<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數(shù)字的數(shù)組
            for (i; i < 16; i++) {
                puzzleArr.push(i)
            }

            // 隨機(jī)打亂數(shù)組
            puzzleArr = puzzleArr.sort(() => {
                return Math.random() - 0.5
            });

            // 頁(yè)面顯示
            this.puzzles = puzzleArr
            this.puzzles.push('')
        },
    },
    ready () {
        this.render()
    }
}

以上代碼,我們利用for循環(huán)生成了一個(gè)1~15的有序數(shù)組,之后我們又利用原生JS的sort方法隨機(jī)打亂數(shù)字,這里還包含了一個(gè)知識(shí)點(diǎn)就是Math.random()方法。

利用sort()方法進(jìn)行自定義排序,我們需要提供一個(gè)比較函數(shù),然后返回一個(gè)用于說(shuō)明這兩個(gè)值的相對(duì)順序的數(shù)字,其返回值如下:

  • 返回一個(gè)小于 0 的值,說(shuō)明 a 小于 b
  • 返回 0,說(shuō)明 a 等于 b
  • 返回一個(gè)大于 0 的值,說(shuō)明 a 大于 b

這里利用Math.random()生成一個(gè) 0 ~ 1 之間的隨機(jī)數(shù),再減去0.5,這樣就會(huì)有一半概率返回一個(gè)小于 0 的值, 一半概率返回一個(gè)大于 0 的值,就保證了生成數(shù)組的隨機(jī)性,實(shí)現(xiàn)了動(dòng)態(tài)隨機(jī)生成數(shù)字格子的功能。

需要注意的是,我們還在數(shù)組最后插了一個(gè)空字符串,用來(lái)生成唯一的空白格子。

交換方塊位置

<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數(shù)字的數(shù)組
            for (i; i < 16; i++) {
                puzzleArr.push(i)
            }

            // 隨機(jī)打亂數(shù)組
            puzzleArr = puzzleArr.sort(() => {
                return Math.random() - 0.5
            });

            // 頁(yè)面顯示
            this.puzzles = puzzleArr
            this.puzzles.push('')
        },

        // 點(diǎn)擊方塊
        moveFn (index) {

            // 獲取點(diǎn)擊位置及其上下左右的值
            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]

            // 和為空的位置交換數(shù)值
            if (leftNum === '' && index % 4) {
                this.puzzles.$set(index - 1, curNum)
                this.puzzles.$set(index, '')
            } else if (rightNum === '' && 3 !== index % 4) {
                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. 這里我們首先在每個(gè)格子的li上添加了點(diǎn)擊事件@click="moveFn($index)",通過(guò)$index參數(shù)獲取點(diǎn)擊方塊在數(shù)組中的位置
  2. 其次獲取其上下左右的數(shù)字在數(shù)組中的index值依次為index - 4、index + 4、index - 1、index + 1
  3. 當(dāng)我們找到上下左右有一處為空的時(shí)候我們將空的位置賦值上當(dāng)前點(diǎn)擊格子的數(shù)字,將當(dāng)前點(diǎn)擊的位置置為空

備注:我們?yōu)槭裁匆褂?set方法,而不直接用等號(hào)賦值呢,這里包含了Vue響應(yīng)式原理的知識(shí)點(diǎn)。

// 因?yàn)?JavaScript 的限制,Vue.js 不能檢測(cè)到下面數(shù)組變化:

// 1.直接用索引設(shè)置元素,如 vm.items[0] = {};
// 2.修改數(shù)據(jù)的長(zhǎng)度,如 vm.items.length = 0。
// 為了解決問(wèn)題 (1),Vue.js 擴(kuò)展了觀察數(shù)組,為它添加了一個(gè) $set() 方法:
// 與 `example1.items[0] = ...` 相同,但是能觸發(fā)視圖更新
example1.items.$set(0, { childMsg: 'Changed!'})

詳見(jiàn):http://cn.vuejs.org/guide/list.html#問(wèn)題

檢測(cè)是否闖關(guān)成功

<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數(shù)字的數(shù)組
            for (i; i < 16; i++) {
                puzzleArr.push(i)
            }

            // 隨機(jī)打亂數(shù)組
            puzzleArr = puzzleArr.sort(() => {
                return Math.random() - 0.5
            });

            // 頁(yè)面顯示
            this.puzzles = puzzleArr
            this.puzzles.push('')
        },

        // 點(diǎn)擊方塊
        moveFn (index) {

            // 獲取點(diǎn)擊位置及其上下左右的值
            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]

            // 和為空的位置交換數(shù)值
            if (leftNum === '' && index % 4) {
                this.puzzles.$set(index - 1, curNum)
                this.puzzles.$set(index, '')
            } else if (rightNum === '' && 3 !== index % 4) {
                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()
        },

        // 校驗(yàn)是否過(guò)關(guān)
        passFn () {
            if (this.puzzles[15] === '') {
                const newPuzzles = this.puzzles.slice(0, 15)

                const isPass = newPuzzles.every((e, i) => e === i + 1)

                if (isPass) {
                    alert ('恭喜,闖關(guān)成功!')
                }
            }
        }
    },
    ready () {
        this.render()
    }
}
</script>

我們?cè)趍oveFn方法里調(diào)用了passFn方法來(lái)進(jìn)行檢測(cè),而passFn方法里又涉及了兩個(gè)知識(shí)點(diǎn):

(1)slice方法

通過(guò)slice方法我們截取數(shù)組的前15個(gè)元素生成一個(gè)新的數(shù)組,當(dāng)然前提是數(shù)組隨后一個(gè)元素為空

(2)every方法

通過(guò)every方法我們來(lái)循環(huán)截取后數(shù)組的每一個(gè)元素是否等于其index+1值,如果全部等于則返回true,只要有一個(gè)不等于則返回false
如果闖關(guān)成功那么isPass的值為true,就會(huì)alert "恭喜,闖關(guān)成功!"提示窗,如果沒(méi)有則不提示。

重置游戲

重置游戲其實(shí)很簡(jiǎn)單,只需添加重置按鈕并在其上調(diào)用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數(shù)字的數(shù)組
            for (i; i < 16; i++) {
                puzzleArr.push(i)
            }

            // 隨機(jī)打亂數(shù)組
            puzzleArr = puzzleArr.sort(() => {
                return Math.random() - 0.5
            });

            // 頁(yè)面顯示
            this.puzzles = puzzleArr
            this.puzzles.push('')
        },

        // 點(diǎn)擊方塊
        moveFn (index) {

            // 獲取點(diǎn)擊位置及其上下左右的值
            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]

            // 和為空的位置交換數(shù)值
            if (leftNum === '' && index % 4) {
                this.puzzles.$set(index - 1, curNum)
                this.puzzles.$set(index, '')
            } else if (rightNum === '' && 3 !== index % 4) {
                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()
        },

        // 校驗(yàn)是否過(guò)關(guān)
        passFn () {
            if (this.puzzles[15] === '') {
                const newPuzzles = this.puzzles.slice(0, 15)

                const isPass = newPuzzles.every((e, i) => e === i + 1)

                if (isPass) {
                    alert ('恭喜,闖關(guān)成功!')
                }
            }
        }
    },
    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代碼。

總結(jié)

其實(shí)本游戲的代碼量不多,功能點(diǎn)也不是很復(fù)雜,不過(guò)通過(guò)Vue來(lái)寫(xiě)這樣的游戲,有助于我們了解Vue以數(shù)據(jù)驅(qū)動(dòng)的響應(yīng)式原理,在簡(jiǎn)化代碼量的同時(shí)也增加了代碼的可讀性。

本實(shí)例的所有源碼我已經(jīng)上傳至我的github,地址為GitHub - luozhihao/vue-puzzle: 基于vue.js的拼圖游戲 需要的童鞋可以自行下載運(yùn)行。

補(bǔ)充:這里個(gè)demo只是為了演示vue的數(shù)據(jù)綁定,沒(méi)有對(duì)算法嚴(yán)格要求,詳情可以參考:https://www.h5jun.com/post/array-shuffle.html


更多內(nèi)容請(qǐng)關(guān)注:極樂(lè)科技

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容