# HarmonyOS ArkTS 井字棋小游戏深度解析 —— 从零构建完整的三子棋对战应用

发布时间:2026/7/29 1:38:29

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

相关新闻

YOLOv11涨点改进| CVPR 2026 | 独家Conv创新改进篇 | 引入MBFE多分支特征增强模块,助力无人机航拍、遥感影像、小目标检测、语义分割、实例分割、目标跟踪任务,有效涨点

YOLOv11涨点改进| CVPR 2026 | 独家Conv创新改进篇 | 引入MBFE多分支特征增强模块,助力无人机航拍、遥感影像、小目标检测、语义分割、实例分割、目标跟踪任务,有效涨点

2026/7/29 1:38:29

一、本文介绍 🔥本文给大家介绍使用 MBFE多分支特征增强模块 改进YOLOv11网络模型,MBFE将输入特征划分为增强分支和恒等分支,并通过像素级深度卷积、不同尺度的空间通道选择单元、频率成分选择单元及残差连接进行并行增强,从而联合提取目标的细粒度空间信息、多尺度局部语…

基于堆的优先队列实现原理与复杂度分析7

基于堆的优先队列实现原理与复杂度分析7

2026/7/29 1:38:29

堆的基本概念与结构 堆的定义:完全二叉树,满足堆性质(最大堆或最小堆)存储方式:数组实现,父子节点索引关系关键操作:上浮(Heapify Up)和下沉(Heapify Down&a…

# 鸿蒙 HarmonyOS 应用开发实战(第26期)|石头剪刀布(Rock-Paper-Scissors)— 游戏逻辑与胜负判定精讲

# 鸿蒙 HarmonyOS 应用开发实战(第26期)|石头剪刀布(Rock-Paper-Scissors)— 游戏逻辑与胜负判定精讲

2026/7/29 1:38:29

一、应用概述 石头剪刀布(Rock-Paper-Scissors) 是一款经典的人机对战游戏,用户与电脑进行石头剪刀布对决。应用提供了直观的图形化选择按钮(✊✌️🖐️),玩家点击选择后,电脑随机出…

51单片机数字电子钟设计全解析:从硬件搭建到软件状态机实现

51单片机数字电子钟设计全解析:从硬件搭建到软件状态机实现

2026/7/29 2:38:31

1. 项目概述与核心价值最近在整理大学时期的项目资料,翻到了当年数电课设的“51单片机数字电子钟”,感触颇多。这个项目可以说是绝大多数电子、自动化、物联网相关专业学生的“启蒙项目”之一。它麻雀虽小,五脏俱全,几乎涵盖了单片…

编译器后端优化:指令选择、寄存器分配与代码生成

编译器后端优化:指令选择、寄存器分配与代码生成

2026/7/29 2:38:31

编译器后端优化:指令选择、寄存器分配与代码生成编译器前端完成了词法分析、语法分析及语义分析后,生成的中间表示(IR)将交由后端进行深度的加工与优化,最终转化为能在特定目标机器上高效运行的目标代码。这一过程的核…

三步找回QQ空间全部历史说说的完整指南

三步找回QQ空间全部历史说说的完整指南

2026/7/29 2:38:31

三步找回QQ空间全部历史说说的完整指南 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 你是否曾试图找回QQ空间中那些被遗忘的青春记忆?那些记录着成长足迹、承载着珍贵情感…

大模型文字生成技术:从Transformer到实战部署

大模型文字生成技术:从Transformer到实战部署

2026/7/29 2:38:31

1. 文字生成任务与大模型基础解析文字生成作为自然语言处理(NLP)的核心任务,已经从早期的规则模板发展到如今的智能创作。我仍记得2018年首次使用GPT-2生成新闻稿时的震撼——那些连贯的段落完全不像机器产物。如今的大模型已经能处理包括创意…

简化本地 AI 部署流程 OpenClaw 可视化安装包使用完整教程(含安装包)

简化本地 AI 部署流程 OpenClaw 可视化安装包使用完整教程(含安装包)

2026/7/29 2:38:31

OpenClaw v2.7.9 最新部署教程|Windows 免配置搭建本地 AI 智能体 如今本地 AI 智能体已经成为提升电脑办公效率的实用工具,OpenClaw(小龙虾)凭借强大的本地自动化、键鼠模拟、程序操控能力,成为很多用户首选的桌面 A…

Arduino智能寻迹小车:从PID算法到灰度传感器的完整实现

Arduino智能寻迹小车:从PID算法到灰度传感器的完整实现

2026/7/29 2:28:31

1. 项目概述:从“甲壳虫”到智能寻迹几年前,我第一次接触Arduino智能小车时,就被它那种“赋予硬件生命”的魅力深深吸引。从简单的电机驱动到复杂的路径规划,每一个环节都充满了挑战与乐趣。这次,我想和大家分享一个经…

[具身智能-649]:个人电脑搭建 RTSP 服务完整方案(Windows / Ubuntu 双平台,适配 RDK X5 rtsp2display 调试)

[具身智能-649]:个人电脑搭建 RTSP 服务完整方案(Windows / Ubuntu 双平台,适配 RDK X5 rtsp2display 调试)

2026/7/28 13:30:18

目标:电脑作为RTSP 服务端,循环推送 H264/H265 视频流; RDK X5 通过 rtsp2display 拉流预览,完全不需要在开发板编译 live555。 提供两套成熟方案: ✅ 方案 A:FFmpeg(最简单,优先推…

PDF合并与动态水印的工程化方案:2026国内免费工具实测对比

PDF合并与动态水印的工程化方案:2026国内免费工具实测对比

2026/7/28 16:04:36

一、背景与测试方案 在实际项目交付中,PDF文件合并与版权保护水印的叠加是一个高频但容易被低估的技术需求。典型的处理链路涉及:多源PDF的文件流合并、页面级水印渲染(含透明度混合与图层叠加)、输出文件体积控制。看似简单的操作…

PDF拆分压完图糊了?2026国内免费实测,档案员都在用的组合方案

PDF拆分压完图糊了?2026国内免费实测,档案员都在用的组合方案

2026/7/28 16:04:35

说实话,提到PDF拆分再压缩,我真是被折腾得够呛。 上个月公司年度合同归档,一份300多页的PDF总合同,需要按年份拆分成三个独立文件,再分别压缩到10MB以内方便邮件发送各部门确认。我心想这还不简单?先找个海…

AI会议纪要怎么做?会议录音转文字加自动整理,三个月实测流程

AI会议纪要怎么做?会议录音转文字加自动整理,三个月实测流程

2026/7/29 0:08:23

打工人总是跑不掉要写会议纪要。 我在一家互联网公司,一周至少八场会:产品评审、数据复盘、项目同步、客户沟通,每场一小时起步。 以前的标准流程是开会拼命记→会后凭记忆补→整理成文档发群,结果经常记不全、记错、记串。 大概年…

重庆化龙桥老旧小区改造,怎么搞定夜景照明“不扰居”又能省成本?

重庆化龙桥老旧小区改造,怎么搞定夜景照明“不扰居”又能省成本?

2026/7/29 0:08:23

重庆化龙桥靠着嘉陵江,老小区多,最近几年城市更新做的勤,不少住户都反映过小区夜景亮了是好事,可有的灯太晃眼,半夜拉着窗帘都透光,睡不好觉。还有物业算账,这灯开一整晚,公摊电费蹭…

目标模糊、资源泛滥、进度失控,AI学习计划制定失败的3大隐形陷阱及救急方案

目标模糊、资源泛滥、进度失控,AI学习计划制定失败的3大隐形陷阱及救急方案

2026/7/29 0:08:23

更多请点击: https://codechina.net 第一章:目标模糊、资源泛滥、进度失控,AI学习计划制定失败的3大隐形陷阱及救急方案 目标模糊:学得越勤,离真实能力越远 当学习目标停留在“学会AI”或“搞懂大模型”这类宽泛表述…