HarmonyOS 诗词填空游戏开发实战教程(非AI生成 提供源代码和演示视频)
HarmonyOS 诗词填空游戏开发实战教程
效果视频
鸿蒙版古诗词填空游戏
前言
本教程将手把手教你开发一个古诗词填空小游戏,适合 HarmonyOS 初学者跟随学习。完成本教程后,你将学会:
- 如何组织 HarmonyOS 项目结构
- 如何管理游戏数据
- 如何实现游戏界面和交互逻辑
- 如何配置页面路由
开发环境要求:
- DevEco Studio(推荐最新版本)
- HarmonyOS SDK 6.0.0 或以上
- 基础的 TypeScript 知识
预计用时: 30-45 分钟
让我们开始吧!
第一步:创建项目和目录结构
1.1 打开你的 HarmonyOS 项目
假设你已经有一个名为 tiankong 的 HarmonyOS 项目。如果没有,请先用 DevEco Studio 创建一个新项目。
1.2 创建所需目录
我们需要创建两个新文件夹来组织代码:
方法一:使用 DevEco Studio
- 在项目视图中,找到
entry/src/main/ets/目录 - 右键点击
ets文件夹 →New→Directory - 输入
data,回车创建 - 再次右键点击
ets文件夹 →New→Directory - 输入
pages/game(会自动创建两级目录),回车创建
方法二:使用命令行
打开终端(Terminal),执行:
cd 你的项目路径/tiankong
mkdir -p entry/src/main/ets/data
mkdir -p entry/src/main/ets/pages/game
验证结果:
你的目录结构应该如下:
tiankong/
└── entry/└── src/└── main/└── ets/├── data/ ← 新建(存放数据文件)├── pages/│ ├── game/ ← 新建(存放游戏页面)│ └── Index.ets├── entryability/└── entrybackupability/
第二步:创建诗词数据库
现在我们来创建第一个文件,存放所有的诗词数据。
2.1 创建文件
- 在 DevEco Studio 中,右键点击
entry/src/main/ets/data文件夹 - 选择
New→ArkTS File - 输入文件名:
CultureData(不需要 .ets 后缀) - 点击确定
2.2 编写数据结构
打开刚创建的 CultureData.ets 文件,先定义数据类型:
/** 诗词园地数据*/// 定义文化类别:诗词、歇后语、故事
export type CultureCategory = 'poetry' | 'idiom' | 'story'// 定义动物类型:鸡、鸭、鹅
export type AnimalType = 'chicken' | 'duck' | 'goose'// 定义文化数据项的结构
export interface CultureItem {id: string // 唯一标识category: CultureCategory // 类别animalType: AnimalType // 动物类型title: string // 标题(诗词名)content: string // 内容(诗词正文)author?: string // 作者dynasty?: string // 朝代explanation?: string // 解释说明tags: string[] // 标签isFavorite: boolean // 是否收藏
}
💡 知识点:
export表示导出,其他文件可以导入使用type定义类型别名interface定义对象的结构?表示可选属性
2.3 创建数据库类
继续在同一个文件中添加数据库类:
export class CultureDatabase {/*** 获取所有诗词数据*/public static getPoetryData(): CultureItem[] {return [// 第一首诗:咏鹅{id: 'poetry_goose_001',category: 'poetry',animalType: 'goose',title: '咏鹅',author: '骆宾王',dynasty: '唐',content: '鹅,鹅,鹅,曲项向天歌。\n白毛浮绿水,红掌拨清波。',explanation: '这首诗是骆宾王七岁时所作,通过对鹅的形态、颜色、动作的描写,生动形象地展现了白鹅的美丽和可爱。',tags: ['骆宾王', '咏物诗', '儿童诗', '经典'],isFavorite: false},// 第二首诗:画鸡{id: 'poetry_chicken_001',category: 'poetry',animalType: 'chicken',title: '画鸡',author: '唐寅',dynasty: '明',content: '头上红冠不用裁,满身雪白走将来。\n平生不敢轻言语,一叫千门万户开。',explanation: '这首诗通过描绘大公鸡的形象,赞美了它的美丽和报晓的功劳。"一叫千门万户开"突出了公鸡报晓的重要作用。',tags: ['唐寅', '咏物诗', '寓意深刻'],isFavorite: false},// 第三首诗:惠崇春江晚景{id: 'poetry_duck_001',category: 'poetry',animalType: 'duck',title: '惠崇春江晚景',author: '苏轼',dynasty: '宋',content: '竹外桃花三两枝,春江水暖鸭先知。\n蒌蒿满地芦芽短,正是河豚欲上时。',explanation: '"春江水暖鸭先知"成为千古名句,生动描绘了鸭子在春江中嬉戏的情景。',tags: ['苏轼', '题画诗', '春景', '名句'],isFavorite: false}// 💡 这里可以继续添加更多诗词// 完整版包含17首诗词,为了简洁,这里只展示3首// 你可以从文末的完整代码中复制其余诗词]}/*** 获取所有歇后语数据(暂时返回空数组)*/public static getIdiomData(): CultureItem[] {return []}/*** 获取所有成语故事数据(暂时返回空数组)*/public static getStoryData(): CultureItem[] {return []}/*** 获取所有文化数据*/public static getAllCultureData(): CultureItem[] {let result: CultureItem[] = []result = result.concat(CultureDatabase.getPoetryData())result = result.concat(CultureDatabase.getIdiomData())result = result.concat(CultureDatabase.getStoryData())return result}/*** 根据分类获取数据*/public static getDataByCategory(category: CultureCategory): CultureItem[] {const allData = CultureDatabase.getAllCultureData()return allData.filter(item => item.category === category)}/*** 根据动物类型获取数据*/public static getDataByAnimalType(animalType: AnimalType): CultureItem[] {const allData = CultureDatabase.getAllCultureData()return allData.filter(item => item.animalType === animalType)}
}
💡 知识点:
static表示静态方法,可以直接通过类名调用,无需创建实例filter()是数组方法,用于筛选符合条件的元素concat()用于合并数组
2.4 保存文件
按 Ctrl + S(Windows)或 Cmd + S(Mac)保存文件。
📝 小贴士: 完整的诗词数据(17首)请参考文末附录,现在继续下一步。
第三步:创建游戏数据管理器
这个文件负责把诗词转换成游戏题目。
3.1 创建文件
- 右键点击
entry/src/main/ets/data文件夹 - 选择
New→ArkTS File - 输入文件名:
GameDataManager - 点击确定
3.2 定义题目数据结构
打开 GameDataManager.ets,先导入数据库和定义题目结构:
/** 游戏数据管理器*/import { CultureDatabase, CultureItem } from './CultureData'/*** 游戏题目接口*/
export interface GameQuestion {id: string // 题目IDtype: 'idiom' | 'proverb' | 'poetry' // 题目类型question: string // 题目内容(带填空的诗句)answer: string // 正确答案options?: string[] // 四个选项hint: string // 提示信息animalType: string // 动物类型
}
3.3 创建游戏数据管理器类
继续添加核心逻辑:
/*** 游戏数据管理器*/
export class GameDataManager {/*** 获取古诗词填空游戏题目*/static getPoetryQuestions(): GameQuestion[] {// 1. 获取所有诗词数据const poetry = CultureDatabase.getPoetryData()const questions: GameQuestion[] = []// 2. 遍历每首诗poetry.forEach(poem => {// 定义要查找的家禽名称(按长度从长到短,避免误匹配)const animalNames = ['公鸡', '母鸡', '雏鸡', '小鸡', '鹅儿', '鸭儿', '鸡', '鸭', '鹅']// 3. 查找诗句中是否包含这些名称for (const animalName of animalNames) {if (poem.content.includes(animalName)) {// 4. 创建填空题:将家禽名称替换为 ____const index = poem.content.indexOf(animalName)const blankContent = poem.content.substring(0, index) +'____' +poem.content.substring(index + animalName.length)// 5. 生成答案和选项let correctOption = animalNamelet options: string[] = []// 将复合词简化为基础词作为答案if (animalName.includes('鸡')) {correctOption = '鸡'options = ['鸡', '鸭', '鹅', '雀']} else if (animalName.includes('鸭')) {correctOption = '鸭'options = ['鸭', '鸡', '鹅', '雁']} else if (animalName.includes('鹅')) {correctOption = '鹅'options = ['鹅', '鸡', '鸭', '雁']} else {correctOption = animalNameoptions = [animalName, '鸡', '鸭', '鹅']}// 6. 打乱选项顺序options = GameDataManager.shuffleArray(options)// 7. 确保正确答案在选项中if (!options.includes(correctOption)) {options[options.length - 1] = correctOption}// 8. 创建题目对象questions.push({id: `${poem.id}_${animalName}`,type: 'poetry',question: `《${poem.title}》\n${poem.author ? `[${poem.dynasty}] ${poem.author}` : ''}\n\n${blankContent}`,answer: correctOption,options: options,hint: poem.explanation ? poem.explanation.substring(0, 50) + '...' : '这是一首咏物诗',animalType: poem.animalType})// 每首诗只生成一个题目break}}})// 9. 打乱题目顺序,并限制为8道题return GameDataManager.shuffleArray(questions).slice(0, 8)}/*** 打乱数组(Fisher-Yates 洗牌算法)*/private static shuffleArray<T>(arr: T[]): T[] {const result: T[] = []// 复制数组for (let i = 0; i < arr.length; i++) {result.push(arr[i])}// 打乱顺序for (let i = result.length - 1; i > 0; i--) {const j = Math.floor(Math.random() * (i + 1))const temp = result[i]result[i] = result[j]result[j] = temp}return result}
}
💡 知识点:
substring()截取字符串includes()检查字符串是否包含某个子串indexOf()查找子串位置slice()截取数组- Fisher-Yates 算法是经典的随机打乱算法
3.4 保存文件
按 Ctrl + S 保存。
第四步:创建游戏界面
这是最重要的一步,我们将创建游戏的用户界面。
4.1 创建文件
- 右键点击
entry/src/main/ets/pages/game文件夹 - 选择
New→ArkTS File - 输入文件名:
PoetryFillGame - 点击确定
4.2 导入必要的模块
打开 PoetryFillGame.ets,首先导入需要的模块:
/** 古诗词填空游戏*/import { router } from '@kit.ArkUI'
import { GameDataManager, GameQuestion } from '../../data/GameDataManager'
import { promptAction } from '@kit.ArkUI'
4.3 创建组件和状态
添加组件定义和状态管理:
@Entry
@Component
struct PoetryFillGame {// 状态变量@StorageProp('app_is_dark_mode') isDarkMode: boolean = false // 是否深色模式@State questions: GameQuestion[] = [] // 题目列表@State currentIndex: number = 0 // 当前题目索引@State selectedAnswer: string = '' // 用户选择的答案@State showHint: boolean = false // 是否显示提示@State score: number = 0 // 得分@State answered: boolean = false // 是否已答题@State isCorrect: boolean = false // 答案是否正确// 组件即将出现时调用aboutToAppear() {this.loadQuestions()}// 加载题目private loadQuestions() {this.questions = GameDataManager.getPoetryQuestions()console.info('PoetryFillGame', `加载了 ${this.questions.length} 道题目`)}// 获取当前题目private getCurrentQuestion(): GameQuestion | null {if (this.currentIndex < this.questions.length) {return this.questions[this.currentIndex]}return null}// 检查答案private checkAnswer(option: string) {if (this.answered) return // 已经答过题了,不能再答this.selectedAnswer = optionthis.answered = trueconst question = this.getCurrentQuestion()if (question) {this.isCorrect = option === question.answerif (this.isCorrect) {this.score++ // 答对了,加分}}}// 下一题private nextQuestion() {if (this.currentIndex < this.questions.length - 1) {// 还有下一题this.currentIndex++this.selectedAnswer = ''this.answered = falsethis.showHint = false} else {// 已经是最后一题,显示结果this.showGameOver()}}// 显示游戏结束对话框private async showGameOver() {const totalQuestions = this.questions.lengthconst percentage = Math.round((this.score / totalQuestions) * 100)promptAction.showDialog({title: '游戏结束',message: `你答对了 ${this.score}/${totalQuestions} 题\n正确率:${percentage}%`,buttons: [{ text: '再玩一次', color: '#4CAF50' },{ text: '返回', color: '#666666' }]}).then((data) => {if (data.index === 0) {// 再玩一次this.currentIndex = 0this.score = 0this.selectedAnswer = ''this.answered = falsethis.showHint = falsethis.loadQuestions()} else {// 返回首页router.back()}})}// 构建界面(稍后填充)build() {Column() {Text('游戏界面')}.width('100%').height('100%')}
}
💡 知识点:
@Entry表示这是一个页面入口@Component表示这是一个组件@State表示状态变量,改变时会触发 UI 更新aboutToAppear()是生命周期函数,页面显示前调用
4.4 构建主界面
现在替换 build() 方法,构建完整界面:
build() {Column() {// 1. 导航栏this.buildNavigationBar()// 2. 游戏内容if (this.questions.length > 0 && this.currentIndex < this.questions.length) {Scroll() {Column() {this.buildProgressIndicator() // 进度指示器this.buildQuestionContent() // 题目内容this.buildOptions() // 选项按钮this.buildHintButton() // 提示按钮if (this.showHint) {this.buildHintContent() // 提示内容}if (this.answered) {this.buildNextButton() // 下一题按钮}Row().height(40) // 底部留白}.width('100%').padding({ left: 16, right: 16, top: 16 })}.layoutWeight(1).scrollBar(BarState.Auto)} else {// 加载中Column() {Text('加载题目中...').fontSize(16).fontColor(this.isDarkMode ? '#AAAAAA' : '#666666')}.width('100%').height('100%').justifyContent(FlexAlign.Center)}}.width('100%').height('100%').backgroundColor(this.isDarkMode ? '#121212' : '#F5F5F5')
}
4.5 构建导航栏
在 build() 方法之后添加导航栏构建器:
@Builder
buildNavigationBar() {Row() {// 返回按钮Row() {Text('←').fontSize(24).fontColor(this.isDarkMode ? '#FFFFFF' : '#212121')}.width(40).height(40).justifyContent(FlexAlign.Center).onClick(() => {router.back() // 返回上一页})// 标题Text('古诗词填空').fontSize(20).fontWeight(FontWeight.Bold).fontColor(this.isDarkMode ? '#FFFFFF' : '#212121').layoutWeight(1).textAlign(TextAlign.Center)// 得分Text(`${this.score}分`).fontSize(16).fontWeight(FontWeight.Medium).fontColor('#95E1D3').padding({ left: 12, right: 12 })}.width('100%').height(56).padding({ left: 8, right: 8 }).backgroundColor(this.isDarkMode ? '#1E1E1E' : '#FFFFFF')
}
4.6 构建进度指示器
@Builder
buildProgressIndicator() {Column() {// 题目序号Row() {Text(`第 ${this.currentIndex + 1} / ${this.questions.length} 题`).fontSize(14).fontColor(this.isDarkMode ? '#AAAAAA' : '#666666')}.width('100%').justifyContent(FlexAlign.Center).margin({ bottom: 8 })// 进度条Stack() {// 背景条Row().width('100%').height(6).backgroundColor(this.isDarkMode ? '#2D2D2D' : '#E0E0E0').borderRadius(3)// 进度条Row().width(`${((this.currentIndex + 1) / this.questions.length) * 100}%`).height(6).backgroundColor('#95E1D3').borderRadius(3)}.width('100%').height(6)}.width('100%').margin({ bottom: 24 })
}
4.7 构建题目内容
@Builder
buildQuestionContent() {if (this.currentIndex < this.questions.length) {Column() {// 提示文字Text('请填入合适的家禽名称').fontSize(14).fontColor('#95E1D3').fontWeight(FontWeight.Medium).margin({ bottom: 16 })// 题目内容Text(this.questions[this.currentIndex].question).fontSize(18).fontColor(this.isDarkMode ? '#FFFFFF' : '#212121').lineHeight(32).textAlign(TextAlign.Center)}.width('100%').padding(24).backgroundColor(this.isDarkMode ? '#1E1E1E' : '#FFFFFF').borderRadius(16).margin({ bottom: 24 })}
}
4.8 构建选项按钮(2x2 网格)
@Builder
buildOptions() {if (this.currentIndex < this.questions.length &&this.questions[this.currentIndex].options &&this.questions[this.currentIndex].options!.length >= 4) {// 2x2 网格布局Column({ space: 12 }) {// 第一行Row({ space: 12 }) {this.buildOptionButton(this.questions[this.currentIndex].options![0])this.buildOptionButton(this.questions[this.currentIndex].options![1])}// 第二行Row({ space: 12 }) {this.buildOptionButton(this.questions[this.currentIndex].options![2])this.buildOptionButton(this.questions[this.currentIndex].options![3])}}.width('100%').margin({ bottom: 16 })}
}@Builder
buildOptionButton(option: string) {Row() {Text(option).fontSize(24).fontColor(this.getOptionTextColor(option)).fontWeight(FontWeight.Bold)}.layoutWeight(1).height(80).justifyContent(FlexAlign.Center).backgroundColor(this.getOptionBackgroundColor(option)).borderRadius(12).border({width: 2,color: this.getOptionBorderColor(option),style: BorderStyle.Solid}).onClick(() => {this.checkAnswer(option)})
}// 获取选项文字颜色
private getOptionTextColor(option: string): string {const currentQuestion = this.getCurrentQuestion()const correctAnswer = currentQuestion?.answer || ''if (!this.answered) {// 未答题:默认颜色return this.isDarkMode ? '#FFFFFF' : '#212121'}if (option === correctAnswer) {// 正确答案:白色return '#FFFFFF'}if (option === this.selectedAnswer && !this.isCorrect) {// 错误选择:白色return '#FFFFFF'}// 其他选项:灰色return this.isDarkMode ? '#AAAAAA' : '#666666'
}// 获取选项背景颜色
private getOptionBackgroundColor(option: string): string {const currentQuestion = this.getCurrentQuestion()const correctAnswer = currentQuestion?.answer || ''if (!this.answered) {// 未答题:白色背景return this.isDarkMode ? '#1E1E1E' : '#FFFFFF'}if (option === correctAnswer) {// 正确答案:绿色return '#4CAF50'}if (option === this.selectedAnswer && !this.isCorrect) {// 错误选择:红色return '#F44336'}return this.isDarkMode ? '#1E1E1E' : '#FFFFFF'
}// 获取选项边框颜色
private getOptionBorderColor(option: string): string {const currentQuestion = this.getCurrentQuestion()const correctAnswer = currentQuestion?.answer || ''if (!this.answered) {return this.isDarkMode ? '#3D3D3D' : '#E0E0E0'}if (option === correctAnswer) {return '#4CAF50'}if (option === this.selectedAnswer && !this.isCorrect) {return '#F44336'}return this.isDarkMode ? '#3D3D3D' : '#E0E0E0'
}
4.9 构建提示按钮和内容
@Builder
buildHintButton() {if (!this.answered) {Button('💡 查看提示').width('100%').height(48).fontSize(16).fontColor('#FFA726').backgroundColor(this.isDarkMode ? '#2D2D2D' : '#FFF3E0').borderRadius(12).onClick(() => {this.showHint = !this.showHint // 切换显示/隐藏}).margin({ bottom: 16 })}
}@Builder
buildHintContent() {if (this.currentIndex < this.questions.length) {Column() {Row() {Text('💡').fontSize(20).margin({ right: 8 })Text('提示').fontSize(16).fontWeight(FontWeight.Bold).fontColor(this.isDarkMode ? '#FFFFFF' : '#212121')}.margin({ bottom: 12 })Text(this.questions[this.currentIndex].hint).fontSize(14).fontColor(this.isDarkMode ? '#CCCCCC' : '#555555').lineHeight(22)}.width('100%').padding(16).backgroundColor(this.isDarkMode ? '#2D2D2D' : '#FFF3E0').borderRadius(12).margin({ bottom: 16 })}
}
4.10 构建下一题按钮
@Builder
buildNextButton() {Button(this.currentIndex < this.questions.length - 1 ? '下一题 →' : '查看结果').width('100%').height(56).fontSize(18).fontWeight(FontWeight.Bold).fontColor('#FFFFFF').backgroundColor('#95E1D3').borderRadius(12).onClick(() => {this.nextQuestion()})
}
4.11 保存文件
按 Ctrl + S 保存。至此,游戏页面已经完成!
第五步:配置页面路由
5.1 打开路由配置文件
在项目中找到并打开:
entry/src/main/resources/base/profile/main_pages.json
5.2 添加游戏页面路由
修改文件内容为:
{"src": ["pages/Index","pages/game/PoetryFillGame"]
}
💡 注意:
- 第一个页面是应用首页
- 路径不包含
.ets后缀 - 路径相对于
entry/src/main/ets/
5.3 保存文件
按 Ctrl + S 保存。
第六步:修改首页,添加游戏入口
6.1 打开首页文件
找到并打开:
entry/src/main/ets/pages/Index.ets
6.2 替换全部内容
用以下代码替换文件的全部内容:
import { router } from '@kit.ArkUI'@Entry
@Component
struct Index {@State message: string = '古诗词填空游戏';build() {Column() {// 标题Text(this.message).fontSize(28).fontWeight(FontWeight.Bold).margin({ bottom: 40 })// 开始游戏按钮Button('开始游戏').fontSize(20).fontWeight(FontWeight.Medium).width(200).height(56).backgroundColor('#95E1D3').borderRadius(12).onClick(() => {router.pushUrl({url: 'pages/game/PoetryFillGame'})})}.height('100%').width('100%').justifyContent(FlexAlign.Center).backgroundColor('#F5F5F5')}
}
6.3 保存文件
按 Ctrl + S 保存。
第七步:编译和运行
7.1 检查文件结构
确保你创建了以下文件:
tiankong/
├── entry/src/main/ets/
│ ├── data/
│ │ ├── CultureData.ets ✅ 已创建
│ │ └── GameDataManager.ets ✅ 已创建
│ ├── pages/
│ │ ├── game/
│ │ │ └── PoetryFillGame.ets ✅ 已创建
│ │ └── Index.ets ✅ 已修改
│ └── ...
└── entry/src/main/resources/base/profile/└── main_pages.json ✅ 已修改
7.2 编译项目
- 在 DevEco Studio 菜单栏,点击
Build→Build Hap(s) / APP(s)→Build Hap(s) - 等待编译完成,查看底部的 Build 窗口
编译成功标志:
BUILD SUCCESSFUL in 30s
常见错误:
| 错误 | 原因 | 解决方法 |
|---|---|---|
| Cannot find module | 导入路径错误 | 检查相对路径 ../../ |
| Property does not exist | 类型不匹配 | 检查接口定义 |
| Page not found | 路由未注册 | 检查 main_pages.json |
7.3 运行应用
- 连接 HarmonyOS 设备或启动模拟器
- 点击 DevEco Studio 顶部的绿色运行按钮 ▶️
- 等待应用安装并启动
第八步:测试游戏
测试清单
✅ 测试 1:启动应用
- 应显示首页,标题"古诗词填空游戏"
- 应显示"开始游戏"按钮(青绿色)
✅ 测试 2:进入游戏
- 点击"开始游戏"
- 应跳转到游戏页面
- 应显示第一道题目
✅ 测试 3:答题
- 应显示诗词标题、作者、朝代
- 应显示带填空的诗句(
____) - 应显示 4 个选项(2x2 网格)
- 点击选项后:
- 正确答案变绿色
- 错误选择变红色,正确答案也变绿色
- 显示"下一题"按钮
✅ 测试 4:提示功能
- 答题前点击"💡 查看提示"
- 应展开诗词解释
- 再次点击可收起
✅ 测试 5:进度显示
- 顶部应显示"第 X / 8 题"
- 进度条应随题目推进
- 右上角应显示当前得分
✅ 测试 6:游戏结束
- 答完 8 道题后应弹出对话框
- 显示得分和正确率
- 点击"再玩一次"应重新开始
- 点击"返回"应回到首页
第九步:添加更多诗词(可选)
目前我们只添加了 3 首诗词,你可以添加更多。
如何添加诗词
打开 CultureData.ets,在 getPoetryData() 方法的数组中添加:
{id: 'poetry_goose_002',category: 'poetry',animalType: 'goose',title: '题鹅',author: '李商隐',dynasty: '唐',content: '眠沙卧水自成群,曲岸残阳极浦云。\n那解将心怜孔翠,羁雌长共故雄分。',explanation: '诗人借鹅的成群而居、夫妻相守来表达人间的情感和世态。',tags: ['李商隐', '咏物', '抒情'],isFavorite: false
},
添加规则:
id必须唯一content中必须包含"鸡"、“鸭"或"鹅”- 用
\n表示换行 - 多首诗之间用逗号分隔
完整代码参考
CultureData.ets 完整版(17首诗词)
由于篇幅限制,完整的 CultureData.ets(包含17首诗词)可以从以下位置获取:
方法一: 从源项目复制
MyApplication8/entry/src/main/ets/data/CultureData.ets
方法二: 手动添加以下诗词到数组中
点击展开完整诗词列表(14首)// 第4首:题鹅
{id: 'poetry_goose_002',category: 'poetry',animalType: 'goose',title: '题鹅',author: '李商隐',dynasty: '唐',content: '眠沙卧水自成群,曲岸残阳极浦云。\n那解将心怜孔翠,羁雌长共故雄分。',explanation: '诗人借鹅的成群而居、夫妻相守来表达人间的情感和世态。',tags: ['李商隐', '咏物', '抒情'],isFavorite: false
},// 第5首:舟前小鹅儿
{id: 'poetry_goose_003',category: 'poetry',animalType: 'goose',title: '舟前小鹅儿',author: '杜甫',dynasty: '唐',content: '鹅儿黄似酒,对酒爱新鹅。\n引颈嗔船逼,无行乱眼多。',explanation: '杜甫描写小鹅的可爱形象,充满童趣。',tags: ['杜甫', '咏物', '生动'],isFavorite: false
},// 第6首:鹅赠鹤
{id: 'poetry_goose_004',category: 'poetry',animalType: 'goose',title: '鹅赠鹤',author: '白居易',dynasty: '唐',content: '君因风送入青云,我被人驱向鸭群。\n雪颈霜毛红网掌,请看何处不如君?',explanation: '白居易借鹅鹤对比,讽刺了重名位轻实际的社会现象。',tags: ['白居易', '寓言', '社会讽刺'],isFavorite: false
},// 第7首:鸡
{id: 'poetry_chicken_002',category: 'poetry',animalType: 'chicken',title: '鸡',author: '崔道融',dynasty: '唐',content: '买得晨鸡共鸡语,常时不用等闲鸣。\n深山月黑风雨夜,欲近晓天啼一声。',explanation: '诗人描写了公鸡司晨的职责,表现了公鸡的尽职尽责。',tags: ['崔道融', '咏物', '职责'],isFavorite: false
},// 第8首:春晓
{id: 'poetry_chicken_003',category: 'poetry',animalType: 'chicken',title: '春晓',author: '孟浩然',dynasty: '唐',content: '春眠不觉晓,处处闻啼鸟。\n夜来风雨声,花落知多少。',explanation: '这首诗虽未直接写鸡,但"春眠不觉晓"的"晓"正是由鸡鸣报晓而来。',tags: ['孟浩然', '春景', '经典名篇'],isFavorite: false
},// 第9首:鸡鸣
{id: 'poetry_chicken_004',category: 'poetry',animalType: 'chicken',title: '鸡鸣',author: '诗经',dynasty: '先秦',content: '鸡既鸣矣,朝既盈矣。\n匪鸡则鸣,苍蝇之声。',explanation: '这是《诗经》中描写鸡鸣的诗句,通过鸡鸣来表现时间的推移。',tags: ['诗经', '古老', '朝政'],isFavorite: false
},// 第10首:风入松·寄柯敬仲
{id: 'poetry_chicken_005',category: 'poetry',animalType: 'chicken',title: '风入松·寄柯敬仲',author: '虞集',dynasty: '元',content: '画堂红袖倚清酣,华发不胜簪。\n几回晚直金銮殿,东风软、花里停骖。\n书诏许传宫烛,香罗初试朝衫。\n御沟冰泮水挼蓝,飞燕语呢喃。\n重重帘幕卷轻寒,凭寄与、旧时相识。\n莫向春风笑我,花前须插红杉。',explanation: '这首词虽未直接写鸡,但"晓"指鸡鸣时分,充满了对往昔的怀念。',tags: ['虞集', '怀旧', '朝堂'],isFavorite: false
},// 第11首:春江晚景
{id: 'poetry_duck_002',category: 'poetry',animalType: 'duck',title: '春江晚景',author: '张舜民',dynasty: '宋',content: '花映新林岸,云迎曲岛隈。\n江流添夜色,鸭宿鹭归来。',explanation: '诗人描绘春江晚景,一幅宁静和谐的春江晚景图。',tags: ['张舜民', '晚景', '和谐'],isFavorite: false
},// 第12首:江上
{id: 'poetry_duck_003',category: 'poetry',animalType: 'duck',title: '江上',author: '王安石',dynasty: '宋',content: '江北秋阴一半开,晚云含雨却低徊。\n青山缭绕疑无路,忽见千帆隐映来。',explanation: '虽然此诗未直接写鸭,但江上秋景常有群鸭嬉戏,富有诗意。',tags: ['王安石', '江景', '秋色'],isFavorite: false
},// 第13首:春日
{id: 'poetry_duck_004',category: 'poetry',animalType: 'duck',title: '春日',author: '朱熹',dynasty: '宋',content: '胜日寻芳泗水滨,无边光景一时新。\n等闲识得东风面,万紫千红总是春。',explanation: '诗人在春天的美好日子里到泗水边寻找春天的气息,春江水暖,鸭子嬉戏。',tags: ['朱熹', '春景', '哲理'],isFavorite: false
},// 第14首:游园不值
{id: 'poetry_duck_005',category: 'poetry',animalType: 'duck',title: '游园不值',author: '叶绍翁',dynasty: '宋',content: '应怜屐齿印苍苔,小扣柴扉久不开。\n春色满园关不住,一枝红杏出墙来。',explanation: '"春色满园关不住,一枝红杏出墙来"表达了春天的勃勃生机,园中池塘里必有鸭子嬉戏。',tags: ['叶绍翁', '春游', '名句'],isFavorite: false
}
常见问题解答
Q1:编译时提示"找不到模块"?
A: 检查导入路径:
// 正确:相对路径
import { CultureDatabase } from './CultureData'
import { GameDataManager } from '../../data/GameDataManager'// 错误:绝对路径(不要用)
import { CultureDatabase } from 'entry/src/main/ets/data/CultureData'
Q2:运行时提示"页面未找到"?
A: 检查 main_pages.json 是否正确添加了路由:
{"src": ["pages/Index","pages/game/PoetryFillGame" // 确保这行存在]
}
Q3:点击按钮没有反应?
A: 检查 onClick 事件是否正确绑定:
.onClick(() => {this.checkAnswer(option) // 确保方法名正确
})
Q4:题目没有显示?
A:
- 检查
CultureData.ets中是否添加了诗词数据 - 打开 DevTools 查看控制台,看是否有错误信息
- 确认
aboutToAppear()方法被调用
Q5:如何调试?
A: 使用 console.info() 输出调试信息:
console.info('PoetryFillGame', `题目数量: ${this.questions.length}`)
console.info('PoetryFillGame', `当前答案: ${question.answer}`)
然后在 DevEco Studio 的 DevTools 中查看 Console 面板。
进阶扩展
扩展 1:添加音效
在答题正确/错误时播放音效:
import { media } from '@kit.MediaKit'// 答题时
if (this.isCorrect) {// 播放正确音效media.createSoundPool().play(correctSoundId)
} else {// 播放错误音效media.createSoundPool().play(wrongSoundId)
}
扩展 2:添加动画效果
为按钮添加点击动画:
Button('开始游戏').animation({duration: 300,curve: Curve.EaseInOut}).scale(this.buttonPressed ? 0.95 : 1.0)
扩展 3:保存最高分
使用 Preferences 保存历史最高分:
import { preferences } from '@kit.ArkData'// 保存最高分
const prefs = await preferences.getPreferences(getContext(), 'game_data')
await prefs.put('high_score', this.score)// 读取最高分
const highScore = await prefs.get('high_score', 0)
扩展 4:添加难度选择
提供简单、中等、困难三种模式:
@State difficulty: 'easy' | 'medium' | 'hard' = 'medium'// 根据难度调整题目数量
private getQuestionCount(): number {switch (this.difficulty) {case 'easy': return 5case 'medium': return 8case 'hard': return 12}
}
总结
恭喜你!你已经完成了一个完整的诗词填空游戏。通过本教程,你学会了:
✅ 数据管理
- 定义数据结构(interface、type)
- 创建数据库类
- 使用静态方法提供数据
✅ 游戏逻辑
- 题目生成算法
- 答案验证
- 分数统计
- 随机打乱算法
✅ UI 开发
- ArkUI 组件使用
- 状态管理(@State)
- 条件渲染
- 事件处理
✅ 页面导航
- 路由配置
- 页面跳转
- 页面返回
下一步学习
- 学习更多 ArkUI 组件
- 探索动画和过渡效果
- 学习数据持久化(Preferences、数据库)
- 尝试网络请求获取诗词数据
项目文件总结
| 文件 | 行数 | 作用 |
|---|---|---|
| CultureData.ets | ~500 | 诗词数据库 |
| GameDataManager.ets | ~140 | 游戏数据管理 |
| PoetryFillGame.ets | ~380 | 游戏界面 |
| Index.ets | ~30 | 首页 |
| main_pages.json | ~6 | 路由配置 |
总计: 约 1050+ 行代码
附录
颜色参考
| 用途 | 颜色代码 | 示例 |
|---|---|---|
| 主题色 | #95E1D3 | 青绿色 |
| 正确 | #4CAF50 | 绿色 |
| 错误 | #F44336 | 红色 |
| 提示 | #FFA726 | 橙色 |
| 背景 | #F5F5F5 | 浅灰 |
快速命令
# 创建目录
mkdir -p entry/src/main/ets/data
mkdir -p entry/src/main/ets/pages/game# 查看文件
find entry/src/main/ets -name "*.ets"# 查看行数
wc -l entry/src/main/ets/data/CultureData.ets
参考文档
- HarmonyOS 开发者文档
- ArkTS 语言
- ArkUI 组件
本教程完成!
如有问题,欢迎查阅 HarmonyOS 官方文档或在社区提问。
祝你开发愉快!🎉
https://developer.huawei.com/consumer/cn/training/classDetail/fd34ff9286174e848d34cde7f512ce22?type=1%3Fha_source%3Dhmosclass&ha_sourceId=89000248
