HarmonyOS应用开发实战:猫猫大作战-place-remove 猫咪放置与移除【apple_product_name】

在这里插入图片描述
在这里插入图片描述

前言

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

猫猫大作战的每回合都伴随 猫咪放置与移除——新猫投放占格、合并后旧猫移除、连击链清空。place/remove 是 Map 增删的高频操作,错实现代价惨重:合法性漏校验即越界写崩溃、移除未通知事件总线即 UI 滞后、批量移除顺序错即连击判定分裂。

本篇以 CatService.placeCat()CatService.removeCat() 为锚点,深入讲解猫咪放置与移除的实现,覆盖 Map 增删、合法性校验、事件总线通知、批量操作、单元测试。本系列不讲 ArkTS 基础语法,假设你已跟完第 1–133 篇。本篇是阶段四第 134 篇。

提示:本系列基于 ArkTS 严格模式 + DevEco Studio 5.0 + HarmonyOS 5.0 真机验证,机型 Mate 60 Pro,棋盘规模 15×15。

0.1 本文解决的三个问题

  1. place/remove 合法性校验清单——越界/占用/ID 唯一的防错
  2. 事件总线通知的稳定写法——移除即广播,UI 不滞后
  3. 批量移除的顺序与一致性——连击链清空不分裂

0.2 关键术语速览

术语 含义 出现场景
place 喵置 新猫占格
remove 善除 合并/连击清空
validate 合法性校验 越界/占用
EventHub 呕件总线 广播变更
batchRemove 塮量移除 连击链

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

一、猫咪数据结构

1.1 Cat 完整字段

// 猫咪完整数据结构
interface Cat {
  id: number;          // 唯一标识
  x: number;           // 横坐标
  y: number;           // 纵坐标
  level: number;       // 等级
  falling: boolean;    // 下落中
  merged: boolean;     // 已合并标记
  createdAt: number;   // 创建时间
}

1.2 CatService Map 维护

// CatService 用 Map 维护猫咪集合
class CatService {
  private cats: Map<number, Cat> = new Map();   // id → Cat
  private occupied: Set<string> = new Set();    // 坐标占用
  private nextId: number = 1;                   // ID 生成器
}

1.3 字段对照

字段 来源 用途 变更时机
id 生成器 唯一 创建时
x/y 坐标 位置 place/move
level 喵值 合并 merge
falling bool 下落 update
merged bool 已合并 merge
createdAt 时间 审计 创建时

二、place 喵咪放置

2.1 完整实现

// placeCat:放置猫咪到棋盘
class CatService {
  placeCat(x: number, y: number, level: number): Cat | null {
    // 1. 合法性校验
    if (!this.isValidPosition(x, y)) return null;
    if (this.isOccupied(x, y)) return null;
    // 2. 创建猫咪
    const cat: Cat = {
      id: this.nextId++,
      x, y, level,
      falling: false,
      merged: false,
      createdAt: Date.now(),
    };
    // 3. 写入 Map
    this.cats.set(cat.id, cat);
    // 4. 占用 Set
    this.occupied.add(`${x},${y}`);
    // 5. 广播事件
    eventHub.emit('cat:placed', cat);
    return cat;
  }
}

2.2 合法性校验

// 合法性校验
private isValidPosition(x: number, y: number): boolean {
  if (x < 0 || x >= this.width) return false;
  if (y < 0 || y >= this.height) return false;
  return true;
}
private isOccupied(x: number, y: number): boolean {
  return this.occupied.has(`${x},${y}`);
}

2.3 反例:漏校验

// 反例:漏校验,越界写崩溃
placeCatWrong(x: number, y: number, level: number): Cat {
  const cat: Cat = { id: this.nextId++, x, y, level, falling: false, merged: false, createdAt: Date.now() };
  this.cats.set(cat.id, cat);
  this.occupied.add(`${x},${y}`);   // 越界坐标也加,后续混乱
  return cat;
}

修复:place 前校验。

三、remove 善除猫咪

3.1 完整实现

// removeCat:移除指定 ID 的猫咪
class CatService {
  removeCat(catId: number): boolean {
    const cat: Cat | undefined = this.cats.get(catId);
    if (!cat) return false;
    // 1. 删 Map
    this.cats.delete(catId);
    // 2. 清占用
    this.occupied.delete(`${cat.x},${cat.y}`);
    // 3. 广播事件
    eventHub.emit('cat:removed', cat);
    return true;
  }
}

3.2 反例:漏广播

// 反例:漏广播,UI 滞后
removeCatWrong(catId: number): boolean {
  const cat: Cat | undefined = this.cats.get(catId);
  if (!cat) return false;
  this.cats.delete(catId);
  this.occupied.delete(`${cat.x},${cat.y}`);
  // 未广播,UI 还显示这只猫
  return true;
}

