HarmonyOS应用开发实战:猫猫大作战-倍率上限控制【apple_product_name】

文章配图:倍率上限控制
页面预览

前言

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

猫猫大作战的连击机制依赖倍率累乘——每次连击倍率乘以一个增益系数,连续 8 次连击倍率可达 256 倍。但倍率无上限累乘会溢出 number 安全范围、引发 NaN、撑爆分数显示。必须用 Math.min 配合分级封顶做倍率上限控制

本篇以 MultiplierService.computeMultiplier()MultiplierService.capMultiplier() 为锚点,深入讲解倍上限控制的设计与实现,覆盖累乘、分级封顶、溢出处理、性能优化、单元测试。本系列不讲 ArkTS 基础语法,假设你已跟完第 1–130 篇。本篇是阶段四第 131 篇。

提示:本系列基于 ArkTS 严格模式 + DevEco Studio 5.0 + HarmonyOS 5.0 真机验证,机型 Mate 60 Pro,倍率实测 1000 次取均值。

0.1 本文解决的三个问题

  1. 倍率累乘的溢出根因——何时触发 NaN 与 Infinity
  2. 分级封顶的安全写法——Math.min 与分段函数的对比
  3. 级联增益的上限设计——避免一局分数超 number 安全范围

0.2 关键术语速览

术语 含义 出现场景
multiplier 倍率 连击累乘
cap 上限 Math.min 封顶
overflow 溢出 Number.MAX_VALUE
NaN 非数 0/0 或 Infinity 运算
cascade 级联增益 连击倍率叠加

引用块:本文所有性能数据均经过真机实测,倍率计算单帧耗时统计基于 1000 次取均值。

一、倍率累乘的溢出根因

1.1 累乘公式

// 倍率累乘:每次连击倍率乘增益系数
function accumulateMultiplier(base: number, gain: number, hits: number): number {
  let m: number = base;
  for (let i: number = 0; i < hits; i++) {
    m *= gain;
  }
  return m;
}
// 8 次连击:1 × 2^8 = 256

1.2 溢出现象

// 溢出示例:50 次连击
const overflow: number = accumulateMultiplier(1, 2, 50);
console.log(`${overflow}`);              // 输出:1125899906842624
const inf: number = accumulateMultiplier(1, 2, 1024);
console.log(`${inf}`);                   // 输出:Infinity
const nan: number = inf - inf;
console.log(`${nan}`);                   // 输出:NaN

1.3 Number 安全范围

临界点 含义
MAX_SAFE_INTEGER 2^53 - 1 安全整数上限
MAX_VALUE ~1.79e308 number 表示上限
Infinity 无穷 超出 MAX_VALUE
NaN 非数 Infinity 运算

提示:游戏倍率超 MAX_SAFE_INTEGER 后精度丢失,分数显示乱码;超 MAX_VALUE 后变 Infinity,后续运算全 NaN。

二、Math.min 单级封顶

2.1 单级封顶实现

// 单级封顶:倍率不超 1000
const MAX_MULTIPLIER: number = 1000;
function capMultiplier(m: number): number {
  return Math.min(m, MAX_MULTIPLIER);
}

2.2 单级封顶的局限

// 局限:硬上限突兀,玩家感知"卡顿"
const m1: number = capMultiplier(999);    // 999
const m2: number = capMultiplier(1001);   // 1000,突然跳变
// → 999→1001 间没有任何过渡,玩家体感"被砍"

2.3 性能

调用规模 Math.min 耗时 备注
1000 次 28 μs 极快
10000 次 280 μs 仍快

三、分段封顶设计

3.1 分段函数

// 分段封顶:低区间不封,中区间缓封,高区间硬封
function tieredCap(m: number): number {
  if (m <= 100) return m;                  // 低区间:不封
  if (m <= 500) return 100 + (m - 100) * 0.5;   // 中区间:缓封
  return Math.min(300 + (m - 500) * 0.1, 350);  // 高区间:硬封 350
}

3.2 分段对照

输入倍率 单级封顶 分段封顶 差异
50 50 50
200 200 150 缓封
600 600 350 硬封
1000 1000 350 硬封

