扫雷游戏
简介:前段时间玩了一下扫雷,还挺感兴趣的,最近闲来无事,尝试自己写一下扫雷游戏。就当是消磨时间啦! 各位大人们,写的不好的话还请见谅。
扫雷界面截图
视频
扫雷游戏实现步骤
step1:搭建二维数组
step2:点击格子事件
step3:右击插旗事件
step4:重新开始游戏
step1:搭建二维数组
该部分代码包含生成的扫雷数据、页面布局、扫雷游戏显示的样式。
<script setup>
import { ref } from 'vue'
// 列数
const column = ref(10)
// 行数
const row = ref(10)
// 炸弹数
const bombNumber = ref(10)
// 1、初始化网格数据
const initGridData = () => {
// 1.1 生成10*10的二维数组
let arr = []
for (let i = 0; i < row.value; i++) {
arr[i] = []
for (let j = 0; j < column.value; j++) {
arr[i][j] = { count: 0, isBomb: false, isOpen: false, isFlag: false, position: { x: i, y: j } }
}
}
// 1.2 随机生成炸弹
let bomb = bombNumber.value
while (bomb > 0) {
const rowIndex = Math.floor(Math.random() * row.value)
const columnIndex = Math.floor(Math.random() * column.value)
if (arr[rowIndex][columnIndex].isBomb == false) {
arr[rowIndex][columnIndex].isBomb = true
bomb--
}
}
// 1.3 生成炸弹周围数字
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr[i].length; j++) {
if (arr[i][j].isBomb === false) {
let count = 0
for (let m = i - 1; m <= i + 1; m++) {
for (let n = j - 1; n <= j + 1; n++) {
if (
m >= 0 &&
m < arr.length &&
n >= 0 &&
n < arr[j].length &&
arr[m][n].isBomb === true
) {
count++
}
}
}
arr[i][j].count = count
}
}
}
return arr
}
// 生成网格
let grid = initGridData()
// 复制网格数据使其具有响应式 (实际真正使用的数据)
const gridArr = ref(grid)
</script>
<template>
<div class="game-container">
<div class="game-title">🎮扫雷游戏🎮</div>
<div class="game-header">
<el-link :underline="false" style="color:#409eff" @click="handleGameReStart">
<svg-icon name="zhongxinkaishihover" width="20px" height="20px" style="margin-right:5px" />重新开始</el-link>
</div>
<div class="game-body">
<div id="game-grid">
<div v-for="(item, index) in gridArr" :key="index">
<div :class="{ 'game-over': isGameOver, active: t.isOpen }" v-for="t in item" :key="t" class="grid-item"
@click="handleChangeOpen(t)" @contextmenu.prevent="handleRightClick(t,$event)"
:style="{ color: gameStatus ? 'green' : 'red' }">
<div v-if="t.isOpen">
<span v-if="t.isBomb">💣</span>
<span v-else>{{ t.count === 0 ? '' : t.count }}</span>
</div>
<div v-if="t.isFlag">🚩</div>
</div>
</div>
</div>
</div>
<div class="game-over game-over-text game-footer" :style="{ color: gameStatus ?'green':'red'}" v-if="isGameOver">
{{
gameOverText }}</div>
</div>
</template>
<style scoped lang="scss">
.game-container {
height: 100%;
width: 100%;
.game-title{
height: 30px;
line-height: 30px;
text-align: center;
font-size: 25px;
color: #fff;
font-weight: bold;
}
.game-header {
height: 50px;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
.el-link{
margin: 0 10px;
}
}
.game-body{
width: 100%;
display: flex;
justify-content: center;
}
.game-footer{
height: 100px;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
}
#game-grid {
display: grid;
grid-template-rows: repeat(10, 2rem);
grid-template-columns: repeat(10, 2rem);
}
.grid-item {
width: 2rem;
height: 2rem;
background-color: #ddd;
border: solid 2px;
border-top-color: #eee;
border-left-color: #eee;
border-right-color: #ccc;
border-bottom-color: #ccc;
display: flex;
justify-content: center;
align-items: center;
}
.active {
background-color: #ccc;
color: green;
}
.game-over {
pointer-events: none;
}
.game-over-text {
font-size: 1.5rem;
}
</style>
step2:点击格子事件
此代码主要包含点击格子之后可能发生的情况:当前的格子已经被打开或者被插旗、当前的格子是💣、当前的格子是空白格子、当前的格子是数字等。(将代码复制到script标签中)
// 点击某个格子(鼠标左击(单击))
const handleChangeOpen = (row) => {
// (1) 判断是否被打开过 或者被插上🚩(如果被打开或者被插旗则不进行打开)
if (row.isOpen || row.isFlag) {
return
}
// (2) 判断是否是雷 如果是雷 打开盒子并显示游戏结束
if (row.isBomb) {
row.isOpen = true
gameOver('error')
return
}
// (3) 判断是否是数字 如是数字直接进行打开 (并判断是否胜利)
if (row.count) {
row.isOpen = true
if (allNonBombsAreRevealed()) {
gameOver('success')
}
return
}
// (4) 如果是空白格 需要将空格旁边的空格进行递归打开
row.isOpen = true
openBlankGrid(row)
}
// 递归打开空白格
const openBlankGrid = (p) => {
const { x, y } = p.position
for (let i = Math.max(x - 1, 0); i <= Math.min(x + 1, 9); i++) {
for (let j = Math.max(y - 1, 0); j <= Math.min(y + 1, 9); j++) {
if (i === x && j === y) continue
const neighbor = gridArr.value[i][j]
if (!neighbor.isBomb && !neighbor.isOpen && !neighbor.isFlag) {
neighbor.isOpen = true
if (neighbor.count === 0) {
openBlankGrid(neighbor)
}
}
}
}
}
// 判断是否成功完成排雷任务 (如果没有打开的格子数 === 设置的炸弹数则挑战成功)
function allNonBombsAreRevealed() {
let revealedCells = 0
for (let i = 0; i < grid.length; i++) {
for (let j = 0; j < grid[i].length; j++) {
if (!grid[i][j].isOpen) {
revealedCells++;
}
}
}
return revealedCells === bombNumber.value;
}
step3:右击插旗事件
鼠标右击首先阻止默认事件、其次将isFlag属性取反 (鼠标右击可切换插旗的状态)(同样将代码复制到script标签中)
// 鼠标右击单元格(进行插旗工作)
const handleRightClick = (t,$event) => {
$event.preventDefault(); // 阻止默认的上下文菜单行为
t.isFlag = !t.isFlag
}
step4:重新开始游戏
重新生成grid二维数组、将gameOver有关的字段进行重置(同样将代码复制到script标签中)
// 重新开始游戏
const handleGameReStart = () => {
grid = initGridData()
gridArr.value = grid
isGameOver.value = false
gameStatus.value = true
}
总结:以上代码基本上能实现简单的扫雷功能,目前该代码还不支持游戏难度的切换和自定义游戏难度,有兴趣的大人们,也可以自己去试试,后面我也会抽时间进行完善。