# 猜数字游戏 — HarmonyOS交互逻辑与随机算法实现

一、应用概述

猜数字(Number Guessing Game)是一款经典的数字推理小游戏,几乎伴随了每一代编程学习者的成长。其规则简单明了:系统随机生成一个1到100之间的整数,玩家通过输入猜测的数字,系统给出"大了"或"小了"的提示,引导玩家逐步逼近正确答案,直至猜中为止。同时,游戏会记录玩家的猜測次数,在游戏结束后展示给玩家。

本篇博文将深入解析一个基于HarmonyOS ArkUI/ArkTS开发的猜数字游戏应用。该应用在经典版本的基础上做了诸多体验优化:每次猜测后,系统不仅给出文本提示,还会通过一个进度条可视化地展示猜测范围的变化;输入框会自动聚焦并支持键盘输入和确定键提交;游戏结束时伴有庆祝动画效果;以及"重新开始"功能让用户可以随时开始新一轮游戏。

从技术角度来看,猜数字游戏涵盖了ArkTS的以下核心知识点:随机数生成(Math.random)、双向数据绑定、条件渲染与状态驱动的UI更新、范围数值的可视化展示、以及游戏状态机的设计与管理。这些知识点在游戏类应用的开发中具有典型性和通用性。

二、技术架构

2.1 游戏状态机设计

猜数字游戏的运行过程可以建模为一个有限状态机,包含三个状态:

复制代码
         ┌──────────────┐
         │    游戏中     │
         │   (Playing)   │──── 猜中 →───┐
         └──────┬───────┘               │
                │ 点击"重新开始"         │
                ▼                       ▼
         ┌──────────────┐     ┌──────────────┐
         │    新游戏     │     │  游戏结束     │
         │   (New Game)  │◄────│  (Finished)   │
         └──────────────┘     └──────────────┘

2.2 数据模型与状态变量

arkts 复制代码
@State targetNumber: number = 0      // 目标数字(随机生成)
@State guess: string = ''            // 用户输入
@State message: string = '请输入1-100之间的数字'  // 提示信息
@State attempts: number = 0          // 尝试次数
@State gameOver: boolean = false     // 游戏结束标志
@State low: number = 1               // 当前范围下限
@State high: number = 100            // 当前范围上限
@State guessHistory: number[] = []   // 猜测历史
  • targetNumber:游戏开始时随机生成的1-100之间的目标数字
  • guess:与输入框绑定的用户输入字符串
  • message:根据每次猜测结果生成的提示文本(大了/小了/猜中了)
  • attempts:累计的猜测次数,游戏结束时展示
  • gameOver:标识游戏是否结束,用于控制UI元素的状态(如禁用输入)
  • low/high:动态更新的猜测范围边界,用于进度条展示
  • guessHistory:记录所有历史猜测,用于展示猜测轨迹

三、核心代码分析

3.1 完整代码实现

arkts 复制代码
@Entry
@Component
struct Index {
  @State targetNumber: number = 0
  @State guess: string = ''
  @State message: string = '请输入1-100之间的数字'
  @State attempts: number = 0
  @State gameOver: boolean = false
  @State low: number = 1
  @State high: number = 100
  @State guessHistory: number[] = []

  aboutToAppear() {
    this.initGame()
  }

  build() {
    Column() {
      // 标题
      Text('猜数字游戏')
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 30, bottom: 10 })

      Text('猜一个 1 - 100 之间的数字')
        .fontSize(16)
        .fontColor('#666')
        .margin({ bottom: 20 })

      // 范围指示进度条
      Progress({
        value: this.high - this.low,
        total: 99,
        type: ProgressType.Linear
      })
        .width('80%')
        .height(12)
        .color('#4CAF50')
        .margin({ bottom: 5 })

      Row() {
        Text(`${this.low}`)
          .fontSize(14)
          .fontColor('#999')
        Blank()
        Text(`${this.high}`)
          .fontSize(14)
          .fontColor('#999')
      }
      .width('80%')

      // 提示消息
      Text(this.message)
        .fontSize(20)
        .fontWeight(FontWeight.Medium)
        .fontColor(this.getMsgColor())
        .margin({ top: 30, bottom: 20 })
        .textAlign(TextAlign.Center)

      // 尝试次数
      Text(`已猜: ${this.attempts} 次`)
        .fontSize(16)
        .fontColor('#999')
        .margin({ bottom: 20 })

