HarmonyOS NEXT 实战:从零开发「猜数字」小游戏

经典游戏新玩法!本文将带你使用 HarmonyOS NEXT 的 ArkTS 框架,开发一个功能完整、交互友好的猜数字小游戏。涵盖状态管理、列表渲染、条件渲染、动画过渡等核心技术,适合初学者快速上手。


一、游戏设计思路

1.1 游戏规则

猜数字是一个经典的逻辑推理游戏:

  • 系统随机生成 1-100 之间的整数
  • 玩家输入猜测的数字
  • 系统提示「大了」或「小了」
  • 直到猜中为止,显示总猜测次数

1.2 功能需求

核心功能

  • 随机数生成
  • 数字输入与验证
  • 大小提示反馈
  • 猜测历史记录
  • 游戏重开

体验优化

  • 快捷数字按钮(常用数字一键填入)
  • 猜中后的庆祝动画
  • 输入框自动清空
  • 猜中后禁用输入

1.3 界面设计

采用卡片式布局:

  • 顶部:标题和副标题
  • 中部:提示区域 + 输入区域(白色卡片)
  • 快捷选择按钮组
  • 猜测历史记录列表
  • 底部:新游戏按钮(游戏结束后显示)

二、数据结构设计

2.1 状态变量定义

@State targetNumber: number = 0      // 目标数字(答案)
@State userInput: string = ''        // 用户输入
@State attempts: number = 0          // 猜测次数
@State hintText: string = '输入 1-100 之间的数字'  // 提示文字
@State hintColor: string = '#888888' // 提示颜色
@State isGameOver: boolean = false   // 游戏是否结束
@State guessHistory: GuessRecord[] = []  // 猜测历史
@State showCelebration: boolean = false  // 是否显示庆祝动画

状态分析

  • targetNumber:每次新游戏随机生成,玩家不可见(开发时可打印日志调试)
  • userInput:绑定输入框,玩家输入时实时更新
  • attempts:每次提交猜测加 1
  • hintText / hintColor:根据猜测结果动态变化,给玩家直观反馈
  • isGameOver:控制界面状态,猜中后禁用输入、显示重开按钮
  • guessHistory:数组存储每次猜测记录,用于列表渲染

2.2 猜测记录类型

interface GuessRecord {
  guess: number        // 猜测的数字
  result: string       // 结果描述("✅ 正确" / "⬆ 小了" / "⬇ 大了")
  resultColor: string  // 结果颜色
}

使用 TypeScript 接口定义数据结构,代码更清晰、IDE 提示更友好。


三、核心逻辑实现

3.1 新游戏初始化

newGame(): void {
  this.targetNumber = Math.floor(Math.random() * 100) + 1
  this.userInput = ''
  this.attempts = 0
  this.hintText = '输入 1-100 之间的数字'
  this.hintColor = '#888888'
  this.isGameOver = false
  this.guessHistory = []
  this.showCelebration = false
  console.info(`[猜数字] 新游戏开始,答案是: ${this.targetNumber}`)
}

要点

  • Math.random() * 100 生成 0-99 的随机小数
  • Math.floor() 向下取整得到 0-99 的整数
  • +1 后范围变成 1-100
  • 重置所有状态变量,确保游戏干净开始

3.2 提交猜测逻辑

submitGuess(): void {
  const guess = parseInt(this.userInput)

  // 输入验证
  if (isNaN(guess) || guess < 1 || guess > 100) {
    this.hintText = '⚠️ 请输入 1-100 的整数'
    this.hintColor = '#E67E22'
    return
  }

  this.attempts++

  let newHint = ''
  let newColor = ''
  let correct = false

  // 判断大小
  if (guess === this.targetNumber) {
    newHint = `🎉 恭喜!就是 ${this.targetNumber}!用了 ${this.attempts} 次猜中!`
    newColor = '#27AE60'
    correct = true
    this.isGameOver = true
    this.showCelebration = true
  } else if (guess < this.targetNumber) {
    newHint = `👆 ${guess} 小了`
    newColor = '#E74C3C'
  } else {
    newHint = `👇 ${guess} 大了`
    newColor = '#E74C3C'
  }

  this.hintText = newHint
  this.hintColor = newColor

  // 记录历史(最新在前)
  this.guessHistory = [
    {
      guess: guess,
      result: correct ? '✅ 正确' : (guess < this.targetNumber ? '⬆ 小了' : '⬇ 大了'),
      resultColor: correct ? '#27AE60' : '#E74C3C'
    },
    ...this.guessHistory
  ]

  this.userInput = ''  // 清空输入框
}

