使用 uniapp 实现的扫雷游戏

1. 效果图

2. 游戏规则

扫雷的规则很简单。盘面上有许多方格,方格中随机分布着一些雷。你的目标是避开雷,打开其他所有格子。一个非雷格中的数字表示其相邻 8 格子中的雷数,你可以利用这个信息推导出安全格和雷的位置。你可以用右键在你认为是雷的地方插旗(称为标雷)。你可以用左键打开安全的地方,左键打开雷将被判定为失败。

3. 实现思路

  1. 创建 row 行 col 列的二维数组,注意行和列创建时都要生成他的唯一 key;
  2. 根据选择的难度,将对应难度的雷的个数随机埋入 row * col 个格子中;
  3. 统计每个格子周边八个格子中雷的个数;
  4. 每个格子的状态:bomb 是否是存在雷的格子,bombCount 当前格子周边格子的雷的数量,flag 当前格子是否被插旗,opened 当前格子是否被翻开;
  5. 根据每个格子的状态,渲染对应的图片;
  6. 点击每个格子,执行对应的操作,比如插旗,翻开,是雷就爆炸等。

4. 创建背景

4.1 HTML 结构

xml 复制代码
<view class="rui-flex-cc">
	<view class="rui-minesweeper-content">
		<view class="rui-minesweeper-header-content">
			<view class="rui-header-counter">{{ bombCount.toString().padStart(3, '0') }}</view>
			<view class="rui-header-btn" @click="start">
				<view v-if="isGameOver">
					<image v-if="isSuccess" :src="icon.iconFaceSuccess" class="rui-btn-icon"></image>
					<image v-else :src="icon.iconFaceFail" class="rui-btn-icon"></image>
				</view>
				<image v-else :src="icon.iconFaceNormal" class="rui-btn-icon"></image>
			</view>
			<view class="rui-header-counter">{{ sec.toString().padStart(3, '0') }}</view>
		</view>
		<!-- 雷区 -->
		<view class="rui-main-content">
			
		</view>
	</view>
</view>

4.2 样式

css 复制代码
$primary: #C0C0C0;
$light: #EEEEEE;
$dark: #969696;
.rui-minesweeper-content{
	background-color: $primary;
	width: 250px;
	height: 300px;
	margin: 20px auto;
	padding: 8px;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	cursor: default;
	user-select: none;
	.rui-minesweeper-header-content{
		width: 100%;
		display: flex;
		justify-content: space-between;
		align-items: center;
		border: 3px inset $light;
		box-sizing: border-box;
		padding: 5px 10px;
		.rui-header-counter {
			background-color: black;
			color: red;
			font-family: Impact;
			min-width: 2em;
			text-align: center;
		}
		.rui-header-btn {
			border: 2px outset #eee;
			width: 25px;
			height: 25px;
			display: flex;
			align-items: center;
			justify-content: center;
			.rui-btn-icon{
				width: 21px;
				height: 21px;
				border-radius: 50%;
				display: block;
			}
		}
	}
}

4.3 实现结果

5. 初始化格子

5.1 代码分析

  1. 获取当前难度等级:通过 find 方法从 navbars 数组中找到 ischeck 属性为 true 的第一个元素,即当前选中的难度等级。
  2. 提取难度等级的属性:使用解构赋值从 curGrade 对象中提取出 bombCount、row 和 col 属性,分别代表地雷数量、行数和列数。
  3. 初始化游戏状态:初始化游戏的各种状态变量,包括计时器(sec)、游戏结束标志(isGameOver)、游戏成功标志(isSuccess)、最大行数(maxrow)、最大列数(maxcol)、地雷数量(bombCount)和空白格子数量(blankCount)。
  4. 初始化网格:调用 initCells 方法来初始化游戏网格,该方法根据传入的行数和列数创建一个二维数组,用于表示游戏的网格。
  5. 随机放置地雷:调用 fixUpMinesweepers 方法在游戏网格中随机放置地雷。
  6. 计算当前位置周边雷的个数:调用 findCurrentPointAroundMinesweeperCount 方法来计算每个格子周围的地雷数量,这个信息通常用于在游戏中显示给玩家。

