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

2.開始開發(fā)
2.1.實現(xiàn)網(wǎng)格地圖
頁面初始數(shù)據(jù):
groundSize: [16, 16], // 地圖大小
minePosition: [], // 保存雷的位置
secondInterval: 0, // 時間定時器
data: {
? second: 0, // 游戲時間
? mineCount: 24, // 雷總數(shù)
? markMineCount: 0, // 已標(biāo)記雷數(shù)
? renderGridList: [], // 網(wǎng)格列表
},此地圖為16*16的地圖,行列大小根據(jù) groundSize 來,后續(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>
? ? ? ?<!-- 點擊到雷圖標(biāo) -->
? ? ? ?<image wx:if="{{!col.mineTag && col.isBoom && col.isMine}}" class="play-ground__col-boom" src="../../static/image/mine/boom.png"></image>
? ? ? ?<!-- 周圍雷數(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, // 手動添加是否是雷的標(biāo)識
? ? ? isBoom: false, // 是否點擊到了雷
? ? ? mineNum: 0, // 周圍雷數(shù)
? ? ? showNum: false, // 是否顯示雷數(shù)
? ? ? value: 0, // 等同于id
? ? ? position: [0, 0], // 標(biāo)志在第幾行第幾列
? ? },
? ? ...
? ],
? ...
]初始化網(wǎng)格方法:
initGrid() {
? const gridList = [];
? ?// 當(dāng)前遍歷gridList到第幾個元素
? ?let currentNum = 0;
? ?// 當(dāng)前遍歷minePosition到第幾個元素
? ?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, // 手動添加是否是雷的標(biāo)識
? ? ? ? ?isBoom: false, // 是否點擊到了雷
? ? ? ? ?mineNum: 0, // 周圍雷數(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)格大小來
? ?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ù)頁面初始數(shù)據(jù)中的 mineCount 來指定生成的雷數(shù),通過隨機(jī)值函數(shù)來生產(chǎn)隨機(jī)的雷的 value 值,每生成一個先判斷值是否在 minePosition 數(shù)組存在,不存在就push到 minePosition 數(shù)組中去。最終結(jié)果如下:在 initGrid 方法中會根據(jù) minePosition 對應(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 => {
? ? ? // 是雷則跳過
? ? ? if (col.isMine) {
? ? ? ? return;
? ? ? }
? ? ? col.mineNum = this.checkMine(gridList, col.position);
? ? });
? });
? return gridList;
},
checkMine(gridList, position) {
? const [i, j] = position;
? let mineNum = 0;
? // 判斷8個方位是否有雷
? // 上 [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個方向上是否有雷時我們需要注意那些在邊角的網(wǎng)格,這些網(wǎng)格方向少于8個,所以我們在做判斷是需先判斷其方向上是否有網(wǎng)格才行。
2.4.長按添加雷的標(biāo)識
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,
? });
},我們在網(wǎng)格上設(shè)置 data-value ,這樣長按事件就能獲取對應(yīng)的 value 值,通過遍歷比較找到對應(yīng)的網(wǎng)格并對網(wǎng)格的 mineTag 屬性取反來達(dá)到長按標(biāo)記或取消的功能,同時 mineTag 為真時需記錄下標(biāo)記數(shù)量。
2.5.點擊網(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個方位是否為空并遞歸遍歷
? 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)點擊的網(wǎng)格的周圍雷數(shù)為空時我們需要遞歸其上下左右方向的網(wǎng)格。效果如圖所示:

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

