HarmonyOS 小游戏《对战五子棋》开发第29篇 - TwoPlayerPage双人对战(一):状态管理与引擎交互

📅 2026/7/11 1:25:01
HarmonyOS 小游戏《对战五子棋》开发第29篇 - TwoPlayerPage双人对战(一):状态管理与引擎交互
UI状态与游戏引擎的桥梁——refreshBoardData的设计双人对战截图代码如下/** * TwoPlayerPage.ets - 双人对战页面 * 两位玩家轮流落子黑方先行 */import{router}fromkit.ArkUI;import{GomokuEngine}from../common/GomokuEngine;import{ChessBoardView}from../common/ChessBoardView;import{BOARD_SIZE,EMPTY,BLACK,WHITE,GameResult,playerName}from../common/GameConstants;EntryComponentstruct TwoPlayerPage{privateengine:GomokuEnginenewGomokuEngine();StateboardData:number[][];StatelastMoveRow:number-1;StatelastMoveCol:number-1;StatecurrentPlayer:numberBLACK;Stateresult:GameResultGameResult.PLAYING;StatemoveCount:number0;StateshowResultDialog:booleanfalse;aboutToAppear():void{this.refreshBoardData();}/** 刷新棋盘数据 */privaterefreshBoardData():void{this.boardDatathis.engine.toFlatArray();this.lastMoveRowthis.engine.lastMove?this.engine.lastMove.row:-1;this.lastMoveColthis.engine.lastMove?this.engine.lastMove.col:-1;this.currentPlayerthis.engine.currentPlayer;this.resultthis.engine.result;this.moveCountthis.engine.history.length;}/** 处理落子 */privateonCellClick(row:number,col:number):void{if(this.result!GameResult.PLAYING){return;}constsuccessthis.engine.placePiece(row,col,this.currentPlayer);if(success){this.refreshBoardData();if(this.result!GameResult.PLAYING){this.showResultDialogtrue;}}}/** 重新开始 */privaterestart():void{this.engine.reset();this.showResultDialogfalse;this.refreshBoardData();}/** 悔棋 */privateundo():void{this.engine.undo();this.refreshBoardData();}/** 获取状态文字 */privategetStatusText():string{if(this.resultGameResult.BLACK_WIN)return黑方获胜;if(this.resultGameResult.WHITE_WIN)return白方获胜;if(this.resultGameResult.DRAW)return平局;return${playerName(this.currentPlayer)}落子;}build(){Stack({alignContent:Alignment.TopStart}){Column(){// 顶部导航栏Row(){Text(←).fontSize(24).fontColor(#333333).onClick((){router.back();}).padding(8)Text(双人对战).fontSize(20).fontWeight(FontWeight.Bold).fontColor(#333333).layoutWeight(1).textAlign(TextAlign.Center)Text( ).fontSize(24).padding(8)}.width(100%).height(56).padding({left:12,right:12})// 状态栏Row({space:8}){// 黑方指示Row({space:6}){Circle({width:16,height:16}).fill(this.currentPlayerBLACKthis.resultGameResult.PLAYING?#1A1A1A:#CCCCCC)Text(黑方).fontSize(14).fontColor(this.currentPlayerBLACKthis.resultGameResult.PLAYING?#333333:#999999)}Text(VS).fontSize(12).fontColor(#CCCCCC)// 白方指示Row({space:6}){Circle({width:16,height:16}).fill(this.currentPlayerWHITEthis.resultGameResult.PLAYING?#FFFFFF:#CCCCCC).border({width:1,color:#999999})Text(白方).fontSize(14).fontColor(this.currentPlayerWHITEthis.resultGameResult.PLAYING?#333333:#999999)}Blank()Text(第${this.moveCount}手).fontSize(13).fontColor(#888888)}.width(92%).padding({top:8,bottom:8})// 状态文字Text(this.getStatusText()).fontSize(16).fontWeight(FontWeight.Medium).fontColor(this.result!GameResult.PLAYING?#E63946:#555555).margin({top:4,bottom:8})// 棋盘Column(){ChessBoardView({boardData:this.boardData,lastMoveRow:this.lastMoveRow,lastMoveCol:this.lastMoveCol,onCellClick:(row:number,col:number){this.onCellClick(row,col);}})}.width(92%).backgroundColor(#D4A868).borderRadius(8).clip(true)// 底部按钮Row({space:16}){Button(悔棋).fontSize(16).fontColor(#555555).backgroundColor(#E8E0D5).borderRadius(24).height(48).layoutWeight(1).enabled(this.moveCount0this.resultGameResult.PLAYING).onClick((){this.undo();})Button(重新开始).fontSize(16).fontColor(#FFFFFF).backgroundColor(#4A90D9).borderRadius(24).height(48).layoutWeight(1).onClick((){this.restart();})}.width(92%).margin({top:16,bottom:16})}.width(100%).height(100%).backgroundColor(#F5F0E8)// 结果弹窗if(this.showResultDialog){Column(){Text(this.resultGameResult.DRAW?平局:游戏结束).fontSize(24).fontWeight(FontWeight.Bold).fontColor(#333333).margin({bottom:8})Text(this.getStatusText()).fontSize(18).fontColor(#E63946).margin({bottom:4})Text(共${this.moveCount}手).fontSize(14).fontColor(#888888).margin({bottom:24})Row({space:16}){Button(返回首页).fontSize(16).fontColor(#555555).backgroundColor(#E8E0D5).borderRadius(24).height(44).onClick((){router.back();})Button(再来一局).fontSize(16).fontColor(#FFFFFF).backgroundColor(#4A90D9).borderRadius(24).height(44).onClick((){this.restart();})}}.width(70%).padding(32).backgroundColor(#FFFFFF).borderRadius(16).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)}}.width(100%).height(100%)}}状态定义EntryComponentstruct TwoPlayerPage{privateengine:GomokuEnginenewGomokuEngine();StateboardData:number[][];StatelastMoveRow:number-1;StatelastMoveCol:number-1;StatecurrentPlayer:numberBLACK;Stateresult:GameResultGameResult.PLAYING;StatemoveCount:number0;StateshowResultDialog:booleanfalse;}State变量职责变量类型用途boardDatanumber[]棋盘数据传给ChessBoardViewlastMoveRow/Colnumber最后一手位置传给ChessBoardViewcurrentPlayernumber当前玩家显示在状态栏resultGameResult游戏结果控制弹窗显示moveCountnumber手数显示在状态栏showResultDialogboolean结果弹窗开关引擎与UI的同步aboutToAppear():void{this.refreshBoardData();}privaterefreshBoardData():void{this.boardDatathis.engine.toFlatArray();this.lastMoveRowthis.engine.lastMove?this.engine.lastMove.row:-1;this.lastMoveColthis.engine.lastMove?this.engine.lastMove.col:-1;this.currentPlayerthis.engine.currentPlayer;this.resultthis.engine.result;this.moveCountthis.engine.history.length;}refreshBoardData是引擎到UI的单向同步——从引擎读取所有状态更新到State变量。为什么需要同步方法引擎是普通class不是响应式的。引擎状态变化不会自动通知UI。refreshBoardData作为手动同步的桥梁引擎状态变化 → refreshBoardData() → State更新 → UI重绘落子流程privateonCellClick(row:number,col:number):void{// 1. 游戏结束则不处理if(this.result!GameResult.PLAYING){return;}// 2. 调用引擎落子constsuccessthis.engine.placePiece(row,col,this.currentPlayer);// 3. 落子成功则刷新UIif(success){this.refreshBoardData();// 4. 游戏结束则显示弹窗if(this.result!GameResult.PLAYING){this.showResultDialogtrue;}}}流程图用户点击 ↓ 检查游戏状态 (PLAYING?) ↓ engine.placePiece() ↓ 成功? → refreshBoardData() → 更新所有State ↓ 游戏结束? → showResultDialog true重新开始privaterestart():void{this.engine.reset();this.showResultDialogfalse;this.refreshBoardData();}引擎重置棋盘关闭结果弹窗同步状态到UI悔棋privateundo():void{this.engine.undo();this.refreshBoardData();}引擎执行悔棋后同步UI——注意悔棋后result会被引擎重置为PLAYING。状态文字privategetStatusText():string{if(this.resultGameResult.BLACK_WIN)return黑方获胜;if(this.resultGameResult.WHITE_WIN)return白方获胜;if(this.resultGameResult.DRAW)return平局;return${playerName(this.currentPlayer)}落子;}根据result和currentPlayer生成状态文字。使用playerName()工具函数来自GameConstants。引擎实例的生命周期privateengine:GomokuEnginenewGomokuEngine();引擎实例在组件创建时初始化随组件销毁而释放。不需要在aboutToDisappear中手动清理——引擎没有持有外部资源。State变化的UI影响StatecurrentPlayer:numberBLACK;currentPlayer变化时状态栏的玩家指示器会自动更新Circle({width:16,height:16}).fill(this.currentPlayerBLACKthis.resultGameResult.PLAYING?#1A1A1A:#CCCCCC)当前是黑方回合时圆圈变黑否则变灰。总结TwoPlayerPage的状态管理展示了引擎与UI的协作模式引擎为权威所有游戏逻辑由引擎处理refreshBoardData同步引擎→UI的单向数据同步State驱动状态变化自动更新UI流程清晰点击→引擎落子→同步→检查结束这种引擎权威手动同步的模式简单有效适合中小型游戏。