5.2 初始化代码实现

ini 复制代码
// 开始重置
start(){
	let curGrade = this.navbars.find(item => item.ischeck);
	let { bombCount, row, col } = curGrade;
	this.sec = 0;
	this.isGameOver = false;
	this.isSuccess = false;
	this.maxrow = row;
	this.maxcol = col;
	this.bombCount = bombCount;
	this.blankCount = row * col - bombCount;
	// 初始化网格
	this.initCells(row, col);
	// 随机放置地雷
	this.fixUpMinesweepers();
	// 计算当前位置周边雷的个数
	this.findCurrentPointAroundMinesweeperCount();
}

5.3 初始化网格

  1. 初始化外层数组:使用 Array.from 方法创建一个长度为 row 的新数组,并使用一个映射函数来初始化每个元素。映射函数的参数 r 代表当前正在处理的行,而 ridx 代表该行的索引。返回一个对象,该对象包含两个属性:
    • keyId: 一个随机生成的字符串,用于标识该行。
    • list: 一个新的数组,使用 Array.from 方法创建,长度为 col。
  2. 初始化内层数组:数组的每个元素也是一个对象,包含以下属性:
    • bomb: 一个布尔值,表示该单元格是否包含地雷,初始值为 false。
    • bombCount: 一个数字,表示该单元格周围的地雷数量,初始值为 0。
    • flag: 一个布尔值,表示该单元格是否被标记为地雷,初始值为 false。
    • opened: 一个布尔值,表示该单元格是否被翻开,初始值为 false。

5.4 网格代码实现

javascript 复制代码
// 初始化网格
initCells(row, col){
	this.cells = Array.from({ length: row }, (r, ridx) => {
		return {
			keyId: `id-${randomString()}`,
			list: Array.from({length: col}, (c, cidx) => {
				return {
					keyId: `id-${randomString()}`,
					bomb: false, 
					bombCount: 0, 
					flag: false, 
					opened: false
				}
			})
		}
	})
}

6. 随机放置地雷

6.1 代码分析

  1. 初始化:初始化一个局部变量 cells,它引用了组件实例的 cells 属性,即二维数组,表示游戏网格。
  2. 放置地雷的循环:使用 for 循环,它将执行 this.bombCount 次,即要放置的地雷数量。在每次循环中,它生成两个随机数 row 和 col,分别代表地雷在棋盘上的行和列索引。
  3. 检查并放置地雷:检查生成的随机位置 cells[row].list[col] 是否已经包含了地雷。如果没有,它将该位置标记为地雷(bomb 属性设置为 true)。如果该位置已经有地雷,它将 i 减一,以便在下一次循环中重新尝试放置地雷。

6.2 代码实现

ini 复制代码
fixUpMinesweepers(){
	let cells = this.cells;
	for(let i = 0; i < this.bombCount; i++){
		let row = Math.floor(Math.random() * this.maxrow);
		let col = Math.floor(Math.random() * this.maxcol);
		if(!cells[row].list[col].bomb){
			cells[row].list[col].bomb = true;
		} else {
			i--;
		}
	}
}

7. 计算当前位置周边雷的个数

7.1 代码分析

  1. 初始化:初始化一个局部变量 cells,它引用了组件实例的 cells 属性,即二维数组,表示游戏棋盘。
  2. 两层嵌套循环遍历单元格:使用两层嵌套循环遍历游戏棋盘上的每个单元格。外层循环遍历每一行,内层循环遍历每一列。对于每个单元格,它调用 handleAroundPoints 函数来处理该单元格周围的点。
  3. 调用 handleAroundPoints 函数:
    • cells:二维数组,表示游戏棋盘。
    • row:当前单元格的行索引。
    • col:当前单元格的列索引。
    • maxrow:游戏棋盘的最大行数。
    • maxcol:游戏棋盘的最大列数。
    • callback:一个回调函数,用于对每个相邻单元格执行操作。在这个回调函数中,它将当前单元格的 bombCount 属性增加 1,如果相邻单元格是地雷;否则,不增加。

