HarmonyOS 5.0案例:井字棋开发案例

本文将分析一个使用ArkUI框架开发的九宫格井字棋游戏,涵盖UI布局、游戏逻辑、数据持久化和网络交互等核心功能。


🎮 一、游戏架构与功能亮点

  1. ​双玩家对战系统​

    • 支持玩家X(红色)和玩家O(蓝色)轮流落子
    • 实时显示当前行动玩家和双方历史得分
    @State Player: string = 'X';  // 当前玩家标识
    @State winNumX: number = 0;   // X玩家得分
    @State winNumO: number = 0;   // O玩家得分
    
  2. ​智能胜负判定​

    • 通过预定义的8种胜利路径(横、竖、斜)检测胜负
    const winningLines = [
     [0,0,0,1,0,2],  // 第一行
     [0,0,1,0,2,0],  // 第一列
     [0,0,1,1,2,2]   // 对角线
    ];
    
  3. ​数据持久化存储​

    • 使用@kit.ArkDatapreferences模块存储得分数据
    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}获胜!`);
    

🛠️ 三、进阶优化建议

  1. ​分布式对战扩展​
    参考OpenHarmony的分布式能力,实现跨设备对战:

    • 使用distributedDataObject同步棋盘状态
    • 通过deviceManager发现附近设备
  2. ​动画效果增强​

    • 为棋子添加sharedTransition转场动画
    • 使用Gesture组合手势实现长按撤销操作
  3. ​状态管理优化​

    • 使用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();
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