新聞中心
接上篇教程:??H5小游戲開發(fā)教程之頁(yè)面基礎(chǔ)布局的開發(fā)??

按需網(wǎng)站策劃可以根據(jù)自己的需求進(jìn)行定制,成都網(wǎng)站制作、網(wǎng)站設(shè)計(jì)構(gòu)思過(guò)程中功能建設(shè)理應(yīng)排到主要部位公司成都網(wǎng)站制作、網(wǎng)站設(shè)計(jì)的運(yùn)用實(shí)際效果公司網(wǎng)站制作網(wǎng)站建立與制做的實(shí)際意義
很抱歉,讓大家久等了,從上周開始,工作很忙,一直沒(méi)時(shí)間寫,在這期間,我也在思考是否有更好或更簡(jiǎn)單的實(shí)現(xiàn)方案,在不同的設(shè)備上都能有不錯(cuò)的體驗(yàn);通過(guò)這篇教程,我為大家?guī)?lái)一個(gè)非常簡(jiǎn)單的掃雷游戲?qū)崿F(xiàn)方案;原本打算用兩篇文章的,由于過(guò)于簡(jiǎn)單,就用一篇文章搞定了。
我們先欣賞下本篇文章實(shí)現(xiàn)的游戲界面:
掃雷游戲界面
掃雷游戲界面
我想,Windows的掃雷游戲大家應(yīng)該都玩過(guò)吧?其實(shí),這個(gè)游戲是有成功訣竅的,它考察了你思考問(wèn)題的能力;如果1個(gè)格子的數(shù)值是1,那么它的周圍8個(gè)方向有且只有一個(gè)雷;同理,格子數(shù)值是2,它的周圍8個(gè)方向有且只有2個(gè)雷;由于1個(gè)格子最多有8個(gè)相鄰格子,所以1個(gè)格子周圍最多包含8個(gè)雷。
現(xiàn)在,我們正式開始。首先,我們?cè)趕rc根目錄創(chuàng)建一個(gè)文件:shared.js文件,這個(gè)文件用于定義所有游戲公用的變量及函數(shù);我們?cè)谠撐募卸x一個(gè)genArr函數(shù);該函數(shù)非常簡(jiǎn)單,用于創(chuàng)建一個(gè)指定長(zhǎng)度的數(shù)組并用指定的值填充;在我們的游戲教程中,會(huì)大量使用該函數(shù)生成用于遍歷的數(shù)組。
export const genArr = (len, v) => Array(len).fill(v)
然后,在src/components/mine文件夾下創(chuàng)建一個(gè)文件game.js。我們首先用JS文檔注釋聲明2個(gè)類型,并引入一些我們將要用到的函數(shù);通過(guò)語(yǔ)義,大家應(yīng)該能明白這2個(gè)類型中字段的含義吧?GameOptions類型中的rows是行數(shù),cols是列數(shù),mineCount是雷的數(shù)量;Block類型的num是格子的值,open是打開標(biāo)識(shí),flag是插旗標(biāo)識(shí),插旗是用于標(biāo)記已確定了的雷,以防誤點(diǎn)擊。
/**
* @typedef {{rows: number, cols: number, mineCount: number}} GameOptions 游戲選項(xiàng)
*
* @typedef {{num: number, open: boolean, flag: boolean}} Block 方格子
*/
import { computed, reactive } from 'vue'
import { genArr } from '../../shared'
然后,我們導(dǎo)出一個(gè)匿名函數(shù)。大家切記:我們以下所有的JS代碼全部寫在該函數(shù)內(nèi)部。
export default () => {}然后,我們創(chuàng)建一個(gè)用于保存游戲狀態(tài)的響應(yīng)式對(duì)象。
const state = reactive({
rows: 9, // 行數(shù)
cols: 9, // 列數(shù)
mineCount: 10, // 雷數(shù)量
/** @type {Block[][]} 存放格子的二維數(shù)組 */
blocks: [],
isOver: false, // 游戲結(jié)束
isFirstClick: true // 是否首次點(diǎn)擊
})然后,我們定義一個(gè)根據(jù)網(wǎng)格行列數(shù)生成二維數(shù)組陣列的函數(shù),初始格子的值num全部設(shè)為0,open和flag屬性都為false。
/** @returns {Block[][]} */
const genBlocks = (rows, cols) => {
return genArr(rows).map(() => genArr(cols).map(() => ({ num: 0, open: false, flag: false })))
}然后,我們定義一個(gè)獲取格子對(duì)象的函數(shù),由于我們很多地方需要獲取格子對(duì)象,所以定義一個(gè)函數(shù)比較好。
const getBlock = (row, col) => (state.blocks[row] || [])[col]
掃雷游戲有一個(gè)原則就是,首次單擊的格子不能是地雷,所以,我們必須在玩家首次點(diǎn)開一個(gè)格子后,再生成地雷分布圖;我們生成地雷分布圖的函數(shù)需要一個(gè)行列坐標(biāo),來(lái)確保該坐標(biāo)一定不是地雷。如下是生成地雷分布圖函數(shù):
const genMap = (row, col) => {
const { blocks } = state
genArr(state.rows)
.reduce((t, _, i) => [...t, ...genArr(state.cols).map((_, j) => [i, j])], []) // 行列坐標(biāo)構(gòu)成的一維數(shù)組
.filter(_ => !(_[0] === row && _[1] === col)) // 過(guò)濾掉玩家首次單擊的坐標(biāo)
.sort(() => Math.random() - .5) // 對(duì)坐標(biāo)隨機(jī)排序
.slice(0, state.mineCount) // 根據(jù)雷的數(shù)量對(duì)數(shù)組切片
.forEach(_ => {
blocks[_[0]][_[1]].num = 9 // 遍歷坐標(biāo)數(shù)組,將對(duì)應(yīng)坐標(biāo)的格子對(duì)象的值設(shè)置為9,9代表雷
})
// 如下遍歷用于更新每個(gè)非雷的格子周圍雷的數(shù)量,num的值就是雷的數(shù)量
blocks.forEach((a, i) => {
a.forEach((b, j) => {
if (b.num < 9) {
b.num = [
getBlock(i - 1, j - 1),
getBlock(i - 1, j),
getBlock(i - 1, j + 1),
getBlock(i, j + 1),
getBlock(i + 1, j + 1),
getBlock(i + 1, j),
getBlock(i + 1, j -1),
getBlock(i, j - 1)
].filter(_ => _ && _.num > 8).length
}
})
})
}當(dāng)玩家點(diǎn)開一個(gè)格子后,如果該格子的值是0,那么我們需要深度遞歸遍歷,將相鄰的值為0和1的格子全部自動(dòng)打開;如下是自動(dòng)打開格子的函數(shù)定義:
const openBlocks = (row, col) => {
;[
[row - 1, col],
[row, col + 1],
[row + 1, col],
[row, col - 1]
].forEach(coords => {
const block = getBlock(...coords) // es6參數(shù)展開
if (block && !block.open && !block.flag) { // 如果格子存在并且沒(méi)被打開且沒(méi)被插旗
if (block.num < 2) { // 如果格子值為0和1,將格子打開
block.open = true
}
if (block.num < 1) { // 如果值為0,進(jìn)行深度遞歸遍歷
openBlocks(...coords)
}
}
})
}當(dāng)玩家點(diǎn)擊的格子值為9時(shí),我們需要打開所有的地雷,并結(jié)束游戲;如下是自動(dòng)打開所有地雷的函數(shù):
const openMineBlocks = () => {
state.blocks.forEach(a => {
a.forEach(b => {
if (b.num > 8) {
b.open = true
}
})
})
}當(dāng)玩家打開了所有不是雷的格子后,我們需要結(jié)束游戲,如下是用于判斷是否已完成挑戰(zhàn)的函數(shù):
const isFinish = () => {
return state.blocks.every(a => a.filter(b => b.num < 9).every(b => b.open))
}我們需要一個(gè)函數(shù),用于開始新游戲,該函數(shù)用于對(duì)游戲狀態(tài)進(jìn)行重置或更新,并啟動(dòng)游戲;如下是開始游戲函數(shù)定義:
/** @param {GameOptions} options */
const start = (options = {}) => {
Object.keys(options).forEach(key => {
if (options[key]) {
state[key] = options[key]
}
})
state.isOver = false
state.isFirstClick = true
state.blocks = genBlocks(state.rows, state.cols)
}我們需要一個(gè)用于處理格子單擊事件的函數(shù)。
const onBlockClick = (row, col) => {
const block = getBlock(row, col)
if (state.isOver || block.flag || block.open) return // 如果游戲結(jié)束或格子插了旗或格子已打開,直接返回
block.open = true
if (state.isFirstClick) { // 如果是首次單擊格子,那么生成地雷分布圖
state.isFirstClick = false
genMap(row, col)
} else if (block.num > 8) { // 如果該格子是地雷,結(jié)束游戲并自動(dòng)炸開所有的地雷
state.isOver = true
openMineBlocks()
return setTimeout(() => confirm('挑戰(zhàn)失??!是否重新開始?') && start(), 100)
} else if (block.num < 1) {
openBlocks(row, col) // 使用深度遞歸遍歷,打開值為0和1的相鄰格子
}
// 如果挑戰(zhàn)成功,那么給玩家2個(gè)選擇:挑戰(zhàn)更高難度或重新挑戰(zhàn)該難度
isFinish() && setTimeout(() => confirm('挑戰(zhàn)成功!是否挑戰(zhàn)更高難度?')
? start({ rows: state.rows + 1, mineCount: state.mineCount + state.cols })
: start(), 100)
}我們需要一個(gè)用于處理格子上下文菜單的函數(shù),該函數(shù)用于插旗和移除旗之間切換。
/** @param {PointerEvent} evt */
const onBlockContextmenu = (row, col, evt) => {
evt.preventDefault()
if (state.isOver) return
const block = getBlock(row, col)
if (!block.open) {
block.flag = !block.flag
}
}如下是3個(gè)計(jì)算屬性定義,分別用于統(tǒng)計(jì)插旗數(shù)量,打開的格子數(shù)量,未打開的格子數(shù)量。
const flagCount = computed(() => {
return state.blocks.reduce((t, a) => t + a.filter(_ => _.flag).length, 0)
})
const openCount = computed(() => {
return state.blocks.reduce((t, a) => t + a.filter(_ => _.open).length, 0)
})
const unopenCount = computed(() => state.rows * state.cols - openCount.value)最后,我們?cè)趯?dǎo)出的匿名函數(shù)的底部返回組件中使用到的變量和函數(shù)。
return { state, flagCount, openCount, unopenCount, start, onBlockClick, onBlockContextmenu }如下是src/components/mine/Index.vue文件源碼,我們使用table承載游戲界面;我認(rèn)為table很適合二維數(shù)組數(shù)據(jù)的可視化。
網(wǎng)格布局:{{ state.rows }}×{{ state.cols }}
雷數(shù)量:{{ state.mineCount }}
插旗數(shù)量:{{ flagCount }}
已打開數(shù)量:{{ openCount }}
未打開數(shù)量:{{ unopenCount }}
{{ b.num }}
如下是本篇教程中用到的幾張圖片,由于是在網(wǎng)上找的,擔(dān)心有版權(quán)問(wèn)題,僅提供截圖,就不放到文章里面了,大家可以用自己的圖片替代,將圖片放到src/components/mine/img文件夾中;其實(shí)這個(gè)不是重點(diǎn),重點(diǎn)是實(shí)現(xiàn)原理。
感謝閱讀!以上就是本篇教程的全部?jī)?nèi)容,童鞋們都理解了嗎?我感覺(jué)掃雷游戲的實(shí)現(xiàn)非常簡(jiǎn)單,幾乎沒(méi)什么難度,童鞋們應(yīng)該都能理解吧?
當(dāng)前標(biāo)題:H5小游戲開發(fā)連載教程之掃雷游戲?qū)崿F(xiàn)
鏈接URL:http://fisionsoft.com.cn/article/dhcgchd.html


咨詢
建站咨詢