逻辑流程

  1. 输入验证:使用 parseInt 转换,isNaN 检查有效性,范围判断确保 1-100
  2. 次数累加:每次有效猜测都 attempts++
  3. 结果判断
    • 相等:恭喜 + 游戏结束 + 庆祝动画
    • 小于:提示「小了」
    • 大于:提示「大了」
  4. 历史记录:使用数组展开运算符 ... 将新记录插入到数组开头
  5. 清空输入:方便玩家继续猜测

3.3 快捷选择按钮

@Builder
quickButton(value: number): void {
  Button(`${value}`)
    .width(58)
    .height(36)
    .backgroundColor('#EEEEEE')
    .borderRadius(8)
    .fontSize(14)
    .fontColor('#555555')
    .fontWeight(FontWeight.Medium)
    .onClick(() => {
      if (!this.isGameOver) {
        this.userInput = value.toString()
      }
    })
}

设计思路

  • 使用 @Builder 装饰器封装可复用组件
  • 传入数字参数,生成对应按钮
  • 点击后填入输入框,玩家可以修改或直接提交

按钮布局

Row({ space: 8 }) {
  this.quickButton(10)
  this.quickButton(25)
  this.quickButton(50)
  this.quickButton(75)
  this.quickButton(90)
}

选择了一些常用数字:边界值(1、100)、中间值(50)、特殊值(25、75、90)等。


四、主界面构建

4.1 整体布局结构

build() {
  Column() {
    Scroll() {
      Column({ space: 12 }) {
        // 标题区域
        Text('🎯 猜数字')
          .fontSize(28)
          .fontWeight(FontWeight.Bold)

        Text('猜一个 1 ~ 100 之间的数字')
          .fontSize(14)
          .fontColor('#999999')

        // 输入区域卡片
        Column({ space: 16 }) {
          // 提示文字
          // 输入框 + 按钮
          // 庆祝动画
        }

        // 快捷选择按钮
        // 新游戏按钮
        // 猜测历史
      }
      .width('100%')
      .alignItems(HorizontalAlign.Center)
    }
    .scrollable(ScrollDirection.Vertical)
  }
  .width('100%')
  .height('100%')
  .backgroundColor('#F0F2F5')
}

布局特点

  • 最外层 Column 作为容器,设置浅灰背景
  • Scroll 包裹内容,防止小屏幕溢出
  • 内部 Column 居中对齐,卡片宽度 85%

4.2 输入区域卡片

