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

发布时间:2026/11/5 20:04:48

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

更多相关文章

2026/10/30 10:58:53

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

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

2026/10/24 0:07:17

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

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

2026/11/5 16:51:33

EasyU优启通PE维护盘实战指南:制作、启动与核心操作全解析

1. 为什么一个PE维护盘值得单独拿出来聊装系统、修引导、救数据、清病毒,这些活儿但凡干过几次的人都知道,真正决定效率的往往不是你手速多快,而是手边那套PE环境靠不靠谱。EasyU(圈内习惯叫优启通)就是在这个场景下被…

2026/11/5 16:26:13

Rust原来很简单二:所有权系统

Part 2 核心篇 —— 所有权系统 本篇覆盖 第 6 ~ 10 章:所有权、借用与引用、生命周期、字符串与切片、内存模型。 ⚠️ 这是全书最重要的一篇。 把它吃透,Rust 从此“很简单”;跳过它,你会处处碰壁。请慢下来,动手敲每一段代码。 第 6 章 所有权:Rust 的第一性原理 6…

2026/11/5 15:00:49

基于Hadoop+Spark的民宿智能推荐系统设计与实现

1. 项目概述:大数据时代的民宿智能推荐这个毕业设计项目融合了当前大数据领域最主流的技术栈,构建了一个完整的民宿推荐系统解决方案。作为一名长期从事大数据系统开发的工程师,我认为这个选题非常贴合实际应用场景——随着旅游业的复苏和共享…

2026/11/5 14:10:30

ADS122U04IPWR+STM32F427ZI工业级温度测量信号链设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 14:10:30

UART接口24位ADC搭配RS485,解决远距离温度监控难题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 14:10:30

UART接口24位ADC搭配PIC18F46K22的RTD与热电偶测温实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 0:02:07

多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中…

2026/11/5 0:02:07

大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#…

2026/11/5 0:02:07

鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的…

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