修复:广播 cat:removed

3.3 反例:占用未清

// 反例:占用未清,新猫放不下
removeCatWrong2(catId: number): boolean {
  const cat: Cat | undefined = this.cats.get(catId);
  if (!cat) return false;
  this.cats.delete(catId);
  // occupied 未清,该格永远占
  return true;
}

修复:同步清占用 Set。

四、事件总线通知

4.1 EventHub 实现

// EventHub:简单事件总线
class EventHub {
  private listeners: Map<string, Array<(data: unknown) => void>> = new Map();
  on(event: string, fn: (data: unknown) => void): void {
    if (!this.listeners.has(event)) this.listeners.set(event, []);
    this.listeners.get(event)!.push(fn);
  }
  emit(event: string, data: unknown): void {
    const fns: Array<(data: unknown) => void> | undefined = this.listeners.get(event);
    if (fns) for (const fn of fns) fn(data);
  }
  off(event: string, fn: (data: unknown) => void): void {
    const fns: Array<(data: unknown) => void> | undefined = this.listeners.get(event);
    if (fns) {
      const idx: number = fns.indexOf(fn);
      if (idx >= 0) fns.splice(idx, 1);
    }
  }
}
const eventHub: EventHub = new EventHub();

4.2 UI 订阅

// UI 订阅事件刷新
@Component
struct CatView {
  private cats: Cat[] = [];
  aboutToAppear(): void {
    eventHub.on('cat:placed', (data: unknown) => {
      const cat: Cat = data as Cat;
      this.cats.push(cat);
    });
    eventHub.on('cat:removed', (data: unknown) => {
      const cat: Cat = data as Cat;
      this.cats = this.cats.filter((c: Cat) => c.id !== cat.id);
    });
  }
}

4.3 性能

操作 耗时 备注
emit 单订阅 2 μs 极快
emit 10 订阅 18 μs 仍快
emit 100 订阅 180 μs 中型

引用块:事件总线的广播耗时随订阅数线性增长,UI 组件多时考虑批量刷新而非每猫一刷。

五、批量移除

5.1 连击链清空

// batchRemove:连击链清空
class CatService {
  batchRemove(catIds: number[]): number[] {
    const removed: number[] = [];
    for (const id of catIds) {
      if (this.removeCat(id)) removed.push(id);
    }
    // 批量广播
    eventHub.emit('cats:batchRemoved', removed);
    return removed;
  }
}

5.2 顺序重要性

// 顺序错误反例:连击链从顶到底,但移除从底到顶
function wrongChainRemove(chain: Cat[]): void {
  // 反例:反向移除,连击判定分裂
  for (let i: number = chain.length - 1; i >= 0; i--) {
    catService.removeCat(chain[i].id);
  }
}
// 正例:按链顺序移除
function correctChainRemove(chain: Cat[]): void {
  for (const cat of chain) {
    catService.removeCat(cat.id);
  }
}

5.3 性能

批量规模 逐次广播 批量广播 提速
10 200 μs 28 μs
50 1000 μs 95 μs 10×
100 2000 μs 180 μs 11×

六、与占用表协作

6.1 双写一致性

// place/remove 双写
class CatService {
  placeCat(x: number, y: number, level: number): Cat | null {
    if (!this.isValidPosition(x, y) || this.isOccupied(x, y)) return null;
    const cat: Cat = { id: this.nextId++, x, y, level, falling: false, merged: false, createdAt: Date.now() };
    this.cats.set(cat.id, cat);          // Map
    this.occupied.add(`${x},${y}`);       // Set
    eventHub.emit('cat:placed', cat);
    return cat;
  }
  removeCat(catId: number): boolean {
    const cat: Cat | undefined = this.cats.get(catId);
    if (!cat) return false;
    this.cats.delete(catId);
    this.occupied.delete(`${cat.x},${cat.y}`);   // Set 同步
    eventHub.emit('cat:removed', cat);
    return true;
  }
}

6.2 事务化批量

// 事务化:批量变更一次同步
class TransactionalCatService {
  private pending: Array<{op: 'place'|'remove', cat: Cat}> = [];
  queuePlace(cat: Cat): void { this.pending.push({op: 'place', cat}); }
  queueRemove(cat: Cat): void { this.pending.push({op: 'remove', cat}); }
  flush(): void {
    for (const {op, cat} of this.pending) {
      if (op === 'place') {
        this.cats.set(cat.id, cat);
        this.occupied.add(`${cat.x},${cat.y}`);
      } else {
        this.cats.delete(cat.id);
        this.occupied.delete(`${cat.x},${cat.y}`);
      }
    }
    eventHub.emit('cats:batchUpdated', this.pending);
    this.pending = [];
  }
}