Column({ space: 16 }) {
  // 提示文字
  Text(this.hintText)
    .fontSize(18)
    .fontWeight(FontWeight.Medium)
    .fontColor(this.hintColor)
    .textAlign(TextAlign.Center)
    .width('100%')
    .animation({ duration: 300 })

  // 已猜次数
  if (this.attempts > 0) {
    Text(`已猜 ${this.attempts}`)
      .fontSize(13)
      .fontColor('#AAAAAA')
  }

  // 输入框 + 提交按钮
  Row({ space: 10 }) {
    TextInput({ text: this.userInput, placeholder: '输入数字...' })
      .type(InputType.Number)
      .maxLength(3)
      .fontSize(20)
      .fontWeight(FontWeight.Bold)
      .textAlign(TextAlign.Center)
      .height(50)
      .layoutWeight(1)
      .backgroundColor('#F5F5F5')
      .borderRadius(12)
      .onChange((val: string) => {
        this.userInput = val
      })
      .onSubmit(() => {
        if (!this.isGameOver) {
          this.submitGuess()
        }
      })
      .enabled(!this.isGameOver)

    Button(this.isGameOver ? '🔄' : '↵')
      .width(50)
      .height(50)
      .backgroundColor(this.isGameOver ? '#3498DB' : '#2D3436')
      .borderRadius(12)
      .fontSize(22)
      .fontColor('#FFFFFF')
      .onClick(() => {
        if (this.isGameOver) {
          this.newGame()
        } else {
          this.submitGuess()
        }
      })
  }
  .width('100%')

  // 猜中后的庆祝动画
  if (this.showCelebration) {
    Text('✨ 🌟 ⭐ 🌟 ✨')
      .fontSize(24)
      .textAlign(TextAlign.Center)
      .width('100%')
      .margin({ top: 4 })
      .transition({ type: TransitionType.Insert, scale: { x: 0, y: 0 } })
  }
}
.width('85%')
.padding(20)
.backgroundColor('#FFFFFF')
.borderRadius(16)
.shadow({ radius: 6, color: '#1A000000', offsetY: 3 })

卡片设计

  • 白色背景 + 圆角 + 阴影,层次分明
  • TextInput 使用 InputType.Number 只弹出数字键盘
  • maxLength(3) 限制最多 3 位(100 也是 3 位)
  • onChange 实时更新 userInput
  • onSubmit 支持回车键提交
  • enabled(!this.isGameOver) 猜中后禁用输入

按钮状态

  • 游戏中:黑色,显示 ↵,点击提交
  • 结束后:蓝色,显示 🔄,点击新游戏

4.3 快捷选择区域

if (!this.isGameOver) {
  Column() {
    Text('快捷选择')
      .fontSize(14)
      .fontColor('#AAAAAA')
      .margin({ bottom: 8 })

    Row({ space: 8 }) {
      this.quickButton(10)
      this.quickButton(25)
      this.quickButton(50)
      this.quickButton(75)
      this.quickButton(90)
    }
    .width('100%')

    Row({ space: 8 }) {
      this.quickButton(1)
      this.quickButton(33)
      this.quickButton(66)
      this.quickButton(99)
      this.quickButton(100)
    }
    .width('100%')
  }
  .width('85%')
  .margin({ bottom: 8 })
}

游戏结束后隐藏快捷按钮,界面更简洁。

4.4 新游戏按钮

if (this.isGameOver) {
  Button('🔄 再来一局')
    .width('85%')
    .height(48)
    .backgroundColor('#3498DB')
    .borderRadius(14)
    .fontSize(17)
    .fontColor('#FFFFFF')
    .onClick(() => {
      this.newGame()
    })
}

只有游戏结束后才显示,防止玩家误触重开。

4.5 猜测历史列表

if (this.guessHistory.length > 0) {
  Column({ space: 8 }) {
    Text('📋 猜测记录')
      .fontSize(16)
      .fontWeight(FontWeight.Medium)
      .fontColor('#666666')
      .width('100%')

    Column({ space: 4 }) {
      ForEach(this.guessHistory, (item: GuessRecord) => {
        Row() {
          Text(`#${this.guessHistory.length - this.guessHistory.indexOf(item)}`)
            .fontSize(14)
            .fontColor('#AAAAAA')
            .width(36)

          Text(`${item.guess}`)
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor('#2D3436')
            .width(60)

          Text(item.result)
            .fontSize(15)
            .fontColor(item.resultColor)
            .fontWeight(FontWeight.Medium)
        }
        .width('100%')
        .padding({ top: 8, bottom: 8, left: 12, right: 12 })
        .backgroundColor('#FAFAFA')
        .borderRadius(10)
      })
    }
  }
  .width('85%')
  .margin({ top: 8, bottom: 32 })
}

