Vue2+JS實(shí)現(xiàn)掃雷小游戲
實(shí)現(xiàn)步驟
1、場景布局實(shí)現(xiàn)
布局就是經(jīng)典的方格布局,對于場景的美觀度可以自行找?guī)讉€(gè)配色網(wǎng)站作為參考。
出現(xiàn)問題: 先初始化一個(gè)二維數(shù)組對應(yīng)方塊坐標(biāo),然后依次渲染 or
直接通過預(yù)期的行、列數(shù)渲染空白方塊
區(qū)別: 直接初始化二維數(shù)組,可以對坐標(biāo)進(jìn)行一些屬性操作,例如標(biāo)記、是否為地雷等等,之后操作的時(shí)候會(huì)方便很多,缺點(diǎn)在初始化的時(shí)候需要進(jìn)行大量的計(jì)算工作(因?yàn)樵邳c(diǎn)開一個(gè)安全坐標(biāo)時(shí)需要顯示周圍的地雷個(gè)數(shù),還要考慮邊緣情況),而渲染空白方塊就可以在點(diǎn)擊坐標(biāo)的時(shí)候再去做計(jì)算,并且在點(diǎn)擊的時(shí)候只需要計(jì)算該方塊的屬性。
這里我選擇了渲染空白方塊的形式。
代碼實(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)在可以暫時(shí)不用管,只需要先做好布局 <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、初始化事件
生成地雷隨機(jī)二維數(shù)組
因?yàn)椴季忠呀?jīng)通過空白方塊生成了,所以我們只需要關(guān)心生成隨機(jī)的地雷坐標(biāo)就可以了
代碼實(shí)現(xiàn):
/* * type: 當(dāng)前模式的地雷個(gè)數(shù)(自己定義數(shù)量) * mineList: 地雷坐標(biāo)數(shù)組 * layoutConfig: { * row: 布局的行數(shù) * col: 布局的列數(shù) * } */ // 生成隨機(jī)地雷坐標(biāo)數(shù)組 initMineListRange () { while (this.mineList.length < this.type) { this.initMineItem() } }, // 生成單個(gè)地雷坐標(biāo)并且放入地雷坐標(biāo)數(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) } }, // 生成一個(gè)在給定范圍內(nèi)的隨機(jī)坐標(biāo) initPositionRange ([xStart, xEnd], [yStart, yEnd]) { return [this.numRange(xStart, xEnd), this.numRange(yStart, yEnd)] }, // 生成一個(gè)在給定范圍內(nèi)的隨機(jī)整數(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、游戲動(dòng)作(action)
指的是游戲中的一些操作以及某個(gè)操作導(dǎo)致的一系列變化
點(diǎn)擊方塊
分析:點(diǎn)擊方塊之后會(huì)出現(xiàn)三種情況
- 該方塊的九宮格范圍內(nèi)沒有地雷
- 該方塊的九宮格方位內(nèi)有地雷
- 踩雷了(game over)
對應(yīng)這三種情況需要分別有不同的表現(xiàn)形式
第一種情況:(方塊的九宮格范圍內(nèi)沒有地雷)
這種情況只需要將該方塊的樣式改為點(diǎn)擊過的樣式即可(class="opened"
)
第二種情況:(方塊的九宮格方位內(nèi)有地雷)
修改樣式為opened
,并且需要計(jì)算周圍的地雷數(shù)量(需要考慮邊緣情況,即當(dāng)前坐標(biāo)是否在邊緣)
第三種情況:(踩雷)
修改樣式為opened
, 并且展示地雷,提示用戶游戲結(jié)束
代碼實(shí)現(xiàn)
因?yàn)樵邳c(diǎn)擊之前該方塊是空白對象,所以需要一個(gè)對象來存儲該方塊的屬性或者狀態(tài)(areaSign
)
/* * areaSign: Object key: 坐標(biāo)('1-2') value: 狀態(tài) * gameProcess:當(dāng)前游戲是否處于進(jìn)行狀態(tài) * statusEnum: 枚舉 方塊狀態(tài)枚舉值(fail,normal,tag) */ // 方塊點(diǎn)擊事件 (傳入坐標(biāo)以及點(diǎn)擊事件對象) open (rowIndex, colIndex, e) { // 判斷當(dāng)前游戲是否 if (!this.gameProcess) { this.gameEndConfirm() return } // 判斷當(dāng)前坐標(biāo)是否被標(biāo)記,被標(biāo)記則不能被點(diǎn)開 if (this.getAreaSignValueWithPosition(rowIndex, colIndex) === statusEnum.tag) { this.confirmMessageBox('該區(qū)域已經(jīng)被標(biāo)記,請選擇其他區(qū)域點(diǎn)擊') return } e.target.className = 'opened' if (this.hasTouchMine([rowIndex, colIndex])) { // 踩雷 this.mineTouched([rowIndex, colIndex]) } else { // 第一、二種情況 this.safeTouched([rowIndex, colIndex]) } }, // 通過傳入的坐標(biāo)判斷是否存在地雷坐標(biāo)數(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) }, // 把傳入坐標(biāo)通過判斷是否有雷設(shè)置對應(yīng)提示 setTips (position) { const total = this.positionAroundMineTotal(position) this.$set(this.areaSign, `${position[0]}-${position[1]}`, total || '') }, // 把傳入坐標(biāo)設(shè)置為對應(yīng)狀態(tài)的svg圖標(biāo) setSvg (position, type) { this.$set(this.areaSign, `${position[0]}-${position[1]}`, type) }, // 傳入坐標(biāo)與地雷坐標(biāo)數(shù)組判斷是否其周圍存在雷 positionAroundMineTotal (position) { const aroundPositionList = this.getAroundPosition(position[0], position[1]) return aroundPositionList.filter(item => this.hasTouchMine(item)).length }, // 獲取傳入坐標(biāo)的周圍九宮格坐標(biāo) 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] } }
標(biāo)記坐標(biāo)
左鍵為點(diǎn)擊方塊,右鍵為標(biāo)記坐標(biāo)(第二次點(diǎn)擊為取消標(biāo)記),當(dāng)該坐標(biāo)為標(biāo)記的時(shí)候,無法進(jìn)行點(diǎn)擊,并且當(dāng)剛好標(biāo)記的坐標(biāo)數(shù)組和地雷數(shù)組一樣時(shí),則游戲結(jié)束,玩家勝利
代碼實(shí)現(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)前坐標(biāo) 為被標(biāo)記過或者以及被取消標(biāo)記 觸發(fā):添加標(biāo)記 this.setSvg([rowIndex, colIndex], statusEnum.tag) } else if (this.getAreaSignValueWithPosition(rowIndex, colIndex) === statusEnum.tag) { // 當(dāng)前坐標(biāo) 被標(biāo)記 觸發(fā):取消標(biāo)記 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() } }, // 通過傳入坐標(biāo)返回對應(yīng)格式的字符串(areaSign的key值) getAreaSignAttrWithPosition (xPosition, yPosition) { return `${xPosition}-${yPosition}` }, // 通過傳入坐標(biāo)返回areaSign的value值(獲取該坐標(biāo)的狀態(tài)) getAreaSignValueWithPosition (xPosition, yPosition) { return this.areaSign[this.getAreaSignAttrWithPosition(xPosition, yPosition)] }
// 被標(biāo)記列表 tagList () { return Object.keys(this.areaSign) .filter(item => this.areaSign[item] === 'tag') .map(attrStr => attrStr.split('-').map(str => parseInt(str))) }, // 判斷所有的地雷是否已經(jīng)被標(biāo)記 hasSignAllMine () { return this.tagList.length === this.mineList.length && this.tagList.every(tagPosition => this.hasPositionIn(tagPosition, this.mineList)) }, // 游戲是否勝利 hasWin () { return this.hasSignAllMine }
游戲收尾
游戲失敗或者勝利的時(shí)候需要重置游戲
代碼實(shí)現(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é)
掃雷的實(shí)現(xiàn)并不復(fù)雜,首先需要對掃雷這個(gè)游戲的機(jī)制有思路,并且可以將一些邏輯捋清楚就可以了,實(shí)現(xiàn)的時(shí)候再將一些邊緣狀態(tài)考慮一下??梢愿嚓P(guān)注一下對于代碼的封裝,對于代碼的提煉很重要,這樣在之后繼續(xù)開發(fā)或者需要修改的時(shí)候很容易上手。
以上就是Vue2+JS實(shí)現(xiàn)掃雷小游戲的詳細(xì)內(nèi)容,更多關(guān)于Vue掃雷游戲的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
ant-design-vue 時(shí)間選擇器賦值默認(rèn)時(shí)間的操作
這篇文章主要介紹了ant-design-vue 時(shí)間選擇器賦值默認(rèn)時(shí)間的操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨過來看看吧2020-10-10解決vue動(dòng)態(tài)路由異步加載import組件,加載不到module的問題
這篇文章主要介紹了解決vue動(dòng)態(tài)路由異步加載import組件,加載不到module的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07Vue實(shí)現(xiàn)點(diǎn)擊當(dāng)前元素以外的地方隱藏當(dāng)前元素(實(shí)現(xiàn)思路)
這篇文章主要介紹了Vue實(shí)現(xiàn)點(diǎn)擊當(dāng)前元素以外的地方隱藏當(dāng)前元素,文中給大家擴(kuò)展了vue實(shí)現(xiàn)點(diǎn)擊其他地方隱藏div的三種方法,需要的朋友可以參考下2019-12-12Vee-validate 父組件獲取子組件表單校驗(yàn)結(jié)果的實(shí)例代碼
vee-validate 是為 Vue.js 量身打造的表單校驗(yàn)框架,允許您校驗(yàn)輸入的內(nèi)容并顯示對應(yīng)的錯(cuò)誤提示信息。這篇文章主要介紹了Vee-validate 父組件獲取子組件表單校驗(yàn)結(jié)果 ,需要的朋友可以參考下2019-05-05寫一個(gè)移動(dòng)端慣性滑動(dòng)&回彈Vue導(dǎo)航欄組件 ly-tab
前一段時(shí)間小編寫一個(gè)移動(dòng)端慣性滑動(dòng)&回彈Vue導(dǎo)航欄組件 ly-tab,覺的非常實(shí)用,大家可能在做項(xiàng)目時(shí)會(huì)用到,感興趣的朋友跟隨腳本之家小編一起學(xué)習(xí)吧2018-03-03Vue如何動(dòng)態(tài)修改el-table的某列數(shù)據(jù)
這篇文章主要介紹了Vue如何動(dòng)態(tài)修改el-table的某列數(shù)據(jù),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04ant-design-vue導(dǎo)航菜單a-menu的使用解讀
這篇文章主要介紹了ant-design-vue導(dǎo)航菜單a-menu的使用解讀,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10