7.2 代码实现

ini 复制代码
findCurrentPointAroundMinesweeperCount(){
	let cells = this.cells;
	for(let row = 0; row < this.maxrow; row++) {
		for(let col = 0; col < this.maxcol; col++) {
			handleAroundPoints({
				cells, 
				row, 
				col, 
				maxrow: this.maxrow,
				maxcol: this.maxcol,
				callback: cCell => cells[row].list[col].bombCount += cCell.bomb ? 1 : 0
			})
		}
	}
}

7.3 遍历当前位置周边代码分析

  1. 函数的参数包括:
    • cells:一个二维数组,表示单元格的网格。
    • row:当前单元格的行索引。
    • col:当前单元格的列索引。
    • maxrow:网格的最大行数。
    • maxcol:网格的最大列数。
    • callback:一个回调函数,用于对每个相邻单元格执行操作。
  2. 函数内部使用了两个嵌套的 for 循环来遍历相邻单元格。外层循环遍历行,内层循环遍历列。通过 Math.max 和 Math.min 函数确保循环的边界不会超出网格的范围。同时,使用 continue 语句跳过当前单元格本身,只处理相邻单元格。
  3. 通过回调函数 callback 来对每个相邻单元格执行操作,传入的参数包括单元格的值、行索引和列索引。

7.4 遍历当前位置周边代码实现

ini 复制代码
function handleAroundPoints({cells, row, col, maxrow, maxcol, callback}) {
	for(let srow = Math.max(row - 1, 0); srow < Math.min(row + 2, maxrow); srow++) {
		for(let scol = Math.max(col - 1, 0); scol < Math.min(col + 2, maxcol); scol++) {
			if (srow === row && scol === col) continue;
			callback(cells[srow].list[scol], srow, scol)
		}
	}
}

7.5 实现结果

8. 点击事件

8.1 逻辑分析

  1. 初始化:接受两个参数:row 和 col,分别代表点击的单元格的行和列索引。方法内部首先检查是否存在一个计时器(timer),如果不存在,则创建一个计时器,每1000毫秒(即1秒)调用一次 onTick 方法。
  2. 点击事件:当前选中的点击类型(curTypeItem.type)是0,即左键【翻开】点击,代码会检查点击的单元格是否被标记为旗子(flag)。如果是,则不做任何操作;如果不是,它会进一步检查单元格是否包含地雷(bomb)。如果是地雷,单元格将被标记为已打开(opened),并且调用 onExplode 方法表示游戏失败;如果不是地雷,它将调用 onOpen 方法来打开单元格。
  3. 插旗点击事件:当前选中的点击类型是1,即右键【插旗】点击,代码将调用 onFlag 方法来标记或取消标记单元格为旗子。

8.2 代码实现

kotlin 复制代码
onClick(row, col) {
	let curTypeItem = this.chooseClickTypes.find(item => item.ischeck);
	if(!this.timer){
		this.timer = setInterval(this.onTick, 1000);
	}
	if(curTypeItem.type == 0){
		const cell = this.cells[row].list[col];
		if (cell.flag){
			return false;
		}
		if(cell.bomb){
			cell.opened = true;
			this.onExplode();
		} else {
			this.onOpen(row, col);
		}
	} else if(curTypeItem.type == 1){
		this.onFlag(row, col);
	}
}

9. 翻开逻辑

