基于Vue.js實(shí)現(xiàn)數(shù)字拼圖游戲
先來(lái)看看效果圖:

功能分析
當(dāng)然玩歸玩,作為一名Vue愛好者,我們理應(yīng)深入游戲內(nèi)部,一探代碼的實(shí)現(xiàn)。接下來(lái)我們就先來(lái)分析一下要完成這樣的一個(gè)游戲,主要需要實(shí)現(xiàn)哪些功能。下面我就直接將此實(shí)例的功能點(diǎn)羅列在下了:
1.隨機(jī)生成1~15的數(shù)字格子,每一個(gè)數(shù)字都必須出現(xiàn)且僅出現(xiàn)一次
2.點(diǎn)擊一個(gè)數(shù)字方塊后,如其上下左右有一處為空,則兩者交換位置
3.格子每移動(dòng)一步,我們都需要校驗(yàn)其是否闖關(guān)成功
4.點(diǎn)擊重置游戲按鈕后需對(duì)拼圖進(jìn)行重新排序
以上便是本實(shí)例的主要功能點(diǎ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ù)的,比如此游戲的方塊格子,我們這里肯定是不能寫死的,代碼如下:
<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ù)字寫死在了一個(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è)用于說明這兩個(gè)值的相對(duì)順序的數(shù)字,其返回值如下:
1.返回一個(gè)小于 0 的值,說明 a 小于 b
2.返回 0,說明 a 等于 b
3.返回一個(gè)大于 0 的值,說明 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 === '') {
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.這里我們首先在每個(gè)格子的li上添加了點(diǎn)擊事件@click="moveFn($index)",通過$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。
// 為了解決問題 (1),Vue.js 擴(kuò)展了觀察數(shù)組,為它添加了一個(gè) $set() 方法:
// 與 `example1.items[0] = ...` 相同,但是能觸發(fā)視圖更新
example1.items.$set(0, { childMsg: 'Changed!'})
檢測(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 === '') {
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()
},
// 校驗(yàn)是否過關(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è)?code>moveFn方法里調(diào)用了passFn方法來(lái)進(jìn)行檢測(cè),而passFn方法里又涉及了兩個(gè)知識(shí)點(diǎn):
(1)slice方法
通過slice方法我們截取數(shù)組的前15個(gè)元素生成一個(gè)新的數(shù)組,當(dāng)然前提了數(shù)組隨后一個(gè)元素為空
(2)every方法
通過every方法我們來(lái)循環(huán)截取后數(shù)組的每一個(gè)元素是否等于其index+1值,如果全部等于則返回true,只要有一個(gè)不等于則返回false
如果闖關(guān)成功那么isPass的值為true,就會(huì)alert "恭喜,闖關(guān)成功!"提示窗,如果沒有則不提示。
重置游戲
重置游戲其實(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 === '') {
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()
},
// 校驗(yàn)是否過關(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é)
以上就是本文的全部?jī)?nèi)容,其實(shí)本游戲的代碼量不多,功能點(diǎn)也不是很復(fù)雜,不過通過Vue來(lái)寫這樣的游戲,有助于我們了解Vue以數(shù)據(jù)驅(qū)動(dòng)的響應(yīng)式原理,在簡(jiǎn)化代碼量的同時(shí)也增加了代碼的可讀性。希望本文對(duì)大家學(xué)些Vue有所幫助。
- JS快速實(shí)現(xiàn)移動(dòng)端拼圖游戲
- JS 拼圖游戲 面向?qū)ο?,注釋完整?/a>
- js+html5實(shí)現(xiàn)可在手機(jī)上玩的拼圖游戲
- JS實(shí)現(xiàn)拼圖游戲
- jQuery+vue.js實(shí)現(xiàn)的九宮格拼圖游戲完整實(shí)例【附源碼下載】
- 基于javascript制作經(jīng)典傳統(tǒng)的拼圖游戲
- 利用原生的JavaScript實(shí)現(xiàn)簡(jiǎn)單拼圖游戲
- javascript結(jié)合Flexbox簡(jiǎn)單實(shí)現(xiàn)滑動(dòng)拼圖游戲
- 基于JS實(shí)現(xiàn)簡(jiǎn)單滑塊拼圖游戲
- 使用js編寫實(shí)現(xiàn)拼圖游戲
相關(guān)文章
Vue替代vuex的存儲(chǔ)庫(kù)Pinia詳細(xì)介紹
這篇文章主要介紹了Vue替代vuex的存儲(chǔ)庫(kù)Pinia,聽說pinia與vue3更配,便開啟了vue3的學(xué)習(xí)之路,pinia 和 vuex 具有相同的功效, 是 Vue 的存儲(chǔ)庫(kù),它允許您跨組件/頁(yè)面共享狀態(tài)2022-09-09
vue3-print-nb實(shí)現(xiàn)頁(yè)面打印(含分頁(yè)打印)示例代碼
大多數(shù)后臺(tái)系統(tǒng)中都存在打印的需求,在有打印需求時(shí),對(duì)前端來(lái)說當(dāng)然是直接打印頁(yè)面更容易,下面這篇文章主要給大家介紹了關(guān)于vue3-print-nb實(shí)現(xiàn)頁(yè)面打印(含分頁(yè)打印)的相關(guān)資料,需要的朋友可以參考下2024-01-01
vue.js源代碼core scedule.js學(xué)習(xí)筆記
這篇文章主要為大家詳細(xì)介紹了vue.js源代碼core scedule.js的學(xué)習(xí)筆記,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
關(guān)于element-ui?select?下拉框位置錯(cuò)亂問題解決
這篇文章主要介紹了關(guān)于element-ui?select?下拉框位置錯(cuò)亂問題解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09
vuejs使用遞歸組件實(shí)現(xiàn)樹形目錄的方法
本篇文章主要介紹了vuejs使用遞歸組件實(shí)現(xiàn)樹形目錄的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2017-09-09
詳解如何實(shí)現(xiàn)在Vue中導(dǎo)入Excel文件
這篇文章主要介紹了如何在Vue中導(dǎo)入Excel文件,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)或工作有一定幫助,感興趣的小伙伴可以跟隨小編一起了解一下2022-01-01
vue利用指令實(shí)現(xiàn)快速設(shè)置元素的高度
在項(xiàng)目中經(jīng)常有需要將列表的高度設(shè)置成剩余可視區(qū)域的高度,本文主要來(lái)和大家介紹一下如何通過指令和css變量的方式快速設(shè)置列表高度,希望對(duì)大家有所幫助2024-03-03

