HarmonyOS 小游戏《对战五子棋》开发第4篇-五子棋架构设计:MVC在ArkTS中的实践
·
好的架构让代码像积木一样可拼装、可替换
为什么需要架构设计
五子棋看似简单,但如果把所有逻辑写在一个文件里,很快就会变成一团乱麻。本项目采用了类MVC的三层架构:
┌─────────────────────────────────┐
│ View 层 (UI) │
│ Index / TwoPlayerPage / │
│ AIBattlePage / ChessBoardView │
└──────────┬──────────────────────┘
│ 调用引擎方法 / 接收回调
┌──────────▼──────────────────────┐
│ Controller/Engine 层 │
│ GomokuEngine (核心引擎) │
│ AIPlayer (AI控制器) │
└──────────┬──────────────────────┘
│ 操作
┌──────────▼──────────────────────┐
│ Model 层 (数据) │
│ board[][] / history[] / result │
│ GameConstants (常量与类型) │
└─────────────────────────────────┘
Model层:GameConstants
// GameConstants.ets — 纯数据定义,无任何逻辑
export const BOARD_SIZE: number = 15;
export const EMPTY: number = 0;
export const BLACK: number = 1;
export const WHITE: number = 2;
export enum Difficulty { EASY = 1, NORMAL = 2, HARD = 3 }
export enum GameResult { PLAYING = 0, BLACK_WIN = 1, WHITE_WIN = 2, DRAW = 3 }
export class Move {
row: number;
col: number;
constructor(row: number, col: number) {
this.row = row;
this.col = col;
}
}
Model层只定义"是什么",不关心"怎么做"。这些常量和类型被所有层共享。
Engine层:GomokuEngine
引擎层是纯逻辑,不依赖任何UI框架:
export class GomokuEngine {
board: number[][];
currentPlayer: number;
result: GameResult;
history: Move[];
lastMove: Move | null;
placePiece(row: number, col: number, player: number): boolean { /* ... */ }
checkWin(row: number, col: number, player: number): boolean { /* ... */ }
undo(): boolean { /* ... */ }
reset(): void { /* ... */ }
toFlatArray(): number[] { /* ... */ }
}
关键设计决策:
- 引擎不import任何ArkUI模块——可以在Node.js中测试
- 引擎不主动通知UI——由调用方负责刷新
- 引擎返回基本类型(boolean/number[])——UI层自行解读
View层:页面与组件
View层负责渲染引擎状态,并将用户输入传递给引擎:
@Entry
@Component
struct TwoPlayerPage {
private engine: GomokuEngine = new GomokuEngine(); // 持有引擎实例
@State boardData: number[] = []; // 从引擎同步的状态
@State currentPlayer: number = BLACK;
@State result: GameResult = GameResult.PLAYING;
// View → Engine:用户点击落子
private onCellClick(row: number, col: number): void {
const success = this.engine.placePiece(row, col, this.currentPlayer);
if (success) {
this.refreshBoardData(); // Engine → View:同步状态
}
}
// 从引擎同步数据到UI状态
private refreshBoardData(): void {
this.boardData = this.engine.toFlatArray();
this.currentPlayer = this.engine.currentPlayer;
this.result = this.engine.result;
}
}
数据流向
用户点击棋盘
↓
ChessBoardView.handleClick() → onCellClick回调
↓
TwoPlayerPage.onCellClick() → engine.placePiece()
↓
GomokuEngine修改board[][],更新result
↓
refreshBoardData() 同步到 @State
↓
@State变化触发UI重绘
↓
ChessBoardView的@Watch('onDataChange')触发drawBoard()
可复用组件设计
ChessBoardView是纯粹的表现组件:
@Component
export struct ChessBoardView {
@Prop @Watch('onDataChange') boardData: number[] = []; // 输入
@Prop lastMoveRow: number = -1; // 输入
@Prop lastMoveCol: number = -1; // 输入
onCellClick: (row: number, col: number) => void = () => {}; // 输出回调
}
它不关心数据从哪来(双人对战还是AI对战),只负责:
- 根据
boardData绘制棋盘 - 将点击事件通过回调传出
这种设计使得ChessBoardView被两个页面复用而无需任何修改。
AIPlayer的架构角色
AIPlayer本质上是一个"虚拟控制器"——它读取引擎状态,计算出最佳落子,然后由页面层调用引擎落子:
// AIBattlePage中
const move = this.aiPlayer.getMove(this.engine.board); // AI计算
this.engine.placePiece(move.row, move.col, WHITE); // 引擎执行
AIPlayer不直接修改棋盘,保持了引擎对棋盘的独占控制权。
架构优势
- 可测试性:GomokuEngine和AIPlayer可以脱离UI单独测试
- 可复用性:ChessBoardView被两个页面复用
- 可扩展性:添加新AI算法只需继承或替换AIPlayer
- 可维护性:修改UI不影响游戏逻辑,反之亦然
总结
好的架构不是过度设计,而是恰到好处的分层。本项目通过Model-Engine-View三层分离,实现了逻辑与表现的解耦。引擎层是项目的核心,UI层是引擎的"显示器",AI层是引擎的"智能大脑"。
附:

更多推荐

所有评论(0)