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

发布时间:2026/9/14 14:17:18

# 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/9/14 23:10:28

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

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

2026/9/14 13:13:23

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

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

2026/9/14 23:11:13

新机调试必做:个性化Word模板制作指南

新机调试这件事,我一般按“装系统—装基础软件—装Office—做模板”这个顺序来。很多人觉得装完Office就万事大吉,直接双击Word开始写东西,但恰恰是少了做模板这一步,后面大半年里所有文档的排版时间都会翻倍。所谓个性化Word模板…

2026/9/14 23:11:13

Office三件套崩溃问题分析与系统化修复方案

1. Office三件套崩溃问题的本质剖析当PowerPoint/Word/Excel突然弹出"很抱歉,遇到错误需要关闭"的对话框时,背后通常隐藏着三类典型问题:1.1 程序文件完整性受损Office应用程序由数千个相互依赖的组件构成。注册表项损坏、关键DLL文…

2026/9/14 23:11:13

Gitee PR 集成大模型:自建 AI 代码审计机器人实战指南

一聊到 AI 写代码,大家眼睛都亮了,可一聊到代码审查,团队的表情就变得微妙起来。我最近跟几个技术负责人交流,大家有个共同的感受:AI 编程助手让一次迭代的代码产出量翻了好几倍,可是 Review 还是那几个人&…

2026/9/14 23:06:13

人脸关键点检测小模型蒸馏实战:轻量高精度部署方案

简介:本资源是一套面向本科生与初学者的人脸关键点检测轻量化模型实战项目,聚焦知识蒸馏技术在模型压缩中的落地应用,适用于人工智能、计算机科学等专业学生的毕业设计、课程设计及算法进阶学习。压缩包含2000个文件,主体为997张人…

2026/9/14 2:17:50

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/14 0:03:22

KCF目标跟踪算法与OTB工程实现:毕业设计实战解析

简介:这是一份基于KCF核相关滤波算法、融合尺度池与抗遮挡处理的目标检测跟踪MATLAB完整源码,主要面向计算机相关专业准备毕业设计、课程设计或期末大作业的学生,也适合需要项目实战练习的初学者。源码在OTB数据集上完成验证,能够…

2026/9/14 0:03:22

语音情感识别实战:Keras实现LSTM、CNN、SVM与MLP多模型对比

简介:面向语音情感识别入门与进阶开发者,这份基于Keras的项目源码完整实现了LSTM、CNN、SVM、MLP四种模型,兼容Python3.8与Keras/TensorFlow2环境。压缩包内含49个文件,大小约70.31MB,主体包括Python脚本、yaml/json配…

2026/9/14 11:59:31

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/14 13:53:59

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/14 11:22:57

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

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

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

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