您好,登錄后才能下訂單哦!
前言
話說(shuō)阿望還在大學(xué)時(shí),某一天寢室突然停網(wǎng)了,于是和室友兩人不約而同地打開了掃雷,比相同難度下誰(shuí)更快找出全部的雷,玩得不亦樂乎,就這樣,掃雷伴我們度過(guò)了斷網(wǎng)的一周,是整整一周啊,不用上課的那種,可想而知我們是有多無(wú)聊了。
這兩天臨近過(guò)年了,該放假的已經(jīng)放假了,不該放假的已經(jīng)請(qǐng)假了,公交不打擠了,地鐵口不堵了,公司也去了大半部分的人了,就留阿望這種不得不留下來(lái)值班的人守著空蕩蕩的辦公室了,于是,多年前那種無(wú)所事事的斷網(wǎng)心態(tài)再次襲來(lái),于是,想玩掃雷的心再次蹦跶出來(lái),于是,點(diǎn)開了電腦的附件,于是,發(fā)現(xiàn)我的電腦上并沒有掃雷【手動(dòng)微笑.jpg】。
怎么想起要寫掃雷的阿望就不多廢話了,直接開干。
掃雷游戲規(guī)則
想當(dāng)年阿望還是在大學(xué)才參透掃雷的游戲規(guī)則的,初高中的時(shí)候都是靠運(yùn)氣瞎點(diǎn),從沒贏過(guò),當(dāng)然了,境界提升以后,追求的自然就不是贏了,而是速度。
來(lái),看規(guī)則:
大白話游戲規(guī)則,計(jì)算邏輯如下:
游戲開始,選擇難度 鼠標(biāo)左鍵隨機(jī)點(diǎn)開一個(gè)格子A(你想用右鍵標(biāo)雷也可以) 左鍵點(diǎn)擊后一共有三種情況 1) 是數(shù)字,假設(shè)是2,表示以該格子A為中心的九宮格除了該格子以外的8個(gè)格子內(nèi)有2個(gè)雷 2)是空白,表示以該格子A為中心的九宮格除了該格子以外的8個(gè)格子內(nèi)沒有雷,并且系統(tǒng)計(jì)算分別以這8個(gè)格子為中心九宮格除了該格子以外的8個(gè)格子是否是空白 a. 是空白,繼續(xù)計(jì)算以這8個(gè)格子為中心九宮格除了該格子以外的8個(gè)格子是否是空白,循環(huán)擴(kuò)散 b. 是格子邊界,停止邊界擴(kuò)散 c. 是數(shù)字,數(shù)字格子翻出來(lái)顯示,停止擴(kuò)散 3)是雷,game over 了。。。 如果點(diǎn)第一次只翻了一個(gè)數(shù)字是比較難開始游戲的,如果第一次翻了一大片出來(lái),恭喜你,可以開始秀你的智商了,在確認(rèn)是雷點(diǎn)的格子點(diǎn)右鍵,即可標(biāo)記地雷格,表示你認(rèn)定這個(gè)格子是地雷 如果數(shù)字周圍的雷被全部標(biāo)記出來(lái)了,但數(shù)字周圍還有沒被翻開的格子的話,雙擊該格子,自動(dòng)將其他格子翻開
好,這下規(guī)則我們了解了,接下來(lái),摩拳擦掌,開始寫代碼咯,本次代碼用vue來(lái)寫,沒有原因,習(xí)慣了而已。
掃雷代碼邏輯
看著游戲規(guī)則,我們先來(lái)理一理,要如何完成這個(gè)功能,我們以最簡(jiǎn)單的,最能使人理解的步驟,來(lái)完成這次功能。
生成項(xiàng)目
避免vue新手不知如何下手,那就從建項(xiàng)目開始吧,環(huán)境安裝我就不講了,腳手架也是要有的
第一步,初始化lovesweeping工程(阿望不喜歡地雷,喜歡小桃心)
項(xiàng)目很小,不需要路由,不需要vuex,即可完成,只帶了sass,連eslint都可以不要,看官們可以根據(jù)自己的喜好建項(xiàng)目
項(xiàng)目生成之后,helloworld就可以刪掉了,它的存在并沒有什么意義
文件切分
這一步主要是構(gòu)建工程結(jié)構(gòu),簡(jiǎn)單畫一下主要的幾個(gè)文件
- src - components - SelectLevel.vue [新增,難度選擇組件] - LoveSweeping.vue [新增,游戲界面組件] - App.vue [父組件,負(fù)責(zé)組件間的切換和某些數(shù)據(jù)傳遞] - main.js - package.json
好,初步認(rèn)識(shí)了項(xiàng)目結(jié)構(gòu)以后,咱把該新建的建好,可以不加?xùn)|西,不報(bào)錯(cuò)就行
然后就是把組件間的切換代碼寫好,再來(lái)一步步的填充代碼
難度選擇
這一步很簡(jiǎn)單,首先畫好難度選擇的頁(yè)面,難度可以自己設(shè)置,你覺得合理就行,我這里的數(shù)據(jù)格式是這樣的,用一個(gè)對(duì)象表示一個(gè)難度等級(jí),對(duì)象中包含了難度描述,以及難度設(shè)置,設(shè)置中包含了格子橫排數(shù),格子縱排數(shù),雷數(shù)
// 難度 level: [ { text: '青銅', // 難度描述 value: [9, 9, 10] // 格子橫排數(shù),格子縱排數(shù),雷數(shù) }, { text: '黃金', value: [12, 9, 20] ]
然后模板中直接渲染列表,這樣做的好處是,想要增加難度直接在數(shù)組中添加數(shù)據(jù)即可
<li v-for="(item, index) in level" :key="index" @click="handleChoseLevel(item.value)"></li>
該組件中只有一個(gè)方法:選擇難度之后,跳轉(zhuǎn)到游戲主界面上去,因?yàn)轫?xiàng)目沒有用路由,直接使用組件間的切換,所以,這個(gè)方法只負(fù)責(zé)告訴父組件,我已經(jīng)選擇好難度了,可以開始游戲了
// 選擇難度 handleChoseLevel(level) { this.$emit("chose-level", level); }
代碼如下:
界面長(zhǎng)這樣,當(dāng)然,你要覺得難看自己換個(gè)樣式也行
游戲界面
畫格盤
通過(guò)游戲難度選擇來(lái)決定游戲格盤的大小,組件間已通過(guò)App將游戲難度傳至界面組件中,我們用props把數(shù)據(jù)接收到,消化成自己的數(shù)據(jù),畫格盤需要的數(shù)據(jù)有:橫排格子數(shù),縱排格子數(shù)
畫格子:我們將格子的索引暴露出來(lái),后續(xù)可以幫助我們?cè)囧e(cuò)。整個(gè)格局有兩種方式來(lái)表示格盤,坐標(biāo)式和索引式,比如橫9縱9的格子,[0, 0]代表第1個(gè)格子,[2, 3]代表第三行第四列也就是第20個(gè)格子。此次使用索引式來(lái)標(biāo)志格盤
<div v-for="col in cols" :key="Math.random() + col" class="game-content-row"> <span v-for="row in rows" :key="Math.random() + row" class="game-block"> <span></span> </span> </div>
隨機(jī)生成地雷
首先data中添加一個(gè)minePosition屬性,用來(lái)記錄雷點(diǎn)位置
隨機(jī)生成地雷比較簡(jiǎn)單,主要注意,生成的地雷點(diǎn)數(shù)在格盤個(gè)數(shù)范圍內(nèi),那么就可以寫出隨機(jī)生成的地雷了。界面組件已收集到橫排格子數(shù)、縱排格子數(shù)、雷數(shù),那么就能得到格子總數(shù),假設(shè)橫9縱9,10個(gè)雷,那么就是生成10個(gè)81以內(nèi)的隨機(jī)數(shù)(如果索引從0開始,即80以內(nèi))。
// 隨機(jī)獲取雷點(diǎn)位置 getMinePosition() { // 定義一個(gè)數(shù)組裝不重復(fù)的格點(diǎn) let mineArr = []; // 循環(huán)雷數(shù)生成不重復(fù)的雷點(diǎn) for (let n = 0; n < this.gameInfo[2]; n++) { const random = Math.floor(Math.random() * this.latticeNum); if (mineArr.indexOf(random) === -1) { mineArr.push(random); } else { n--; } } this.minePosition = mineArr; },
把地雷位置暴露出來(lái)
格子周圍的雷數(shù)
確認(rèn)了雷點(diǎn)位置,接下來(lái)要做的就是確認(rèn)每一個(gè)非雷點(diǎn)位置周圍的雷的數(shù)量,我們用對(duì)象來(lái)描述一個(gè)格子,這個(gè)對(duì)象主要包含以下幾個(gè)屬性
// 格子屬性 lattice: [{ index: 0, // 格子索引 mineNum: 0, // 周圍雷數(shù) isMine: false, // 是否是雷 isOpen: false, // 是否已經(jīng)被點(diǎn)開 isMark: false, // 是否被標(biāo)記 }],
這里我們主要用到index, isMine, mineNum屬性,這一步,主要是計(jì)算每個(gè)格子元素的mineNum值,依賴于以下兩個(gè)方法,個(gè)人覺得掃雷游戲最難理解的,最難捋清的邏輯,其中一個(gè)就是獲取非雷點(diǎn)位置周圍8個(gè)位置索引的方法getLatticeIndex(另一個(gè)是點(diǎn)擊空白格擴(kuò)散)
// 獲取格子周圍的雷數(shù), getMineNumAroundLattice(lattice, index) { // 先獲取格子周圍的有效索引 const latticeIndexArr = this.getLatticeIndex(index); // 循環(huán)索引,索引值在雷點(diǎn)數(shù)組中的,即為雷,當(dāng)前格子的雷點(diǎn)數(shù)加1 latticeIndexArr.forEach(i => { if (this.minePosition.indexOf(i) > -1) { lattice.mineNum ++; } }); }, // 獲取格子周圍的有效索引 getLatticeIndex(index) { // 存索引值的變量 let latticeIndexArr = []; // 當(dāng)前格子位于第幾行 const latticeRow = Math.ceil(index / this.rows); // 當(dāng)前格子位于第幾列(求余為0說(shuō)明是最右邊一列) const latticeCol = Math.ceil(index % this.rows) || this.rows; // 第一行沒有上一行,不需要計(jì)算減1的行值,最后一行沒有下一行,不需要計(jì)算加1的行值 for (let i = (latticeRow === 1 ? 0 : -1); i < (latticeRow === this.cols ? 1 : 2); i++) { // 第一列沒有左列,不需要計(jì)算減1的列值,最后一列沒有右列,不需要計(jì)算加1的列值 for (let j = (latticeCol === 1 ? 0 : -1); j < (latticeCol === this.rows ? 1 : 2); j++) { // 索引值 = (當(dāng)前行值 + (上一行【-1】/當(dāng)前行【0】/下一行【+1】) - 1【1是索引從0開始,所以需要減去】) * 每行格子數(shù) + 當(dāng)前列值 + (上一列【-1】/當(dāng)前列【0】/下一列【+1】) const latticeIndex = (latticeRow + i - 1) * this.rows + (latticeCol + j); latticeIndexArr.push(latticeIndex); } } return latticeIndexArr; },
有了這兩個(gè)方法,咱成功地獲取到了每個(gè)非雷點(diǎn)格子周圍的雷的數(shù)量,來(lái),展示出來(lái),這樣展示的好處是,我們一眼就可以看出算法是否正確
沒問題了,來(lái),接著往下走,格盤數(shù)據(jù)基本都設(shè)置好了,那我們接下來(lái)要做的就是,點(diǎn)開格子操作
點(diǎn)擊交互
這一步先做簡(jiǎn)單點(diǎn),有個(gè)明顯的區(qū)別就可以了,點(diǎn)雷我們先不管,先看點(diǎn)數(shù)字和空白的情況,首先得明確,到時(shí)候格子的可見屬性是全部要被隱藏的,點(diǎn)擊了才會(huì)顯示出來(lái),這就用到了我們上一步提到的isOpen屬性,默認(rèn)肯定全是不可見的,點(diǎn)擊之后,非雷翻開
點(diǎn)數(shù)字
點(diǎn)數(shù)字很簡(jiǎn)單,直接翻開,將isOpen屬性設(shè)置為true
來(lái)點(diǎn)不一樣的,isOpen === true 的時(shí)候字體變紅色,走你┏ (゜ω゜)=☞
@click.left="handleClickLattice(lattice[(col - 1) * rows + row - 1])" // 點(diǎn)了格子 handleClickLattice(lattice) { // 是數(shù)字 if (lattice.mineNum) { if (!lattice.isOpen && !lattice.isMark) { lattice.isOpen = true; } } },
點(diǎn)空白
第二個(gè)難點(diǎn)來(lái)咯,點(diǎn)空白格需要注意以下幾點(diǎn):1、空白格表示周圍8格都沒有雷 2、擴(kuò)散周圍8格,判斷雷數(shù),循環(huán)往復(fù) 3、遇邊界停止擴(kuò)散,遇數(shù)字停止擴(kuò)散
假設(shè)橫9縱9的格盤,第二排第三列格為空白格即第12格,那么點(diǎn)了該空白格之后,首先將其與周圍8格(2,3,4,11,12,13,20,21,22)一起,isOpen置為true,然后分別以周圍8格為中心,判斷該格子是數(shù)字,停止擴(kuò)散,是空白格,繼續(xù)擴(kuò)散
這一步寫完,
// 代碼把下半部分補(bǔ)齊 handleClickLattice(lattice) { ... else { // 是空白 const latticeIndexArr = this.getLatticeIndex(lattice.index); this.showWhiteAround(lattice, latticeIndexArr); } }, // 展示周圍的空白標(biāo)記,直至邊緣(格子邊緣或者數(shù)字) showWhiteAround(lattice, latticeIndexArr) { // 避免有重復(fù)的數(shù)據(jù)停不下來(lái),去個(gè)重 latticeIndexArr = [...new Set(latticeIndexArr)]; for (let i = 0; i < latticeIndexArr.length; i++) { const item = latticeIndexArr[i]; latticeIndexArr.splice(i, 1); i--; if (this.lattice[item].isOpen) { continue; } this.lattice[item].isOpen = true; if (!this.lattice[item].mineNum) { const arr = this.getLatticeIndex(this.lattice[item].index); this.showWhiteAround(this.lattice[item], latticeIndexArr.concat(arr)); } } },
基本明面上的掃雷步驟就已經(jīng)完成了,handleClickLattice方法再加一步判斷,如果是雷,游戲結(jié)束
右鍵標(biāo)記雷點(diǎn)
這個(gè)就很簡(jiǎn)單了,寫個(gè)右擊事件,修改一下格子的isMark和isOpen屬性,這一步的基本邏輯就是
右擊格子 格子本身已經(jīng)被打開 1)是:格子已經(jīng)被標(biāo)記為地雷? a. 是:取消標(biāo)記(isMark和isOpen取反),剩余地雷數(shù)+1 b. 否:說(shuō)明是右擊了已經(jīng)被點(diǎn)開的數(shù)字格,不做任何操作 2)否:isMark和isOpen取反,記錄該格子已經(jīng)被標(biāo)記為地雷,格子處于打開狀態(tài),剩余地雷數(shù)-1,判斷是否結(jié)束
// 右鍵確認(rèn)是雷點(diǎn) handleSureMinePoint(lattice) { if (!lattice.isOpen) { lattice.isMark = true; lattice.isOpen = true; this.minePosition.splice(this.minePosition.indexOf(lattice.index), 1); this.judgeIsOver(); } else { if (lattice.isMark) { lattice.isMark = false; lattice.isOpen = false; this.minePosition.push(lattice.index); } } },
游戲是否結(jié)束
游戲結(jié)束一共有三種情況:1、點(diǎn)到雷了,直接結(jié)束 2、雷被標(biāo)記完了(有可能失敗了,標(biāo)錯(cuò)了) 3、翻開的格子數(shù) + 雷數(shù) = 總格子數(shù)
完成功能
接下來(lái)要做的就是把格子屬性隱藏起來(lái),假裝不知道,再假裝鼠標(biāo)一點(diǎn),格子就翻過(guò)來(lái)了。這就用到之前提到的格子屬性isMark和isOpen,本身元素處于隱藏狀態(tài),當(dāng)被標(biāo)記或者被打開的時(shí)候設(shè)置相應(yīng)的屬性使其可見就行了,如此,便完成了掃雷的基本功能,有興趣的小朋友可以自己融合多種功能試一試
當(dāng)然,這只是其中一種實(shí)現(xiàn)方式,把所有的計(jì)算全部放在玩游戲之前了,愛動(dòng)腦筋的朋友們也可以想想,如果放在每一次點(diǎn)擊時(shí)做計(jì)算該如何組織代碼
阿望的源代碼中還集合了【重開一局】、【改變難度】、【游戲計(jì)時(shí)】等功能,樣式兼容手機(jī)和PC在線玩,在手機(jī)上玩的時(shí)候我在糾結(jié)手機(jī)如何模仿PC端的右鍵點(diǎn)擊標(biāo)雷操作,沒有好的想法,不想用雙擊,于是多加了一個(gè)狀態(tài),點(diǎn)擊頁(yè)面【標(biāo)記】按鈕,即表示標(biāo)記雷點(diǎn),再點(diǎn)擊一次表示還原,正常點(diǎn)開數(shù)字格,坐火車無(wú)聊的小朋友可以玩一玩喲
查看阿望的源碼: mineSweeping
在線試玩:mine-sweeping-online
希望各位看官不吝右上角賜個(gè)小星星哦,阿望這廂有禮啦,新年快樂啦 ★,° :.☆( ̄▽ ̄)/$: .°★ 。
總結(jié)
以上所述是小編給大家介紹的阿望教你用vue寫掃雷小游戲,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)億速云網(wǎng)站的支持!
如果你覺得本文對(duì)你有幫助,歡迎轉(zhuǎn)載,煩請(qǐng)注明出處,謝謝!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。