
一、引言井字棋Tic-Tac-Toe是世界上规则最简单的策略游戏之一——3×3 的九宫格两人轮流画 ✕ 和 ◯先连成一线者胜。它的规则用一句话就能说清楚但背后的胜负判定、策略树和状态管理却是众多游戏算法的入门课题。从技术角度看井字棋是一个回合制状态机。每个棋盘格有三种状态空/✕/◯整个游戏在玩家回合→落子→判定→AI回合→落子→判定的循环中推进。与记忆翻牌实时翻牌延时不同井字棋的交互是异步回合式的——玩家点击后AI 需要经过一段短暂的思考延时约 400ms再落子模拟真实对局中的等待感。本文用 ArkUI 从零构建一个井字棋游戏包含双人对战和人机对战两种模式、五级优先级 AI 策略、胜负平判定、得分记录和胜利连线高亮。棋盘使用双层ForEach构建 3×3 网格AI 延时使用setInterval实现。阅读完本文你将能够用双层ForEach构建 3×3 网格棋盘实现 8 条获胜线的胜负判定算法构建五级优先级规则 AI赢 堵 中 角 任意用setInterval实现 AI 思考延时400ms管理回合制状态机玩家回合 / AI回合 / 游戏结束二、游戏规则与设计2.1 核心规则井字棋的规则用三句话概括棋盘为 3×3 九宫格玩家 1 使用 ✕玩家 2或 AI使用 ◯双方轮流在空格落子先在横、竖、斜任一方向上连成三子者获胜若棋盘填满而无人获胜则为平局8 条获胜线覆盖了所有可能的连线方式[0] [1] [2] 行0-1-2, 3-4-5, 6-7-8 [3] [4] [5] 列0-3-6, 1-4-7, 2-5-8 [6] [7] [8] 对角线0-4-8, 2-4-6棋盘用一维数组number[9]表示索引 0-8 对应九宫格的九个位置。每个位置的值0 空1 ✕2 ◯。一维数组而非二维数组的选择是为了简化遍历和获胜检测——8 条获胜线预先定义为索引三元组数组检测时直接取值比较。2.2 两种模式游戏提供两种对战模式人机对战默认玩家执 ✕ 先手AI 执 ◯ 后手。AI 在玩家落子后经过 400ms 延时自动落子。AI 使用五级优先级策略不会犯低级错误但也不是不可战胜——它没有使用 MinMax 全搜索因此玩家有机会取胜。双人对战两人在同一设备上轮流落子✕ 先手。模式切换按钮位于棋盘上方。模式切换会立即重置当前棋局——包括棋盘、回合和连胜提示但保留历史得分记录。2.3 交互流程一局典型的人机对局包含以下交互点选择模式可选点击双人对战或人机对战玩家落子点击空格 → ✕ 出现 → 判定 → 回合切换AI思考400ms 延时 → AI 自动落子 → 判定 → 回合切回游戏结束有人获胜高亮连线或平局 → 得分更新重新开始点击按钮 → 棋盘清空得分保留三、数据模型与状态管理3.1 棋盘数组棋盘用一个 9 元素的一维数组表示Stateboard:number[][0,0,0,0,0,0,0,0,0];索引与九宫格位置的映射关系0 │ 1 │ 2 ──┼───┼── 3 │ 4 │ 5 ──┼───┼── 6 │ 7 │ 8一维数组的好处是获胜线可以用索引三元组简洁表达constWIN_LINES:number[][][[0,1,2],[3,4,5],[6,7,8],// 行[0,3,6],[1,4,7],[2,5,8],// 列[0,4,8],[2,4,6]// 对角线];3.2 状态变量页面的State变量分为棋盘状态、游戏状态和得分三类Stateboard:number[][...];// 棋盘核心数据9个0/1/2StatecurrentPlayer:number1;// 当前回合1✕, 2◯StategameOver:booleanfalse;// 游戏是否结束Statewinner:number0;// 胜者0无, 1✕, 2◯, 3平局StatewinLine:number[][];// 获胜线的三个索引用于高亮StatescoreX:number0;// ✕ 累计得分StatescoreO:number0;// ◯ 累计得分Statedraws:number0;// 平局次数StatevsAI:booleantrue;// 当前模式winLine是一个关键的辅助变量——它不直接控制游戏逻辑但决定了 UI 中哪些格子需要高亮。当checkWin()检测到获胜时winLine被设置为获胜线的三个索引isWinCell()方法据此判断每个格子是否需要橙色边框。winner的值有四种含义0 游戏未结束1 ✕ 胜2 ◯ 胜3 平局。平局用3而非-1是为了在条件判断中与0无胜者明确区分——if (this.winner 0)可以覆盖三种结束状态。3.3 不可变棋盘更新与记忆翻牌中的updateCard()模式一致井字棋使用makeMove()辅助方法创建新棋盘数组makeMove(b:number[],idx:number,player:number):number[]{constnb:number[][];for(leti0;ib.length;i){nb.push(iidx?player:b[i]);}returnnb;}每次落子都创建一个全新的 9 元素数组。这个方法被玩家落子、AI 试走检测能否赢/堵等多个场景复用避免了重复的数组拷贝代码。四、胜负判定引擎4.1 checkWin 方法胜负检测的核心是一个遍历 8 条获胜线的方法checkWin(b:number[]):number{for(leti0;iWIN_LINES.length;i){constaWIN_LINES[i][0];constb1WIN_LINES[i][1];constcWIN_LINES[i][2];if(b[a]!0b[a]b[b1]b[b1]b[c]){this.winLine[a,b1,c];returnb[a];}}return0;}对于每条获胜线[a, b1, c]检测三个条件b[a] ! 0第一个格子不为空b[a] b[b1]第一个和第二个相同b[b1] b[c]第二个和第三个相同三个条件同时满足 → 返回该格子中的玩家值1 或 2同时设置winLine用于 UI 高亮。返回0表示当前棋盘上无人获胜。4.2 isBoardFull 方法平局检测更为简单——遍历 9 个格子只要有一个空格就未满isBoardFull(b:number[]):boolean{for(leti0;ib.length;i){if(b[i]0)returnfalse;}returntrue;}isBoardFull在checkWin返回 0 之后才被调用确保先检测胜负再检测平局的顺序——因为存在最后一手同时满足连胜和填满棋盘的情况此时应判胜而非平。五、AI 策略设计5.1 五级优先级AI 不搜索整棵博弈树而是使用基于规则的即时决策。五条规则按优先级从高到低排列aiMove():void{constbthis.board;// 1. 赢如果有一格能让 AI 连成三子立刻落子for(leti0;i9;i){if(b[i]0){consttestthis.makeMove(b,i,2);if(this.checkWin(test)2){this.applyAIMove(test);return;}}}// 2. 堵如果有一格能让玩家连成三子堵住它for(leti0;i9;i){if(b[i]0){consttestthis.makeMove(b,i,1);if(this.checkWin(test)1){this.applyAIMove(this.makeMove(b,i,2));return;}}}// 3. 中占据中心位置索引 4if(b[4]0){this.applyAIMove(this.makeMove(b,4,2));return;}// 4. 角占据角位索引 0, 2, 6, 8constcorners[0,2,6,8];for(letci0;cicorners.length;ci){if(b[corners[ci]]0){this.applyAIMove(this.makeMove(b,corners[ci],2));return;}}// 5. 任意选择第一个空格for(leti0;i9;i){if(b[i]0){this.applyAIMove(this.makeMove(b,i,2));return;}}}为什么不用 MinMaxMinMax 算法对井字棋的搜索空间仅为 9! 362880 个节点完全可以暴力搜索。但本文的目标是展示规则 AI 的设计模式——通过优先级分层用 5 个独立的规则块替代递归搜索树代码结构清晰、没有递归深度问题且每一步的决策都是 O(n) 时间复杂度。这个 AI 的水平大约相当于熟悉规则的初学者——它会赢、会堵、会占好位置但不会主动构造双重威胁。5.2 AI 思考延时AI 落子不是瞬时的而是通过setInterval实现 400ms 延时// 在 placePiece 中玩家落子后if(this.vsAI){this.aiTimerIdsetInterval((){clearInterval(this.aiTimerId);this.aiTimerId-1;this.aiMove();},400);}400ms 的选择是一个微妙的交互设计决策。如果小于 200ms玩家感受不到 AI 在思考如果大于 600ms游戏节奏过慢。400ms 恰好是一个短暂的停顿——足够让玩家意识到回合已经切换又不会让等待变得不耐烦。延时期间棋盘处于锁定状态——placePiece方法检查this.aiTimerId ! -1并拒绝任何点击。这防止了玩家在 AI 思考期间连续落子。六、回合制交互逻辑6.1 placePiece 方法玩家落子的完整流程包含五层守卫条件placePiece(idx:number):void{if(this.aiTimerId!-1)return;// AI 思考中棋盘锁定if(this.board[idx]!0)return;// 已有棋子if(this.gameOver)return;// 游戏已结束if(this.vsAIthis.currentPlayer2)return;// AI 的回合// 落子不可变更新this.boardthis.makeMove(this.board,idx,1);// 胜负检测constwthis.checkWin(this.board);if(w0){this.gameOvertrue;this.winnerw;if(w1)this.scoreX;return;}// 平局检测if(this.isBoardFull(this.board)){this.gameOvertrue;this.winner3;this.draws;return;}// 回合切换this.currentPlayer2;// AI 回合人机模式if(this.vsAI){this.aiTimerIdsetInterval((){clearInterval(this.aiTimerId);this.aiTimerId-1;this.aiMove();},400);}}五层守卫条件反映了回合制状态机的复杂性——在任何时间点棋盘可能处于等待玩家落子、“等待 AI 落子”、游戏已结束三种宏观状态之一每个状态对点击事件的响应截然不同。6.2 applyAIMove 方法AI 落子后的处理与玩家落子对称但更新的是 ◯ 的得分applyAIMove(newBoard:number[]):void{this.boardnewBoard;constwthis.checkWin(newBoard);if(w0){this.gameOvertrue;this.winnerw;if(w2)this.scoreO;return;}if(this.isBoardFull(newBoard)){this.gameOvertrue;this.winner3;this.draws;return;}this.currentPlayer1;// 回合返还玩家}applyAIMove被设计为接收一个已构造好的新棋盘而非内部构造因为它被 AI 的五个策略分支调用——每个分支已经构造了候选棋盘直接传入避免了重复构造。七、UI 设计7.1 信息架构页面从上到下分为五个区域┌────────────────────────────┐ │ 井字棋深色标题栏 │ ├────────────────────────────┤ │ [双人对战] [人机对战] │ ← 模式切换 ├────────────────────────────┤ │ 你 (✕) 3 平局 1 AI (◯) 2 │ ← 得分栏 ├────────────────────────────┤ │ 你的回合 (✕) │ ← 状态提示 ├────────────────────────────┤ │ ┌────┬────┬────┐ │ │ │ │ ✕ │ │ │ ← 3×3 棋盘 │ ├────┼────┼────┤ │ │ │ │ ◯ │ │ │ │ ├────┼────┼────┤ │ │ │ ✕ │ │ ◯ │ │ │ └────┴────┴────┘ │ ├────────────────────────────┤ │ 重新开始 │ └────────────────────────────┘7.2 棋盘构建棋盘使用双层ForEach构建 3×3 网格Column(){ForEach(this.rowsArr,(row:number,ri:number){Row(){ForEach(this.rowsArr,(col:number,ci:number){Column(){Text(this.cellSymbol(this.board[ri*3ci])).fontSize(40).fontColor(this.cellColor(this.board[ri*3ci])).fontWeight(FontWeight.Bold)}.width(88).height(88).backgroundColor(this.isWinCell(ri*3ci)?#FFF3E0:(this.board[ri*3ci]0?#F5F5FA:#FFFFFF)).borderRadius(BorderRadius.MD).border({width:this.isWinCell(ri*3ci)?2:0,color:#FF9800}).onClick((){this.placePiece(ri*3ci);})},(col:number,ci:number)${ci})}},(row:number,ri:number)${ri})}扁平索引的计算ri * 3 ci——行索引乘以 3 加上列索引将二维坐标映射为一维数组索引。例如第 2 行第 1 列索引 1, 0 1*30 3。每个格子的视觉效果分为四种状态格子状态内容文字颜色背景色空无—#F5F5FA浅灰✕ 占据✕#FF4D4F红#FFFFFF白◯ 占据◯#1677FF蓝#FFFFFF白获胜线✕/◯同上#FFF3E0浅橙 2px#FF9800边框获胜线通过isWinCell()判断当前格子是否在winLine数组中。获胜时三个格子同时获得橙色边框 浅橙背景形成醒目的连线效果。7.3 双色符号系统✕ 和 ◯ 使用两种高对比度颜色✕#FF4D4F红色玩家的颜色热情、主动、有攻击性。红色在所有文化中都意味着行动符合玩家先手落子的主动性。◯#1677FF蓝色AI 的颜色冷静、计算、被动。蓝色暗示理性和算法与 AI 的机器属性一致。在双人对战模式下✕ 和 ◯ 分别代表两位玩家红色和蓝色提供了清晰的视觉区分——玩家不会混淆刚才是我下的还是对方下的。八、完整代码结构TicTacToePage (~260 行) ├── 常量定义 │ └── WIN_LINES[] — 8 条获胜线行/列/对角线 ├── 状态变量 │ ├── State board[9] — 棋盘0空, 1✕, 2◯ │ ├── State currentPlayer / gameOver / winner / winLine — 游戏状态 │ ├── State scoreX / scoreO / draws — 得分记录 │ └── State vsAI — 双人/人机模式 ├── 棋盘操作 │ ├── makeMove() — 不可变落子返回新数组 │ ├── checkWin() — 遍历 8 条线检测胜负设置 winLine │ └── isBoardFull() — 平局检测 ├── 玩家交互 │ ├── placePiece() — 玩家落子五层守卫 胜负判定 AI触发 │ └── cellSymbol() / cellColor() — 格子显示 ├── AI 引擎 │ ├── aiMove() — 五级优先级决策赢堵中角任意 │ └── applyAIMove() — AI落子后的状态更新 ├── 游戏控制 │ ├── newGame() — 清空棋盘保留得分 │ └── toggleMode() — 切换双人/人机模式 ├── UI 视图 │ ├── 标题栏 — 井字棋 │ ├── 模式选择 — 双人对战 / 人机对战 │ ├── 得分栏 — X得分 / 平局 / O得分 │ ├── 状态提示 — 回合/胜负提示 │ ├── 3×3棋盘 — 双层ForEach网格 │ └── 重新开始按钮 └── 生命周期 └── aboutToDisappear() — 清理AI定时器九、总结本文从零构建了一个井字棋游戏。与记忆翻牌的记忆力挑战不同井字棋是一场策略的较量——它的核心不是记住位置而是在有限的棋盘上预测对手的下一步。从技术角度看井字棋也是回合制状态机的典型实现——玩家回合、AI 回合、游戏结束三种宏观状态通过守卫条件严格隔离AI 延时实现了回合之间的自然过渡。核心要点回顾一维棋盘数组number[9]表示 3×3 九宫格每个格子的值0/1/2编码了空/✕/◯ 三种状态。一维数组简化了索引计算和获胜检测——每个获胜线只需 3 个整数即可表达。8 条获胜线3 行 3 列 2 对角线 8 种获胜方式。checkWin()遍历这 8 条线检测三个格子非空且相等。平局检测在胜负检测之后确保最后一手同时连线填满判胜不平。五级优先级 AI赢 堵 中 角 任意。每条规则独立地扫描棋盘并返回首个满足条件的落子。时间复杂度 O(n)不需要递归或搜索树。这个 AI 不会犯漏堵的低级错误但也不会主动创建双重威胁。AI 思考延时setInterval(fn, 400) 即时clearInterval实现 400ms 的思考停顿。延时期间aiTimerId充当棋盘锁防止玩家在 AI 回合落子。这个模式与记忆翻牌的 800ms 翻转延时使用相同的setInterval技术。不可变棋盘更新makeMove()每次返回全新的 9 元素数组不修改原数组。同一个方法被玩家落子、AI 试走、AI 实际落子三个场景复用——共用的代码减少了状态不一致的风险。双色符号系统✕ 红色#FF4D4F、◯ 蓝色#1677FF——两种颜色在白色棋盘上形成高对比度区分。获胜线使用橙色#FF9800边框 浅橙#FFF3E0背景高亮橙色作为红蓝之间的中性胜利色不偏向任何一方。得分跨局保留scoreX、scoreO、draws三个变量在多次游戏中持续累积。newGame()清空棋盘和胜负状态但保留得分——这是一个有意为之的设计选择鼓励玩家多玩几局。井字棋的魅力在于它的简单——规则三秒学会策略五分钟入门。但这个 260 行的 ArkUI 实现展示了一个完整的回合制游戏所需的全部要素状态机、胜负判定、AI 决策、延时交互和得分系统。它是游戏开发的最小完备示例也是状态管理模式的一次集中实践。