Vue2+JS實現(xiàn)掃雷小游戲
實現(xiàn)步驟
1、場景布局實現(xiàn)
布局就是經(jīng)典的方格布局,對于場景的美觀度可以自行找?guī)讉€配色網(wǎng)站作為參考。
出現(xiàn)問題: 先初始化一個二維數(shù)組對應(yīng)方塊坐標,然后依次渲染 or 直接通過預(yù)期的行、列數(shù)渲染空白方塊
區(qū)別: 直接初始化二維數(shù)組,可以對坐標進行一些屬性操作,例如標記、是否為地雷等等,之后操作的時候會方便很多,缺點在初始化的時候需要進行大量的計算工作(因為在點開一個安全坐標時需要顯示周圍的地雷個數(shù),還要考慮邊緣情況),而渲染空白方塊就可以在點擊坐標的時候再去做計算,并且在點擊的時候只需要計算該方塊的屬性。
這里我選擇了渲染空白方塊的形式。
代碼實現(xiàn)
使用了 element-ui組件
template
<div class="layout">
<div class="row" v-for="row in layoutConfig.row" :key="row">
<div
class="cell"
:style="{ width: edgeLength, height: edgeLength }"
v-for="col in layoutConfig.cell"
:key="col">
<div
class="block"
@click="open(row, col, $event)"
@contextmenu.prevent="sign(row, col, $event)"
>
// 這里的邏輯現(xiàn)在可以暫時不用管,只需要先做好布局
<template v-if="areaSign[`${row}-${col}`] === 'fail'">
<img src="../../assets/svg/fail.svg" alt="">
</template>
<template v-else-if="areaSign[`${row}-${col}`] === 'tag'">
<img src="../../assets/svg/Flag.svg" alt="">
</template>
<template v-else-if="areaSign[`${row}-${col}`] === 'normal'">
</template>
<template v-else>
{{areaSign[`${row}-${col}`]}}
</template>
</div>
</div>
</div>
</div>
style:
<style scoped lang="less">
.container {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
margin-top: 100px;
.typeChoose {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
margin-bottom: 20px;
.item {
margin: 0 10px;
}
}
.layout {
width: 500px;
height: 500px;
.row {
display: flex;
justify-content: center;
align-items: center;
.cell {
border: 1px solid #735E30;
caret-color: transparent;
cursor: pointer;
line-height: 50px;
.block {
height: 100%;
background: #292E17;
}
.block:hover {
background: #96875F;
}
.opened {
height: 100%;
background: #E8E0D8;
}
}
}
}
}
</style>
2、初始化事件
生成地雷隨機二維數(shù)組
因為布局已經(jīng)通過空白方塊生成了,所以我們只需要關(guān)心生成隨機的地雷坐標就可以了
代碼實現(xiàn):
/*
* type: 當(dāng)前模式的地雷個數(shù)(自己定義數(shù)量)
* mineList: 地雷坐標數(shù)組
* layoutConfig: {
* row: 布局的行數(shù)
* col: 布局的列數(shù)
* }
*/
// 生成隨機地雷坐標數(shù)組
initMineListRange () {
while (this.mineList.length < this.type) {
this.initMineItem()
}
},
// 生成單個地雷坐標并且放入地雷坐標數(shù)組(mineList)中
initMineItem () {
const position = this.initPositionRange([1, this.layoutConfig.row], [1, this.layoutConfig.cell])
if (!this.hasPositionIn(position, this.mineList)) {
this.mineList.push(position)
}
},
// 生成一個在給定范圍內(nèi)的隨機坐標
initPositionRange ([xStart, xEnd], [yStart, yEnd]) {
return [this.numRange(xStart, xEnd), this.numRange(yStart, yEnd)]
},
// 生成一個在給定范圍內(nèi)的隨機整數(shù)
numRange (start, end) {
return Math.floor((Math.random() * (end - start + 1))) + start
},
// 判斷參數(shù)中的 position 是否已經(jīng)存在與 參數(shù)中的 positionList 中
hasPositionIn (position, positionList) {
console.assert(position.length === 2, 'position length < 2, not a position item')
return positionList.some(p => {
return p[0] === position[0] && p[1] === position[1]
})
}
3、游戲動作(action)
指的是游戲中的一些操作以及某個操作導(dǎo)致的一系列變化
點擊方塊
分析:點擊方塊之后會出現(xiàn)三種情況
- 該方塊的九宮格范圍內(nèi)沒有地雷
- 該方塊的九宮格方位內(nèi)有地雷
- 踩雷了(game over)
對應(yīng)這三種情況需要分別有不同的表現(xiàn)形式
第一種情況:(方塊的九宮格范圍內(nèi)沒有地雷)
這種情況只需要將該方塊的樣式改為點擊過的樣式即可(class="opened")
第二種情況:(方塊的九宮格方位內(nèi)有地雷)
修改樣式為opened,并且需要計算周圍的地雷數(shù)量(需要考慮邊緣情況,即當(dāng)前坐標是否在邊緣)
第三種情況:(踩雷)
修改樣式為opened, 并且展示地雷,提示用戶游戲結(jié)束
代碼實現(xiàn)
因為在點擊之前該方塊是空白對象,所以需要一個對象來存儲該方塊的屬性或者狀態(tài)(areaSign)
/*
* areaSign: Object key: 坐標('1-2') value: 狀態(tài)
* gameProcess:當(dāng)前游戲是否處于進行狀態(tài)
* statusEnum: 枚舉 方塊狀態(tài)枚舉值(fail,normal,tag)
*/
// 方塊點擊事件 (傳入坐標以及點擊事件對象)
open (rowIndex, colIndex, e) {
// 判斷當(dāng)前游戲是否
if (!this.gameProcess) {
this.gameEndConfirm()
return
}
// 判斷當(dāng)前坐標是否被標記,被標記則不能被點開
if (this.getAreaSignValueWithPosition(rowIndex, colIndex) === statusEnum.tag) {
this.confirmMessageBox('該區(qū)域已經(jīng)被標記,請選擇其他區(qū)域點擊')
return
}
e.target.className = 'opened'
if (this.hasTouchMine([rowIndex, colIndex])) {
// 踩雷
this.mineTouched([rowIndex, colIndex])
} else {
// 第一、二種情況
this.safeTouched([rowIndex, colIndex])
}
},
// 通過傳入的坐標判斷是否存在地雷坐標數(shù)組中
hasTouchMine ([xPosition, yPosition]) {
return this.hasPositionIn([xPosition, yPosition], this.mineList)
},
mineTouched (position) {
this.setSvg(position, statusEnum.fail)
// 游戲失敗提示
this.gameProcess = false
this.gameEndConfirm()
},
safeTouched (position) {
this.setTips(position)
},
// 把傳入坐標通過判斷是否有雷設(shè)置對應(yīng)提示
setTips (position) {
const total = this.positionAroundMineTotal(position)
this.$set(this.areaSign, `${position[0]}-${position[1]}`, total || '')
},
// 把傳入坐標設(shè)置為對應(yīng)狀態(tài)的svg圖標
setSvg (position, type) {
this.$set(this.areaSign, `${position[0]}-${position[1]}`, type)
},
// 傳入坐標與地雷坐標數(shù)組判斷是否其周圍存在雷
positionAroundMineTotal (position) {
const aroundPositionList = this.getAroundPosition(position[0], position[1])
return aroundPositionList.filter(item => this.hasTouchMine(item)).length
},
// 獲取傳入坐標的周圍九宮格坐標
getAroundPosition (xPosition, yPosition) {
const aroundPositionList = [
[xPosition - 1, yPosition - 1],
[xPosition - 1, yPosition],
[xPosition - 1, yPosition + 1],
[xPosition, yPosition - 1],
[xPosition, yPosition + 1],
[xPosition + 1, yPosition - 1],
[xPosition + 1, yPosition],
[xPosition + 1, yPosition + 1]
]
return aroundPositionList.filter(position => isInRange(position[0]) && isInRange(position[1]))
// 判斷傳入數(shù)字是否在對應(yīng)范圍中
function isInRange (num, range = [1, 10]) {
return num >= range[0] && num <= range[1]
}
}
標記坐標
左鍵為點擊方塊,右鍵為標記坐標(第二次點擊為取消標記),當(dāng)該坐標為標記的時候,無法進行點擊,并且當(dāng)剛好標記的坐標數(shù)組和地雷數(shù)組一樣時,則游戲結(jié)束,玩家勝利
代碼實現(xiàn)
/*
* hasWin 見下文的 vue computed
*/
sign (rowIndex, colIndex, e) {
// 判斷游戲當(dāng)前狀態(tài)
if (!this.gameProcess) {
this.gameEndConfirm()
return
}
if (this.getAreaSignValueWithPosition(rowIndex, colIndex) === undefined ||
this.getAreaSignValueWithPosition(rowIndex, colIndex) === statusEnum.normal) {
// 當(dāng)前坐標 為被標記過或者以及被取消標記 觸發(fā):添加標記
this.setSvg([rowIndex, colIndex], statusEnum.tag)
} else if (this.getAreaSignValueWithPosition(rowIndex, colIndex) === statusEnum.tag) {
// 當(dāng)前坐標 被標記 觸發(fā):取消標記
this.setSvg([rowIndex, colIndex], statusEnum.normal)
}
console.log(this.tagList, this.mineList)
// 檢測游戲是否結(jié)束
this.gameInspector()
},
// 游戲提示
gameEndConfirm () {
const message = this.hasWin ? '恭喜你通關(guān),是否繼續(xù)?' : '游戲失敗,是否重新開始?'
this.confirmMessageBox(message, {
callback: () => {
this.resetGame()
},
cancelCallback: () => {}
}, 'confirm')
},
// 游戲狀態(tài)檢測員(判斷當(dāng)前游戲是否結(jié)束)
gameInspector () {
if (this.hasWin) {
this.gameEndConfirm()
}
},
// 通過傳入坐標返回對應(yīng)格式的字符串(areaSign的key值)
getAreaSignAttrWithPosition (xPosition, yPosition) {
return `${xPosition}-${yPosition}`
},
// 通過傳入坐標返回areaSign的value值(獲取該坐標的狀態(tài))
getAreaSignValueWithPosition (xPosition, yPosition) {
return this.areaSign[this.getAreaSignAttrWithPosition(xPosition, yPosition)]
}
// 被標記列表
tagList () {
return Object.keys(this.areaSign)
.filter(item => this.areaSign[item] === 'tag')
.map(attrStr => attrStr.split('-').map(str => parseInt(str)))
},
// 判斷所有的地雷是否已經(jīng)被標記
hasSignAllMine () {
return this.tagList.length === this.mineList.length &&
this.tagList.every(tagPosition => this.hasPositionIn(tagPosition, this.mineList))
},
// 游戲是否勝利
hasWin () {
return this.hasSignAllMine
}
游戲收尾
游戲失敗或者勝利的時候需要重置游戲
代碼實現(xiàn)
resetGame () {
this.gameProcess = true
this.areaSign = {}
this.mineList = []
this.resetOpenedClass()
// 初始化游戲
this.initMineListRange()
},
// 將class = "opened" 的元素改回 "block" 狀態(tài)
resetOpenedClass () {
document.querySelectorAll('.opened').forEach(node => {
node.className = 'block'
})
}
總結(jié)
掃雷的實現(xiàn)并不復(fù)雜,首先需要對掃雷這個游戲的機制有思路,并且可以將一些邏輯捋清楚就可以了,實現(xiàn)的時候再將一些邊緣狀態(tài)考慮一下。可以更多關(guān)注一下對于代碼的封裝,對于代碼的提煉很重要,這樣在之后繼續(xù)開發(fā)或者需要修改的時候很容易上手。
以上就是Vue2+JS實現(xiàn)掃雷小游戲的詳細內(nèi)容,更多關(guān)于Vue掃雷游戲的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解決vue動態(tài)路由異步加載import組件,加載不到module的問題
這篇文章主要介紹了解決vue動態(tài)路由異步加載import組件,加載不到module的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07
Vue實現(xiàn)點擊當(dāng)前元素以外的地方隱藏當(dāng)前元素(實現(xiàn)思路)
這篇文章主要介紹了Vue實現(xiàn)點擊當(dāng)前元素以外的地方隱藏當(dāng)前元素,文中給大家擴展了vue實現(xiàn)點擊其他地方隱藏div的三種方法,需要的朋友可以參考下2019-12-12
Vee-validate 父組件獲取子組件表單校驗結(jié)果的實例代碼
vee-validate 是為 Vue.js 量身打造的表單校驗框架,允許您校驗輸入的內(nèi)容并顯示對應(yīng)的錯誤提示信息。這篇文章主要介紹了Vee-validate 父組件獲取子組件表單校驗結(jié)果 ,需要的朋友可以參考下2019-05-05
寫一個移動端慣性滑動&回彈Vue導(dǎo)航欄組件 ly-tab
前一段時間小編寫一個移動端慣性滑動&回彈Vue導(dǎo)航欄組件 ly-tab,覺的非常實用,大家可能在做項目時會用到,感興趣的朋友跟隨腳本之家小編一起學(xué)習(xí)吧2018-03-03
Vue如何動態(tài)修改el-table的某列數(shù)據(jù)
這篇文章主要介紹了Vue如何動態(tài)修改el-table的某列數(shù)據(jù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-04-04
ant-design-vue導(dǎo)航菜單a-menu的使用解讀
這篇文章主要介紹了ant-design-vue導(dǎo)航菜單a-menu的使用解讀,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10

