在这里插入图片描述

在这里插入图片描述

引言

找不同数字游戏是一款经典的视觉注意力训练游戏,玩家需要在一堆相同数字中快速找到唯一不同的那个。这款游戏看似简单,但随着网格规模的增大,难度会呈指数级增长。在本文中,我们将深入探讨如何使用HarmonyOS ArkTS技术栈实现这款游戏,包括核心算法、动态网格布局、视觉设计和性能优化等方面。

一、游戏概述与设计理念

1.1 游戏规则

找不同数字游戏的核心玩法非常直观:

  • 游戏界面显示一个N×N的网格,大部分格子中显示相同的数字
  • 只有一个格子中的数字与其他不同
  • 玩家需要快速找到并点击这个不同的数字
  • 游戏记录完成时间,挑战更快的反应速度
  • 点击正确的数字后游戏胜利

1.2 设计目标

在设计这款游戏时,我们设定了以下核心目标:

目标类型 具体目标 实现方式
训练价值 提升视觉注意力和快速识别能力 在大量相似元素中寻找差异
难度递进 从简单到困难的渐进式关卡 网格大小4×4→6×6→8×8→10×10
公平竞争 所有数字视觉样式完全一致 统一字体颜色和背景,消除视觉线索
用户体验 流畅的交互和即时反馈 点击即判定,无需额外确认
进度追踪 记录通关进度和最佳时间 全局状态管理+数据持久化

1.3 难度递增策略

游戏难度主要通过网格大小来控制:

难度等级 网格大小 格子总数 找到不同数字的概率
入门 4×4 16 1/16 ≈ 6.25%
简单 6×6 36 1/36 ≈ 2.78%
中等 8×8 64 1/64 ≈ 1.56%
困难 10×10 100 1/100 = 1%

随着网格增大,玩家需要扫描的区域呈平方级增长,大大增加了游戏难度。

二、项目架构与组件设计

2.1 页面结构

找不同数字游戏包含两个核心页面:

├── FindDifferentLevelPage.ets  # 关卡选择页面
└── FindDifferentPage.ets       # 游戏主页面

页面职责划分:

  • FindDifferentLevelPage:展示关卡列表,按难度分类(入门/简单/中等/困难),显示通关时间,提供解锁功能
  • FindDifferentPage:游戏核心逻辑,包括数字生成、点击验证、计时、胜利判定

2.2 数据模型设计

为了支持多样化的关卡配置,我们设计了两个核心数据类:

class LevelInfo {
  level: number = 0;
  gridSize: number = 0;
  label: string = '';

  constructor(level: number, gridSize: number, label: string) {
    this.level = level;
    this.gridSize = gridSize;
    this.label = label;
  }
}

class Category {
  name: string = '';
  description: string = '';
  color: string = '';
  levels: Array<LevelInfo> = [];

  constructor(name: string, description: string, color: string) {
    this.name = name;
    this.description = description;
    this.color = color;
    this.levels = [];
  }
}

LevelInfo字段说明:

字段 类型 说明
level number 关卡编号
gridSize number 网格大小(4/6/8/10)
label string 显示标签(如"4×4")

2.3 组件状态定义

游戏页面使用了以下状态变量:

@State gridSize: number = 5;
@State currentLevel: number = 1;
@State isPlaying: boolean = false;
@State numbers: Array<number> = [];
@State targetNumber: number = 0;
@State timerSeconds: number = 0;
@State bestTime: number = 0;
private timerId: number = 0;

状态变量说明:

变量 类型 说明
gridSize number 当前网格大小
currentLevel number 当前关卡
isPlaying boolean 是否正在游戏中
numbers Array<number> 网格中的所有数字
targetNumber number 需要找到的目标数字
timerSeconds number 计时秒数
bestTime number 当前关卡最佳时间
timerId number 计时器ID

三、核心游戏逻辑实现

3.1 数字生成算法

游戏的核心在于生成大部分相同、只有一个不同的数字数组:

private startGame(): void {
  this.isPlaying = true;
  this.timerSeconds = 0;
  this.numbers = [];
  let mainNumber: number = Math.floor(Math.random() * 9) + 1;
  let differentNumber: number = mainNumber;
  while (differentNumber === mainNumber) {
    differentNumber = Math.floor(Math.random() * 9) + 1;
  }
  this.targetNumber = differentNumber;

  let total: number = this.gridSize * this.gridSize;
  for (let i = 0; i < total - 1; i++) {
    this.numbers.push(mainNumber);
  }
  this.numbers.push(differentNumber);
  this.numbers = this.shuffle(this.numbers);
  this.startTimer();
}