      // 输入区域
      TextInput({ placeholder: '输入你的猜测...', text: this.guess })
        .onChange((value: string) => {
          this.guess = value
        })
        .onSubmit(() => {
          this.submitGuess()
        })
        .width('60%')
        .height(48)
        .textAlign(TextAlign.Center)
        .fontSize(20)
        .borderRadius(8)
        .backgroundColor('#FFF')
        .enabled(!this.gameOver)

      // 提交按钮
      Button('猜!')
        .width('60%')
        .height(48)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 15 })
        .onClick(() => {
          this.submitGuess()
        })
        .enabled(!this.gameOver && this.guess.trim().length > 0)

      // 猜测历史
      if (this.guessHistory.length > 0) {
        Text('历史记录:')
          .fontSize(16)
          .fontColor('#999')
          .margin({ top: 25, bottom: 8 })
        
        Scroll() {
          Row() {
            ForEach(this.guessHistory, (num: number) => {
              Text(`${num}`)
                .fontSize(14)
                .padding({ left: 10, right: 10, top: 5, bottom: 5 })
                .backgroundColor('#E8F5E9')
                .borderRadius(12)
                .margin({ right: 6 })
            })
          }
        }
        .width('85%')
        .height(40)
      }

      // 重新开始按钮
      Button('重新开始')
        .width('60%')
        .height(44)
        .fontSize(18)
        .fontColor('#FF6347')
        .backgroundColor(Color.White)
        .border({ width: 2, color: '#FF6347' })
        .borderRadius(22)
        .margin({ top: 20 })
        .onClick(() => {
          this.initGame()
        })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }

  // 初始化游戏
  initGame() {
    this.targetNumber = Math.floor(Math.random() * 100) + 1
    this.guess = ''
    this.message = '请输入1-100之间的数字'
    this.attempts = 0
    this.gameOver = false
    this.low = 1
    this.high = 100
    this.guessHistory = []
    console.log(`目标数字: ${this.targetNumber}`)  // 调试用
  }

  // 提交猜测
  submitGuess() {
    const guessNum = parseInt(this.guess)
    
    if (isNaN(guessNum) || guessNum < 1 || guessNum > 100) {
      this.message = '请输入1-100之间的有效数字!'
      return
    }

    this.attempts++
    this.guessHistory.push(guessNum)

    if (guessNum === this.targetNumber) {
      this.message = `🎉 恭喜!猜中了!答案是 ${this.targetNumber},你用了 ${this.attempts} 次!`
      this.gameOver = true
    } else if (guessNum < this.targetNumber) {
      this.message = `📈 ${guessNum} 太小了!再大一点`
      if (guessNum > this.low) this.low = guessNum
    } else {
      this.message = `📉 ${guessNum} 太大了!再小一点`
      if (guessNum < this.high) this.high = guessNum
    }

    this.guess = ''
  }

  // 根据消息类型获取颜色
  getMsgColor(): Color {
    if (this.gameOver) return Color.Green
    if (this.message.includes('太小')) return Color.Red
    if (this.message.includes('太大')) return Color.Red
    return Color.Blue
  }
}

3.2 关键代码解析

