基于vue編寫一個(gè)月餅連連看游戲

"月圓花好夜,花好月圓人更圓。"
前言
中秋節(jié)快要到啦,我們一起用Vue創(chuàng)建一個(gè)簡(jiǎn)單的連連看游戲。
連連看大家一定都玩過(guò)吧,通過(guò)消除相同的圖案來(lái)清理棋盤。上面就是我們今天要做的效果圖,看上去也挺像那么回事,不過(guò)實(shí)現(xiàn)起來(lái)也是非常簡(jiǎn)單。
我將一步步引導(dǎo)大家完成整個(gè)游戲的制作過(guò)程,讓我們開(kāi)始吧,一起為中秋節(jié)增添一些互動(dòng)和娛樂(lè)!
設(shè)計(jì)思路
在構(gòu)建連連看游戲之前,首先需要考慮游戲的設(shè)計(jì)思路。
- 游戲界面:我們將創(chuàng)建一個(gè)網(wǎng)格狀的游戲界面,每個(gè)格子上都有一個(gè)圖標(biāo)或者數(shù)字。
- 游戲邏輯:玩家可以點(diǎn)擊兩個(gè)相同的格子來(lái)連接它們,但是連接的路徑上下左右不能有障礙物
- 游戲結(jié)束:當(dāng)所有的格子都被連接后,游戲結(jié)束。
初始棋盤
<template>
<div class="game-board">
<!-- 棋盤網(wǎng)格 -->
<div v-for="(row,rowIndex) in grid" :key="rowIndex" class="row">
<div
v-for="(cell,colIndex) in row"
class="cell"
:key="colIndex"
>
{{grid[rowIndex][colIndex]}}
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
const grid = ref<number[][]>([])
/**
* 隨機(jī)生成grid
*/
const randomizeGrid = () => {
const rows = 10
const cols = 10
for(let i = 0 ; i < rows ; i ++) {
const row = []
for(let j = 0 ; j < cols ; j ++) {
row.push(Math.floor(Math.random() * 3 + 1))
}
grid.value.push(row)
}
}
onMounted(() => {
randomizeGrid()
})
</script>
<style scoped lang="less">
.game-board {
background-color: palegoldenrod;
background-size: contain;
.row {
display: flex;
.cell {
width: 60px;
height: 60px;
margin: 5px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all .3s;
}
}
}
</style>
這樣就生成了一個(gè)隨機(jī)棋盤,后續(xù)我們將數(shù)組換成對(duì)應(yīng)的圖片就可以達(dá)到效果。
但是上面的寫法是我前面寫的,后續(xù)發(fā)現(xiàn)一個(gè)問(wèn)題,我們不能簡(jiǎn)單的隨機(jī)1、2、3,我們要確保每種值都是成對(duì)存在的。為了實(shí)現(xiàn)這一點(diǎn),可以采用以下步驟:
- 初始化一個(gè)存儲(chǔ)可用格子坐標(biāo)的數(shù)組
availableCells。 - 遍歷棋盤格子,并將每個(gè)格子的坐標(biāo)添加到
availableCells。 - 隨機(jī)選擇一個(gè)可用格子,為其分配一個(gè)隨機(jī)圖案值,然后從
availableCells中移除該格子。 - 再次隨機(jī)選擇一個(gè)可用格子,并為其分配相同的圖案值。
- 重復(fù)步驟 3 和 4,直到生成了足夠的圖案對(duì)。
- 將這些圖案值填充到游戲棋盤中。
/**
* 隨機(jī)生成grid,確保每一個(gè)值都是成對(duì)存在的
*/
const randomizeGrid = () => {
const rows = 10;
const cols = 10;
const numPairs = (rows * cols) / 2; // 總共需要的值對(duì)數(shù)
const igrid:number[][] = Array(rows).fill(0).map(() => Array(cols).fill(0))
const availableCells = []; // 存儲(chǔ)可用格子的坐標(biāo)
// 初始化可用格子的坐標(biāo)數(shù)組
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
availableCells.push([i, j]);
}
}
// 隨機(jī)生成成對(duì)的圖案,并填充到grid中
for (let pair = 1; pair <= numPairs; pair++) {
// 隨機(jī)選擇一個(gè)可用格子
const cellIndex = Math.floor(Math.random() * availableCells.length);
const [row, col] = availableCells[cellIndex];
// 隨機(jī)生成一個(gè)圖案
const value = Math.floor(Math.random() * 4+ 1);
igrid[row][col] = value;
availableCells.splice(cellIndex, 1);
// 隨機(jī)選擇一個(gè)可用格子,將相同圖案填充到第二個(gè)位置
const secondCellIndex = Math.floor(Math.random() * availableCells.length);
const [secondRow, secondCol] = availableCells[secondCellIndex];
igrid[secondRow][secondCol] = value;
// 從可用格子數(shù)組中移除已經(jīng)填充的格子
availableCells.splice(secondCellIndex, 1);
}
grid.value = igrid
};上面注釋已經(jīng)很清楚啦,如果大家有更好的方法歡迎在評(píng)論區(qū)交流 !
點(diǎn)擊動(dòng)作
上一個(gè)步驟中,我們已經(jīng)獲取到了每個(gè)格子的值。現(xiàn)在我們寫一下選中邏輯,由于每次點(diǎn)擊都需要和上一次結(jié)果進(jìn)行對(duì)比,就需要將上一個(gè)點(diǎn)擊位置存起來(lái)
現(xiàn)在我們要實(shí)現(xiàn)一下功能
const lastSelectedCell = ref<number[]>([])
const selectCell = (rowIndex: number, colIndex: number) => {
// 上次選中了
if(lastSelectedCell.value?.length) {
if(canConnect(lastSelectedCell.value,[rowIndex,colIndex])) {
console.log('可以連接~~');
grid.value[lastSelectedCell.value[0]][lastSelectedCell.value[1]] = 0
grid.value[rowIndex][colIndex] = 0
}
lastSelectedCell.value = []
} else {
lastSelectedCell.value = [rowIndex,colIndex]
}
}
/**
* 格子是否被選中
*/
const isCellSelected = (rowIndex: number, colIndex: number): boolean => {
return lastSelectedCell.value[0] === rowIndex && lastSelectedCell.value[1] === colIndex;
};isCellSelected是我們選中要配對(duì)的那個(gè)方格,為了是對(duì)它添加動(dòng)畫方便。
點(diǎn)擊時(shí)有兩種情況:
- 正在選第一個(gè),我們需要記錄次方格,為了下一次連接配對(duì)
- 選了第一個(gè)了,此時(shí)點(diǎn)擊選中第二個(gè),判斷是否配對(duì)成功 (配對(duì)成功的邏輯下一步介紹)
連接判定
連接判斷使用bfs來(lái)做
這里需要注意,判斷邏輯需要寫在for循環(huán)里面,因?yàn)樾枰鋵?duì)的那個(gè)方格值一定不為0,也就是說(shuō)目標(biāo)方格只要有值就到達(dá)不了
/**
* 使用 BFS 檢查路徑是否通
*/
const directions = [
[-1, 0], // 上
[1, 0], // 下
[0, -1], // 左
[0, 1], // 右
];
const isPathConnected = (startCell: number[], endCell: number[]): boolean => {
const visited = new Set<string>();
const queue: number[][] = [startCell];
while (queue.length) {
const [row, col] = queue.shift()!;
// 檢查四個(gè)方向的相鄰格子
for (const [dx, dy] of directions) {
const newRow = row + dx;
const newCol = col + dy;
const key = `${newRow}-${newCol}`;
if(endCell[0] === newRow && endCell[1] === newCol) return true
if (
newRow >= 0 &&
newRow < grid.value.length &&
newCol >= 0 &&
newCol < grid.value[0].length &&
!visited.has(key) &&
grid.value[newRow][newCol] === 0
) {
queue.push([newRow, newCol]);
visited.add(key);
}
}
}
return false; // 沒(méi)找到路徑
};此時(shí)我們就可以點(diǎn)擊配對(duì)了,最簡(jiǎn)單的版本也就完成了,貼一下完整代碼:
<template>
<div class="game-board">
<!-- 棋盤網(wǎng)格 -->
<div v-for="(row,rowIndex) in grid" :key="rowIndex" class="row">
<div
v-for="(cell,colIndex) in row"
class="cell"
:class="{isSelected : isCellSelected(rowIndex,colIndex)}"
:key="colIndex"
@click="selectCell(rowIndex,colIndex)"
>
{{grid[rowIndex][colIndex]}}
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
const grid = ref<number[][]>([])
const lastSelectedCell = ref<number[]>([])
const selectCell = (rowIndex: number, colIndex: number) => {
// 上次選中了
if(lastSelectedCell.value?.length) {
if(canConnect(lastSelectedCell.value,[rowIndex,colIndex])) {
console.log('可以連接~~');
grid.value[lastSelectedCell.value[0]][lastSelectedCell.value[1]] = 0
grid.value[rowIndex][colIndex] = 0
}
lastSelectedCell.value = []
} else {
lastSelectedCell.value = [rowIndex,colIndex]
}
}
/**
* 格子是否被選中
*/
const isCellSelected = (rowIndex: number, colIndex: number): boolean => {
return lastSelectedCell.value[0] === rowIndex && lastSelectedCell.value[1] === colIndex;
};
/**
* 檢查兩個(gè)格子是否可以連接
*/
const canConnect = (cell1: number[],cell2: number[]): boolean => {
// 點(diǎn)擊同一個(gè)格子
if(cell1[0] === cell2[0] && cell1[1] === cell2[1]) {
return false
}
// 是否值相同
if(grid.value[cell1[0]][cell1[1]] !== grid.value[cell2[0]][cell2[1]]) {
return false
}
// 路徑是否通
return isPathConnected(cell1,cell2)
}
/**
* 使用 BFS 檢查路徑是否通
*/
const directions = [
[-1, 0], // 上
[1, 0], // 下
[0, -1], // 左
[0, 1], // 右
];
const isPathConnected = (startCell: number[], endCell: number[]): boolean => {
const visited = new Set<string>();
const queue: number[][] = [startCell];
while (queue.length) {
const [row, col] = queue.shift()!;
// 檢查四個(gè)方向的相鄰格子
for (const [dx, dy] of directions) {
const newRow = row + dx;
const newCol = col + dy;
const key = `${newRow}-${newCol}`;
if(endCell[0] === newRow && endCell[1] === newCol) return true
if (
newRow >= 0 &&
newRow < grid.value.length &&
newCol >= 0 &&
newCol < grid.value[0].length &&
!visited.has(key) &&
grid.value[newRow][newCol] === 0
) {
queue.push([newRow, newCol]);
visited.add(key);
}
}
}
return false; // 沒(méi)找到路徑
};
/**
* 隨機(jī)生成grid
*/
const randomizeGrid = () => {
const rows = 10
const cols = 10
for(let i = 0 ; i < rows ; i ++) {
const row = []
for(let j = 0 ; j < cols ; j ++) {
row.push(Math.floor(Math.random() * 3 + 1))
}
grid.value.push(row)
}
}
onMounted(() => {
randomizeGrid()
})
</script>
<style scoped lang="less">
.game-board {
// width: 80vw;
// height: 80vh;
background-color: palegoldenrod;
background-size: contain;
.row {
display: flex;
.cell {
width: 60px;
height: 60px;
margin: 5px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all .3s;
background-color: aquamarine;
}
}
}
</style>現(xiàn)在效果:

