微信小程序?qū)崿F(xiàn)經(jīng)典window掃雷游戲
前言
打開(kāi)手機(jī)游戲列表發(fā)現(xiàn)了一款經(jīng)典的掃雷游戲,在玩的過(guò)程中發(fā)現(xiàn)游戲邏輯應(yīng)該不難,想著是不是能自己寫(xiě)寫(xiě)這個(gè)游戲,后來(lái)用了1天實(shí)現(xiàn)了整體游戲開(kāi)發(fā),于是有了這篇文章來(lái)總結(jié)整體的游戲開(kāi)發(fā)思路。
一、掃雷游戲規(guī)則是什么?
1、游戲?yàn)樵?0*10或其它排序組合網(wǎng)格中找雷
2、網(wǎng)格中隱藏著一定數(shù)量的雷,點(diǎn)擊到雷即為輸
3、點(diǎn)擊無(wú)雷的網(wǎng)格會(huì)顯示其臨近8個(gè)方向上的總雷數(shù),若為0則臨近8個(gè)方向上的網(wǎng)格也會(huì)自動(dòng)顯示雷數(shù),以此類(lèi)推,直到出現(xiàn)不為0的網(wǎng)格
4、長(zhǎng)按網(wǎng)格可以標(biāo)記網(wǎng)格為雷
5、找出所有的雷即為勝利
二、開(kāi)發(fā)前準(zhǔn)備
1.創(chuàng)建小程序項(xiàng)目
使用微信開(kāi)發(fā)者工具創(chuàng)建一個(gè)小程序項(xiàng)目。推薦使用官方推薦模板(此游戲項(xiàng)目使用js來(lái)實(shí)現(xiàn))

2.開(kāi)始開(kāi)發(fā)
2.1.實(shí)現(xiàn)網(wǎng)格地圖
頁(yè)面初始數(shù)據(jù):
groundSize: [16, 16], // 地圖大小
minePosition: [], // 保存雷的位置
secondInterval: 0, // 時(shí)間定時(shí)器
data: {
? second: 0, // 游戲時(shí)間
? mineCount: 24, // 雷總數(shù)
? markMineCount: 0, // 已標(biāo)記雷數(shù)
? renderGridList: [], // 網(wǎng)格列表
},此地圖為16*16的地圖,行列大小根據(jù) groundSize 來(lái),后續(xù)可以設(shè)置不同的地圖大小。

