鸿蒙NEXT小游戏开发:扫雷
1. 引言
本文将介绍如何使用鸿蒙NEXT框架开发一个简单的扫雷游戏。通过本案例,您将学习到如何利用鸿蒙NEXT的组件化特性、状态管理以及用户交互设计来构建一个完整的游戏应用。

2. 环境准备
电脑系统:windows 10
工程版本:API 12
真机:Mate 60 Pro
语言:ArkTS、ArkUI
3. 项目结构
本项目主要由两个类组成:Cell类表示游戏中的每一个方块,MineSweeper类则是整个扫雷游戏的核心组件。
Cell类
Cell类用于表示游戏面板上的每一个方块,包含以下属性:
row和column:方块的行列位置。
hasMine:标记该方块是否有地雷。
neighborMines:记录周围地雷的数量。
isFlag:标记该方块是否被标记为地雷。
value:方块的显示值。
MineSweeper类
MineSweeper类是游戏的主要逻辑,包含以下关键方法:
初始化游戏:initializeGame方法重置游戏状态,生成新的游戏面板。
生成游戏面板:generateBoard方法创建一个10x10的方块数组,并随机放置地雷。
计算周围地雷数量:calculateNumbers方法为每个方块计算周围的地雷数量。
揭示方块:revealCell方法处理方块的揭示逻辑,包括递归揭示周围方块。
胜利与失败判断:isVictory和showGameOverDialog方法用于判断游戏的胜利或失败,并显示相应的对话框。
4. 用户界面
游戏的用户界面通过build方法构建,使用了鸿蒙NEXT的布局组件。主要包括:
重新开始按钮:点击后重新初始化游戏。
游戏面板:使用ForEach遍历每个方块,显示其值,并处理用户的点击和长按事件。
5. 交互设计
游戏支持单击揭示方块和长按标记地雷的操作,用户体验友好。通过状态管理,游戏能够实时更新方块的显示状态和游戏进程。
6. 完整代码
import { promptAction } from '@kit.ArkUI'; // 导入用于显示对话框的工具// 定义方块类
@ObservedV2 // 使用@ObservedV2装饰器使得类的状态可以被自动追踪
class Cell {row: number; // 方块所在的行column: number; // 方块所在的列hasMine: boolean = false; // 是否有地雷,默认没有neighborMines: number = 0; // 周围地雷数量,默认为0@Trace isFlag: boolean = false; // 是否被标记为地雷,默认没有标记@Trace value: string; // 方块值,显示数字或“雷”// 构造函数,初始化方块的位置和值constructor(row: number, column: number) {this.row = row;this.column = column;this.value = ''; // 初始为空字符串}
}// 定义扫雷游戏组件
@Entry // 使用@Entry装饰器声明这是一个入口组件
@Component // 使用@Component装饰器声明这是一个组件
struct MineSweeper {@State private gameBoard: Cell[][] = []; // 游戏面板数据@State private mineCount: number = 10; // 地雷总数@State private revealedCells: Set<string> = new Set(); // 已经揭示的方块集合@State private flaggedCells: Set<string> = new Set(); // 标记为地雷的方块集合@State private cellSize: number = 60; // 方块大小@State private cellMargin: number = 2; // 方块之间的边距private startTime: number = Date.now(); // 游戏开始时间@State private isGameOver: boolean = false; // 游戏结束标志// 当组件即将显示时调用此方法aboutToAppear(): void {this.initializeGame(); // 初始化游戏}// 初始化游戏private initializeGame() {this.isGameOver = false; // 设置游戏未结束this.startTime = Date.now(); // 重置游戏开始时间this.revealedCells.clear(); // 清空已揭示的方块集合this.flaggedCells.clear(); // 清空标记为地雷的方块集合this.generateBoard(); // 生成新的游戏面板}// 生成游戏面板private generateBoard() {this.gameBoard = []; // 清空游戏面板for (let i = 0; i < 10; i++) { // 循环创建10行this.gameBoard.push([]); // 每行初始化为空数组for (let j = 0; j < 10; j++) { // 循环创建10列this.gameBoard[i].push(new Cell(i, j)); // 为每个位置创建一个新方块}}this.placeMines(); // 随机放置地雷this.calculateNumbers(); // 计算每个方块周围的地雷数量}// 随机放置地雷private placeMines() {let placed = 0; // 已放置的地雷数量while (placed < this.mineCount) { // 当已放置的地雷少于设定数量时继续放置let x = Math.floor(Math.random() * 10); // 随机选择一行let y = Math.floor(Math.random() * 10); // 随机选择一列if (!this.gameBoard[x][y].hasMine) { // 如果该位置还没有地雷this.gameBoard[x][y].hasMine = true; // 在该位置放置地雷placed++; // 地雷计数加1}}}// 计算每个方块周围的地雷数量private calculateNumbers() {for (let i = 0; i < 10; i++) { // 遍历每一行for (let j = 0; j < 10; j++) { // 遍历每一列if (!this.gameBoard[i][j].hasMine) { // 如果当前方块不是地雷this.gameBoard[i][j].neighborMines = this.countNeighborMines(i, j); // 计算并设置周围地雷数量this.gameBoard[i][j].value = this.gameBoard[i][j].neighborMines.toString(); // 将数量转换为字符串} else {this.gameBoard[i][j].value = '雷'; // 如果是地雷,则设置值为"雷"}}}}// 计算给定坐标周围地雷的数量private countNeighborMines(row: number, col: number): number {let count = 0; // 周围地雷计数for (let dx = -1; dx <= 1; dx++) { // 遍历上下左右及对角线方向for (let dy = -1; dy <= 1; dy++) {if (dx === 0 && dy === 0) { // 跳过当前位置continue;}let newRow = row + dx, newCol = col + dy; // 新的行列坐标if (newRow >= 0 && newRow < 10 && newCol >= 0 && newCol < 10 && this.gameBoard[newRow][newCol].hasMine) { // 如果新位置有效且是地雷count++; // 地雷计数加1}}}return count; // 返回地雷计数}// 揭示方块private revealCell(row: number, col: number) {if (this.isGameOver || this.revealedCells.has(`${row},${col}`)) { // 如果游戏结束或者该方块已经被揭示return; // 不执行任何操作}const key = `${row},${col}`; // 创建方块唯一标识this.revealedCells.add(key); // 添加到已揭示的方块集合if (this.gameBoard[row][col].hasMine) { // 如果揭示的方块是地雷this.showGameOverDialog(); // 显示游戏结束对话框} else {if (this.gameBoard[row][col].neighborMines === 0) { // 如果揭示的方块周围没有地雷for (let dx = -1; dx <= 1; dx++) { // 自动揭示周围所有方块for (let dy = -1; dy <= 1; dy++) {if (dx === 0 && dy === 0) { // 跳过当前位置continue;}let newRow = row + dx, newCol = col + dy;if (newRow >= 0 && newRow < 10 && newCol >= 0 && newCol < 10) { // 如果新位置有效this.revealCell(newRow, newCol); // 递归揭示相邻方块}}}}}if (this.isVictory()) { // 如果游戏胜利this.showVictoryDialog(); // 显示胜利对话框}}// 显示游戏结束对话框private showGameOverDialog() {this.isGameOver = true; // 设置游戏结束promptAction.showDialog({ // 显示对话框title: '游戏结束: 游戏失败!', // 对话框标题buttons: [{ text: '重新开始', color: '#ffa500' }] // 对话框按钮}).then(() => { // 点击按钮后执行this.initializeGame(); // 重新开始游戏});}// 显示胜利对话框private showVictoryDialog() {this.isGameOver = true; // 设置游戏结束promptAction.showDialog({ // 显示对话框title: '恭喜你,游戏胜利!', // 对话框标题message: `用时:${((Date.now() - this.startTime) / 1000).toFixed(3)}秒`, // 对话框消息buttons: [{ text: '重新开始', color: '#ffa500' }] // 对话框按钮}).then(() => { // 点击按钮后执行this.initializeGame(); // 重新开始游戏});}// 判断游戏是否胜利private isVictory(): boolean {let revealedNonMineCount = 0; // 非地雷方块的揭示数量for (let i = 0; i < this.gameBoard.length; i++) { // 遍历每一行for (let j = 0; j < this.gameBoard[i].length; j++) { // 遍历每一列if (this.revealedCells.has(`${i},${j}`)) { // 如果方块已被揭示revealedNonMineCount++; // 非地雷方块的揭示数量加1}}}return revealedNonMineCount == 90; // 如果非地雷方块全部揭示,则游戏胜利}// 决定是否显示方块值private isShowValue(cell: Cell): string {if (this.isGameOver) { // 如果游戏结束return cell.value === '0' ? '' : cell.value; // 显示方块值,除非是0} else {if (this.revealedCells.has(`${cell.row},${cell.column}`)) { // 如果方块已被揭示return cell.value === '0' ? '' : cell.value; // 显示方块值,除非是0} else {return ''; // 否则不显示值}}}// 构建用户界面build() {Column({ space: 10 }) { // 创建垂直布局,元素之间间距10Button('重新开始').onClick(() => this.initializeGame()); // 创建“重新开始”按钮,点击时重新开始游戏Flex({ wrap: FlexWrap.Wrap }) { // 创建可换行的水平布局ForEach(this.gameBoard, (row: Cell[], rowIndex: number) => { // 遍历每一行ForEach(row, (cell: Cell, colIndex: number) => { // 遍历每一列Stack() { // 创建层叠布局Text(this.isShowValue(cell)) // 显示方块上的数字或“雷”.width(`${this.cellSize}lpx`) // 设置宽度.height(`${this.cellSize}lpx`) // 设置高度.margin(`${this.cellMargin}lpx`) // 设置边距.fontSize(`${this.cellSize / 2}lpx`) // 设置字体大小.textAlign(TextAlign.Center) // 文本居中.backgroundColor(this.revealedCells.has(`${rowIndex},${colIndex}`) ? // 设置背景颜色(this.isShowValue(cell) === '雷' ? Color.Red : Color.White) : Color.Gray).fontColor(!this.revealedCells.has(`${rowIndex},${colIndex}`) || this.isShowValue(cell) === '雷' ? // 设置字体颜色Color.White : Color.Black).borderRadius(5) // 设置圆角.parallelGesture(GestureGroup(GestureMode.Exclusive, // 设置手势TapGesture({ count: 1, fingers: 1 }) // 单击揭示方块.onAction(() => this.revealCell(rowIndex, colIndex)),LongPressGesture({ repeat: true }) // 长按标记地雷.onAction(() => cell.isFlag = true)));Text(`${!this.revealedCells.has(`${rowIndex},${colIndex}`) ? '旗' : ''}`) // 显示标记旗帜.width(`${this.cellSize}lpx`) // 设置宽度.height(`${this.cellSize}lpx`) // 设置高度.margin(`${this.cellMargin}lpx`) // 设置边距.fontSize(`${this.cellSize / 2}lpx`) // 设置字体大小.textAlign(TextAlign.Center) // 文本居中.fontColor(Color.White) // 设置字体颜色.visibility(cell.isFlag && !this.isGameOver ? Visibility.Visible : Visibility.None) // 设置可见性.onClick(() => { // 点击取消标记cell.isFlag = false;})}});});}.width(`${(this.cellSize + this.cellMargin * 2) * 10}lpx`); // 设置游戏面板宽度}.backgroundColor(Color.Orange) // 设置背景颜色.width('100%') // 设置宽度为100%.height('100%'); // 设置高度为100%}
}
相关文章:
鸿蒙NEXT小游戏开发:扫雷
1. 引言 本文将介绍如何使用鸿蒙NEXT框架开发一个简单的扫雷游戏。通过本案例,您将学习到如何利用鸿蒙NEXT的组件化特性、状态管理以及用户交互设计来构建一个完整的游戏应用。 2. 环境准备 电脑系统:windows 10 工程版本:API 12 真机&…...
【doris】Apache Doris简介
目录 1. 概述2. 技术特点2.1 高性能查询2.2 实时数据导入2.3 易于使用2.4 高可扩展性2.5 数据模型2.6 容错性 3. 适用场景4. 部署与架构4.1 部署方式4.2 架构特点 5. 优势 1. 概述 1.Apache Doris(原名Palo)最早诞生于百度广告报表业务,2017…...
LangChain4j 入门(二)
LangChain 整合 SpringBoot 下述代码均使用 阿里云百炼平台 提供的模型。 创建项目,引入依赖 通过 IDEA 创建 SpringBoot 项目,并引入 Spring Web 依赖,SpringBoot 推荐使用 3.x 版本。 引入 LangChain4j 和 WebFlux 依赖 <!--阿里云 D…...
小智机器人关键函数解析:MqttProtocol::SendAudio()对输入的音频数据进行加密处理,通过UDP发送加密后的音频数据
MqttProtocol::SendAudio()对输入的音频数据进行加密处理,通过UDP发送加密后的音频数据。 源码: void MqttProtocol::SendAudio(const std::vector<uint8_t>& data) {// 使用互斥锁保护临界区,确保同一时间只有一个线程可以访问该…...
npm i 失败
当npm i 失败 且提示下面的错误 尝试降低npm 的版本 npm install npm6.14.15 -g...
音视频基础(音视频的录制和播放原理)
文章目录 一、录制原理**1. 音视频数据解析****2. 音频处理流程****3. 视频处理流程****4. 同步控制****5. 关键技术点****总结** 二、播放原理**1. 音视频数据解析****2. 音频处理流程****3. 视频处理流程****4. 同步控制****5. 关键技术点****总结** 一、录制原理 这张图展示…...
CoAP Shell 笔记
CoAP Shell 笔记 1. 概述 CoAP (Constrained Application Protocol) 是一种专为物联网 (IoT) 中资源受限的节点和网络设计的 RESTful Web 传输协议。CoAP Shell 是一个基于命令行的交互式工具,用于与支持 CoAP 的服务器进行交互。 2. 主要功能 协议支持ÿ…...
回溯(子集型):分割回文串
一、多维递归 -> 回溯 1.1:17. 电话号码的字母组合(力扣hot100) 代码: mapping ["","", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv&qu…...
2022年蓝桥杯第十三届CC++大学B组真题及代码
目录 1A:九进制转十进制 2B:顺子日期(存在争议) 3C:刷题统计 解析代码(模拟) 4D:修剪灌木 解析代码(找规律) 5E:X进制减法 解析代码1&…...
1.oracle修改配置文件
1.找到oracle的安装路径 D:\app\baozi\product\11.2.0\dbhome_1\NETWORK\ADMIN ,修改下面的两个文件。如果提示没有权限,可以先把这两个文件复制到桌面,修改完后,在复制回来。 2.查看自己电脑的主机名, 右击 - 此电脑 …...
通义万相2.1 你的视频创作之路
通义万相2.1的全面介绍 一、核心功能与技术特点 通义万相2.1是阿里巴巴达摩院研发的多模态生成式AI模型,以视频生成为核心,同时支持图像、3D内容及中英文文字特效生成。其核心能力包括: 复杂动作与物理规律建模 能够稳定生成包含人体旋转、…...
Muduo网络库实现 [四] - Channel模块
设计思路 具体来说每一个套接字都会对应一个 Channel 对象,用于对它的事件进行管理。可以对于描述符的监控事件在用户态更容易维护,以及触发事件后的操作流程更加的清晰 Channel模块是用于对一个描述符所需要监控的事件以及事件触发之后要执行的回调函…...
“自动驾驶背后的数学” 专栏导读
专栏链接: 自动驾驶背后的数学 专栏以“自动驾驶背后的数学”为主题,从基础到深入,再到实际应用和未来展望,全面解析自动驾驶技术中的数学原理。开篇用基础数学工具搭建自动驾驶的整体框架,吸引儿童培养兴趣࿰…...
XSS 攻击(详细)
目录 引言 一、XSS 攻击简介 二、XSS 攻击类型 1.反射型 XSS 2.存储型 XSS 3.基于 DOM 的 XSS 4.Self - XSS 三、XSS 攻击技巧 1.基本变形 2.事件处理程序 3.JS 伪协议 4.编码绕过 5.绕过长度限制 6.使用标签 四、XSS 攻击工具与平台 1.XSS 攻击平台 2.BEEF 五…...
K8s负载均衡全解析:从入门到实战的完整指南
Kubernetes(K8s)作为容器编排的标准,其负载均衡机制是构建高可用、高弹性应用的关键。本文将全面介绍K8s负载均衡的核心概念、实现方式及最佳实践,帮助开发者和运维人员构建稳定高效的云原生应用。 一、K8s负载均衡的基础概念 在Kubernetes生态系统中,负载均衡是指将工作负…...
《ZooKeeper Zab协议深度剖析:构建高可用分布式系统的基石》
《ZooKeeper Zab协议深度剖析:构建高可用分布式系统的基石》 一、分布式协调的挑战与ZooKeeper的解决方案 1.1 分布式系统一致性难题 #mermaid-svg-iigak7YlgEw7o6lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-sv…...
OpenCV 图形API(6)将一个矩阵(或图像)与一个标量值相加的函数addC()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 addC 函数将给定的标量值加到给定矩阵的每个元素上。该功能可以用矩阵表达式替换: dst src1 c \texttt{dst} \texttt{src1} \te…...
同步SVPWM调制策略的初步学习记录
最近项目需要用到一些同步调制SVPWM相关的内容(现在的我基本都是项目驱动了),因此对该内容进行一定的学习。 1 同步SVPWM调制的背景 我们熟知的一些知识是:SVPWM(空间矢量脉宽调制)是一种用于逆变器的调制…...
六十天Linux从0到项目搭建(第十五天)(程序替换、exec流程示意图、核心特性)
1 为什么要有程序替换? 程序替换(Process Replacement)是操作系统中一个关键机制,它的核心目的是:让一个正在运行的进程(通常是子进程)停止执行当前代码,转而加载并执行一个全新的程…...
排序算法3-交换排序
目录 1.常见排序算法 2.排序算法的预定函数 2.1交换函数 2.2测试算法运行时间的函数 2.3已经实现过的排序算法 3.交换排序的实现 3.1冒泡排序 3.2快速排序 3.2.1递归的快速排序 3.2.1.1hoare版本的排序 3.2.1.2挖坑法 3.2.1.3lomuto前后指针法 3.2.2非递归版本的快…...
【Qt】数据库管理
数据库查询工具开发学习笔记 一、项目背景与目标 背景:频繁编写数据库查询语句,希望通过工具简化操作,提升效率。 二、总体设计思路 1. 架构设计 MVC模式:通过Qt控件实现视图(UI),业务逻辑…...
Ant Design Vue 中的table表格高度塌陷,造成行与行不齐的问题
前言: Ant Design Vue: 1.7.2 Vue2 less 问题描述: 在通过下拉框选择之后,在获取接口数据,第一列使用了fixed:left,就碰到了高度塌陷,查看元素的样式结果高度不一致,如&#x…...
面经-项目
项目 项目(重点)问题1:描述在网页中题目点击提交后到题目结果出现的一系列后台反应【1】如何获取到用户提交的代码的?【2】_1. 题目细节都有哪些?【2】_2. 题目信息怎么存储的?【3】负载均衡算法的实现?【4】oj_server怎么连接对应的compile_server(编译主机)的?【5】oj_…...
Win10安装Linux的三种方法
通过 Windows 子系统 for Linux(WSL)安装 启用 “适用于 Linux 的 Windows 子系统” 可选功能: 图形界面方式:在【设置 -> 更新与安全 -> 开发者选项】中开启【开发人员模式】;在【程序和功能 -> 启用或关闭…...
【qt】文件类(QFile)
很高兴你能看到这篇文章,同时我的语雀文档也更新了许多嵌入式系列的学习笔记希望能帮到你 : https://www.yuque.com/alive-m4b9n 目录 QFile 主要功能QFile 操作步骤QFile 其他常用函数案例分析及实现功能一实现:打开文件并显示功能二实现:另…...
力扣hot100——最长连续序列(哈希unordered_set)
题目链接:最长连续序列 1、错解:数组做哈希表(内存超出限制) int longestConsecutive(vector<int>& nums) {vector<bool> hash(20000000010, false);for(int i0; i<nums.size();i){hash[1000000000nums[i]]t…...
3. 实战(一):Spring AI Trae ,助力开发微信小程序
1、前言 前面介绍了Spring boot快速集成Spring AI实现简单的Chat聊天模式。今天立马来实战一番,通过Trae这个火爆全网的工具,来写一个微信小程序。照理说,我们只是极少量的编码应该就可以完成这项工作。开撸~ 2、需求描述 微信小程序实现一…...
MySQL高级语句深度解析与应用实践
一、窗口函数:数据分析的利器 1. 窗口函数基础概念 窗口函数(Window Function)是MySQL 8.0引入的强大特性,它可以在不减少行数的情况下对数据进行聚合计算和分析 SELECT employee_name,department,salary,RANK() OVER (PARTITION BY department ORDER…...
SSE服务器主动推送至浏览器客户端,让你不再需要websocket
Server-Sent Events(SSE)是一种服务器向客户端推送实时更新的技术,基于HTTP协议。客户端通过EventSource API来接收事件流,而服务器则保持一个长连接,持续发送数据。这与传统的请求-响应模式不同,允许服务器…...
UE5新材质系统效果Demo展示
1、玉质材质,透明玻璃材质,不同透射和散射。 2、浅水地面,地面层,水层,地面湿度,水面高度,水下扰动,水下浇洒,水下折射 Substrate-Water Substrate-Water-CodeV2...