9.1 逻辑分析

  1. 初始化:接受两个参数:row 和 col,分别代表点击的单元格的行和列索引。方法内部首先获取当前单元格的引用,然后将其标记为已打开(opened),并清除其标记为旗子的状态(flag)。接着,它减少空白单元格计数器(blankCount)的值,表示已经打开了一个单元格。
  2. 检查游戏胜利条件:检查 blankCount 是否小于1,如果是,则表示所有非地雷单元格都已被打开,游戏胜利,因此调用 onSuccess 方法。
  3. 检查是否需要开启周围单元格:当前单元格的地雷数量(bombCount)为0,则表示其周围没有地雷,需要开启周围的单元格。因此,调用 openAround 方法来开启当前单元格周围的单元格。

9.2 代码实现

kotlin 复制代码
// 开启当前位置
onOpen(row, col) {
	const cell = this.cells[row].list[col];
	cell.opened = true;
	cell.flag = false;
	this.blankCount--;
	if (this.blankCount < 1) {
		this.onSuccess()
	} else if (cell.bombCount === 0) {
		this.openAround(row, col)
	}
}

10. 翻开当前位置的周边没有雷区和没有开启的位置

10.1 逻辑分析

  1. 获取了当前的单元格数组 cells,然后调用了一个名为 handleAroundPoints 的函数。
  2. 函数接收一个对象作为参数,该对象包含以下属性:
    • cells:当前的单元格数组。
    • row:当前单元格的行索引。
    • maxrow:网格的最大行数。
    • maxcol:网格的最大列数。
    • callback:一个回调函数,该函数接收三个参数:cCell(当前处理的单元格),crow(当前处理的单元格的行索引),ccol(当前处理的单元格的列索引)。回调函数的作用是检查当前单元格是否满足翻开条件,如果满足,则翻开该单元格。
  3. 检查当前单元格是否已经被打开、是否是炸弹或者是否被标记为旗子。如果这些条件都不满足,则调用 this.onOpen(crow, ccol) 方法来打开当前单元格。

10.2 代码实现

javascript 复制代码
// 开启当前位置的周边没有雷区和没有开启的位置
openAround(row, col) {
	let cells = this.cells;
	handleAroundPoints({
		cells, 
		row, 
		col, 
		maxrow: this.maxrow,
		maxcol: this.maxcol,
		callback: (cCell, crow, ccol) => {
			if (!cCell.opened && !cCell.bomb && !cCell.flag){
				this.onOpen(crow, ccol);
			}
		}
	})
}

11. 增加难度选择

11.1 代码实现

ini 复制代码
// 切换难度
changeGrade(idx){
	let navbars = this.navbars;
	navbars.forEach(item => item.ischeck = false);
	navbars[idx].ischeck = true;
	this.start();
}

11.2 实现效果

11.2.1 初级
11.2.1 中级

12. 翻开或插旗切换

12.1 代码实现

ini 复制代码
// 切换点击事件类型
changeClickType(idx){
	let chooseClickTypes = this.chooseClickTypes;
	chooseClickTypes.forEach(item => item.ischeck = false);
	chooseClickTypes[idx].ischeck = true;
}

12.2 实现效果

13. 全部代码

