HarmonyOS 小游戏《对战五子棋》开发第33篇 - AI异步落子与setTimeout

让AI"思考"一下再落子------异步交互的体验设计

截图如下:

AI落子的时序问题

如果AI在玩家落子后立即响应,用户体验会很奇怪------感觉不像在"对战"。通过setTimeout添加延迟,让AI有"思考"的时间。

玩家落子触发AI

typescript 复制代码
private onCellClick(row: number, col: number): void {
  if (this.result !== GameResult.PLAYING || this.aiThinking) return;
  if (this.currentPlayer !== BLACK) return;  // 只有玩家(黑方)能点击

  const success = this.engine.placePiece(row, col, BLACK);
  if (!success) return;

  this.refreshBoardData();

  if (this.result !== GameResult.PLAYING) {
    this.handleGameEnd();
    return;
  }

  // AI回合
  this.aiThinking = true;
  this.aiTimerId = setTimeout(() => {
    this.makeAIMove();
  }, 400);
}

aiThinking状态

typescript 复制代码
@State aiThinking: boolean = false;

aiThinking有双重作用:

  1. UI展示:显示"AI思考中..."和LoadingProgress动画
  2. 交互锁:AI思考时禁止玩家点击棋盘
typescript 复制代码
// 交互锁
if (this.result !== GameResult.PLAYING || this.aiThinking) return;

// UI展示
Text(this.getStatusText())  // 返回 "AI思考中..."
if (this.aiThinking) {
  LoadingProgress().width(16).height(16).color('#FA8C16')
}

// 按钮状态
Button('重新开始')
  .enabled(!this.aiThinking)  // AI思考时禁用

makeAIMove实现

typescript 复制代码
private makeAIMove(): void {
  this.aiTimerId = -1;  // 清除定时器ID
  
  if (this.result !== GameResult.PLAYING) {
    this.aiThinking = false;
    return;
  }

  // AI计算最佳落子
  const move = this.aiPlayer.getMove(this.engine.board);
  
  // 引擎执行落子
  this.engine.placePiece(move.row, move.col, WHITE);
  
  // 解除思考状态
  this.aiThinking = false;
  
  // 刷新UI
  this.refreshBoardData();

  // 检查游戏结束
  if (this.result !== GameResult.PLAYING) {
    this.handleGameEnd();
  }
}

setTimeout的作用

typescript 复制代码
this.aiTimerId = setTimeout(() => {
  this.makeAIMove();
}, 400);

400ms延迟的设计考量:

  1. 用户体验:给AI"思考"的感觉
  2. 避免UI卡顿:困难模式的Minimax搜索需要200-500ms,setTimeout让搜索在下一个事件循环中执行
  3. 可取消:保存timerId用于清理

定时器清理

typescript 复制代码
aboutToDisappear(): void {
  if (this.aiTimerId !== -1) {
    clearTimeout(this.aiTimerId);
    this.aiTimerId = -1;
  }
}

private backToLevelSelect(): void {
  if (this.aiTimerId !== -1) {
    clearTimeout(this.aiTimerId);
    this.aiTimerId = -1;
  }
  this.inGame = false;
  // ...
}

清理时机:

  1. 页面销毁时(aboutToDisappear)
  2. 返回选关时(backToLevelSelect)

为什么要清理? 如果不清理,定时器在页面销毁后仍会执行makeAIMove,可能访问已销毁的组件状态,导致错误。

AI落子的完整流程

复制代码
玩家点击落子
    ↓
engine.placePiece(玩家黑棋)
    ↓
游戏结束? → 是 → handleGameEnd()
    ↓ 否
aiThinking = true
    ↓
setTimeout(400ms)
    ↓
makeAIMove()
    ↓
aiPlayer.getMove(board) → 计算最佳位置
    ↓
engine.placePiece(AI白棋)
    ↓
aiThinking = false
    ↓
refreshBoardData()
    ↓
游戏结束? → 是 → handleGameEnd()
    ↓ 否
等待玩家下一手

状态文字的变化

typescript 复制代码
private getStatusText(): string {
  if (this.result === GameResult.BLACK_WIN) return '你赢了!';
  if (this.result === GameResult.WHITE_WIN) return 'AI获胜!';
  if (this.result === GameResult.DRAW) return '平局!';
  if (this.aiThinking) return 'AI思考中...';
  return '你的回合';
}

状态文字随游戏进程动态变化:

复制代码
你的回合 → AI思考中... → 你的回合 → ... → 你赢了!/AI获胜!

LoadingProgress组件

typescript 复制代码
if (this.aiThinking) {
  LoadingProgress()
    .width(16)
    .height(16)
    .margin({ left: 8 })
    .color('#FA8C16')  // 橙色加载圈
}

LoadingProgress是ArkUI内置的加载动画组件------一个旋转的圆圈,在AI思考时显示。

总结

AI异步落子的设计要点:

  1. setTimeout延迟:400ms模拟AI"思考"
  2. aiThinking状态:UI展示+交互锁双重作用
  3. LoadingProgress:内置加载动画
  4. 定时器清理:页面销毁/返回时清除
  5. 流程清晰:玩家落子→延迟→AI落子→刷新

这种异步设计让对战体验自然流畅,避免了"AI瞬间响应"的违和感。

相关推荐
非优秀程序员2 小时前
第12章 烧钱无底洞!!!不烧就死,烧了也不知道啥时候死|《互联网产品恩仇录》
人工智能
Zelman9453 小时前
第06章-交互与观察动作空间
人工智能
冬奇Lab3 小时前
开源项目第258期:Harness² —— Google Research 的「递归式 Agent Harness 优化」,模型权重不动,只改 Agent 的外壳
人工智能·开源
字节跳动视频云技术团队4 小时前
每两家头部手机厂商就有一家选它:火山引擎如何成为 AI 手机助手的“隐形底座”?
人工智能
泯泷4 小时前
像项目经理一样指挥 AI 团队:管理者模式
人工智能·llm·agent
京东云开发者5 小时前
双11囤Token火了:京东搜索量暴涨7倍,AI生产力成新刚需
人工智能
ahccqw5 小时前
OpenCV 车辆数量统计
人工智能·opencv·计算机视觉
人工智能AI技术5 小时前
LangGraph 本地 build 频频翻车,最终在服务器跑通流式对话
人工智能
Flynn Liang6 小时前
同一需求三套写法:用 Spring AI、LangChain4j、AgentScope Java 各实现一个带工具调用的 RAG 助手(实战对比)
java·人工智能·大模型·架构设计·spring ai·langchain4j·agentscope