当前位置: 首页 > news >正文

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

相关文章:

  • docker stats和/proc/pid/status内存统计的差异问题
  • 生成式人工智能实战 | WGAN(Wasserstein Generative Adversarial Network, GAN)
  • GO 语言学习 之 变量和常量
  • 【git学习】学习目标及课程安排
  • React Native 如何实现拉起App
  • Spring Boot 3.2.11 Swagger版本推荐
  • js防止重复提交的3种解决方案
  • 小程序学习笔记:自定义组件创建、引用、应用场景及与页面的区别
  • AI辅助编写前端VUE应用流程
  • 开疆智能CCLinkIE转ModbusTCP网关连接组态王配置案例
  • MySQL在C中常用的API接口
  • [Python] -基础篇2-Python中的变量和数据类型详解
  • Maven生命周期与阶段扩展深度解析
  • Tomcat Maven 插件
  • 本年度TOP5服装收银系统对比推荐
  • 工作台-01.需求分析与设计
  • Java基础(五):流程控制全解析——分支(if/switch)和循环(for/while)的深度指南
  • iOS 远程调试与离线排查实战:构建非现场问题复现机制
  • 如何构建个人AIagent
  • RabitQ 量化:既省内存又提性能