話說阿望還在大學時,某一天寢室忽然停網了,因而和室友兩人不約而同地打開了掃雷,比相同難度下誰更快找出所有的雷,玩得不亦樂乎,就這樣,掃雷伴咱們度過了斷網的一週,是整整一週啊,不用上課的那種,可想而知咱們是有多無聊了。html
這兩天臨近過年了,該放假的已經放假了,不應放假的已經請假了,公交不打擠了,地鐵口不堵了,公司也去了大半部分的人了,就留阿望這種不得不留下來值班的人守着空蕩蕩的辦公室了,因而,多年前那種無所事事的斷網心態再次襲來,因而,想玩掃雷的心再次蹦躂出來,因而,點開了電腦的附件,因而,發現個人電腦上並無掃雷【手動微笑.jpg】。vue
怎麼想起要寫掃雷的阿望就很少廢話了,直接開幹。git
想當年阿望仍是在大學才參透掃雷的遊戲規則的,初高中的時候都是靠運氣瞎點,從沒贏過,固然了,境界提高之後,追求的天然就不是贏了,而是速度。github
來,看規則:算法
大白話遊戲規則,計算邏輯以下:vuex
好,這下規則咱們瞭解了,接下來,摩拳擦掌,開始寫代碼咯,本次代碼用vue來寫,沒有緣由,習慣了而已。json
看着遊戲規則,咱們先來理一理,要如何完成這個功能,咱們以最簡單的,最能令人理解的步驟,來完成此次功能。數組
避免vue新手不知如何下手,那就從建項目開始吧,環境安裝我就不講了,腳手架也是要有的sass
第一步,初始化lovesweeping工程(阿望不喜歡地雷,喜歡小桃心)dom
項目很小,不須要路由,不須要vuex,便可完成,只帶了sass,連eslint均可以不要,看官們能夠根據本身的喜愛建項目
項目生成以後,helloworld就能夠刪掉了,它的存在並無什麼意義
這一步主要是構建工程結構,簡單畫一下主要的幾個文件
- src - components - SelectLevel.vue [新增,難度選擇組件] - LoveSweeping.vue [新增,遊戲界面組件] - App.vue [父組件,負責組件間的切換和某些數據傳遞] - main.js - package.json
好,初步認識了項目結構之後,咱把該新建的建好,能夠不加東西,不報錯就行
而後就是把組件間的切換代碼寫好,再來一步步的填充代碼
這一步很簡單,首先畫好難度選擇的頁面,難度能夠本身設置,你以爲合理就行,我這裏的數據格式是這樣的,用一個對象表示一個難度等級,對象中包含了難度描述,以及難度設置,設置中包含了格子橫排數,格子縱排數,雷數
// 難度 level: [ { text: '青銅', // 難度描述 value: [9, 9, 10] // 格子橫排數,格子縱排數,雷數 }, { text: '黃金', value: [12, 9, 20] ]
而後模板中直接渲染列表,這樣作的好處是,想要增長難度直接在數組中添加數據便可
<li v-for="(item, index) in level" :key="index" @click="handleChoseLevel(item.value)">{{ item.text }}</li>
該組件中只有一個方法:選擇難度以後,跳轉到遊戲主界面上去,由於項目沒有用路由,直接使用組件間的切換,因此,這個方法只負責告訴父組件,我已經選擇好難度了,能夠開始遊戲了
// 選擇難度 handleChoseLevel(level) { this.$emit("chose-level", level); }
代碼以下:
界面長這樣,固然,你要以爲難看本身換個樣式也行
經過遊戲難度選擇來決定遊戲格盤的大小,組件間已經過App將遊戲難度傳至界面組件中,咱們用props把數據接收到,消化成本身的數據,畫格盤須要的數據有:橫排格子數,縱排格子數
畫格子:咱們將格子的索引暴露出來,後續能夠幫助咱們試錯。整個格局有兩種方式來表示格盤,座標式和索引式,好比橫9縱9的格子,[0, 0]表明第1個格子,[2, 3]表明第三行第四列也就是第20個格子。這次使用索引式來標誌格盤
<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>{{(col - 1) * rows + row - 1}}</span> </span> </div>
首先data中添加一個minePosition屬性,用來記錄雷點位置
隨機生成地雷比較簡單,主要注意,生成的地雷點數在格盤個數範圍內,那麼就能夠寫出隨機生成的地雷了。界面組件已收集到橫排格子數、縱排格子數、雷數,那麼就能獲得格子總數,假設橫9縱9,10個雷,那麼就是生成10個81之內的隨機數(若是索引從0開始,即80之內)。
// 隨機獲取雷點位置 getMinePosition() { // 定義一個數組裝不重複的格點 let mineArr = []; // 循環雷數生成不重複的雷點 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; },
把地雷位置暴露出來
確認了雷點位置,接下來要作的就是確認每個非雷點位置周圍的雷的數量,咱們用對象來描述一個格子,這個對象主要包含如下幾個屬性
// 格子屬性 lattice: [{ index: 0, // 格子索引 mineNum: 0, // 周圍雷數 isMine: false, // 是不是雷 isOpen: false, // 是否已經被點開 isMark: false, // 是否被標記 }],
這裏咱們主要用到index, isMine, mineNum屬性,這一步,主要是計算每一個格子元素的mineNum值,依賴於如下兩個方法,我的以爲掃雷遊戲最難理解的,最難捋清的邏輯,其中一個就是獲取非雷點位置周圍8個位置索引的方法getLatticeIndex(另外一個是點擊空白格擴散)
// 獲取格子周圍的雷數, getMineNumAroundLattice(lattice, index) { // 先獲取格子周圍的有效索引 const latticeIndexArr = this.getLatticeIndex(index); // 循環索引,索引值在雷點數組中的,即爲雷,當前格子的雷點數加1 latticeIndexArr.forEach(i => { if (this.minePosition.indexOf(i) > -1) { lattice.mineNum ++; } }); }, // 獲取格子周圍的有效索引 getLatticeIndex(index) { // 存索引值的變量 let latticeIndexArr = []; // 當前格子位於第幾行 const latticeRow = Math.ceil(index / this.rows); // 當前格子位於第幾列(求餘爲0說明是最右邊一列) const latticeCol = Math.ceil(index % this.rows) || this.rows; // 第一行沒有上一行,不須要計算減1的行值,最後一行沒有下一行,不須要計算加1的行值 for (let i = (latticeRow === 1 ? 0 : -1); i < (latticeRow === this.cols ? 1 : 2); i++) { // 第一列沒有左列,不須要計算減1的列值,最後一列沒有右列,不須要計算加1的列值 for (let j = (latticeCol === 1 ? 0 : -1); j < (latticeCol === this.rows ? 1 : 2); j++) { // 索引值 = (當前行值 + (上一行【-1】/當前行【0】/下一行【+1】) - 1【1是索引從0開始,因此須要減去】) * 每行格子數 + 當前列值 + (上一列【-1】/當前列【0】/下一列【+1】) const latticeIndex = (latticeRow + i - 1) * this.rows + (latticeCol + j); latticeIndexArr.push(latticeIndex); } } return latticeIndexArr; },
有了這兩個方法,咱成功地獲取到了每一個非雷點格子周圍的雷的數量,來,展現出來,這樣展現的好處是,咱們一眼就能夠看出算法是否正確
沒問題了,來,接着往下走,格盤數據基本都設置好了,那咱們接下來要作的就是,點開格子操做
這一步先作簡單點,有個明顯的區別就能夠了,點雷咱們先無論,先看點數字和空白的狀況,首先得明確,到時候格子的可見屬性是所有要被隱藏的,點擊了纔會顯示出來,這就用到了咱們上一步提到的isOpen屬性,默認確定全是不可見的,點擊以後,非雷翻開
點數字
點數字很簡單,直接翻開,將isOpen屬性設置爲true
來點不同的,isOpen === true 的時候字體變紅色,走你┏ (゜ω゜)=☞
@click.left="handleClickLattice(lattice[(col - 1) * rows + row - 1])"
// 點了格子 handleClickLattice(lattice) { // 是數字 if (lattice.mineNum) { if (!lattice.isOpen && !lattice.isMark) { lattice.isOpen = true; } } },
點空白
第二個難點來咯,點空白格須要注意如下幾點:一、空白格表示周圍8格都沒有雷 二、擴散周圍8格,判斷雷數,循環往復 三、遇邊界中止擴散,遇數字中止擴散
假設橫9縱9的格盤,第二排第三列格爲空白格即第12格,那麼點了該空白格以後,首先將其與周圍8格(2,3,4,11,12,13,20,21,22)一塊兒,isOpen置爲true,而後分別以周圍8格爲中心,判斷該格子是數字,中止擴散,是空白格,繼續擴散
// 代碼把下半部分補齊 handleClickLattice(lattice) { ... else { // 是空白 const latticeIndexArr = this.getLatticeIndex(lattice.index); this.showWhiteAround(lattice, latticeIndexArr); } }, // 展現周圍的空白標記,直至邊緣(格子邊緣或者數字) showWhiteAround(lattice, latticeIndexArr) { // 避免有重複的數據停不下來,去個重 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)); } } },
這一步寫完,基本明面上的掃雷步驟就已經完成了,handleClickLattice方法再加一步判斷,若是是雷,遊戲結束
這個就很簡單了,寫個右擊事件,修改一下格子的isMark和isOpen屬性,這一步的基本邏輯就是
// 右鍵確認是雷點 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); } } },
遊戲結束一共有三種狀況:一、點到雷了,直接結束 二、雷被標記完了(有可能失敗了,標錯了) 三、翻開的格子數 + 雷數 = 總格子數
接下來要作的就是把格子屬性隱藏起來,僞裝不知道,再僞裝鼠標一點,格子就翻過來了。這就用到以前提到的格子屬性isMark和isOpen,自己元素處於隱藏狀態,當被標記或者被打開的時候設置相應的屬性使其可見就好了,如此,便完成了掃雷的基本功能,有興趣的小朋友能夠本身融合多種功能試一試
固然,這只是其中一種實現方式,把全部的計算所有放在玩遊戲以前了,愛動腦筋的朋友們也能夠想一想,若是放在每一次點擊時作計算該如何組織代碼
阿望的源代碼中還集合了【重開一局】、【改變難度】、【遊戲計時】等功能,樣式兼容手機和PC在線玩,在手機上玩的時候我在糾結手機如何模仿PC端的右鍵點擊標雷操做,沒有好的想法,不想用雙擊,因而多加了一個狀態,點擊頁面【標記】按鈕,即表示標記雷點,再點擊一次表示還原,正常點開數字格,坐火車無聊的小朋友能夠玩一玩喲
查看阿望的源碼:mineSweeping
在線試玩:mine-sweeping-online
但願各位看官不吝右上角賜個小星星哦,阿望這廂有禮啦,新年快樂啦★,°:.☆( ̄▽ ̄)/$:.°★ 。