HarmonyOS 5.0井字棋小游戏开发案例
本文深入解析了基于ArkUI框架的井字棋游戏开发流程。游戏采用双玩家对战系统(玩家X/O轮流落子),并利用@kit.ArkData/preferences模块实现得分数据持久化存储。UI布局采用双层ForEach动态网格构建3×3棋盘,结合玩家身份响应式样式(X红色/O蓝色);游戏逻辑涵盖落子校验等算法;并且使用了集成网络API调用实现格言展示与胜者
·
HarmonyOS 5.0案例:井字棋开发案例
本文将分析一个使用ArkUI框架开发的九宫格井字棋游戏,涵盖UI布局、游戏逻辑、数据持久化和网络交互等核心功能。
文章目录
🎮 一、游戏架构与功能亮点
-
双玩家对战系统
- 支持玩家X(红色)和玩家O(蓝色)轮流落子
- 实时显示当前行动玩家和双方历史得分
@State Player: string = 'X'; // 当前玩家标识 @State winNumX: number = 0; // X玩家得分 @State winNumO: number = 0; // O玩家得分 -
智能胜负判定
- 通过预定义的8种胜利路径(横、竖、斜)检测胜负
const winningLines = [ [0,0,0,1,0,2], // 第一行 [0,0,1,0,2,0], // 第一列 [0,0,1,1,2,2] // 对角线 ]; -
数据持久化存储
- 使用
@kit.ArkData的preferences模块存储得分数据
class Data { addWin(winner: string, winNum: number) { preferences.putSync(winner, winNum.toString()); } } - 使用
⚙️ 二、核心实现解析
1. UI布局方案
-
网格布局:通过双层
ForEach循环构建3×3游戏棋盘Flex({ wrap: FlexWrap.Wrap }) { ForEach(this.chessBoard, (row: Grid[]) => { ForEach(row, (cell: Grid) => { Text(cell.value).onClick(() => {...}) }) }) } -
动态样式:根据玩家身份切换字体颜色和大小
.backgroundColor(cell.value === 'X' ? Color.Red : Color.Blue) .fontSize(this.winner === 'X' ? 26 : 20)
2. 游戏逻辑控制
-
落子处理:检查目标格子是否为空并更新棋盘状态
if (!this.ifGameOver && this.chessBoard[rowIndex][columnIndex].value === '') { this.chessBoard[rowIndex][columnIndex].value = this.Player; } -
胜负判定:遍历所有胜利路径,检测是否达成三连
const win = this.chessBoard[line[0]][line[1]].value; if (win && win === this.chessBoard[line[2]][line[3]].value && ...)
3. 网络交互功能
-
格言展示:通过axios获取网络格言并显示
axios.get('http://shanhe.kim/api/za/yiyan.php').then(res => { this.Text = res.data; }); -
MVP图片生成:根据胜者动态生成图片URL
this.winPlayer = http.getWinner(`${result}获胜!`);
🛠️ 三、进阶优化建议
-
分布式对战扩展
参考OpenHarmony的分布式能力,实现跨设备对战:- 使用
distributedDataObject同步棋盘状态 - 通过
deviceManager发现附近设备
- 使用
-
动画效果增强
- 为棋子添加
sharedTransition转场动画 - 使用
Gesture组合手势实现长按撤销操作
- 为棋子添加
-
状态管理优化
- 使用
AppStorage全局状态管理替代多处@State - 将游戏逻辑抽离为独立
GameService类
- 使用
💡 四、关键代码片段
数据持久化实现:
export class Data {
private data?: preferences.Preferences;
init() {
this.data = preferences.getPreferencesSync(ctx, { name: 'wins' });
}
addWin(winner: string, winNum: number) {
this.data!.putSync(winner, winNum.toString());
}
}
游戏重置逻辑:
initGame() {
this.chessBoard.forEach(row =>
row.forEach(cell => cell.value = '')
);
this.Player = 'X';
this.ifGameOver = false;
}
代码参考:
import { promptAction } from '@kit.ArkUI';
import axios, { AxiosResponse } from '@ohos/axios';
import { data, winningLines } from './Gamescore';//导入
class Http {
getWinner(msg: string) {
const res = `http://shanhe.kim/api/qq/ju2.php?msg=${encodeURI(msg)}` as string;
return res
}
}
const http = new Http();
@ObservedV2
export class Grid {
@Trace value: string = '';
rowIndex: number = 0;
columnIndex: number = 0;
constructor(rowIndex: number, colIndex: number) {
this.rowIndex = rowIndex;
this.columnIndex = colIndex;
}
}
const chessBoard: Grid[][] = [
[new Grid(0, 0), new Grid(0, 1), new Grid(0, 2)],
[new Grid(1, 0), new Grid(1, 1), new Grid(1, 2)],
[new Grid(2, 0), new Grid(2, 1), new Grid(2, 2)]
];
@Entry
@Component
struct ChessPlay {
@State chessBoard: Grid[][] = chessBoard; // 九宫格游戏板
@State Player: string = 'X';
@State ifGameOver: boolean = false;
@State winner: string = '';
@State winNumX: number = 0;
@State winNumO: number = 0;
@State winPlayer: string = '';
@State Text: string = ''
// 初始化游戏
aboutToAppear() {
this.chessBoard = chessBoard;
this.initGame();
this.winNumX = Number(data.getWin('winNumX'))
this.winNumO = Number(data.getWin('winNumO'))
this.winPlayer = http.getWinner('开始游戏')
this.getText()
}
async getText() {
const res: AxiosResponse<string> =
await axios.get<string, AxiosResponse<string>, null>('http://shanhe.kim/api/za/yiyan.php')
this.Text = res.data
}
// 重置游戏
initGame() {
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
this.chessBoard[i][j].value = '';
}
}
this.Player = 'X';
this.ifGameOver = false;
this.winner = '';
this.getText()
}
// 检查是否有玩家获胜
checkForWinner() {
for (let line of winningLines) {
const win = this.chessBoard[line[0]][line[1]].value;
if (win &&
win === this.chessBoard[line[2]][line[3]].value &&
win === this.chessBoard[line[4]][line[5]].value) {
this.ifGameOver = true;
this.winner = win;
return win;
}
}
const allCellsFilled = this.chessBoard.flat().every(cell => cell.value !== '');
if (allCellsFilled) {
this.ifGameOver = true;
this.winner = '平局';
return '平局';
}
return '';
}
// 玩家点击单元格时触发的方法
FallChess(rowIndex: number, columnIndex: number) {
if (!this.ifGameOver && this.chessBoard[rowIndex][columnIndex].value === '') {
this.chessBoard[rowIndex][columnIndex].value = this.Player;
const result = this.checkForWinner();
if (result) {
if (result === 'X') {
this.winNumX += 1;
data.addWin('winNumX', this.winNumX);
} else if (result === 'O') {
this.winNumO += 1;
data.addWin('winNumO', this.winNumO);
}
let message = `${result} 获胜!`;
this.winPlayer = http.getWinner(`${result}获胜!`)
if (result === '平局') {
message = '平局!';
this.winPlayer = http.getWinner(`平局!`)
}
promptAction.showDialog({
title: `游戏结束`,
message,
buttons: [
{
text: '再来一局',
color: '#ffa500'
}
],
}).then(() => {
this.initGame();
});
} else {
this.Player = this.Player === 'X' ? 'O' : 'X'; // 切换玩家
if (this.Player === 'O') {
this.FallChess(rowIndex, columnIndex)
}
}
}
}
build() {
Column({ space: 20 }) {
Row({ space: 20 }) {
Text(`玩家X得分: ${this.winNumX}`)
.fontColor('#ff0000')
.fontSize(this.winner === 'X' ? 26 : 20)
Text(`玩家O得分: ${this.winNumO}`)
.fontColor('#0000ff')
.fontSize(this.winner === 'O' ? 26 : 20)
}
Text(`当前玩家: ${this.Player}`)
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.chessBoard, (row: Grid[]) => {
ForEach(row, (cell: Grid) => {
Text(cell.value)
.width(60)
.height(60)
.margin(5)
.fontSize(26)
.textAlign(TextAlign.Center)
.backgroundColor(cell.value === 'X' ? Color.Red :
cell.value === 'O' ? Color.Blue : Color.Gray)
.fontColor(Color.White)
.borderRadius(5)
.onClick(() => {
this.FallChess(cell.rowIndex, cell.columnIndex);
});
})
})
}
.width(210)
Row() {
Button('重新开始')
.width('50%')
.height('5%')
.onClick(() => {
this.initGame(); // 重新开始游戏
});
Button('清楚得分数据')
.width('50%')
.height('5%')
.onClick(() => {
data.deleteWin()
this.winNumX = 0;
this.winNumO = 0;
});
}.width(210)
Column({ space: 10 }) {
Text('上一局MVP得主:')
.width(150)
Image(this.winPlayer)
.width(150)
.height(150)
.objectFit(ImageFit.Fill)
Text(this.Text)
.width(150)
}.width('100%')
}
.width('100%')
.height('100%')
}
}
import { preferences } from "@kit.ArkData";
// 胜利的线路
export const winningLines = [
[0, 0, 0, 1, 0, 2],
[1, 0, 1, 1, 1, 2],
[2, 0, 2, 1, 2, 2],
[0, 0, 1, 0, 2, 0],
[0, 1, 1, 1, 2, 1],
[0, 2, 1, 2, 2, 2],
[0, 0, 1, 1, 2, 2],
[0, 2, 1, 1, 2, 0]
];
class Data {
data?: preferences.Preferences
// 初始化数据存储
init() {
if (!this.data) {
const ctx = AppStorage.get<Context>('context')
this.data = preferences.getPreferencesSync(ctx, { name: 'wins' });
}
return this.data;
}
// 添加得分
addWin(winner: string, winNum: number) {
this.init().putSync(winner, winNum.toString())
this.init().flushSync()
}
// 获取得分数据
getWin(winner: string) {
return this.init().getSync(winner, 0)
}
// 删除双方得分
deleteWin() {
this.init().clearSync()
this.init().flushSync()
}
}
export const data = new Data();
更多推荐


所有评论(0)