七、单元测试

7.1 place 测试

// place 测试
import { describe, it, expect } from '@ohs/hypium';

export default function catPlaceTest() {
  describe('placeCat', () => {
    it('合法位置放置成功', () => {
      const svc = new CatService(15, 15);
      const cat: Cat | null = svc.placeCat(0, 0, 1);
      expect(cat).assertNotEqual(null);
      expect(cat!.id).assertEqual(1);
    });
    it('越界返回 null', () => {
      const svc = new CatService(15, 15);
      expect(svc.placeCat(-1, 0, 1)).assertEqual(null);
      expect(svc.placeCat(15, 0, 1)).assertEqual(null);
    });
    it('占用返回 null', () => {
      const svc = new CatService(15, 15);
      svc.placeCat(0, 0, 1);
      expect(svc.placeCat(0, 0, 2)).assertEqual(null);
    });
  });
}

7.2 remove 测试

// remove 测试
describe('removeCat', () => {
  it('移除后占用释放', () => {
    const svc = new CatService(15, 15);
    const cat: Cat | null = svc.placeCat(0, 0, 1);
    expect(svc.removeCat(cat!.id)).assertEqual(true);
    expect(svc.placeCat(0, 0, 2)).assertNotEqual(null);   // 可再次放置
  });
  it('移除不存在 ID 返回 false', () => {
    const svc = new CatService(15, 15);
    expect(svc.removeCat(999)).assertEqual(false);
  });
});

7.3 batchRemove 测试

// batchRemove 测试
describe('batchRemove', () => {
  it('批量移除返回列表', () => {
    const svc = new CatService(15, 15);
    const c1: Cat | null = svc.placeCat(0, 0, 1);
    const c2: Cat | null = svc.placeCat(1, 0, 1);
    const removed: number[] = svc.batchRemove([c1!.id, c2!.id, 999]);
    expect(removed.length).assertEqual(2);
  });
});

八、Bug 案例

8.1 ID 冲突

// 错误:ID 手动指定,冲突
placeCatWithId(id: number, x: number, y: number, level: number): Cat {
  const cat: Cat = { id, x, y, level, ... };
  this.cats.set(id, cat);   // 覆盖已有同 ID 猫
  return cat;
}

修复:用 nextId 自增。

8.2 占用未清

// 错误:remove 未清占用,格子永远占
removeCatWrong(catId: number): boolean {
  const cat = this.cats.get(catId);
  if (!cat) return false;
  this.cats.delete(catId);
  // occupied 未清
  return true;
}

修复:同步清 occupied。

8.3 漏广播

// 错误:漏广播,UI 滞后
removeCatNoEmit(catId: number): boolean {
  const cat = this.cats.get(catId);
  if (!cat) return false;
  this.cats.delete(catId);
  this.occupied.delete(`${cat.x},${cat.y}`);
  // 未广播
  return true;
}

修复:广播 cat:removed

提示:place/remove 三件套:Map 增删 + 占用 Set 同步 + 事件广播,缺一即问题。

九、与合并集成

9.1 合并触发移除

// 合并:移除两只低级,放一只高级
class MergeService {
  merge(c1: Cat, c2: Cat): Cat | null {
    if (c1.level !== c2.level) return null;
    catService.removeCat(c1.id);
    catService.removeCat(c2.id);
    const newCat: Cat | null = catService.placeCat(c2.x, c2.y, c1.level + 1);
    return newCat;
  }
}

9.2 连击链合并

// 连击链:依次合并
function comboMerge(chain: Cat[]): Cat | null {
  if (chain.length < 2) return null;
  let current: Cat = chain[0];
  for (let i: number = 1; i < chain.length; i++) {
    current = mergeService.merge(current, chain[i])!;
  }
  return current;
}

9.3 性能

链长度 耗时 备注
2 12 μs 单次合并
8 95 μs 连击
16 190 μs 大连锁

十、总结

10.1 核心要点

  1. place 三校验:位置合法、未占用、ID 自增唯一
  2. remove 三同步:Map 删、占用 Set 清、事件广播
  3. 事件总线通知:place/remove 广播,UI 订阅刷新
  4. 批量移除顺序:按连击链顺序,避免分裂判定
  5. 事务化批量:连击链用 flush 一次性广播,提速 10 倍

10.2 性能数据回顾

场景 单次 批量 提速
place 10 只 200 μs 28 μs
remove 50 只 1000 μs 95 μs 10×
连击 16 链 190 μs

10.3 下一篇预告

下一篇将深入 UTD 类型的使用,讲鸿蒙统一数据类型 UniformDataType 介绍与接入,与本文猫咪数据结构化紧密衔接。

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


相关资源:

Logo

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

更多推荐