地圖wxml代碼(具體樣式自行規(guī)劃):
<view class="play-ground">
? <view class="play-ground__row" wx:for="{{renderGridList}}" wx:for-item="row" wx:key="index">
? ? ?<view class="play-ground__col {{col.showNum && col.mineNum === 0 ? 'play-ground__col--empty' : ''}}" wx:for="{{row}}" wx:for-item="col" wx:key="index" data-value="{{col.value}}" bindlongpress="setMineTag" bindtap="clearBox">
? ? ? ?<!-- 標(biāo)記雷圖標(biāo) -->
? ? ? ?<image wx:if="{{col.mineTag}}" class="play-ground__col-boom" src="../../static/image/mine/mine.png"></image>
? ? ? ?<!-- 點(diǎn)擊到雷圖標(biāo) -->
? ? ? ?<image wx:if="{{!col.mineTag && col.isBoom && col.isMine}}" class="play-ground__col-boom" src="../../static/image/mine/boom.png"></image>
? ? ? ?<!-- 周?chē)讛?shù) -->
? ? ? ?<text wx:if="{{col.showNum && col.mineNum}}" class="play-ground__col-num play-ground__col-num--{{col.mineNum}}">{{col.mineNum}}</text>
? ? ?</view>
? ?</view>
</view>renderGridList 渲染列表結(jié)構(gòu)(二維數(shù)組):
[
? [
? ? {
? ? ? isMine: false, // 是否為雷
? ? ? mineTag: false, // 手動(dòng)添加是否是雷的標(biāo)識(shí)
? ? ? isBoom: false, // 是否點(diǎn)擊到了雷
? ? ? mineNum: 0, // 周?chē)讛?shù)
? ? ? showNum: false, // 是否顯示雷數(shù)
? ? ? value: 0, // 等同于id
? ? ? position: [0, 0], // 標(biāo)志在第幾行第幾列
? ? },
? ? ...
? ],
? ...
]初始化網(wǎng)格方法:
initGrid() {
? const gridList = [];
? ?// 當(dāng)前遍歷gridList到第幾個(gè)元素
? ?let currentNum = 0;
? ?// 當(dāng)前遍歷minePosition到第幾個(gè)元素
? ?let currentMineIndex = 0;
? ?for (let i = 0; i < this.groundSize[0]; i++) {
? ? ?const row = [];
? ? ?for (let j = 0; j < this.groundSize[1]; j++) {
? ? ? ?let isMine = false;
? ? ? ?// 判斷是否是雷
? ? ? ?if (currentNum === this.minePosition[currentMineIndex]) {
? ? ? ? ?isMine = true;
? ? ? ? ?currentMineIndex += 1;
? ? ? ?}
? ? ? ?row.push({
? ? ? ? ?isMine,
? ? ? ? ?mineTag: false, // 手動(dòng)添加是否是雷的標(biāo)識(shí)
? ? ? ? ?isBoom: false, // 是否點(diǎn)擊到了雷
? ? ? ? ?mineNum: 0, // 周?chē)讛?shù)
? ? ? ? ?showNum: false, // 是否顯示雷數(shù)
? ? ? ? ?value: currentNum,
? ? ? ? ?position: [i, j],
? ? ? ?});
? ? ? ?currentNum += 1;
? ? ?}
? ? ?gridList.push(row);
? ?}
? ?this.setData({
? ? ?renderGridList: this.generateMineNum(gridList),
? ?});
?}2.2.生成雷
generateMine() {
? ?this.minePosition = [];
? ?// 已設(shè)置的雷總數(shù)
? ?let hadSetCount = 0;
? ?// 隨機(jī)最大值根據(jù)網(wǎng)格大小來(lái)
? ?const groundCount = this.groundSize[0] * this.groundSize[1];
? ?if (this.data.mineCount >= groundCount) {
? ? ? return;
? ? }
? ? while (hadSetCount < this.data.mineCount) {
? ? ? // 生成隨機(jī)數(shù)
? ? ? const randomNum = ~~(Math.random() * groundCount);
? ? ? // 判斷隨機(jī)數(shù)是否存在
? ? ? if (!this.minePosition.includes(randomNum)) {
? ? ? ? this.minePosition.push(randomNum);
? ? ? ? hadSetCount += 1;
? ? ? }
? ? }
? ? // 從小到大排序
? ? this.minePosition.sort((a, b) => (a > b ? 1 : -1));
? }根據(jù)頁(yè)面初始數(shù)據(jù)中的 mineCount 來(lái)指定生成的雷數(shù),通過(guò)隨機(jī)值函數(shù)來(lái)生產(chǎn)隨機(jī)的雷的 value 值,每生成一個(gè)先判斷值是否在 minePosition 數(shù)組存在,不存在就push到 minePosition 數(shù)組中去。最終結(jié)果如下:在 initGrid 方法中會(huì)根據(jù) minePosition 對(duì)應(yīng)的值和網(wǎng)格的value值作比較,相等即為雷。
minePosition (24) [10, 17, 25, 28, 34, 35, 48, 73, 106, 132, 152, 187, 196, 197, 199, 203, 210, 217, 220, 226, 234, 238, 240, 245]
2.3.生成雷數(shù)
generateMineNum(gridList) {
?gridList.forEach(row => {
? ? row.forEach(col => {
? ? ? // 是雷則跳過(guò)
? ? ? if (col.isMine) {
? ? ? ? return;
? ? ? }
? ? ? col.mineNum = this.checkMine(gridList, col.position);
? ? });
? });
? return gridList;
},
checkMine(gridList, position) {
? const [i, j] = position;
? let mineNum = 0;
? // 判斷8個(gè)方位是否有雷
? // 上 [i - 1][j]
? if (gridList[i - 1] && gridList[i - 1][j].isMine) {
? ? mineNum += 1;
? }
? // 右上 [i - 1][j + 1]
? if (gridList[i - 1] && gridList[i - 1][j + 1] && gridList[i - 1][j + 1].isMine) {
? ? mineNum += 1;
? }
? // 右 [i][j + 1]
? if (gridList[i][j + 1] && gridList[i][j + 1].isMine) {
? ? mineNum += 1;
? }
? // 右下 [i + 1][j + 1]
? if (gridList[i + 1] && gridList[i + 1][j + 1] && gridList[i + 1][j + 1].isMine) {
? ? mineNum += 1;
? }
? // 下 [i + 1][j]
? if (gridList[i + 1] && gridList[i + 1][j].isMine) {
? ? mineNum += 1;
? }
? // 左下 [i + 1][j - 1]
? if (gridList[i + 1] && gridList[i + 1][j - 1] && gridList[i + 1][j - 1].isMine) {
? ? mineNum += 1;
? }
? // 左 [i][j - 1]
? if (gridList[i][j - 1] && gridList[i][j - 1].isMine) {
? ? mineNum += 1;
? }
? // 左上 [i - 1][j - 1]
? if (gridList[i - 1] && gridList[i - 1][j - 1] && gridList[i - 1][j - 1].isMine) {
? ? mineNum += 1;
? }
? return mineNum;
}判斷8個(gè)方向上是否有雷時(shí)我們需要注意那些在邊角的網(wǎng)格,這些網(wǎng)格方向少于8個(gè),所以我們?cè)谧雠袛嗍切柘扰袛嗥浞较蛏鲜欠裼芯W(wǎng)格才行。
2.4.長(zhǎng)按添加雷的標(biāo)識(shí)
setMineTag(e) {
? const {
? ? currentTarget: {
? ? ? dataset: { value },
? ? },
? } = e;
? const renderGridList = this.data.renderGridList;
? let markMineCount = 0;
? for (const row of renderGridList) {
? ? for (const col of row) {
? ? ? if (col.value === value) {
? ? ? ? col.mineTag = !col.mineTag;
? ? ? }
? ? ? if (col.mineTag) {
? ? ? ? markMineCount += 1;
? ? ? }
? ? }
? }
? this.setData({
? ? renderGridList,
? ? markMineCount,
? });
},我們?cè)诰W(wǎng)格上設(shè)置 data-value ,這樣長(zhǎng)按事件就能獲取對(duì)應(yīng)的 value 值,通過(guò)遍歷比較找到對(duì)應(yīng)的網(wǎng)格并對(duì)網(wǎng)格的 mineTag 屬性取反來(lái)達(dá)到長(zhǎng)按標(biāo)記或取消的功能,同時(shí) mineTag 為真時(shí)需記錄下標(biāo)記數(shù)量。
2.5.點(diǎn)擊網(wǎng)格事件
clearBox(e) {
? const {
? ? currentTarget: {
? ? ? dataset: { value },
? ? },
? } = e;
? let renderGridList = this.data.renderGridList;
? out: for (const row of renderGridList) {
? ? for (const col of row) {
? ? ? if (col.value === value) {
? ? ? ? // 判斷是否是雷,為雷則輸
? ? ? ? col.isBoom = col.isMine;
? ? ? ? if (col.isBoom) {
? ? ? ? ? wx.showToast({
? ? ? ? ? ? icon: 'error',
? ? ? ? ? ? title: '踩到雷了',
? ? ? ? ? });
? ? ? ? ? break out;
? ? ? ? }
? ? ? ? renderGridList = this.loopClearBox(renderGridList, col);
? ? ? ? break out;
? ? ? }
? ? }
? }
? this.setData({
? ? renderGridList,
? });
},
loopClearBox(gridList, col) {
? if (col.isMine || col.showNum) {
? ? return gridList;
? }
? col.showNum = true;
? if (col.mineNum) {
? ? return gridList;
? }
? // 判斷相鄰的4個(gè)方位是否為空并遞歸遍歷
? const [i, j] = col.position;
? if (gridList[i - 1]) {
? ?? ?// 上
? ? col = gridList[i - 1][j];
? ? if (col) {
? ? ? if (!col.mineNum) {
? ? ? ? gridList = this.loopClearBox(gridList, col);
? ? ? } else {
? ? ? ? col.showNum = !col.isMine;
? ? ? }
? ? }
? }
? if (gridList[i + 1]) {
? ?? ?// 下
? ? col = gridList[i + 1][j];
? ? if (col) {
? ? ? if (!col.mineNum) {
? ? ? ? gridList = this.loopClearBox(gridList, col);
? ? ? } else {
? ? ? ? col.showNum = !col.isMine;
? ? ? }
? ? }
? }
? // 左
? col = gridList[i][j - 1];
? if (col) {
? ? if (!col.mineNum) {
? ? ? gridList = this.loopClearBox(gridList, col);
? ? } else {
? ? ? col.showNum = !col.isMine;
? ? }
? }
? // 右
? col = gridList[i][j + 1];
? if (col) {
? ? if (!col.mineNum) {
? ? ? gridList = this.loopClearBox(gridList, col);
? ? } else {
? ? ? col.showNum = !col.isMine;
? ? }
? }
? return gridList;
}loopClearBox 是遞歸遍歷方法,當(dāng)點(diǎn)擊的網(wǎng)格的周?chē)讛?shù)為空時(shí)我們需要遞歸其上下左右方向的網(wǎng)格。效果如圖所示:

