# 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 开发者提供有价值的参考。

相关新闻

# HarmonyOS ArkTS 数字时钟应用深度解析 —— 时针/分针/秒针旋转角度计算与定时器机制

# HarmonyOS ArkTS 数字时钟应用深度解析 —— 时针/分针/秒针旋转角度计算与定时器机制

一、应用概述 数字时钟(Digital Analog Clock)是智能设备上最经典的应用之一,它同时以数字形式和模拟表盘形式展示当前时间。本项目基于 HarmonyOS ArkTS 框架实现了一个功能完整的双模式时钟应用,包含数字时间显示(时…

2026/7/29 1:36:10阅读更多 →
游戏音频提取完全指南:3步轻松解密ACB/AWB文件

游戏音频提取完全指南:3步轻松解密ACB/AWB文件

游戏音频提取完全指南:3步轻松解密ACB/AWB文件 【免费下载链接】acbDecrypter 项目地址: https://gitcode.com/gh_mirrors/ac/acbDecrypter 你是否曾想提取游戏中的背景音乐和音效,却苦于复杂的加密格式?acbDecrypter正是为你量身定制…

2026/7/29 1:36:10阅读更多 →
终极免费在线GPX编辑器:gpx.studio的完整使用指南

终极免费在线GPX编辑器:gpx.studio的完整使用指南

终极免费在线GPX编辑器:gpx.studio的完整使用指南 【免费下载链接】gpxstudio.github.io The online GPX file editor 项目地址: https://gitcode.com/gh_mirrors/gp/gpxstudio.github.io 核心关键词:GPX编辑器、在线GPX编辑、GPS轨迹编辑 长尾关…

2026/7/29 1:36:10阅读更多 →
护网行动是什么?凭什么它能让网安人年薪翻倍?(第十九弹·护网篇)

护网行动是什么?凭什么它能让网安人年薪翻倍?(第十九弹·护网篇)

你可能经常听到"护网"这两个字——网安群里在聊护网,招聘JD上写着"有护网经验优先",朋友圈有人晒"护网先进个人"的奖杯。 但它到底是个啥?凭什么参加过护网的人,身价能翻倍? 全文约2000…

2026/7/29 6:27:43阅读更多 →
车载通信新选择:SEN协议原理、硬件设计与实战调优

车载通信新选择:SEN协议原理、硬件设计与实战调优

1. 从CAN到SEN:为什么我们需要另一种车载通信?在汽车电子圈里混了十几年,CAN总线(Controller Area Network)的大名可以说是无人不知,无人不晓。它就像汽车内部的“神经系统”,负责连接发动机控制…

2026/7/29 6:27:43阅读更多 →
VRTK:Unity VR开发核心交互框架深度解析与实战指南

VRTK:Unity VR开发核心交互框架深度解析与实战指南

1. 项目概述:为什么VRTK是VR开发的“瑞士军刀”?如果你正在或者打算进入Unity VR应用开发领域,那么“VRTK”这个名字你大概率绕不过去。它不是一个游戏引擎,也不是一个建模软件,但它却是连接你的创意与VR世界之间最得力…

2026/7/29 6:27:43阅读更多 →
玩客云刷Armbian部署Home Assistant:低成本打造智能家居服务器

玩客云刷Armbian部署Home Assistant:低成本打造智能家居服务器

1. 项目概述:让旧设备焕发智能家居新生命手头闲置的玩客云,除了吃灰还能做什么?如果你对智能家居感兴趣,那么把它刷成 Home Assistant 服务器,绝对是一个低成本、高回报的“废物利用”方案。Home Assistant 是一个开源…

2026/7/29 6:27:43阅读更多 →
IIM-20670与MK20DX128VFM5在运动跟踪系统中的硬件设计与优化

IIM-20670与MK20DX128VFM5在运动跟踪系统中的硬件设计与优化

1. IIM-20670与MK20DX128VFM5硬件选型解析在运动跟踪系统中,传感器与微控制器的选型直接决定了最终性能表现。IIM-20670是TDK InvenSense推出的6轴MEMS运动传感器,集成了3轴陀螺仪和3轴加速度计。其陀螺仪测量范围可编程为41dps至1966dps,加速…

2026/7/29 6:27:43阅读更多 →
电阻在电路设计中的核心作用:从限流分压到高速匹配的全面解析

电阻在电路设计中的核心作用:从限流分压到高速匹配的全面解析

1. 从“阻碍”到“塑造”:重新认识电阻的核心价值提起电阻,很多刚接触电子电路的朋友第一反应往往是“阻碍电流的元件”,甚至觉得它是个“麻烦制造者”,因为它会消耗能量、产生热量,让电路效率降低。这种理解不能说错&…

2026/7/29 6:25:42阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/28 4:06:39阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/28 2:08:06阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/28 1:38:28阅读更多 →
28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“!

28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“!

28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“! 在构建复杂的 Agent 系统时,我们经常会遇到这样的场景:Agent 正在执行一个多步骤的任务,比如“下单购买商品”,但执行到一半时,我们…

2026/7/29 0:01:46阅读更多 →
自律同行,突破无界!NANK南卡正式官宣曾舜晞成为品牌代言人

自律同行,突破无界!NANK南卡正式官宣曾舜晞成为品牌代言人

近日,国际专注开放式技术研发的声学品牌Nank南卡,正式官宣实力艺人曾舜晞担任品牌代言人。消息一经发出便轰动全网。为什么耳机品牌不选择流量明星、老牌歌手?而且是选择曾舜晞?让我们一起来探索一下!比起短期的流量&a…

2026/7/29 0:01:46阅读更多 →
【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

一、本文介绍 🔥本文在RT-DETR多模态融合目标检测中引入RLAB残差线性注意力模块,可在不同模态特征交互阶段进行多次残差细化,使可见光、红外等特征在尺度、语义和空间位置上更好对齐;随后将细化特征与解码器输出拼接并生成Q、K、V,通过线性注意力自适应强化关键通道、目…

2026/7/29 0:01:46阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/28 20:22:24阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/29 4:31:51阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/28 2:35:58阅读更多 →