纯血Harmony NETX 5 打造趣味五子棋:(附源文件)

发布于:2025-06-12 ⋅ 阅读:(17) ⋅ 点赞:(0)

一、引言:当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')
  }
}