生成流程分析:

1. 随机生成主数字(1-9)
         ↓
2. 生成不同数字,确保与主数字不同
         ↓
3. 根据网格大小生成(total-1)个主数字
         ↓
4. 添加1个不同数字
         ↓
5. 打乱数组顺序
         ↓
6. 开始计时

算法特点:

  • 使用1-9的数字范围,避免0的混淆
  • 通过while循环确保不同数字与主数字确实不同
  • 只生成一个不同数字,保持游戏的简洁性

3.2 Fisher-Yates洗牌算法

生成的数字数组需要随机打乱,确保不同数字的位置不确定:

private shuffle(array: Array<number>): Array<number> {
  let result: Array<number> = [...array];
  for (let i = result.length - 1; i > 0; i--) {
    let j: number = Math.floor(Math.random() * (i + 1));
    let temp: number = result[i];
    result[i] = result[j];
    result[j] = temp;
  }
  return result;
}

算法分析:

特性 Fisher-Yates洗牌
时间复杂度 O(n)
空间复杂度 O(n)(创建副本)
随机性 均匀随机,每个排列概率相等
公平性 确保每次游戏的不同数字位置完全随机

注意事项:

代码中使用了展开运算符[...array]来复制数组,但根据ArkTS语法约束,展开运算符仅支持数组展开到rest参数或数组字面量中。在实际编译时,这可能需要调整为手动复制:

private shuffle(array: Array<number>): Array<number> {
  let result: Array<number> = [];
  for (let i = 0; i < array.length; i++) {
    result.push(array[i]);
  }
  for (let i = result.length - 1; i > 0; i--) {
    let j: number = Math.floor(Math.random() * (i + 1));
    let temp: number = result[i];
    result[i] = result[j];
    result[j] = temp;
  }
  return result;
}

3.3 点击验证逻辑

点击验证是游戏的核心交互逻辑:

private clickNumber(num: number): void {
  if (num === this.targetNumber) {
    this.stopTimer();
    let completedLevels: Array<number> = appState.findDifferentCompletedLevels;
    let exists: boolean = false;
    for (let i = 0; i < completedLevels.length; i++) {
      if (completedLevels[i] === this.currentLevel) {
        exists = true;
        break;
      }
    }
    if (!exists) {
      completedLevels.push(this.currentLevel);
      completedLevels.sort((a: number, b: number) => a - b);
      appState.findDifferentCompletedLevels = completedLevels;
    }
    let levelTimes: Array<number> = appState.findDifferentLevelTimes;
    while (levelTimes.length <= this.currentLevel) {
      levelTimes.push(0);
    }
    if (levelTimes[this.currentLevel] === 0 || this.timerSeconds < levelTimes[this.currentLevel]) {
      levelTimes[this.currentLevel] = this.timerSeconds;
      appState.findDifferentLevelTimes = levelTimes;
      this.bestTime = this.timerSeconds;
    }
    this.isPlaying = false;
  }
}

验证流程:

用户点击格子 → 获取点击的数字 → 比较是否等于目标数字
    ↓
匹配成功 → 停止计时 → 记录通关进度 → 更新最佳时间 → 结束游戏
    ↓
匹配失败 → 无反应,继续游戏

胜利处理步骤:

  1. 停止计时:立即停止计时器,记录当前用时
  2. 检查通关状态:判断当前关卡是否已通关
  3. 记录通关进度:如果是首次通关,将关卡编号加入已完成数组
  4. 更新最佳时间:比较当前用时与历史最佳时间,更新更优的记录
  5. 结束游戏:设置isPlaying为false,返回开始界面

3.4 视觉一致性设计

为了增加游戏难度,所有数字必须使用完全相同的视觉样式,消除任何可能的视觉线索:

Grid() {
  ForEach(this.numbers, (num: number) => {
    GridItem() {
      Column() {
        Text(num.toString())
          .fontSize(this.getFontSize())
          .fontColor('#FFFFFF')
      }
      .backgroundColor('rgba(255, 215, 0, 0.05)')
      .borderColor('#FFD70040')
      .borderWidth(this.gridSize <= 6 ? 1 : 0.5)
      .borderRadius(this.gridSize <= 6 ? 8 : 2)
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
      .alignItems(HorizontalAlign.Center)
      .onClick(() => this.clickNumber(num))
    }
  })
}

