記憶翻牌游戲是一款經典的益智游戲,它能有效鍛煉玩家的記憶力和觀察能力。本文將詳細介紹如何使用鴻蒙(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);})
}
關鍵技術與注意事項
- 狀態管理:在鴻蒙開發中,直接修改數組元素不會觸發UI更新。我們需要使用
slice(0)
創建新數組,然后修改并重新賦值給狀態變量。 - 定時器管理:游戲計時器需要在組件銷毀或游戲重新開始時正確清理,避免內存泄漏。
- UI更新優化:通過將卡片視圖提取為獨立的
@Builder
方法,可以提高代碼的可讀性和維護性。 - 用戶體驗:
-
- 添加了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;
}
總結
通過這個記憶翻牌游戲的開發,我們學習了鴻蒙應用開發中的幾個重要概念:
- 使用
@State
管理應用狀態 - 聲明式UI構建方式
- 數組狀態更新的正確方法
- 定時器的使用和管理
- 用戶交互處理的最佳實踐
這款游戲雖然簡單,但涵蓋了鴻蒙應用開發的許多核心概念。開發者可以在此基礎上進一步擴展,比如添加難度選擇、音效、動畫效果、高分記錄等功能,打造更加豐富的游戲體驗。
鴻蒙的ArkUI框架為開發者提供了強大的工具來構建響應式、高性能的應用。通過這個實戰項目,希望能幫助開發者更好地理解鴻蒙應用開發的思路和方法。