鸿蒙HarmonyOS 5小游戏实践:记忆翻牌(附:源代码)

记忆翻牌游戏是一款经典的益智游戏,它能有效锻炼玩家的记忆力和观察能力。本文将详细介绍如何使用鸿蒙(HarmonyOS)的ArkUI框架开发一款完整的记忆翻牌游戏,涵盖游戏设计、核心逻辑实现和界面构建的全过程。

游戏设计概述

记忆翻牌游戏的基本规则很简单:玩家需要翻开卡片并找出所有匹配的卡片对。在我们的实现中,游戏包含以下特点:

  • 4×4的棋盘布局(16张卡片,8对图案)
  • 使用可爱的动物表情符号作为卡片内容
  • 计时和计步功能
  • 新游戏和重新开始功能
  • 游戏胜利提示

游戏状态管理

在鸿蒙开发中,状态管理是关键。我们使用@State装饰器来管理游戏的各种状态:

@State cards: Card[] = [];          // 所有卡片数组
@State firstCard: number | null = null;  // 第一张翻开的卡片索引
@State secondCard: number | null = null; // 第二张翻开的卡片索引
@State moves: number = 0;          // 移动步数
@State gameOver: boolean = false;   // 游戏是否结束
@State timer: number = 0;          // 游戏用时

这种状态管理方式确保了当这些值发生变化时,UI能够自动更新。

核心游戏逻辑实现

1. 游戏初始化

游戏初始化包括创建卡片对、洗牌和设置初始状态:

startNewGame() {// 重置游戏状态this.moves = 0;this.timer = 0;this.gameOver = false;this.firstCard = null;this.secondCard = null;// 创建卡片对let cardValues: string[] = [];for (let i = 0; i < this.PAIRS_COUNT; i++) {cardValues.push(this.CARD_TYPES[i]);cardValues.push(this.CARD_TYPES[i]);}// 洗牌this.shuffleArray(cardValues);// 初始化卡片状态this.cards = cardValues.map(value => ({value,flipped: false,matched: false})).slice(0); // 使用slice(0)确保UI更新// 开始计时this.timerInterval = setInterval(() => {this.timer++;}, 1000);
}

2. 洗牌算法

我们使用经典的Fisher-Yates洗牌算法来随机排列卡片:

private shuffleArray(array: string[]) {for (let i = array.length - 1; i > 0; i--) {const j = Math.floor(Math.random() * (i + 1));const temp = array[i];array[i] = array[j];array[j] = temp;}
}

3. 卡片点击处理

卡片点击是游戏的核心交互,需要处理多种情况:

handleCardClick(index: number) {// 检查是否可点击if (this.gameOver || this.cards[index].matched || this.cards[index].flipped) {return;}if (this.firstCard !== null && this.secondCard !== null) {return;}// 创建新数组触发UI更新let newCards = this.cards.slice(0);newCards[index].flipped = true;this.cards = newCards;// 设置第一张或第二张卡片if (this.firstCard === null) {this.firstCard = index;} else {this.secondCard = index;this.moves++;this.checkMatch(); // 检查匹配}
}

4. 匹配检查

匹配检查逻辑决定了游戏的胜负:

