一、应用概述井字棋Tic-Tac-Toe是一款经典的两人轮流在 3×3 格子上标记 X 和 O 的智力游戏。本篇文章将带领读者在 HarmonyOS 平台上使用 ArkTS 语言和声明式 UI 框架从零构建一个完整的井字棋对战应用。文章将深入分析游戏的核心算法——胜负判定8 线检测、交替落子逻辑、状态管理以及响应式的 UI 交互设计。二、架构设计2.1 整体模块划分井字棋应用采用标准的单页面 组件化架构主要划分为三个层次层级职责核心文件数据层游戏状态管理、棋盘数据、胜负判定逻辑GameModel.ets业务层交替落子、重置、历史记录GameController.ets表现层网格渲染、状态指示、交互反馈TicTacToeView.ets, GridCell.ets2.2 数据流向用户点击 → GridCell.onClick() → GameController.handleMove(row, col) → GameModel.updateBoard(row, col, player) → GameModel.checkWinner() → 状态更新触发 UI 重渲染ArkTS 的State和Prop装饰器驱动了从数据到视图的单向数据流确保任何状态的变更都能实时反映到界面。三、核心技术实现3.1 游戏状态与数据结构// GameModel.ets enum Player { None 0, X 1, O 2 } class GameState { board: Player[][] [ [Player.None, Player.None, Player.None], [Player.None, Player.None, Player.None], [Player.None, Player.None, Player.None] ]; currentPlayer: Player Player.X; winner: Player Player.None; moveCount: number 0; isDraw: boolean false; }board使用二维数组存储 3×3 棋盘状态Player.None表示空格currentPlayer记录当前轮到谁X 先手winner存储获胜方isDraw标记平局3.2 胜负判定算法 —— 8 线检查这是整个游戏最核心的算法模块。在 3×3 棋盘中可能的胜利组合有 8 条线3 行 3 列 2 条对角线。算法需要在每次落子后快速扫描这 8 条线判断是否有玩家完成了连线。// GameController.ets function checkWinner(board: Player[][]): Player { // 1. 检查所有行 for (let i 0; i 3; i) { if (board[i][0] ! Player.None board[i][0] board[i][1] board[i][1] board[i][2]) { return board[i][0]; } } // 2. 检查所有列 for (let j 0; j 3; j) { if (board[0][j] ! Player.None board[0][j] board[1][j] board[1][j] board[2][j]) { return board[0][j]; } } // 3. 检查主对角线左上到右下 if (board[0][0] ! Player.None board[0][0] board[1][1] board[1][1] board[2][2]) { return board[0][0]; } // 4. 检查副对角线右上到左下 if (board[0][2] ! Player.None board[0][2] board[1][1] board[1][1] board[2][0]) { return board[0][2]; } return Player.None; // 暂无胜者 }算法复杂度分析时间复杂度O(1) —— 固定检查 8 条线每条线 3 个格子总操作次数恒定空间复杂度O(1) —— 仅使用常数额外变量优化思考在真实对战中我们可以在每次落子后只检查与当前落子位置相关的行、列和对角线而非每次都检查全部 8 条线。例如若在 (1,1) 中心落子只需检查第 1 行、第 1 列、主对角线和副对角线最多 4 条。但对于 3×3 的规模全量检查已经足够高效。3.3 交替落子逻辑function handleMove(row: number, col: number): boolean { // 1. 合法性校验位置已被占用或游戏已结束 if (gameState.board[row][col] ! Player.None) { return false; // 无效操作 } if (gameState.winner ! Player.None || gameState.isDraw) { return false; } // 2. 落子 gameState.board[row][col] gameState.currentPlayer; gameState.moveCount; // 3. 检查胜负 const result checkWinner(gameState.board); if (result ! Player.None) { gameState.winner result; return true; } // 4. 检查平局所有格子已满且无胜者 if (gameState.moveCount 9) { gameState.isDraw true; return true; } // 5. 切换玩家 gameState.currentPlayer (gameState.currentPlayer Player.X) ? Player.O : Player.X; return true; }关键边界条件格子已被占用时拒绝落子游戏已分出胜负或平局后拒绝继续操作第 9 步落子后若仍未分出胜负则自动判定平局玩家切换使用三元运算符保证简洁性3.4 声明式 UI 构建// TicTacToeView.ets Component struct TicTacToeView { State board: Player[][] [...]; State currentPlayer: Player Player.X; State winner: Player Player.None; State isDraw: boolean false; build() { Column() { // 标题与状态栏 Text(this.getStatusText()) .fontSize(24) .fontWeight(FontWeight.Bold) .margin({ bottom: 20 }) // 3x3 棋盘网格 Grid() { ForEach(this.board, (row: Player[], rowIndex: number) { ForEach(row, (cell: Player, colIndex: number) { GridCell({ value: cell, onClick: () this.handleCellClick(rowIndex, colIndex) }) }, (colIndex: number) colIndex.toString()) }, (rowIndex: number) rowIndex.toString()) } .columnsTemplate(1fr 1fr 1fr) .rowsTemplate(1fr 1fr 1fr) .width(300) .height(300) .padding(4) // 重置按钮 Button(重新开始) .onClick(() this.resetGame()) .margin({ top: 20 }) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Center) } getStatusText(): string { if (this.winner ! Player.None) { return 玩家 ${this.winner Player.X ? X : O} 获胜; } if (this.isDraw) { return 平局; } return 轮到 ${this.currentPlayer Player.X ? X : O} 落子; } }3.5 网格单元格组件Component struct GridCell { Prop value: Player; onClick?: () void; build() { Button() { if (this.value Player.X) { Text(X) .fontSize(40) .fontWeight(FontWeight.Bold) .fontColor(Color.Red) } else if (this.value Player.O) { Text(O) .fontSize(40) .fontWeight(FontWeight.Bold) .fontColor(Color.Blue) } } .width(100%) .height(100%) .backgroundColor(this.value Player.None ? Color.White : Color.LightGray) .border({ width: 2, color: Color.Gray }) .onClick(() { if (this.onClick) this.onClick(); }) } }四、HarmonyOS 特性分析4.1 State 与 Prop 装饰器State标记组件内部状态任何改变会自动触发该组件及其子组件的重新渲染Prop从父组件接收单向数据子组件内修改不会影响父组件在井字棋应用中根视图持有完整的游戏状态board、currentPlayer、winner通过State实现响应式。子组件GridCell使用Prop接收单个格子的值实现高效的局部更新。4.2 Grid 容器组件HarmonyOS 的Grid组件提供了强大的网格布局能力Grid() { // ...子元素 } .columnsTemplate(1fr 1fr 1fr) // 三列等宽 .rowsTemplate(1fr 1fr 1fr) // 三行等高 .width(300) .height(300)1fr表示按比例分配剩余空间三列各占 1/3自动适配不同屏幕尺寸。相比于使用Row/Column嵌套Grid在性能上更优且代码更简洁。4.3 声明式 UI 的优势可预测性UI 是状态的函数 ——UI f(state)易维护性改状态即改界面无需手动操作 DOM性能优化框架自动计算最小重绘范围五、算法深入探讨5.1 胜负判定的扩展思考虽然 8 线检测在 3×3 棋盘上已经足够但如果扩展到 N×N 棋盘五子棋场景就需要更通用的算法function checkWinnerGeneric(board: Player[][], row: number, col: number, winLength: number): boolean { const directions [ [0, 1], // 水平 [1, 0], // 垂直 [1, 1], // 主对角线 [1, -1] // 副对角线 ]; const player board[row][col]; if (player Player.None) return false; for (const [dx, dy] of directions) { let count 1; // 当前棋子 // 正方向延伸 for (let step 1; step winLength; step) { const r row dx * step; const c col dy * step; if (r 0 r board.length c 0 c board[0].length board[r][c] player) { count; } else break; } // 反方向延伸 for (let step 1; step winLength; step) { const r row - dx * step; const c col - dy * step; if (r 0 r board.length c 0 c board[0].length board[r][c] player) { count; } else break; } if (count winLength) return true; } return false; }这种通用算法在 3×3 棋盘上同样可用且代码的可扩展性更好。5.2 极小极大算法AI 对手如果希望加入单机 AI 模式可以使用极小极大Minimax算法function minimax(board: Player[][], depth: number, isMaximizing: boolean): number { const winner checkWinner(board); if (winner Player.X) return -10 depth; // AI 输 if (winner Player.O) return 10 - depth; // AI 赢 if (isBoardFull(board)) return 0; // 平局 if (isMaximizing) { let bestScore -Infinity; for (let i 0; i 3; i) { for (let j 0; j 3; j) { if (board[i][j] Player.None) { board[i][j] Player.O; const score minimax(board, depth 1, false); board[i][j] Player.None; bestScore Math.max(bestScore, score); } } } return bestScore; } else { let bestScore Infinity; for (let i 0; i 3; i) { for (let j 0; j 3; j) { if (board[i][j] Player.None) { board[i][j] Player.X; const score minimax(board, depth 1, true); board[i][j] Player.None; bestScore Math.min(bestScore, score); } } } return bestScore; } }Minimax 算法的时间复杂度为 O(9!)在 3×3 棋盘上搜索空间为 362,880 种排列完全可接受。六、UI/UX 设计6.1 交互反馈点击反馈格子被点击时有背景色变化和轻微的缩放动画状态提示顶部文字实时显示当前玩家或胜者信息操作限制无效点击已占格子、游戏已结束不产生反应6.2 视觉设计使用 Grid 容器保证网格严格对齐X 用红色、O 用蓝色颜色对比鲜明背景色区分已占与未占格子按键大小自适应屏幕6.3 动画增强// 落子动画 Button() { Text(X) .scale({ x: this.animate ? 1 : 0, y: this.animate ? 1 : 0 }) .animation({ duration: 300, curve: Curve.EaseOut }) }七、最佳实践总结7.1 代码组织将模型、控制器、视图分离遵循单一职责原则使用枚举类型而非魔法值表示玩家和状态将重复逻辑抽象为工具函数7.2 状态管理避免在多个组件中维护冗余状态使用单向数据流状态提升到最近的公共父组件合理使用 State、Prop、Link 装饰器7.3 性能优化对于 3×3 的小规模棋盘无需额外优化如扩展到更大的棋盘考虑使用 LazyForEach 按需渲染7.4 可测试性将纯逻辑棋盘状态更新、胜负判定与 UI 分离胜负判定函数可独立于 UI 进行单元测试八、完整代码清单// GameModel.ets export enum Player { None 0, X 1, O 2 } export class GameModel { board: Player[][] [ [Player.None, Player.None, Player.None], [Player.None, Player.None, Player.None], [Player.None, Player.None, Player.None] ]; currentPlayer: Player Player.X; winner: Player Player.None; moveCount: number 0; isDraw: boolean false; reset(): void { this.board [ [Player.None, Player.None, Player.None], [Player.None, Player.None, Player.None], [Player.None, Player.None, Player.None] ]; this.currentPlayer Player.X; this.winner Player.None; this.moveCount 0; this.isDraw false; } }// GameController.ets import { GameModel, Player } from ./GameModel; export function checkWinner(board: Player[][]): Player { for (let i 0; i 3; i) { if (board[i][0] ! Player.None board[i][0] board[i][1] board[i][1] board[i][2]) { return board[i][0]; } } for (let j 0; j 3; j) { if (board[0][j] ! Player.None board[0][j] board[1][j] board[1][j] board[2][j]) { return board[0][j]; } } if (board[0][0] ! Player.None board[0][0] board[1][1] board[1][1] board[2][2]) { return board[0][0]; } if (board[0][2] ! Player.None board[0][2] board[1][1] board[1][1] board[2][0]) { return board[0][2]; } return Player.None; } export function handleMove(model: GameModel, row: number, col: number): boolean { if (model.board[row][col] ! Player.None) return false; if (model.winner ! Player.None || model.isDraw) return false; model.board[row][col] model.currentPlayer; model.moveCount; const result checkWinner(model.board); if (result ! Player.None) { model.winner result; return true; } if (model.moveCount 9) { model.isDraw true; return true; } model.currentPlayer (model.currentPlayer Player.X) ? Player.O : Player.X; return true; }// Index.ets (主页面) import { GameModel, Player } from ./GameModel; import { checkWinner, handleMove } from ./GameController; Entry Component struct Index { State model: GameModel new GameModel(); build() { Column() { Text(this.getStatusText()) .fontSize(24) .fontWeight(FontWeight.Bold) .margin({ bottom: 20 }) Grid() { ForEach(this.model.board, (row: Player[], rowIndex: number) { ForEach(row, (cell: Player, colIndex: number) { Button() { if (cell Player.X) { Text(X).fontSize(40).fontWeight(FontWeight.Bold) .fontColor(Color.Red) } else if (cell Player.O) { Text(O).fontSize(40).fontWeight(FontWeight.Bold) .fontColor(Color.Blue) } } .width(100) .height(100) .backgroundColor(cell Player.None ? Color.White : Color.LightGray) .border({ width: 2, color: Color.Gray }) .onClick(() { handleMove(this.model, rowIndex, colIndex); }) }, (colIndex: number) colIndex.toString()) }, (rowIndex: number) rowIndex.toString()) } .columnsTemplate(1fr 1fr 1fr) .rowsTemplate(1fr 1fr 1fr) .width(300) .height(300) .padding(4) Button(重新开始) .fontSize(18) .margin({ top: 20 }) .onClick(() { this.model.reset(); }) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Center) } getStatusText(): string { if (this.model.winner ! Player.None) { return 玩家 ${this.model.winner Player.X ? X : O} 获胜; } if (this.model.isDraw) { return 平局; } return 轮到 ${this.model.currentPlayer Player.X ? X : O} 落子; } }九、总结通过本篇文章我们完整地实现了 HarmonyOS 平台上的井字棋游戏应用。核心知识点包括8 线胜负判定算法—— 3 行 3 列 2 对角线的恒定时间复杂度检测交替落子逻辑—— 状态转换、边界条件处理声明式 UI 框架—— State/Prop 装饰器、Grid 容器、单向数据流算法扩展—— Minimax AI、通用 N×N 棋盘检测井字棋虽然规则简单但涵盖了游戏开发中的许多基础概念状态管理、事件处理、算法设计、UI 组织。这些知识可以平滑地迁移到更复杂的游戏和应用开发中。希望本文能为 HarmonyOS 开发者提供有价值的参考。
# HarmonyOS ArkTS 井字棋小游戏深度解析 —— 从零构建完整的三子棋对战应用
一、应用概述井字棋Tic-Tac-Toe是一款经典的两人轮流在 3×3 格子上标记 X 和 O 的智力游戏。本篇文章将带领读者在 HarmonyOS 平台上使用 ArkTS 语言和声明式 UI 框架从零构建一个完整的井字棋对战应用。文章将深入分析游戏的核心算法——胜负判定8 线检测、交替落子逻辑、状态管理以及响应式的 UI 交互设计。二、架构设计2.1 整体模块划分井字棋应用采用标准的单页面 组件化架构主要划分为三个层次层级职责核心文件数据层游戏状态管理、棋盘数据、胜负判定逻辑GameModel.ets业务层交替落子、重置、历史记录GameController.ets表现层网格渲染、状态指示、交互反馈TicTacToeView.ets, GridCell.ets2.2 数据流向用户点击 → GridCell.onClick() → GameController.handleMove(row, col) → GameModel.updateBoard(row, col, player) → GameModel.checkWinner() → 状态更新触发 UI 重渲染ArkTS 的State和Prop装饰器驱动了从数据到视图的单向数据流确保任何状态的变更都能实时反映到界面。三、核心技术实现3.1 游戏状态与数据结构// GameModel.ets enum Player { None 0, X 1, O 2 } class GameState { board: Player[][] [ [Player.None, Player.None, Player.None], [Player.None, Player.None, Player.None], [Player.None, Player.None, Player.None] ]; currentPlayer: Player Player.X; winner: Player Player.None; moveCount: number 0; isDraw: boolean false; }board使用二维数组存储 3×3 棋盘状态Player.None表示空格currentPlayer记录当前轮到谁X 先手winner存储获胜方isDraw标记平局3.2 胜负判定算法 —— 8 线检查这是整个游戏最核心的算法模块。在 3×3 棋盘中可能的胜利组合有 8 条线3 行 3 列 2 条对角线。算法需要在每次落子后快速扫描这 8 条线判断是否有玩家完成了连线。// GameController.ets function checkWinner(board: Player[][]): Player { // 1. 检查所有行 for (let i 0; i 3; i) { if (board[i][0] ! Player.None board[i][0] board[i][1] board[i][1] board[i][2]) { return board[i][0]; } } // 2. 检查所有列 for (let j 0; j 3; j) { if (board[0][j] ! Player.None board[0][j] board[1][j] board[1][j] board[2][j]) { return board[0][j]; } } // 3. 检查主对角线左上到右下 if (board[0][0] ! Player.None board[0][0] board[1][1] board[1][1] board[2][2]) { return board[0][0]; } // 4. 检查副对角线右上到左下 if (board[0][2] ! Player.None board[0][2] board[1][1] board[1][1] board[2][0]) { return board[0][2]; } return Player.None; // 暂无胜者 }算法复杂度分析时间复杂度O(1) —— 固定检查 8 条线每条线 3 个格子总操作次数恒定空间复杂度O(1) —— 仅使用常数额外变量优化思考在真实对战中我们可以在每次落子后只检查与当前落子位置相关的行、列和对角线而非每次都检查全部 8 条线。例如若在 (1,1) 中心落子只需检查第 1 行、第 1 列、主对角线和副对角线最多 4 条。但对于 3×3 的规模全量检查已经足够高效。3.3 交替落子逻辑function handleMove(row: number, col: number): boolean { // 1. 合法性校验位置已被占用或游戏已结束 if (gameState.board[row][col] ! Player.None) { return false; // 无效操作 } if (gameState.winner ! Player.None || gameState.isDraw) { return false; } // 2. 落子 gameState.board[row][col] gameState.currentPlayer; gameState.moveCount; // 3. 检查胜负 const result checkWinner(gameState.board); if (result ! Player.None) { gameState.winner result; return true; } // 4. 检查平局所有格子已满且无胜者 if (gameState.moveCount 9) { gameState.isDraw true; return true; } // 5. 切换玩家 gameState.currentPlayer (gameState.currentPlayer Player.X) ? Player.O : Player.X; return true; }关键边界条件格子已被占用时拒绝落子游戏已分出胜负或平局后拒绝继续操作第 9 步落子后若仍未分出胜负则自动判定平局玩家切换使用三元运算符保证简洁性3.4 声明式 UI 构建// TicTacToeView.ets Component struct TicTacToeView { State board: Player[][] [...]; State currentPlayer: Player Player.X; State winner: Player Player.None; State isDraw: boolean false; build() { Column() { // 标题与状态栏 Text(this.getStatusText()) .fontSize(24) .fontWeight(FontWeight.Bold) .margin({ bottom: 20 }) // 3x3 棋盘网格 Grid() { ForEach(this.board, (row: Player[], rowIndex: number) { ForEach(row, (cell: Player, colIndex: number) { GridCell({ value: cell, onClick: () this.handleCellClick(rowIndex, colIndex) }) }, (colIndex: number) colIndex.toString()) }, (rowIndex: number) rowIndex.toString()) } .columnsTemplate(1fr 1fr 1fr) .rowsTemplate(1fr 1fr 1fr) .width(300) .height(300) .padding(4) // 重置按钮 Button(重新开始) .onClick(() this.resetGame()) .margin({ top: 20 }) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Center) } getStatusText(): string { if (this.winner ! Player.None) { return 玩家 ${this.winner Player.X ? X : O} 获胜; } if (this.isDraw) { return 平局; } return 轮到 ${this.currentPlayer Player.X ? X : O} 落子; } }3.5 网格单元格组件Component struct GridCell { Prop value: Player; onClick?: () void; build() { Button() { if (this.value Player.X) { Text(X) .fontSize(40) .fontWeight(FontWeight.Bold) .fontColor(Color.Red) } else if (this.value Player.O) { Text(O) .fontSize(40) .fontWeight(FontWeight.Bold) .fontColor(Color.Blue) } } .width(100%) .height(100%) .backgroundColor(this.value Player.None ? Color.White : Color.LightGray) .border({ width: 2, color: Color.Gray }) .onClick(() { if (this.onClick) this.onClick(); }) } }四、HarmonyOS 特性分析4.1 State 与 Prop 装饰器State标记组件内部状态任何改变会自动触发该组件及其子组件的重新渲染Prop从父组件接收单向数据子组件内修改不会影响父组件在井字棋应用中根视图持有完整的游戏状态board、currentPlayer、winner通过State实现响应式。子组件GridCell使用Prop接收单个格子的值实现高效的局部更新。4.2 Grid 容器组件HarmonyOS 的Grid组件提供了强大的网格布局能力Grid() { // ...子元素 } .columnsTemplate(1fr 1fr 1fr) // 三列等宽 .rowsTemplate(1fr 1fr 1fr) // 三行等高 .width(300) .height(300)1fr表示按比例分配剩余空间三列各占 1/3自动适配不同屏幕尺寸。相比于使用Row/Column嵌套Grid在性能上更优且代码更简洁。4.3 声明式 UI 的优势可预测性UI 是状态的函数 ——UI f(state)易维护性改状态即改界面无需手动操作 DOM性能优化框架自动计算最小重绘范围五、算法深入探讨5.1 胜负判定的扩展思考虽然 8 线检测在 3×3 棋盘上已经足够但如果扩展到 N×N 棋盘五子棋场景就需要更通用的算法function checkWinnerGeneric(board: Player[][], row: number, col: number, winLength: number): boolean { const directions [ [0, 1], // 水平 [1, 0], // 垂直 [1, 1], // 主对角线 [1, -1] // 副对角线 ]; const player board[row][col]; if (player Player.None) return false; for (const [dx, dy] of directions) { let count 1; // 当前棋子 // 正方向延伸 for (let step 1; step winLength; step) { const r row dx * step; const c col dy * step; if (r 0 r board.length c 0 c board[0].length board[r][c] player) { count; } else break; } // 反方向延伸 for (let step 1; step winLength; step) { const r row - dx * step; const c col - dy * step; if (r 0 r board.length c 0 c board[0].length board[r][c] player) { count; } else break; } if (count winLength) return true; } return false; }这种通用算法在 3×3 棋盘上同样可用且代码的可扩展性更好。5.2 极小极大算法AI 对手如果希望加入单机 AI 模式可以使用极小极大Minimax算法function minimax(board: Player[][], depth: number, isMaximizing: boolean): number { const winner checkWinner(board); if (winner Player.X) return -10 depth; // AI 输 if (winner Player.O) return 10 - depth; // AI 赢 if (isBoardFull(board)) return 0; // 平局 if (isMaximizing) { let bestScore -Infinity; for (let i 0; i 3; i) { for (let j 0; j 3; j) { if (board[i][j] Player.None) { board[i][j] Player.O; const score minimax(board, depth 1, false); board[i][j] Player.None; bestScore Math.max(bestScore, score); } } } return bestScore; } else { let bestScore Infinity; for (let i 0; i 3; i) { for (let j 0; j 3; j) { if (board[i][j] Player.None) { board[i][j] Player.X; const score minimax(board, depth 1, true); board[i][j] Player.None; bestScore Math.min(bestScore, score); } } } return bestScore; } }Minimax 算法的时间复杂度为 O(9!)在 3×3 棋盘上搜索空间为 362,880 种排列完全可接受。六、UI/UX 设计6.1 交互反馈点击反馈格子被点击时有背景色变化和轻微的缩放动画状态提示顶部文字实时显示当前玩家或胜者信息操作限制无效点击已占格子、游戏已结束不产生反应6.2 视觉设计使用 Grid 容器保证网格严格对齐X 用红色、O 用蓝色颜色对比鲜明背景色区分已占与未占格子按键大小自适应屏幕6.3 动画增强// 落子动画 Button() { Text(X) .scale({ x: this.animate ? 1 : 0, y: this.animate ? 1 : 0 }) .animation({ duration: 300, curve: Curve.EaseOut }) }七、最佳实践总结7.1 代码组织将模型、控制器、视图分离遵循单一职责原则使用枚举类型而非魔法值表示玩家和状态将重复逻辑抽象为工具函数7.2 状态管理避免在多个组件中维护冗余状态使用单向数据流状态提升到最近的公共父组件合理使用 State、Prop、Link 装饰器7.3 性能优化对于 3×3 的小规模棋盘无需额外优化如扩展到更大的棋盘考虑使用 LazyForEach 按需渲染7.4 可测试性将纯逻辑棋盘状态更新、胜负判定与 UI 分离胜负判定函数可独立于 UI 进行单元测试八、完整代码清单// GameModel.ets export enum Player { None 0, X 1, O 2 } export class GameModel { board: Player[][] [ [Player.None, Player.None, Player.None], [Player.None, Player.None, Player.None], [Player.None, Player.None, Player.None] ]; currentPlayer: Player Player.X; winner: Player Player.None; moveCount: number 0; isDraw: boolean false; reset(): void { this.board [ [Player.None, Player.None, Player.None], [Player.None, Player.None, Player.None], [Player.None, Player.None, Player.None] ]; this.currentPlayer Player.X; this.winner Player.None; this.moveCount 0; this.isDraw false; } }// GameController.ets import { GameModel, Player } from ./GameModel; export function checkWinner(board: Player[][]): Player { for (let i 0; i 3; i) { if (board[i][0] ! Player.None board[i][0] board[i][1] board[i][1] board[i][2]) { return board[i][0]; } } for (let j 0; j 3; j) { if (board[0][j] ! Player.None board[0][j] board[1][j] board[1][j] board[2][j]) { return board[0][j]; } } if (board[0][0] ! Player.None board[0][0] board[1][1] board[1][1] board[2][2]) { return board[0][0]; } if (board[0][2] ! Player.None board[0][2] board[1][1] board[1][1] board[2][0]) { return board[0][2]; } return Player.None; } export function handleMove(model: GameModel, row: number, col: number): boolean { if (model.board[row][col] ! Player.None) return false; if (model.winner ! Player.None || model.isDraw) return false; model.board[row][col] model.currentPlayer; model.moveCount; const result checkWinner(model.board); if (result ! Player.None) { model.winner result; return true; } if (model.moveCount 9) { model.isDraw true; return true; } model.currentPlayer (model.currentPlayer Player.X) ? Player.O : Player.X; return true; }// Index.ets (主页面) import { GameModel, Player } from ./GameModel; import { checkWinner, handleMove } from ./GameController; Entry Component struct Index { State model: GameModel new GameModel(); build() { Column() { Text(this.getStatusText()) .fontSize(24) .fontWeight(FontWeight.Bold) .margin({ bottom: 20 }) Grid() { ForEach(this.model.board, (row: Player[], rowIndex: number) { ForEach(row, (cell: Player, colIndex: number) { Button() { if (cell Player.X) { Text(X).fontSize(40).fontWeight(FontWeight.Bold) .fontColor(Color.Red) } else if (cell Player.O) { Text(O).fontSize(40).fontWeight(FontWeight.Bold) .fontColor(Color.Blue) } } .width(100) .height(100) .backgroundColor(cell Player.None ? Color.White : Color.LightGray) .border({ width: 2, color: Color.Gray }) .onClick(() { handleMove(this.model, rowIndex, colIndex); }) }, (colIndex: number) colIndex.toString()) }, (rowIndex: number) rowIndex.toString()) } .columnsTemplate(1fr 1fr 1fr) .rowsTemplate(1fr 1fr 1fr) .width(300) .height(300) .padding(4) Button(重新开始) .fontSize(18) .margin({ top: 20 }) .onClick(() { this.model.reset(); }) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Center) } getStatusText(): string { if (this.model.winner ! Player.None) { return 玩家 ${this.model.winner Player.X ? X : O} 获胜; } if (this.model.isDraw) { return 平局; } return 轮到 ${this.model.currentPlayer Player.X ? X : O} 落子; } }九、总结通过本篇文章我们完整地实现了 HarmonyOS 平台上的井字棋游戏应用。核心知识点包括8 线胜负判定算法—— 3 行 3 列 2 对角线的恒定时间复杂度检测交替落子逻辑—— 状态转换、边界条件处理声明式 UI 框架—— State/Prop 装饰器、Grid 容器、单向数据流算法扩展—— Minimax AI、通用 N×N 棋盘检测井字棋虽然规则简单但涵盖了游戏开发中的许多基础概念状态管理、事件处理、算法设计、UI 组织。这些知识可以平滑地迁移到更复杂的游戏和应用开发中。希望本文能为 HarmonyOS 开发者提供有价值的参考。