一、引言:当ArkTS遇见传统棋类游戏
在HarmonyOS生态开发中,ArkTS凭借其简洁的语法和强大的跨设备能力,成为构建交互式应用的优选。本文将通过开发一款五子棋游戏,展示ArkTS在状态管理、UI布局和事件处理上的独特优势,带您体验用现代框架重构传统游戏的乐趣。
二、核心功能设计与实现
1. 棋盘架构:声明式UI的魅力
数据模型设计
@State private board: Array<Array<boolean | undefined>> = Array(15).fill(0).map(() => Array(15).fill(undefined));
- 使用二维数组存储棋盘状态,
true
(黑棋)、false
(白棋)、undefined
(空位) @State
装饰器实现数据与UI的自动同步,无需手动操作DOM
网格布局实现
Grid() {
ForEach(this.board, (row, rowIndex) => {
ForEach(row, (cell, colIndex) => {
GridItem() {
this.renderCell(rowIndex, colIndex)
}
})
})
}
.columnsTemplate('repeat(15, 1fr)')
.rowsTemplate('repeat(15, 1fr)')
.width('90%')
.aspectRatio(1)
- 通过
Grid
组件快速搭建15×15棋盘,ForEach
实现数据驱动渲染 aspectRatio(1)
保持棋盘正方形,适配不同屏幕比例
2. 交互逻辑:响应式编程的实践
落子逻辑处理
private handleCellClick(rowIndex: number, colIndex: number) {
if (this.gameOver || this.board[rowIndex][colIndex] !== undefined) return;
// 不可变数据更新
const newBoard = this.board.map(arr => arr.slice());
newBoard[rowIndex][colIndex] = this.currentPlayer;
this.board = newBoard;
// 状态切换与胜利检测
if (this.checkWin(rowIndex, colIndex)) {
this.showWinDialog();
} else {
this.currentPlayer = !this.currentPlayer;
}
}
- 采用不可变数据模式(
map+slice
)避免直接修改状态,触发UI重新渲染 - 通过
@State
自动管理currentPlayer
状态,切换玩家无需手动操作UI
胜利检测算法
private checkWin(row: number, col: number): boolean {
const directions = [ [0,1], [1,0], [1,1], [1,-1] ]; // 四个基础方向
for (const [dx, dy] of directions) {
let count = 1;
// 向两个相反方向延伸检查
for (const sign of [-1, 1]) {
const x = row + dx * sign;
const y = col + dy * sign;
while (x >=0 && x<15 && y >=0 && y<15 && this.board[x][y] === this.currentPlayer) {
count++;
}
}
if (count >=5) return true;
}
return false;
}
- 优化方向检测逻辑,通过基础方向+符号组合减少代码冗余
- 边界条件检查确保数组越界安全,提升代码健壮性
3. 增强体验:细节功能完善
模态对话框
AlertDialog.show({
title: '胜利通知',
message: `${this.winner} 获胜!`,
primaryButton: {
value: '新局',
action: () => this.resetGame()
}
});
- 使用ArkUI原生对话框组件,提供沉浸式交互反馈
- 按钮点击直接绑定状态重置函数,保持逻辑简洁
主题化设计
.backgroundColor('#fdf6e3') // 米黄色背景
.border({ width: 2, color: '#000' }) // 加粗棋盘边框
.fontFamily('cursive') // 手写体字体
- 通过CSS式属性配置视觉风格,支持动态主题切换(后续可扩展)
- 棋子使用文本符号(⚫/⚪)替代图片,减少资源依赖
三、附源文件:
@Component
export struct play_9 {
// 棋盘数据:true=黑棋,false=白棋,undefined=空位
@State private board: Array<Array<boolean | undefined>> = Array(15).fill(0).map(() => Array(15).fill(undefined));
// 当前玩家
@State private currentPlayer: boolean = true; // true=黑棋
// 游戏状态
@State private gameOver: boolean = false;
@State private winner: string = '';
// 背景音乐状态
@State private backgroundMusicOn: boolean = true;
// 检查胜负
private checkWin(row: number, col: number): boolean {
const directions = [
[[0, 1], [0, -1]], // 横向
[[1, 0], [-1, 0]], // 纵向
[[1, 1], [-1, -1]], // 正斜线
[[1, -1], [-1, 1]] // 反斜线
];
for (const dir of directions) {
let count = 1; // 当前位置已经有一个棋子
// 检查两个方向
for (let i = 0; i < dir.length; i++) {
const dx = dir[i][0];
const dy = dir[i][1];
let x = row + dx;
let y = col + dy;
while (x >= 0 && x < 15 && y >= 0 && y < 15 && this.board[x][y] === this.currentPlayer) {
count++;
x += dx;
y += dy;
}
}
if (count >= 5) {
return true; // 五子连珠,获胜
}
}
return false; // 没有获胜
}
// 处理棋格点击
private handleCellClick(rowIndex: number, colIndex: number) {
// 如果游戏已结束或该位置已有棋子,不做处理
if (this.gameOver || this.board[rowIndex][colIndex] !== undefined) {
return;
}
// 创建新的棋盘副本并更新
let newBoard = this.board.map(arr => arr.slice());
newBoard[rowIndex][colIndex] = this.currentPlayer;
this.board = newBoard;
// 检查是否获胜
if (this.checkWin(rowIndex, colIndex)) {
this.gameOver = true;
this.winner = this.currentPlayer ? '黑棋' : '白棋';
// 显示弹窗提示
AlertDialog.show({
title: '游戏结束',
message: `${this.winner} 获胜!`,
primaryButton: {
value: '重新开始',
action: () => {
this.resetGame();
}
},
secondaryButton: {
value: '关闭',
action: () => {}
}
});
} else {
// 切换玩家
this.currentPlayer = !this.currentPlayer;
}
}
// 重置游戏
private resetGame() {
this.board = Array(15).fill(0).map(() => Array(15).fill(undefined));
this.currentPlayer = true;
this.gameOver = false;
this.winner = '';
}
// 渲染单个棋格
@Builder
private renderCell(rowIndex: number, colIndex: number) {
Column() {
// 棋格点击区域
Row() {
// 显示棋子
if (this.board[rowIndex][colIndex] !== undefined) {
// 使用简单图片样式
if (this.board[rowIndex][colIndex]) {
// 黑棋 - 使用简单图片
Text('⚫') // 假设已添加black_stone.png到资源目录
.width(30)
.height(30)
} else {
// 白棋 - 使用简单图片
Text('⚪')
.width(30)
.height(30)
}
}
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(VerticalAlign.Center)
.onClick(() => {
this.handleCellClick(rowIndex, colIndex);
})
}
.width('100%')
.height('100%')
.borderWidth(1)
.borderColor('#444') // 更明显的线条
.backgroundColor('#f5e8cc')
}
build() {
Column() {
// 页面标题和控制区域
Row() {
// 标题
Text('五子棋对弈')
.fontSize(36)
.fontWeight(FontWeight.Bold)
.fontFamily('cursive')
.margin({ top: 15, bottom: 15 })
// 音乐开关按钮
Toggle({ type: ToggleType.Switch })
.onChange((isOn: boolean) => {
this.backgroundMusicOn = isOn;
})
.width(55)
.height(32)
.margin({ left: 20 })
}
.width('100%')
.justifyContent(FlexAlign.Center)
// 游戏状态显示
Text(this.gameOver ? `${this.winner} 获胜!` : `轮到 ${this.currentPlayer ? '黑棋' : '白棋'} 下`)
.fontSize(28)
.fontColor(this.currentPlayer ? '#5d4c40' : '#8b7500')
.fontFamily('cursive')
.margin({ top: 10 })
// 棋盘区域
Grid() {
ForEach(this.board, (row: Array<boolean | undefined>, rowIndex: number) => {
ForEach(row, (cell: boolean | undefined, colIndex: number) => {
GridItem() {
this.renderCell(rowIndex, colIndex)
}
})
})
}
.columnsTemplate('repeat(15, 1fr)')
.rowsTemplate('repeat(15, 1fr)')
.width('90%')
.aspectRatio(1)
.margin({ top: 25 })
.border({ width: 2, color: '#000' }) // 更明显的棋盘边框
// 控制按钮区域
Row() {
// 重新开始按钮
Button('🔄 新局')
.onClick(() => {
this.resetGame();
})
.width('40%')
.margin({ right: 10 })
.backgroundColor('#4caf50')
.fontColor('#fff')
.fontSize(18)
.padding({ left: 10, right: 10 })
// 悔棋按钮
Button('↩️ 悔棋')
.onClick(() => {
// 这里可以添加悔棋功能
})
.width('40%')
.backgroundColor('#ff9800')
.fontColor('#fff')
.fontSize(18)
.padding({ left: 10, right: 10 })
}
.width('90%')
.justifyContent(FlexAlign.SpaceEvenly)
.margin({ top: 25 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Center)
.padding({ top: 15, bottom: 15, left: 15, right: 15 })
.backgroundColor('#fdf6e3')
}
}