引入月餅圖片
現(xiàn)在簡(jiǎn)單功能算是完成了,但是太簡(jiǎn)單了。我們得做一個(gè)看起來(lái)高大上的練練看。
此時(shí),直接請(qǐng)班里妹子畫幾個(gè)好看的月餅圖片:

真的太好看了,項(xiàng)目直接升個(gè)檔次。
拿ps切出來(lái):

然后就可以根據(jù)當(dāng)前格子渲染出對(duì)應(yīng)的小月餅啦!
動(dòng)態(tài)加載圖片需要用到vite自己的方法
加了點(diǎn)擊的動(dòng)畫,選中的圖片放大并且添加了光圈。大家自己看一看具體實(shí)現(xiàn)吧,也是很簡(jiǎn)單。

小結(jié)
最后貼一下項(xiàng)目地址
項(xiàng)目地址:https://github.com/Bbbtt04/mooncake-match
主要有兩個(gè)難點(diǎn):
- bfs判斷連接
- 隨機(jī)生成成對(duì)的值
- 最難的一點(diǎn):找一個(gè)會(huì)手繪的妹子
大家也可以試一試自己實(shí)現(xiàn)一下,做出來(lái)成就感滿滿!
以上就是基于vue編寫一個(gè)月餅連連看游戲的詳細(xì)內(nèi)容,更多關(guān)于vue月餅連連看的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于vue中的ajax請(qǐng)求和axios包問(wèn)題
大家在vue中,經(jīng)常會(huì)用到數(shù)據(jù)請(qǐng)求問(wèn)題,常用的有vue-resourse、axios ,今天小編給大家介紹下axios的post請(qǐng)求 ,感興趣的朋友跟隨腳本之家小編一起看看吧2018-04-04
基于VUE.JS的移動(dòng)端框架Mint UI的使用
本篇文章主要介紹了基于VUE.JS的移動(dòng)端框架Mint UI的使用,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10
vue3+element-plus動(dòng)態(tài)路由菜單示例代碼
這篇文章主要介紹了vue3+element-plus動(dòng)態(tài)路由菜單示例代碼,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-11-11
Vue+Vite+Axios項(xiàng)目多環(huán)境以及部署前后端跨域
本文介紹了如何在Vue+Vite+Axios項(xiàng)目中處理多環(huán)境部署和跨域問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-11-11
在Nginx上部署前端Vue項(xiàng)目的詳細(xì)步驟(超級(jí)簡(jiǎn)單!)
這篇文章主要介紹了在Nginx上部署前端Vue項(xiàng)目的詳細(xì)步驟,Nginx是一款高效的HTTP和反向代理Web服務(wù)器,作為開(kāi)源軟件,Nginx以其高性能、可擴(kuò)展性和靈活性廣泛應(yīng)用于Web架構(gòu)中,文中將步驟介紹的非常詳細(xì),需要的朋友可以參考下2024-10-10
vue中v-for循環(huán)數(shù)字,index從0開(kāi)始的實(shí)現(xiàn)方式
這篇文章主要介紹了vue中v-for循環(huán)數(shù)字,index從0開(kāi)始的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04
使用vue如何構(gòu)建一個(gè)自動(dòng)建站項(xiàng)目
這篇文章主要介紹了使用vue如何構(gòu)建一個(gè)自動(dòng)建站項(xiàng)目,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02
vue 動(dòng)態(tài)創(chuàng)建組件的兩種方法
這篇文章主要介紹了vue 動(dòng)態(tài)創(chuàng)建組件的兩種方法,幫助大家更好的理解和使用vue框架,感興趣的朋友可以了解下2020-12-12
vue和react等項(xiàng)目中更簡(jiǎn)單的實(shí)現(xiàn)展開(kāi)收起更多等效果示例
這篇文章主要介紹了vue和react等項(xiàng)目中更簡(jiǎn)單的實(shí)現(xiàn)展開(kāi)收起更多等效果示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02
Vue3滑動(dòng)到最右驗(yàn)證功能實(shí)現(xiàn)
在登錄頁(yè)面需要啟動(dòng)向右滑塊驗(yàn)證功能,遇到這樣的需求怎么實(shí)現(xiàn)呢,下面小編通過(guò)示例代碼給大家分享Vue3滑動(dòng)到最右驗(yàn)證功能實(shí)現(xiàn),感興趣的朋友一起看看吧2024-06-06