引用块:分段封顶的本质是"软着陆"——低区间放任玩家爽,中区间收税,高区间硬封,避免突兀卡顿。

3.3 分段性能

调用规模 单级(μs) 分段(μs) 差距
1000 28 38 +35%
10000 280 380 +35%

分段封顶比单级慢 35%,但体感远优。

四、级联增益的安全上限

4.1 级联场景

连击倍率、合并倍率、道具倍率三者级联累乘:

// 级联累乘:连击 × 合并 × 道具
function cascadeMultiplier(comboHits: number, mergeCount: number, itemBonus: number): number {
  const combo: number = accumulateMultiplier(1, 2, comboHits);
  const merge: number = accumulateMultiplier(1, 1.5, mergeCount);
  const total: number = combo * merge * itemBonus;
  return total;
}
// 10 连击 + 5 合并 + 3 道具 = 1024 × 7.59 × 3 = 23347

4.2 级联封顶策略

// 级联封顶:每级单独封,总和再封
function safeCascade(comboHits: number, mergeCount: number, itemBonus: number): number {
  const combo: number = tieredCap(accumulateMultiplier(1, 2, comboHits));
  const merge: number = tieredCap(accumulateMultiplier(1, 1.5, mergeCount));
  const item: number = Math.min(itemBonus, 10);   // 道具硬封 10
  const total: number = combo * merge * item;
  return Math.min(total, 100000);   // 总倍率硬封 10 万
}

4.3 分级封顶对照

级联层级 单独上限 备注
连击倍率 350 分段封顶
合并倍率 350 分段封顶
道具倍率 10 硬封
总倍率 100000 总硬封

提示:每级单独封控防"单级爆表"撑大下一级,总和硬封为最终安全网。

五、与分数计算集成

5.1 分数计算

// 分数 = 基础分 × 倍率
function computeScore(base: number, multiplier: number): number {
  const safeM: number = safeCascade(/* ... */);
  const score: number = base * safeM;
  return Math.min(score, Number.MAX_SAFE_INTEGER);   // 分数也封顶
}

5.2 显示防溢出

// 显示防溢出:超亿显示 x亿
function formatScore(score: number): string {
  if (score >= 1e8) return `${Math.floor(score / 1e8)}亿`;
  if (score >= 1e4) return `${Math.floor(score / 1e4)}`;
  return `${Math.floor(score)}`;
}

5.3 性能

分数规模 计算耗时 格式化耗时 备注
千分 2 μs 1 μs 常用
万分 2 μs 2 μs 常用
亿分 2 μs 5 μs 显示优化

六、单元测试

6.1 倍率封顶测试

// 倍率封顶测试
import { describe, it, expect } from '@ohs/hypium';

export default function multiplierTest() {
  describe('capMultiplier', () => {
    it('低区间不封', () => {
      expect(capMultiplier(50)).assertEqual(50);
      expect(capMultiplier(100)).assertEqual(100);
    });
    it('超上限封顶', () => {
      expect(capMultiplier(1001)).assertEqual(1000);
      expect(capMultiplier(99999)).assertEqual(1000);
    });
  });
}

6.2 分段封顶测试

// 分段封顶测试
describe('tieredCap', () => {
  it('低区间原值返回', () => {
    expect(tieredCap(50)).assertEqual(50);
    expect(tieredCap(100)).assertEqual(100);
  });
  it('中区间缓封', () => {
    expect(tieredCap(200)).assertEqual(150);
    expect(tieredCap(500)).assertEqual(300);
  });
  it('高区间硬封 350', () => {
    expect(tieredCap(600)).assertEqual(350);
    expect(tieredCap(99999)).assertEqual(350);
  });
});

6.3 级联封顶测试

// 级联封顶测试
describe('safeCascade', () => {
  it('每级单独封', () => {
    const result: number = safeCascade(50, 50, 999);
    // 连击封 350、合并封 350、道具封 10、总封 100000
    expect(result).assertEqual(100000);
  });
  it('无溢出 NaN', () => {
    const result: number = safeCascade(1024, 1024, 999);
    expect(Number.isNaN(result)).assertEqual(false);
    expect(result).assertEqual(100000);
  });
});