列表渲染

  • 使用 ForEach 遍历 guessHistory 数组
  • 每条记录显示序号、猜测数字、结果
  • 序号计算:数组长度 - 当前索引(因为新记录在数组开头)

五、生命周期回调

5.1 aboutToAppear 初始化

aboutToAppear(): void {
  this.newGame()
}

组件即将显示时,调用 newGame() 初始化游戏。


六、踩坑记录与解决方案

6.1 输入框无法清空

问题:调用 this.userInput = '' 后,输入框仍有内容。

原因TextInputtext 属性是单向绑定。

解决方案:使用双向绑定语法或手动触发更新。在 ArkTS 中,onChange 会同步更新状态变量,设置 this.userInput = '' 后输入框会自动清空。

6.2 猜测历史序号错误

问题:历史记录显示的序号是倒序(最新的显示 #1)。

分析:因为新记录插入到数组开头,indexOf 返回的是数组索引(0 是最新的)。

解决方案

Text(`#${this.guessHistory.length - this.guessHistory.indexOf(item)}`)

用数组长度减去索引,得到正确的序号。

6.3 庆祝动画不显示

问题:猜中后庆祝动画没有出现。

原因transition 动画需要配合条件渲染。

解决方案

if (this.showCelebration) {
  Text('✨ 🌟 ⭐ 🌟 ✨')
    .transition({ type: TransitionType.Insert, scale: { x: 0, y: 0 } })
}

确保使用 if 条件渲染,transition 才会生效。

6.4 游戏结束后仍可输入

问题:猜中后还能继续猜测。

解决方案

TextInput(...)
  .enabled(!this.isGameOver)

Button(...)
  .onClick(() => {
    if (!this.isGameOver) {
      this.submitGuess()
    }
  })

双重保护:禁用输入框 + 点击事件判断。

6.5 快捷按钮点击无效

问题:点击快捷按钮后输入框没有变化。

原因:忘记判断游戏状态。

解决方案

.onClick(() => {
  if (!this.isGameOver) {
    this.userInput = value.toString()
  }
})

七、功能扩展思路

7.1 难度选择

enum Difficulty {
  EASY = { min: 1, max: 50 },
  MEDIUM = { min: 1, max: 100 },
  HARD = { min: 1, max: 500 }
}

添加难度选择界面,不同难度对应不同数字范围。

7.2 计时功能

@State startTime: number = 0
@State elapsedTime: number = 0

// 开始计时
startTimer(): void {
  this.startTime = Date.now()
  this.timerId = setInterval(() => {
    this.elapsedTime = Math.floor((Date.now() - this.startTime) / 1000)
  }, 1000)
}

记录猜测用时,增加挑战性。

7.3 最佳记录

interface GameRecord {
  attempts: number
  time: number
  date: string
}

@State bestRecords: GameRecord[] = []

保存历史最佳成绩,激励玩家挑战。

7.4 提示功能

getHint(): string {
  if (this.attempts > 5) {
    const diff = this.targetNumber - parseInt(this.userInput)
    if (Math.abs(diff) <= 10) {
      return '💡 非常接近了!'
    }
  }
  return ''
}

猜测多次后给出额外提示。


八、运行效果展示

在这里插入图片描述


九、总结

通过这个猜数字游戏的开发,我们学习了:

  1. 状态管理:使用 @State 管理游戏状态,实现响应式 UI
  2. 条件渲染:根据游戏状态显示/隐藏不同组件
  3. 列表渲染:使用 ForEach 渲染猜测历史
  4. 组件封装:使用 @Builder 封装快捷按钮
  5. 输入处理TextInput 的双向绑定与验证
  6. 动画效果transition 实现庆祝动画

这个项目虽然简单,但涵盖了鸿蒙开发的基础知识,非常适合初学者练手。建议动手敲一遍代码,加深理解。


项目信息

  • 包名com.example.myapplication
  • API 版本:API 23
  • 开发工具:DevEco Studio

相关链接


如果这篇文章对你有帮助,欢迎点赞收藏!有问题欢迎在评论区讨论~

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