ini 复制代码
<template>
<view>
	<view class="rui-flex-cc">
		<view class="rui-flex-ac">
			<view :class="{
				'rui-navbar-li': true,
				'rui-active': nav.ischeck
			}" 
			@click="changeGrade(idx)"
			v-for="(nav,idx) in navbars" :key="nav.keyId">
				{{nav.name}}
			</view>
		</view>
	</view>
	<view class="rui-flex-cc">
		<view class="rui-minesweeper-content">
			<view class="rui-minesweeper-header-content">
				<view class="rui-header-counter">{{ bombCount.toString().padStart(3, '0') }}</view>
				<view class="rui-header-btn" @click="start">
					<view v-if="isGameOver">
						<image v-if="isSuccess" :src="icon.iconFaceSuccess" class="rui-btn-icon"></image>
						<image v-else :src="icon.iconFaceFail" class="rui-btn-icon"></image>
					</view>
					<image v-else :src="icon.iconFaceNormal" class="rui-btn-icon"></image>
				</view>
				<view class="rui-header-counter">{{ sec.toString().padStart(3, '0') }}</view>
			</view>
			<!-- 雷区 -->
			<view class="rui-main-content">
				<view class="rui-row" v-for="(row,ridx) in cells" :key="row.keyId">
					<view class="rui-col" v-for="(col,cidx) in  row.list"
					@click="onClick(ridx, cidx)"
					@contextmenu.prevent="onFlag(ridx, cidx)"
					:key="col.keyId">
						<view v-if="isGameOver">
							<image v-if="!col.bomb" :src="icon[`icon${col.bombCount}`]" class="rui-icon"></image>
							<view v-else>
								<image v-if="col.opened" :src="icon.iconBlood" class="rui-icon"></image>
								<image v-else :src="icon.iconMine" class="rui-icon"></image>
							</view>
						</view>
						<view v-else>
							<image v-if="col.flag" :src="icon.iconFlag" class="rui-icon"></image>
							<image v-else-if="col.opened" :src="icon[`icon${col.bombCount}`]" class="rui-icon"></image>
							<image v-else-if="!col.opened" :src="icon.iconBlank" class="rui-icon"></image>
						</view>
					</view>
				</view>
			</view>
		</view>
	</view>
	<!-- 切换点击事件类型 -->
	<view class="rui-flex-cc">
		<view class="rui-flex-ac">
			<view :class="{
				'rui-navbar-li': true,
				'rui-active': typeItem.ischeck
			}" 
			@click="changeClickType(idx)"
			v-for="(typeItem,idx) in chooseClickTypes" :key="typeItem.keyId">
				{{typeItem.name}}
			</view>
		</view>
	</view>
</view>
</template>