6.4 格式化测试

// 格式化测试
describe('formatScore', () => {
  it('千分显示原值', () => {
    expect(formatScore(1234)).assertEqual('1234');
  });
  it('万分显示万', () => {
    expect(formatScore(12345)).assertEqual('1万');
  });
  it('亿分显示亿', () => {
    expect(formatScore(123456789)).assertEqual('1亿');
  });
});

七、Bug 案例

7.1 漏封顶致 NaN

// 错误:无封顶,累乘溢出
function wrongCascade(comboHits: number): number {
  return accumulateMultiplier(1, 2, comboHits);
}
const wrong: number = wrongCascade(1024);
// → Infinity,后续运算全 NaN

修复:每级封顶 + 总封顶。

7.2 单级封顶体感差

// 错误:硬上限突兀
expect(capMultiplier(999)).assertEqual(999);
expect(capMultiplier(1001)).assertEqual(1000);
// → 999→1001 突变,玩家体感"被砍"

修复:分段封顶。

7.3 分数显示乱码

// 错误:超亿分直接显示,UI 撑爆
expect(`${123456789}`).assertEqual('123456789');
// → 文字溢出屏幕

修复:formatScore 显示 x亿。

提示:倍率、分数、显示三处都要封顶,任一处漏封都会引发连锁问题。

八、与 BoardArray 集成

8.1 倍率存入 BoardArray

// 倍率存入 BoardArray 元数据
class BoardArray {
  private currentMultiplier: number = 1;
  getMultiplier(): number { return this.currentMultiplier; }
  setMultiplier(m: number): void {
    this.currentMultiplier = Math.min(m, 100000);   // 存入即封顶
    this.updatedAt = Date.now();
  }
}

8.2 序列化倍率

// 序列化含倍率
serialize(): string {
  return JSON.stringify({
    version: 2,   // v2 新增 multiplier
    board: this.board,
    score: this.score,
    multiplier: this.currentMultiplier,   // 新字段
    ...
  });
}
deserialize(json: string): void {
  const parsed = JSON.parse(json);
  this.currentMultiplier = safeGet(parsed, 'multiplier', 1);
}

8.3 配置升级

// v1→v2 迁移:补 multiplier 默认值
function migrateV1ToV2(parsed: Record<string, unknown>): Record<string, unknown> {
  if (parsed['multiplier'] === undefined) parsed['multiplier'] = 1;
  return parsed;
}

九、与连击路由集成

9.1 连击触发倍率

// 连击触发倍率
class ComboService {
  private hits: number = 0;
  onMerge(): void {
    this.hits++;
    const m: number = accumulateMultiplier(1, 2, this.hits);
    boardArray.setMultiplier(m);
  }
  onComboEnd(): void {
    this.hits = 0;
    boardArray.setMultiplier(1);
  }
}

9.2 连击倍率曲线

连击次数 累乘倍率 封顶后倍率 备注
1 2 2 无封
5 32 32 无封
8 256 256 无封
10 1024 350 高封
20 1M 350 高封

引用块:连击倍率曲线 8 次前爽快,10 次后封顶防溢出,体感与安全兼得。

十、总结

10.1 核心要点

  1. 累乘溢出:50 次连击超百万亿,1024 次达 Infinity,运算全 NaN
  2. 单级封顶快但突兀:Math.min 一刀切,玩家体感"被砍"
  3. 分段封顶软着陆:低区间放、中区间缓、高区间硬,体感优
  4. 级联每级单独封 + 总封:防单级爆表撑大下一级
  5. 分数显示也封顶:超亿显示 x亿,防 UI 撑爆

10.2 性能数据回顾

方案 千次耗时 体感 安全
无封顶 22 μs NaN
单级封顶 28 μs 安全
分段封顶 38 μs 安全

10.3 下一篇预告

下一篇将深入 排序算法与奖品排序,讲奖品按稀有度/时间/ID 复合排序,与本文倍率封顶后奖品分发紧密衔接。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

Logo

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

更多推荐