视觉设计要点:

  • 统一字体颜色:所有数字使用白色(#FFFFFF),消除颜色差异
  • 统一背景颜色:所有格子使用相同的金色透明背景(rgba(255, 215, 0, 0.05))
  • 统一边框样式:根据网格大小调整边框宽度和圆角
  • 动态字体大小:根据网格大小自适应调整字体大小

这种设计确保玩家只能通过数字本身的差异来找到目标,而不能依赖任何视觉提示。

四、关卡系统设计

4.1 关卡分类体系

游戏采用四级难度递进体系,共20个关卡:

难度等级 关卡范围 网格大小 颜色标识
入门 1-5 4×4 绿色 #32CD32
简单 6-10 6×6 青色 #00CED1
中等 11-15 8×8 金色 #FFD700
困难 16-20 10×10 红色 #FF6347

4.2 关卡初始化

private initCategories(): void {
  let cat1: Category = new Category('入门', '4×4 网格', '#32CD32');
  for (let i = 1; i <= 5; i++) {
    cat1.levels.push(new LevelInfo(i, 4, '4×4'));
  }

  let cat2: Category = new Category('简单', '6×6 网格', '#00CED1');
  for (let i = 6; i <= 10; i++) {
    cat2.levels.push(new LevelInfo(i, 6, '6×6'));
  }

  let cat3: Category = new Category('中等', '8×8 网格', '#FFD700');
  for (let i = 11; i <= 15; i++) {
    cat3.levels.push(new LevelInfo(i, 8, '8×8'));
  }

  let cat4: Category = new Category('困难', '10×10 网格', '#FF6347');
  for (let i = 16; i <= 20; i++) {
    cat4.levels.push(new LevelInfo(i, 10, '10×10'));
  }

  this.categories = [cat1, cat2, cat3, cat4];
}

4.3 难度设计思路

阶段1(1-5关):4×4网格

  • 16个格子,其中15个相同数字,1个不同数字
  • 适合初学者熟悉游戏规则
  • 找到不同数字的概率约为6.25%

阶段2(6-10关):6×6网格

  • 36个格子,其中35个相同数字,1个不同数字
  • 需要更仔细的观察
  • 找到不同数字的概率约为2.78%

阶段3(11-15关):8×8网格

  • 64个格子,其中63个相同数字,1个不同数字
  • 需要高效的搜索策略
  • 找到不同数字的概率约为1.56%

阶段4(16-20关):10×10网格

  • 100个格子,其中99个相同数字,1个不同数字
  • 最高难度挑战
  • 找到不同数字的概率仅为1%

五、计时系统实现

5.1 计时器设计

游戏使用基于setTimeout的递归计时方案:

private startTimer(): void {
  this.timerId = 1;
  this.tick();
}

private tick(): void {
  if (this.timerId !== 0) {
    this.timerSeconds++;
    setTimeout(() => {
      this.tick();
    }, 1000);
  }
}

private stopTimer(): void {
  if (this.timerId !== 0) {
    clearInterval(this.timerId);
    this.timerId = 0;
  }
}

计时实现说明:

  • 使用递归setTimeout而非setInterval,避免累积误差
  • timerId作为计时状态标志,非零表示计时器运行中
  • 停止时使用clearInterval清除定时器(实际上应使用clearTimeout)

5.2 时间格式化

private formatTime(seconds: number): string {
  let mins: number = Math.floor(seconds / 60);
  let secs: number = seconds % 60;
  return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}

格式输出示例:

秒数 格式化结果
3 00:03
45 00:45
120 02:00

5.3 最佳时间显示

Text('最佳: ' + this.formatTime(this.bestTime))
  .fontSize(10)
  .fontColor('#87CEEB')

最佳时间在页面头部显示,让玩家随时了解自己的历史最佳成绩。

六、UI布局与动态适配

6.1 页面布局结构

build() {
  Column({ space: 16 }) {
    Row() {
      Button('← 返回')
        .fontSize(14)
        .fontColor('#FFD700')
        .backgroundColor('transparent')
        .borderColor('#FFD700')
        .borderWidth(1)
        .padding({ left: 12, right: 12 })
        .onClick(() => {
          this.stopTimer();
          router.back();
        })

      Text('找不同数字')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor('#FFD700')

      Column({ space: 2 }) {
        Text('第' + this.currentLevel + '关')
          .fontSize(12)
          .fontColor('#87CEEB')
        Text('最佳: ' + this.formatTime(this.bestTime))
          .fontSize(10)
          .fontColor('#87CEEB')
      }
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
    .padding({ left: 20, right: 20 })
    .margin({ top: 40 })

    Text(this.formatTime(this.timerSeconds))
      .fontSize(32)
      .fontColor('#87CEEB')
      .fontWeight(FontWeight.Bold)

    if (!this.isPlaying) {
      Column({ space: 16 }) {
        Text('找到不同的数字')
          .fontSize(18)
          .fontColor('#FFD700')
        Button('开始游戏')
          .fontSize(18)
          .fontColor('#0A192F')
          .backgroundColor('#FFD700')
          .borderRadius(12)
          .padding({ left: 40, right: 40, top: 12, bottom: 12 })
          .onClick(() => this.startGame())
      }
    } else {
      Column() {
        Text('找到不同的数字')
          .fontSize(18)
          .fontColor('#FFD700')
        Scroll() {
          Grid() {
            // 网格内容
          }
        }
        .width('100%')
        .height(this.gridSize <= 6 ? 350 : 450)
        Column({ space: 8 }) {
          Text('游戏说明')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FFD700')
          Text('在一堆相同数字中找到唯一不同的那个')
            .fontSize(12)
            .fontColor('#87CEEB')
            .textAlign(TextAlign.Center)
        }
        .width('90%')
        .padding({ top: 8, bottom: 16 })
      }
    }
  }
  .width('100%')
  .height('100%')
  .backgroundColor('#0A192F')
  .padding({ bottom: 80 })
}

布局层次结构:

Column (主容器)
├── Row (顶部导航栏)
│   ├── Button (返回)
│   ├── Text (标题)
│   └── Column (关卡信息)
│       ├── Text (关卡)
│       └── Text (最佳时间)
├── Text (计时器)
└── Column (内容区域)
    ├── [未开始] Column
    │   ├── Text (游戏提示)
    │   └── Button (开始游戏)
    └── [游戏中] Column
        ├── Text (游戏提示)
        ├── Scroll (滚动容器)
        │   └── Grid (游戏网格)
        └── Column (游戏说明)

6.2 动态网格配置

网格的列数和行数需要根据网格大小动态生成:

private getColumnsTemplate(): string {
  let result: string = '';
  for (let i = 0; i < this.gridSize; i++) {
    result += '1fr';
    if (i < this.gridSize - 1) {
      result += ' ';
    }
  }
  return result;
}

private getRowsTemplate(): string {
  let result: string = '';
  for (let i = 0; i < this.gridSize; i++) {
    result += '1fr';
    if (i < this.gridSize - 1) {
      result += ' ';
    }
  }
  return result;
}

模板生成示例:

网格大小 columnsTemplate rowsTemplate
4 “1fr 1fr 1fr 1fr” “1fr 1fr 1fr 1fr”
6 “1fr 1fr 1fr 1fr 1fr 1fr” “1fr 1fr 1fr 1fr 1fr 1fr”
8 “1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr” “1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr”
10 “1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr” “1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr”

6.3 动态字体大小适配

随着网格增大,每个格子的空间变小,需要动态调整字体大小以确保可读性:

private getFontSize(): number {
  if (this.gridSize <= 4) {
    return 36;
  } else if (this.gridSize <= 6) {
    return 28;
  } else if (this.gridSize <= 8) {
    return 24;
  } else {
    return 20;
  }
}

字体大小适配策略:

网格大小 字体大小 说明
4×4 36sp 16个格子,空间充裕
6×6 28sp 36个格子,适当缩小
8×8 24sp 64个格子,继续缩小
10×10 20sp 100个格子,最小字体

这种渐进式的字体调整确保了在任何网格大小下,数字都能清晰显示。

6.4 滚动容器设计

对于较大的网格(8×8和10×10),需要使用Scroll容器来确保所有内容都能被访问:

Scroll() {
  Grid() {
    // 网格内容
  }
  .width('98%')
  .height(this.gridSize <= 6 ? 350 : 450)
  .columnsTemplate(this.getColumnsTemplate())
  .rowsTemplate(this.getRowsTemplate())
}
.width('100%')
.height(this.gridSize <= 6 ? 350 : 450)

滚动容器高度适配:

网格大小 容器高度 说明
4×4 350px 较小网格,不需要滚动
6×6 350px 中等网格,不需要滚动
8×8 450px 较大网格,需要滚动
10×10 450px 最大网格,需要滚动

6.5 边框样式适配

根据网格大小调整边框宽度和圆角:

.borderWidth(this.gridSize <= 6 ? 1 : 0.5)
.borderRadius(this.gridSize <= 6 ? 8 : 2)

边框样式适配策略:

网格大小 边框宽度 圆角半径 说明
≤6 1px 8px 较大格子,边框清晰可见
>6 0.5px 2px 较小格子,边框细窄不影响视觉

七、路由与参数传递

7.1 路由参数定义

interface RouteParams {
  level: number;
  gridSize: number;
}

7.2 页面跳转

private startLevel(info: LevelInfo): void {
  router.pushUrl({ 
    url: 'pages/FindDifferentPage', 
    params: { 
      level: info.level, 
      gridSize: info.gridSize 
    } 
  });
}

7.3 参数接收

aboutToAppear(): void {
  let params: RouteParams = router.getParams() as RouteParams;
  if (params !== undefined) {
    if (params.level !== undefined) {
      this.currentLevel = params.level;
    }
    if (params.gridSize !== undefined) {
      this.gridSize = params.gridSize;
    }
  }
  let levelTimes: Array<number> = appState.findDifferentLevelTimes;
  if (levelTimes.length > this.currentLevel && levelTimes[this.currentLevel] > 0) {
    this.bestTime = levelTimes[this.currentLevel];
  }
}

参数传递流程:

关卡选择页 → 点击关卡 → 构造参数 → router.pushUrl → 游戏页
                                                       ↓
                                              aboutToAppear → 解析参数 → 加载最佳时间

八、状态管理与数据持久化

8.1 全局状态管理

游戏进度通过AppState单例进行管理:

findDifferentCompletedLevels: Array<number> = [];
findDifferentLevelTimes: Array<number> = [];

8.2 通关记录更新

let completedLevels: Array<number> = appState.findDifferentCompletedLevels;
let exists: boolean = false;
for (let i = 0; i < completedLevels.length; i++) {
  if (completedLevels[i] === this.currentLevel) {
    exists = true;
    break;
  }
}
if (!exists) {
  completedLevels.push(this.currentLevel);
  completedLevels.sort((a: number, b: number) => a - b);
  appState.findDifferentCompletedLevels = completedLevels;
}

注意事项:

由于ArkTS的响应式状态更新机制,直接修改数组元素不会触发UI更新,必须重新赋值。在这个实现中,我们直接在原数组上push元素并排序,然后重新赋值给appState,这是一种可行的方案。

8.3 最佳时间记录

let levelTimes: Array<number> = appState.findDifferentLevelTimes;
while (levelTimes.length <= this.currentLevel) {
  levelTimes.push(0);
}
if (levelTimes[this.currentLevel] === 0 || this.timerSeconds < levelTimes[this.currentLevel]) {
  levelTimes[this.currentLevel] = this.timerSeconds;
  appState.findDifferentLevelTimes = levelTimes;
  this.bestTime = this.timerSeconds;
}

时间记录策略:

  • 使用数组索引对应关卡编号,便于快速查找
  • 0表示未通关或无记录
  • 只记录最佳时间(最短用时)
  • 同时更新本地bestTime状态,用于UI显示

九、性能优化策略

9.1 数组操作优化

在游戏中,数组操作是频繁发生的,因此需要注意优化:

// 初始化数组
this.numbers = [];
for (let i = 0; i < total - 1; i++) {
  this.numbers.push(mainNumber);
}
this.numbers.push(differentNumber);

优化要点:

  • 使用push方法逐步添加元素,避免一次性创建大数组
  • 在shuffle方法中创建新数组,避免直接修改原数组

9.2 滚动性能优化

对于较大的网格,滚动性能尤为重要:

Scroll() {
  Grid() {
    ForEach(this.numbers, (num: number) => {
      GridItem() {
        // 内容
      }
    })
  }
  .width('98%')
  .height(this.gridSize <= 6 ? 350 : 450)
}
.width('100%')
.height(this.gridSize <= 6 ? 350 : 450)

优化策略:

  • 设置固定的Grid高度,避免动态计算
  • 使用ForEach进行列表渲染,ArkUI会自动优化渲染性能
  • 对于10×10的网格(100个元素),渲染压力较小,无需额外优化

9.3 计时器优化

使用递归setTimeout避免累积误差:

private tick(): void {
  if (this.timerId !== 0) {
    this.timerSeconds++;
    setTimeout(() => {
      this.tick();
    }, 1000);
  }
}

优势:

  • setTimeout在回调执行后重新计算下一次延迟
  • 避免setInterval因执行时间波动导致的累积误差
  • 适合需要高精度计时的场景

十、训练价值与教育意义

10.1 认知训练原理

找不同数字游戏基于以下认知心理学原理:

训练维度 原理说明 游戏机制
视觉注意力 通过在相似元素中寻找差异训练视觉辨别能力 在大量相同数字中找到不同的一个
反应速度 限时挑战提升信息处理速度 计时器显示和记录
空间扫描 训练系统的视觉搜索策略 有序地扫描网格区域
专注力 需要高度集中注意力才能找到差异 长时间保持专注

10.2 不同难度的训练价值

难度等级 网格大小 训练重点 适用人群
入门 4×4 基础视觉辨别 儿童、初学者
简单 6×6 中等难度视觉搜索 普通玩家
中等 8×8 高效扫描策略 进阶玩家
困难 10×10 极限专注力 高级玩家

10.3 科学训练建议

根据认知科学研究,建议以下训练方案:

每周训练次数:3-5次
每次训练时长:10-15分钟
训练间隔:每次训练后休息5分钟
难度递进:从4×4开始,逐步提升到10×10

训练效果评估:

评估指标 说明
完成时间 记录每关用时,观察是否逐渐缩短
错误次数 统计点击错误的次数(当前版本不记录)
连续通关数 一次训练中连续通关的关卡数

10.4 实际应用场景

这款游戏在以下场景中具有实际应用价值:

场景 应用方式
儿童教育 训练儿童的数字识别和视觉注意力
老年人认知训练 延缓认知衰退,保持大脑活跃
职业训练 提升需要高度专注的职业技能(如医生、飞行员)
日常放松 快速放松大脑,缓解工作压力

十一、代码优化建议

11.1 洗牌算法兼容性

当前代码使用了展开运算符,这在ArkTS中可能不支持:

// 当前实现(可能不兼容)
let result: Array<number> = [...array];

// 推荐实现(兼容ArkTS)
let result: Array<number> = [];
for (let i = 0; i < array.length; i++) {
  result.push(array[i]);
}

11.2 计时器实现改进

当前停止计时器使用clearInterval,但实际上应该使用clearTimeout:

private stopTimer(): void {
  if (this.timerId !== 0) {
    clearTimeout(this.timerId); // 改为clearTimeout
    this.timerId = 0;
  }
}

11.3 游戏结束提示

当前游戏结束后只是返回开始界面,没有胜利提示。可以添加胜利弹窗:

// 添加胜利状态
@State isGameWon: boolean = false;

// 胜利时设置状态
private clickNumber(num: number): void {
  if (num === this.targetNumber) {
    this.stopTimer();
    // ... 记录进度 ...
    this.isGameWon = true;
  }
}

// 在build中添加胜利界面
if (this.isGameWon) {
  Column() {
    Text('恭喜通关!')
      .fontSize(36)
      .fontColor('#FFD700')
      .fontWeight(FontWeight.Bold)
    // ... 其他胜利信息 ...
  }
}

11.4 错误点击反馈

当前错误点击没有任何反馈,可以添加简单的动画或震动反馈:

// 添加错误状态
@State lastClickWrong: boolean = false;

private clickNumber(num: number): void {
  if (num === this.targetNumber) {
    // 正确处理
  } else {
    this.lastClickWrong = true;
    setTimeout(() => {
      this.lastClickWrong = false;
    }, 200);
  }
}

十二、总结

通过本文的详细分析,我们深入了解了找不同数字游戏的完整实现过程。从核心算法到UI交互,从状态管理到性能优化,每个环节都体现了HarmonyOS ArkTS开发的最佳实践。

关键技术要点:

  1. 数字生成策略:生成大部分相同、只有一个不同的数字数组
  2. Fisher-Yates洗牌:高效的随机打乱算法,保证游戏的公平性
  3. 视觉一致性:所有数字使用完全相同的视觉样式,消除视觉线索
  4. 动态适配:根据网格大小自适应调整字体大小、边框样式和容器高度
  5. 滚动容器:支持较大网格的滚动浏览
  6. 全局状态管理:AppState单例实现跨页面数据共享

这款游戏不仅是一个娱乐应用,更是一个科学的认知训练工具。通过合理的难度设计和动态适配,玩家可以在游戏中提升视觉注意力、反应速度和专注力。

在未来的迭代中,可以考虑增加更多难度级别(如12×12网格),引入多人对战模式,添加错误点击反馈,以及添加更详细的训练数据分析功能,为用户提供更全面的认知训练体验。

Logo

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

更多推荐