<script>
	import icon from './icon';
	import { randomString, handleAroundPoints } from './utils';
	export default {
		name:"RuiMinesweeper",
		data() {
			return {
				icon,
				sec: 0,
				bombCount: 0,
				blankCount: 0,
				maxrow: 0,
				maxcol: 0,
				isGameOver: false, 
				isSuccess: false,
				cells: [],
				chooseClickTypes: [{
					name: '翻开',
					type: 0,
					keyId: `id-${randomString()}`,
					ischeck: true
				},{
					name: '插旗',
					type: 1,
					keyId: `id-${randomString()}`,
					ischeck: false
				}],
				navbars: [{
					name: '初级',
					row: 9,
					col: 9,
					bombCount: 10,
					keyId: `id-${randomString()}`,
					ischeck: true
				},{
					name: '中级',
					row: 16,
					col: 16,
					bombCount: 40,
					keyId: `id-${randomString()}`,
					ischeck: false
				},{
					name: '高级',
					row: 20,
					col: 20,
					bombCount: 99,
					keyId: `id-${randomString()}`,
					ischeck: false
				},{
					name: '自定义',
					row: 30,
					col: 30,
					bombCount: 150,
					keyId: `id-${randomString()}`,
					ischeck: false
				}],
			};
		},
		created(){
			this.start()
		},
		methods: {
			// 切换难度
			changeGrade(idx){
				let navbars = this.navbars;
				navbars.forEach(item => item.ischeck = false);
				navbars[idx].ischeck = true;
				this.start();
			},
			// 切换点击事件类型
			changeClickType(idx){
				let chooseClickTypes = this.chooseClickTypes;
				chooseClickTypes.forEach(item => item.ischeck = false);
				chooseClickTypes[idx].ischeck = true;
			},
			// 开始重置
			start(){
				let curGrade = this.navbars.find(item => item.ischeck);
				let { bombCount, row, col } = curGrade;
				this.sec = 0;
				this.isGameOver = false;
				this.isSuccess = false;
				this.maxrow = row;
				this.maxcol = col;
				this.bombCount = bombCount;
				this.blankCount = row * col - bombCount;
				// 初始化网格
				this.initCells(row, col);
				// 随机放置地雷
				this.fixUpMinesweepers();
				// 计算当前位置周边雷的个数
				this.findCurrentPointAroundMinesweeperCount();
			},
			// 初始化网格
			initCells(row, col){
				this.cells = Array.from({ length: row }, (r, ridx) => {
					return {
						keyId: `id-${randomString()}`,
						list: Array.from({length: col}, (c, cidx) => {
							return {
								keyId: `id-${randomString()}`,
								bomb: false, 
								bombCount: 0, 
								flag: false, 
								opened: false
							}
						})
					}
				})
			},
			// 随机放置地雷
			fixUpMinesweepers(){
				let cells = this.cells;
				for(let i = 0; i < this.bombCount; i++){
					let row = Math.floor(Math.random() * this.maxrow);
					let col = Math.floor(Math.random() * this.maxcol);
					if(!cells[row].list[col].bomb){
						cells[row].list[col].bomb = true;
					} else {
						i--;
					}
				}
			},
			// 计算当前位置周边雷的个数
			findCurrentPointAroundMinesweeperCount(){
				let cells = this.cells;
				for(let row = 0; row < this.maxrow; row++) {
					for(let col = 0; col < this.maxcol; col++) {
						handleAroundPoints({
							cells, 
							row, 
							col, 
							maxrow: this.maxrow,
							maxcol: this.maxcol,
							callback: cCell => cells[row].list[col].bombCount += cCell.bomb ? 1 : 0
						})
					}
				}
			},
			// 计时器一千秒
			onTick() {
				if (this.sec < 999){
					this.sec++;
				} else {
					// 时间完成,结束游戏
					this.onStop();
				}
			},
			// 结束游戏
			onStop() {
				this.isGameOver = true;
				if(this.timer) {
					clearInterval(this.timer);
					this.timer = null;
				}
			},
			// 爆炸
			onExplode(){
				this.isSuccess = false;
				this.onStop();
			},
			// 成功
			onSuccess(){
				this.isSuccess = true;
				this.onStop();
			},
			// 插旗操作
			onFlag(row, col){
				const cell = this.cells[row].list[col];
				cell.flag = !cell.flag
				cell.flag ? this.bombCount-- : this.bombCount++; 
			},
			// 点击事件
			onClick(row, col) {
				let curTypeItem = this.chooseClickTypes.find(item => item.ischeck);
				if(!this.timer){
					this.timer = setInterval(this.onTick, 1000);
				}
				if(curTypeItem.type == 0){
					const cell = this.cells[row].list[col];
					if (cell.flag){
						return false;
					}
					if(cell.bomb){
						cell.opened = true;
						this.onExplode();
					} else {
						this.onOpen(row, col);
					}
				} else if(curTypeItem.type == 1){
					this.onFlag(row, col);
				}
			},
			// 开启当前位置
			onOpen(row, col) {
				const cell = this.cells[row].list[col];
				cell.opened = true;
				cell.flag = false;
				this.blankCount--;
				if (this.blankCount < 1) {
					this.onSuccess()
				} else if (cell.bombCount === 0) {
					this.openAround(row, col)
				}
			},
			// 开启当前位置的周边没有雷区和没有开启的位置
			openAround(row, col) {
				let cells = this.cells;
				handleAroundPoints({
					cells, 
					row, 
					col, 
					maxrow: this.maxrow,
					maxcol: this.maxcol,
					callback: (cCell, crow, ccol) => {
						if (!cCell.opened && !cCell.bomb && !cCell.flag){
							this.onOpen(crow, ccol);
						}
					}
				})
			}
		}
	}
</script>