private checkMatch() {if (this.firstCard === null || this.secondCard === null) return;if (this.cards[this.firstCard].value === this.cards[this.secondCard].value) {// 匹配成功let newCards = this.cards.slice(0);newCards[this.firstCard].matched = true;newCards[this.secondCard].matched = true;this.cards = newCards;this.firstCard = null;this.secondCard = null;this.checkGameOver(); // 检查游戏是否结束} else {// 不匹配,1秒后翻回setTimeout(() => {let newCards = this.cards.slice(0);if (this.firstCard !== null) newCards[this.firstCard].flipped = false;if (this.secondCard !== null) newCards[this.secondCard].flipped = false;this.cards = newCards;this.firstCard = null;this.secondCard = null;}, 1000);}
}

界面构建

鸿蒙的ArkUI框架提供了声明式的UI构建方式,我们使用Grid布局来构建4×4的游戏棋盘:

build() {Column() {// 游戏标题和信息显示Text('记忆翻牌游戏').fontSize(24).fontWeight(FontWeight.Bold)Row() {Text(`步数: ${this.moves}`)Text(`时间: ${this.formatTime(this.timer)}`)}// 游戏棋盘Grid() {ForEach(this.cards, (card: Card, index) => {GridItem() {this.CardView(card, index)}})}.columnsTemplate('1fr 1fr 1fr 1fr').rowsTemplate('1fr 1fr 1fr 1fr')// 新游戏按钮Button('新游戏').onClick(() => this.startNewGame())// 游戏结束提示if (this.gameOver) {Text('恭喜通关!')}}
}

卡片视图使用Stack和Column组合实现:

@Builder
CardView(card: Card, index: number) {Stack() {Column() {if (!card.flipped) {Text('?') // 卡片背面} else {Text(card.value) // 卡片正面}}.backgroundColor(card.flipped ? (card.matched ? '#4CAF50' : '#FFFFFF') : '#2196F3').borderRadius(10)}.onClick(() => {this.handleCardClick(index);})
}

关键技术与注意事项

  1. 状态管理:在鸿蒙开发中,直接修改数组元素不会触发UI更新。我们需要使用slice(0)创建新数组,然后修改并重新赋值给状态变量。
  2. 定时器管理:游戏计时器需要在组件销毁或游戏重新开始时正确清理,避免内存泄漏。
  3. UI更新优化:通过将卡片视图提取为独立的@Builder方法,可以提高代码的可读性和维护性。
  4. 用户体验
    • 添加了1秒的延迟让玩家有机会记住不匹配的卡片
    • 匹配成功的卡片变为绿色,提供视觉反馈
    • 显示游戏时间和步数,增加挑战性

附:代码

// MemoryGame.ets
@Entry
@Component
struct MemoryGame {// 游戏配置private readonly CARD_TYPES = ['🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼'];private readonly PAIRS_COUNT = 8;private readonly BOARD_SIZE = 4;// 游戏状态@State cards: Card[] = [];@State firstCard: number | null = null;@State secondCard: number | null = null;@State moves: number = 0;@State gameOver: boolean = false;@State timer: number = 0;private timerInterval: number | null = null;aboutToAppear() {this.startNewGame();}startNewGame() {if (this.timerInterval) {clearInterval(this.timerInterval);}this.moves = 0;this.timer = 0;this.gameOver = false;this.firstCard = null;this.secondCard = null;let cardValues: string[] = [];for (let i = 0; i < this.PAIRS_COUNT; i++) {cardValues.push(this.CARD_TYPES[i]);cardValues.push(this.CARD_TYPES[i]);}this.shuffleArray(cardValues);// 使用slice(0)创建新数组触发UI更新// 初始化卡片this.cards = cardValues.map(value => {let card: Card = {value: value,flipped: false,matched: false};return card}).slice(0);this.timerInterval = setInterval(() => {this.timer++;}, 1000);}private shuffleArray(array: string[]) {for (let i = array.length - 1; i > 0; i--) {const j = Math.floor(Math.random() * (i + 1));const temp = array[i];array[i] = array[j];array[j] = temp;}}handleCardClick(index: number) {if (this.gameOver || this.cards[index].matched || this.cards[index].flipped) {return;}if (this.firstCard !== null && this.secondCard !== null) {return;}// 创建新数组触发UI更新let newCards = this.cards.slice(0);newCards[index].flipped = true;this.cards = newCards;if (this.firstCard === null) {this.firstCard = index;} else {this.secondCard = index;this.moves++;this.checkMatch();}}private checkMatch() {if (this.firstCard === null || this.secondCard === null) return;if (this.cards[this.firstCard].value === this.cards[this.secondCard].value) {// 匹配成功let newCards = this.cards.slice(0);newCards[this.firstCard].matched = true;newCards[this.secondCard].matched = true;this.cards = newCards;this.firstCard = null;this.secondCard = null;this.checkGameOver();} else {// 不匹配,1秒后翻回setTimeout(() => {let newCards = this.cards.slice(0);if (this.firstCard !== null) newCards[this.firstCard].flipped = false;if (this.secondCard !== null) newCards[this.secondCard].flipped = false;this.cards = newCards;this.firstCard = null;this.secondCard = null;}, 1000);}}private checkGameOver() {this.gameOver = this.cards.every(card => card.matched);if (this.gameOver && this.timerInterval) {clearInterval(this.timerInterval);}}private formatTime(seconds: number): string {const mins = Math.floor(seconds / 60);const secs = seconds % 60;return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;}build() {Column() {Text('记忆翻牌游戏').fontSize(24).fontWeight(FontWeight.Bold).margin({ bottom: 20 })Row() {Text(`步数: ${this.moves}`).fontSize(16).layoutWeight(1)Text(`时间: ${this.formatTime(this.timer)}`).fontSize(16).layoutWeight(1)}.width('100%').margin({ bottom: 20 })Grid() {ForEach(this.cards, (card: Card, index) => {GridItem() {this.CardView(card, index)}})}.columnsTemplate('1fr 1fr 1fr 1fr').rowsTemplate('1fr 1fr 1fr 1fr').width('100%').height(400).margin({ bottom: 20 })Button('新游戏').width(200).height(40).backgroundColor('#4CAF50').fontColor(Color.White).onClick(() => this.startNewGame())if (this.gameOver) {Text('恭喜通关!').fontSize(20).fontColor(Color.Red).margin({ top: 20 })}}.width('100%').height('100%').padding(20).justifyContent(FlexAlign.Center)}@BuilderCardView(card: Card, index: number) {Stack() {Column() {if (!card.flipped) {Text('?').fontSize(30)} else {Text(card.value).fontSize(30)}}.width('90%').height('90%').backgroundColor(card.flipped ? (card.matched ? '#4CAF50' : '#FFFFFF') : '#2196F3').borderRadius(10).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)}.width('100%').height('100%').onClick(() => {this.handleCardClick(index);})}
}interface Card {value: string;flipped: boolean;matched: boolean;
}

总结

通过这个记忆翻牌游戏的开发,我们学习了鸿蒙应用开发中的几个重要概念:

  1. 使用@State管理应用状态
  2. 声明式UI构建方式
  3. 数组状态更新的正确方法
  4. 定时器的使用和管理
  5. 用户交互处理的最佳实践

这款游戏虽然简单,但涵盖了鸿蒙应用开发的许多核心概念。开发者可以在此基础上进一步扩展,比如添加难度选择、音效、动画效果、高分记录等功能,打造更加丰富的游戏体验。

鸿蒙的ArkUI框架为开发者提供了强大的工具来构建响应式、高性能的应用。通过这个实战项目,希望能帮助开发者更好地理解鸿蒙应用开发的思路和方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/912408.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/912408.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux庖丁解牛】— 文件系统!

1 引⼊"块"概念 其实硬盘是典型的“块”设备&#xff0c;操作系统读取硬盘数据的时候&#xff0c;其实是不会⼀个个扇区地读取&#xff0c;这样 效率太低&#xff0c;⽽是⼀次性连续读取多个扇区&#xff0c;即⼀次性读取⼀个”块”&#xff08;block&#xff09;。…

如何通过自动化减少重复性工作

通过自动化减少重复性工作的关键策略包括&#xff1a;1、识别可被规则化操作的任务、2、引入RPA&#xff08;机器人流程自动化&#xff09;工具、3、整合AI与业务流程系统、4、部署脚本与低代码平台、5、持续优化自动化场景与效率。 其中&#xff0c;“引入RPA工具”被广泛认为…

知识变现全链路设计:从IP打造到商业闭环的系统方法论|创客匠人

一、变现低效根源&#xff1a;碎片化努力为何换不来持续增长&#xff1f; 创客匠人服务上千位知识创业者后发现&#xff0c;变现乏力多因缺乏系统设计&#xff1a;某营销专家的课程因定位模糊、表达生硬、渠道单一&#xff0c;低价仍少有人问。文档中提出的“六大超级设计公式…

如何利用人工智能大模型提升流量质量

摘要 流量质量是衡量数字化营销效果的重要指标之一&#xff0c;它反映了用户对网站或应用的兴趣和满意度。流量质量的常用评估方法有点击率、跳出率和用户停留时间等。本文将介绍如何利用人工智能大模型来分析和优化这些指标&#xff0c;提高流量质量&#xff0c;从而提升数字…

从单体架构到微服务:微服务架构演进与实践

一、单体架构的困境与演进 &#xff08;一&#xff09;单体应用的初始优势与演进路径 在系统发展的初期&#xff0c;单体架构凭借其简单性和开发效率成为首选。单体应用将整个系统的所有功能模块整合在一个项目中&#xff0c;以单一进程的方式运行&#xff0c;特别适合小型系…

Elasticsearch 自定义排序:使用 Painless 脚本实现复杂排序逻辑

需求背景&#xff1a; 从es查询数据出来的时候&#xff0c;要求type为CATALOG的数据排在最前面&#xff0c;也就是目录类型的要放在最前面&#xff0c;而且要求按照层级排序&#xff0c;从L1到L5顺序排序 直接上解法&#xff1a; {//查询条件"query": {"bool…

华为云Flexus+DeepSeek征文|华为云数字人 + DeepSeek:智能交互的革命性突破

目录 前言 关于华为云数字人和云服务 1、华为云数字人 &#xff08;1&#xff09;MetaStudio介绍 &#xff08;2&#xff09;应用场景 &#xff08;3&#xff09;功能特性 &#xff08;4&#xff09;使用体验 2、华为云云服务 华为云数字人结合DeepSeek的核心流程 1、…

【GESP】C++四级练习 luogu-P5729 【深基5.例7】工艺品制作

GESP C四级练习&#xff0c;二维/多维数组练习&#xff0c;难度★★☆☆☆。 题目题解详见&#xff1a;【GESP】C四级练习 luogu-P5729 【深基5.例7】工艺品制作 | OneCoder 【GESP】C四级练习 luogu-P5729 【深基5.例7】工艺品制作 | OneCoderGESP C四级练习&#xff0c;二维…

通过npm install -g yarn安装Yarn显示Proxy代理相关问题如何解决?

手动下载yarn.msi安装包或者yarn.js文件 参考&#xff1a;windows 怎么下载yarn安装包并将下载的yarn文件移动到全局目录并添加执行权限&#xff1f;-CSDN博客

arm交叉编译qt应用中含opengl问题解决

问题是采用正点原子方案中&#xff0c;用虚拟机交叉编译含opengl的qt程序会出现编译失败问题&#xff0c;因为正点原子中的交叉编译qt源码时没有编opengl。 野火似乎有解决&#xff1a; https://doc.embedfire.com/linux/rk356x/Qt/zh/latest/lubancat_qt/install/install_arm…

服务器排查与加固服务详细介绍

一、服务概述 服务器排查与加固服务是针对企业核心信息资产&#xff08;服务器&#xff09;的全方位安全保障方案&#xff0c;旨在通过系统性排查潜在风险、修复漏洞、优化配置&#xff0c;提升服务器抗攻击能力&#xff0c;确保业务连续性和数据安全性。该服务覆盖硬件、操作…

提升开发思维的设计模式(下)

上期回顾 提升开发思维的设计模式&#xff08;上&#xff09; 2. 设计模式分类&#xff08;23种设计模式&#xff09; 2.13 组合模式&#xff08;Composite Pattern&#xff09; 将对象组合成树形结构&#xff0c;以表示“整体-部分”的层次结构。 通过对象的多态表现&#…

h5学习笔记:前端打包

这2天做了一个实验。在非module传统的网页&#xff0c;要实现改名和避免缓存。原本这个事情早在几年前就做过借助gulp的方式或者fis3 的工具来完成。然而随着nodejs 来到了24版本后&#xff0c;似乎nodejs的版本这事情就变动复杂多变了。 为什么那么麻烦&#xff1f;实际上开发…

14.OCR字符识别

目录 1. 识别方法 1. OCR识别 2. OCR识别方法1-助手识别 3. OCR识别方法2-算子分割识别 4.文本分割识别 2. 文本分割 1. 借用助手设置参数文本分割+混合识别 2. 借用助手设置参数文本分割场景2 3.不同字符场景 1.倾斜字符 1. 识别方法 1. OCR识别 *OCR *1. 概念 * …

如果将Word里每页的行数设置成50行

https://www.zhihu.com/question/357856175 本文来自知乎林听晴 第一步&#xff1a;新建一个Word文档 打开“页面布局”&#xff0c;之后点击图片圈起来的小图标&#xff0c;即可出现“页面设置”页面。 ​ ​ 路径&#xff1a;页面设置—文档网络&#xff0c;可以看到默认行…

WebRTC(十一):RTCP和SRTCP

RTCP 基本概念 RTCP 是 RTP 的控制协议&#xff0c;用于监控媒体传输质量和参与者状态&#xff0c;并与 RTP 一起工作。RTP 用于传输媒体数据&#xff08;如音视频&#xff09;&#xff0c;RTCP 则用于传输控制信息。 RTCP 通常和 RTP 同时使用&#xff0c;并通过 不同端口&…

将element-plus table背景改成透明色

方法一:全局修改(推荐) /* 全局透明表格样式 */ .el-table, .el-table__header-wrapper, .el-table__body-wrapper, .el-table__row {background-color: transparent !important; }/* 可选:自定义表头和斑马纹行的透明度 */ .el-table__header th {background-color: rgba(…

安全运营中的漏洞管理和相关KPI

漏洞管理一直是企业网络安全运维中的关键环节,但又是安全运维的痛点。不仅要投入大量的人力物力,还无法被其他运维团队所理解。那么,向领导层和相关团队反映出当前漏洞管理的现状和挑战便是一个急需解决的问题。 通过有效的数据讲好故事,发现问题,或许是做好漏洞管理的突破…

机器学习框架(1)

以吴恩达的《机器学习》课程为蓝本&#xff0c;整理课程框架&#xff0c;自己学习的简单记录。 课程讲解很清楚&#xff0c;建议有空可以看看原课程。 01 单变量线性回归 回归偏向于连续属性&#xff0c;分类偏向于离散属性。 监督学习是给定标签的学习&#xff1b;而无监督学…

AI Ready数据库,OceanBase打了一个样

大数据产业创新服务媒体 ——聚焦数据 改变商业 过去一年&#xff0c;企业对AI的兴趣不减。从接入大模型&#xff0c;到部署RAG&#xff08;检索增强生成&#xff09;系统、探索AI Agent&#xff0c;AI从“新技术”变成了“业务工具”的候选项。但一个技术能否真正落地&#x…