遞歸只有遇到有雷數(shù)的網(wǎng)格才會(huì)停下。
2.6.輸贏判斷
checkWin() {
? // 當(dāng)標(biāo)記數(shù)小于總雷數(shù)時(shí)才判斷輸贏
? if (this.data.mineCount >= this.data.markMineCount) {
? ? // 遍歷網(wǎng)格判斷標(biāo)記的雷是否正確
? ? for (let row in this.data.renderGridList) {
? ? ? for (let col of row) {
? ? ? ? if (col.isMine !== col.mineTag) {
? ? ? ? ? return false;
? ? ? ? }
? ? ? }
? ? }
? ? return true;
? }
? return false;
}輸贏判斷是在點(diǎn)擊網(wǎng)格事件中執(zhí)行的,當(dāng)返回值為true時(shí)即為通關(guān)。
總結(jié)
以上就是整個(gè)游戲開(kāi)發(fā)的整體思路講解,代碼量不多,總體js代碼只有2百多行,設(shè)計(jì)思路也比較簡(jiǎn)單。對(duì)于在開(kāi)發(fā)中的收獲,或許就是當(dāng)你玩著自己開(kāi)發(fā)的游戲時(shí),作為程序員的快樂(lè)。
希望這篇文章對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
javascript相等運(yùn)算符與等同運(yùn)算符詳細(xì)介紹
不管是java、c++、php都有相等運(yùn)算符與等同運(yùn)算符,當(dāng)然javasript也不例外,下面介紹一下2013-11-11
javascript實(shí)現(xiàn)劃詞標(biāo)記劃詞搜索功能修正版
javascript實(shí)現(xiàn)劃詞標(biāo)記劃詞搜索功能修正版...2006-12-12
JS實(shí)現(xiàn)的數(shù)字格式化功能示例
這篇文章主要介紹了JS實(shí)現(xiàn)的數(shù)字格式化功能,結(jié)合實(shí)例形式分析了javascript針對(duì)數(shù)字與字符的相關(guān)運(yùn)算處理技巧,需要的朋友可以參考下2017-02-02
Javascript簡(jiǎn)單實(shí)現(xiàn)面向?qū)ο缶幊汤^承實(shí)例代碼
這篇文章主要介紹了Javascript簡(jiǎn)單實(shí)現(xiàn)面向?qū)ο缶幊汤^承實(shí)例代碼,簡(jiǎn)單分析了面向?qū)ο蟪绦蛟O(shè)計(jì)的特征與繼承的具體實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11
在ASP.NET MVC項(xiàng)目中使用RequireJS庫(kù)的用法示例
這篇文章主要介紹了在ASP.NET MVC項(xiàng)目中使用RequireJS的用法示例,文中主要講解了網(wǎng)站項(xiàng)目的一些基本目錄結(jié)構(gòu)思想,并給出了一個(gè)半自動(dòng)壓縮的例子,的朋友可以參考下2016-02-02
js記錄點(diǎn)擊某個(gè)按鈕的次數(shù)-刷新次數(shù)為初始狀態(tài)的實(shí)例
下面小編就為大家?guī)?lái)一篇js記錄點(diǎn)擊某個(gè)按鈕的次數(shù)-刷新次數(shù)為初始狀態(tài)的實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02
js扁平數(shù)組和樹(shù)結(jié)構(gòu)相互轉(zhuǎn)換處理方法
這篇文章主要給大家介紹了關(guān)于js扁平數(shù)組和樹(shù)結(jié)構(gòu)相互轉(zhuǎn)換處理方法的相關(guān)資料,之前面試有遇到過(guò)這個(gè)問(wèn)題,面試官問(wèn)如何把一個(gè)數(shù)組數(shù)據(jù)扁平,然后轉(zhuǎn)化為T(mén)ree結(jié)構(gòu)數(shù)據(jù),工作中剛好也用到了,所以總結(jié)下,需要的朋友可以參考下2023-07-07
JavaScript鏈?zhǔn)秸{(diào)用實(shí)例淺析
這篇文章主要介紹了JavaScript鏈?zhǔn)秸{(diào)用,結(jié)合實(shí)例形式分析了javascript鏈?zhǔn)秸{(diào)用的相關(guān)原理、實(shí)現(xiàn)方法及操作注意事項(xiàng),需要的朋友可以參考下2018-12-12