<style lang="scss" scoped>
	$primary: #C0C0C0;
	$light: #EEEEEE;
	$dark: #969696;
	.rui-flex-cc{
		display: flex;
		justify-content: center;
		align-items: center;
	}
	.rui-flex-ac{
		display: flex;
		align-items: center;
	}
	.rui-icon{
		width: 25px;
		height: 25px;
		display: block;
	}
	.rui-navbar-li{
		color: #23527c;
		margin: 20px;
	}
	.rui-navbar-li.rui-active{
		color: #444;
		font-weight: 700;
	}
	.rui-minesweeper-content{
		background-color: $primary;
		margin: 20px auto;
		padding: 8px;
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
		cursor: default;
		user-select: none;
		.rui-minesweeper-header-content{
			width: 100%;
			display: flex;
			justify-content: space-between;
			align-items: center;
			border: 3px inset $light;
			box-sizing: border-box;
			padding: 5px 10px;
			.rui-header-counter {
				background-color: black;
				color: red;
				font-family: Impact;
				min-width: 2em;
				text-align: center;
			}
			.rui-header-btn {
				border: 2px outset #eee;
				width: 25px;
				height: 25px;
				display: flex;
				align-items: center;
				justify-content: center;
				.rui-btn-icon{
					width: 21px;
					height: 21px;
					border-radius: 50%;
					display: block;
				}
			}
		}
		.rui-main-content{
			width: 100%;
			height: 100%;
			border: 3px inset #eee;
			box-sizing: border-box;
		}
		.rui-row{
			display: flex;
			align-items: center;
		}
		.rui-col {
			flex: none;
			width: 25px;
			height: 25px;
			display: flex;
			align-items: center;
			justify-content: center;
		}
		.rui-col.no-event {
		  pointer-events: none;
		}
	}
</style>

14. 工具函数

ini 复制代码
export function randomString(e) {
  e = e || 32;
  const t = "abcdefghijklmnopqrstwxyz1234567890";
  const a = t.length;
  let n = "";
  for (let i = 0; i < e; i++) {
    n += t.charAt(Math.floor(Math.random() * a));
  }
  return n;
}

export function handleAroundPoints({cells, row, col, maxrow, maxcol, callback}) {
	for(let srow = Math.max(row - 1, 0); srow < Math.min(row + 2, maxrow); srow++) {
		for(let scol = Math.max(col - 1, 0); scol < Math.min(col + 2, maxcol); scol++) {
			if (srow === row && scol === col) continue;
			callback(cells[srow].list[scol], srow, scol)
		}
	}
}

15. 总结

  1. 逻辑实现其实不是很难,最主要的是需要理清逻辑,然后按部就班的实现就可以了,写代码实现很重要,不要永远在思考的层面。
  2. 由于这个是按照 PC 实现的界面,所以在移动端游戏界面会超出视图,因此可以在此基础,将难度在一个页面选择,然后动态计算每个难度格子的大小。
  3. 基本的逻辑已完善,如果需要引流等代码,需要对饮的自己添加,由于使用的都是图片,换UI界面也比较容易,设计一套新的UI,替换图片就可以。
相关推荐
咸虾米3 小时前
微信小程序服务端api签名,安全鉴权模式介绍,通过封装方法实现请求内容加密与签名
vue.js·微信小程序·uni-app
YuShiYue4 小时前
【uni-app】自定义导航栏以及状态栏,胶囊按钮位置信息的获取
uni-app·notepad++
2501_915921435 小时前
iOS 应用上架多环境实战,Windows、Linux 与 Mac 的不同路径
android·ios·小程序·https·uni-app·iphone·webview
yede7 小时前
uniapp - 自定义页面的tabBar
vue.js·uni-app
谢泽豪9 小时前
解决 uniapp 修改index.html文件不生效的问题
前端·uni-app
00后程序员张9 小时前
iOS 应用上架常见问题与解决方案,多工具组合的实战经验
android·ios·小程序·https·uni-app·iphone·webview
奶糖 肥晨19 小时前
解决 UniApp 自定义弹框被图片或 Canvas 覆盖的 Bug
uni-app·bug
荷花微笑20 小时前
HBuilderX升级,Vue2 scss 预编译器默认已由 node-sass 更换为 dart-sass
uni-app·css3
2501_916007471 天前
iOS App 上架实战 从内测到应用商店发布的全周期流程解析
android·ios·小程序·https·uni-app·iphone·webview