微信小程序?qū)崿F(xiàn)經(jīng)典window掃雷游戲
前言
打開手機(jī)游戲列表發(fā)現(xiàn)了一款經(jīng)典的掃雷游戲,在玩的過程中發(fā)現(xiàn)游戲邏輯應(yīng)該不難,想著是不是能自己寫寫這個(gè)游戲,后來用了1天實(shí)現(xiàn)了整體游戲開發(fā),于是有了這篇文章來總結(jié)整體的游戲開發(fā)思路。
一、掃雷游戲規(guī)則是什么?
1、游戲?yàn)樵?0*10或其它排序組合網(wǎng)格中找雷
2、網(wǎng)格中隱藏著一定數(shù)量的雷,點(diǎn)擊到雷即為輸
3、點(diǎn)擊無雷的網(wǎng)格會(huì)顯示其臨近8個(gè)方向上的總雷數(shù),若為0則臨近8個(gè)方向上的網(wǎng)格也會(huì)自動(dòng)顯示雷數(shù),以此類推,直到出現(xiàn)不為0的網(wǎng)格
4、長按網(wǎng)格可以標(biāo)記網(wǎng)格為雷
5、找出所有的雷即為勝利
二、開發(fā)前準(zhǔn)備
1.創(chuàng)建小程序項(xiàng)目
使用微信開發(fā)者工具創(chuàng)建一個(gè)小程序項(xiàng)目。推薦使用官方推薦模板(此游戲項(xiàng)目使用js來實(shí)現(xiàn))
2.開始開發(fā)
2.1.實(shí)現(xiàn)網(wǎng)格地圖
頁面初始數(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 來,后續(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> ? ? ? ?<!-- 周圍雷數(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, // 周圍雷數(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, // 周圍雷數(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 值,每生成一個(gè)先判斷值是否在 minePosition 數(shù)組存在,不存在就push到 minePosition 數(shù)組中去。最終結(jié)果如下:在 initGrid 方法中會(huì)根據(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個(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è),所以我們在做判斷是需先判斷其方向上是否有網(wǎng)格才行。
2.4.長按添加雷的標(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, ? }); },
我們在網(wǎng)格上設(shè)置 data-value ,這樣長按事件就能獲取對應(yīng)的 value 值,通過遍歷比較找到對應(yīng)的網(wǎng)格并對網(wǎng)格的 mineTag 屬性取反來達(dá)到長按標(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)格的周圍雷數(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è)游戲開發(fā)的整體思路講解,代碼量不多,總體js代碼只有2百多行,設(shè)計(jì)思路也比較簡單。對于在開發(fā)中的收獲,或許就是當(dāng)你玩著自己開發(fā)的游戲時(shí),作為程序員的快樂。
希望這篇文章對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
javascript相等運(yùn)算符與等同運(yùn)算符詳細(xì)介紹
不管是java、c++、php都有相等運(yùn)算符與等同運(yùn)算符,當(dāng)然javasript也不例外,下面介紹一下2013-11-11javascript實(shí)現(xiàn)劃詞標(biāo)記劃詞搜索功能修正版
javascript實(shí)現(xiàn)劃詞標(biāo)記劃詞搜索功能修正版...2006-12-12JS實(shí)現(xiàn)的數(shù)字格式化功能示例
這篇文章主要介紹了JS實(shí)現(xiàn)的數(shù)字格式化功能,結(jié)合實(shí)例形式分析了javascript針對數(shù)字與字符的相關(guān)運(yùn)算處理技巧,需要的朋友可以參考下2017-02-02Javascript簡單實(shí)現(xiàn)面向?qū)ο缶幊汤^承實(shí)例代碼
這篇文章主要介紹了Javascript簡單實(shí)現(xiàn)面向?qū)ο缶幊汤^承實(shí)例代碼,簡單分析了面向?qū)ο蟪绦蛟O(shè)計(jì)的特征與繼承的具體實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11在ASP.NET MVC項(xiàng)目中使用RequireJS庫的用法示例
這篇文章主要介紹了在ASP.NET MVC項(xiàng)目中使用RequireJS的用法示例,文中主要講解了網(wǎng)站項(xiàng)目的一些基本目錄結(jié)構(gòu)思想,并給出了一個(gè)半自動(dòng)壓縮的例子,的朋友可以參考下2016-02-02js記錄點(diǎn)擊某個(gè)按鈕的次數(shù)-刷新次數(shù)為初始狀態(tài)的實(shí)例
下面小編就為大家?guī)硪黄猨s記錄點(diǎn)擊某個(gè)按鈕的次數(shù)-刷新次數(shù)為初始狀態(tài)的實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02js扁平數(shù)組和樹結(jié)構(gòu)相互轉(zhuǎn)換處理方法
這篇文章主要給大家介紹了關(guān)于js扁平數(shù)組和樹結(jié)構(gòu)相互轉(zhuǎn)換處理方法的相關(guān)資料,之前面試有遇到過這個(gè)問題,面試官問如何把一個(gè)數(shù)組數(shù)據(jù)扁平,然后轉(zhuǎn)化為Tree結(jié)構(gòu)數(shù)據(jù),工作中剛好也用到了,所以總結(jié)下,需要的朋友可以參考下2023-07-07JavaScript鏈?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