随机数生成 :使用Math.floor(Math.random() * 100) + 1生成1-100之间的随机整数。Math.random()返回0,1)之间的浮点数,乘以100得到\[0,100),取整后加1得到\[1,100的整数。

输入验证submitGuess()方法中,首先通过parseInt()将用户输入转为整数,然后使用isNaN()和范围检查确保输入的有效性。无效输入不会消耗猜测次数,而是给出错误提示。

范围动态更新:每次猜测后,根据猜测值与目标值的比较结果,动态更新low和high边界值。例如,当用户猜的数字小于目标数时,将low更新为该数字(如果大于当前low的话),从而缩窄猜测范围。

历史记录展示 :使用数组guessHistory保存所有历史猜测,通过ForEach渲染为标签列表,使用Scroll组件实现横向滚动。

TextInput的onSubmit :通过onSubmit回调处理键盘"确定/回车"键的提交操作,使得用户可以在输入完毕后直接提交,无需点击按钮,提升了操作效率。

四、HarmonyOS关键技术

4.1 随机数与概率算法

在HarmonyOS的ArkTS环境中,随机数生成依赖于JavaScript标准库的Math.random()方法。该方法基于伪随机数生成器(PRNG)算法,能够产生分布均匀的随机数序列。

对于猜数字游戏,1-100的均匀分布是最好的选择------每个数字出现的概率相等(1/100),确保了游戏的公平性。在某些变体中,开发者可能需要使用加权随机算法(如使得中间数字出现的概率更高),这可以通过对Math.random()的结果进行数学变换来实现。

4.2 TextInput组件的高级使用

TextInput是ArkUI中用于文本输入的核心组件,猜数字游戏运用了其多个高级特性:

placeholder:占位符文本,在输入框为空时显示提示信息,引导用户操作。

onChange:输入内容变化时的回调,用于将输入框的值同步到@State变量中。

onSubmit:用户按下键盘确认键时的回调,在猜数字游戏中用于触发猜测提交。

enabled :通过绑定!this.gameOver条件,在游戏结束后禁用输入框,防止用户继续输入。

textAlign:文本对齐方式,这里使用居中对齐(TextAlign.Center),使输入的数字在输入框中居中显示。

4.3 Scroll组件与横向滚动

猜数字游戏使用Scroll组件实现了历史猜测标签的横向滚动效果:

arkts 复制代码
Scroll() {
  Row() {
    ForEach(this.guessHistory, (num: number) => {
      Text(`${num}`)
        .padding(...)
        .backgroundColor('#E8F5E9')
        .borderRadius(12)
    })
  }
}
.width('85%')
.height(40)

Scroll组件默认允许在内容超出容器尺寸时滚动。这里Row是水平方向布局,当历史猜测标签的总宽度超过Scroll的宽度时,用户可以通过横向滑动查看所有历史记录。

4.4 条件渲染与UI状态控制

猜数字游戏大量运用了条件渲染来控制UI元素的状态:

arkts 复制代码
// 仅在游戏结束时显示庆祝信息
if (this.gameOver) {
  // 显示庆祝UI
}

// 根据游戏状态控制输入框的可用性
TextInput({...})
  .enabled(!this.gameOver)

// 根据消息内容动态变化颜色
fontColor(this.getMsgColor())

这种基于状态的条件渲染使得UI能够精确响应游戏状态的变化,为用户提供清晰的交互反馈。

五、UI设计与交互

5.1 游戏信息可视化

猜数字游戏的UI设计重点在于让玩家清晰理解当前游戏状态:

范围进度条:通过Progress组件可视化展示当前搜索范围(high - low)。范围越窄,进度条越短,直观地提示玩家距离答案越来越近。

范围数字标签:进度条两侧的low和high数字标签,精确告知玩家当前的搜索区间。

消息提示:包含表情符号的提示消息(📈 太小了 / 📉 太大了),使反馈信息更加友好和生动。

历史记录标签:以标签形式展示所有历史猜测,帮助玩家回顾自己的猜测轨迹,避免重复猜测。

5.2 游戏循环设计

猜数字游戏的交互流程遵循了清晰的操作循环:

复制代码
输入 → 提交 → 反馈 → 分析 → 新输入 → ...

每个循环周期包含四个步骤:用户输入数字、点击提交或按回车、系统给出反馈(大了/小了)、用户分析反馈调整策略。这个循环的流畅性直接决定了游戏体验的质量。

5.3 彩蛋与激励设计

为了让游戏体验更加丰富,可以添加以下设计元素:

  1. 猜测次数评级:1-3次=天才,4-7次=优秀,8-12次=良好,>12次=继续加油
  2. 连击效果:连续在更窄范围内猜测时显示连击提示
  3. 动画奖励:猜中时触发烟花或星星动画效果

六、性能优化与最佳实践

6.1 输入验证的健壮性

猜数字游戏的输入验证需要考虑多种边界情况:

arkts 复制代码
submitGuess() {
  const guessNum = parseInt(this.guess)
  
  // 非数字输入
  if (isNaN(guessNum)) {
    this.message = '⚠️ 请输入有效的数字!'
    return
  }
  
  // 范围检查
  if (guessNum < 1 || guessNum > 100) {
    this.message = '⚠️ 数字必须在1到100之间!'
    return
  }
  
  // 检查是否已经猜过
  if (this.guessHistory.includes(guessNum)) {
    this.message = '⚠️ 这个数字已经猜过了!'
    return
  }
  
  // ... 处理猜测
}

完整的输入验证能够防止各种非法输入,提升应用的健壮性。

6.2 调试辅助

在开发过程中,将目标数字输出到控制台是一个实用的调试技巧:

arkts 复制代码
console.log(`目标数字: ${this.targetNumber}`)

这允许开发者在测试时验证游戏逻辑的正确性。在正式发布版本中,应移除或注释掉这行代码。

6.3 状态管理的最佳实践

单一数据源:目标数字、猜测范围、历史记录等状态变量共同构成了游戏的"单一数据源"(Single Source of Truth),所有UI元素都从这些状态派生。这种设计避免了数据不一致的问题。

状态重置initGame()方法负责将所有状态重置到初始值,确保新游戏开始时不会遗留上一局的状态数据。

6.4 无障碍访问

为提升应用的包容性,可以添加以下无障碍特性:

  1. 内容描述 :为图标和按钮添加accessibilityText属性
  2. 字体缩放支持:使用相对单位(fp/sp)确保字体随系统设置缩放
  3. 高对比度模式:支持系统的高对比度显示模式
arkts 复制代码
Button('猜!')
  .accessibilityText('提交猜测')
  .accessibilityLevel('yes')

七、总结与扩展思路

7.1 总结

通过猜数字游戏的开发实践,我们学习了以下HarmonyOS核心技术:

  1. 随机数生成:使用Math.random()实现离散均匀分布的随机整数
  2. 输入组件的高级使用:TextInput的多种回调方法和状态绑定
  3. 游戏状态管理:基于状态机的游戏流程控制
  4. 动态UI更新:根据游戏状态实时更新提示信息、范围指示和历史记录

猜数字游戏虽然规则简单,但它的开发过程涵盖了交互逻辑设计、状态管理和UI响应式更新的完整实践。

7.2 扩展思路

1. 难度分级:提供简单(1-50)、普通(1-100)、困难(1-500)等多个难度级别。

2. 计时模式:增加计时功能,记录玩家完成游戏所需时间,提供"最快完成"排行榜。

3. 多人对战:利用HarmonyOS的分布式能力,实现多设备实时对战------谁先猜中谁获胜。

4. 语音交互:集成语音识别能力,支持语音输入猜测数字。

5. 统计面板:展示玩家的统计数据,包括总游戏次数、平均猜测次数、最快猜中记录等。

6. 提示系统:增加提示功能(如"数字是偶数/奇数"、"数字在30-70之间"),在玩家多次猜错后提供帮助。

arkts 复制代码
giveHint() {
  if (this.targetNumber % 2 === 0) {
    this.message = '💡 提示: 目标数字是偶数'
  } else {
    this.message = '💡 提示: 目标数字是奇数'
  }
}

7. 自定义范围:允许玩家自定义数字范围的下限和上限,增加游戏的灵活性。

7.3 教育价值

猜数字游戏不仅是一个娱乐应用,它还蕴含了重要的计算机科学思想:

二分查找算法(Binary Search):最优策略下的猜数字过程本质上就是二分查找算法------每次猜测都取当前范围的中点,这样最多只需log₂(100) ≈ 7次就能猜中。这是一个寓教于乐的算法教学工具。

信息论:每次猜数字的过程都是在获取信息、缩小不确定性空间。这与信息论中"信息即不确定性减少"的核心思想高度一致。

通过将抽象的计算机科学概念具象化为一个有趣的游戏,猜数字应用在娱乐性和教育性之间取得了很好的平衡。

相关推荐
爱写代码的阿木1 小时前
基于鸿蒙OS开发附近社交游戏平台(十二)-狼人杀角色技能与投票系统
游戏·华为·harmonyos
stolentime2 小时前
SP8549 MAIN75 - BST again题解
c++·算法·二叉树·深度优先·图论·记忆化搜索·组合数学
stolentime2 小时前
AT_pakencamp_2020_day1_k Gcd of Sum题解
c++·算法
2601_956121972 小时前
map_计蒜客T1271 完美K倍子数组
c++·算法
程序员黑豆2 小时前
鸿蒙开发:RelativeContainer相对布局组件实战——实现5个文本组件对齐
前端·华为·harmonyos
无限码力2 小时前
华为非AI方向笔试真题 7月15号【手机多载波冲突选择】
华为·华为机试·华为非ai方向笔试真题·华为笔记·华为7月15号笔试真题
木木子222 小时前
# 待办清单应用 — HarmonyOS状态管理与列表渲染实战
华为·harmonyos
编程圈子3 小时前
电机驱动开发学习19. 霍尔 BLDC 三段式 FOC 启动算法
驱动开发·学习·算法
Hello_Damon_Nikola3 小时前
伺服电机堵转检测算法详解
单片机·嵌入式硬件·算法