利用Vue.js實現拼圖遊戲

以前寫過一篇《基於Vue.js的表格分頁組件》的文章,主要介紹了Vue組件的編寫方法,有興趣的能夠訪問這裏進行閱讀:http://www.cnblogs.com/luozhihao/p/5516065.htmlcss

前言

爲了進一步讓你們瞭解Vue.js的神奇魅力,瞭解Vue.js的一種以數據爲驅動的理念,本文主要利用Vue實現了一個數字拼圖遊戲,其原理並非很複雜,效果圖以下:html

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

有能力的能夠玩玩,拼出來有賞哦~~git

功能分析

固然玩歸玩,做爲一名Vue愛好者,咱們理應深刻遊戲內部,一探代碼的實現。接下來咱們就先來分析一下要完成這樣的一個遊戲,主要須要實現哪些功能。下面我就直接將此實例的功能點羅列在下了:github

  • 隨機生成1~15的數字格子,每個數字都必須出現且僅出現一次bootstrap

  • 點擊一個數字方塊後,如其上下左右有一處爲空,則二者交換位置數組

  • 格子每移動一步,咱們都須要校驗其是否闖關成功框架

  • 點擊重置遊戲按鈕後需對拼圖進行從新排序dom

以上即是本實例的主要功能點,可見遊戲功能並不複雜,咱們只需一個個攻破就OK了,接下來我就來展現一下各個功能點的Vue代碼。ide

構建遊戲面板

做爲一款以數據驅動的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 === '' && 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. 這裏咱們首先在每一個格子的li上添加了點擊事件@click="moveFn($index)",經過$index參數獲取點擊方塊在數組中的位置

  2. 其次獲取其上下左右的數字在數組中的index值依次爲index - 四、index + 四、index - 一、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 === '' && 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()
        },

        // 校驗是否過關
        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 === '' && 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()
        },

        // 校驗是否過關
        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 須要的童鞋能夠自行下載運行。

相關文章
相關標籤/搜索