HarmonyOS鸿蒙Next中应用如何实现五子棋小游戏?

HarmonyOS鸿蒙Next中应用如何实现五子棋小游戏? 应用如何实现五子棋小游戏?

4 回复

学到了

更多关于HarmonyOS鸿蒙Next中应用如何实现五子棋小游戏?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


cke_463.png

引言

五子棋是一款经典的策略游戏,规则简单但乐趣无穷。本文将带你使用 HarmonyOS 的 ArkUI 框架,以组件化的思想快速实现一个双人对战的五子棋游戏。我们将逻辑与 UI 分离,打造一个结构清晰、易于维护的应用。

1. 棋盘

cke_898.png

ets/pages/gobang/01-棋盘.ets

@Entry
@Component
struct Study {

  // 一个15x15的二维数组,表示棋盘,初始值为0表格空位,1表示黑棋,2表示白棋。
  @State board: number[][] = Array(15).fill(null).map(() => Array(15).fill(0))
  @State currentPlayer: number = 1 // 1表示黑棋,2表示白棋。
  @State gameOver: boolean = false

  build() {
    Column() {
      // 游戏标题和状态显示
      Row() {
        Text('五子棋').fontSize(24).fontWeight(FontWeight.Bold)
        Text(`当前:${this.currentPlayer==1?'黑':'白'}棋`).fontSize(16).margin({ left: 20 })
      }
      .width('100%')
      .margin({ bottom: 10 })
      .justifyContent(FlexAlign.Center)

      // 棋盘
      Column() {
        // 行
        ForEach(this.board, (row: number[], rowIndex:number) => {
          Row() {
            ForEach(row, (cell: number, colIndex:number) => {
              // 列
              Column() {
                if (cell) {
                  Text().width(18).height(18).backgroundColor(cell === 1 ? '#000' : '#fff').borderRadius(9)
                }
              }
              .width(20).height(20).backgroundColor('#DEB887')
              .border({ width: 1, color: '#999' })
            })
          }
        })
      }.margin(10)

      // 操作按钮
      Row() {
        Button('悔棋').width(120)
        Button('重新开始').width(120).margin({ left: 10 })
      }
      .width('100%')
      .justifyContent(FlexAlign.Center)
    }.padding(20)
  }
}

2. 棋子

cke_2915.png

ets/pages/gobang/02-棋子.ets

@Entry
@Component
struct Study {

  // 一个15x15的二维数组,表示棋盘,初始值为0表格空位,1表示黑棋,2表示白棋。
  @State board: number[][] = Array(15).fill(null).map(() => Array(15).fill(0))
  @State currentPlayer: number = 1 // 1表示黑棋,2表示白棋。
  @State gameOver: boolean = false

  // 下棋
  playChess(row: number, col: number) {
    // 1. 过滤数据
    if (this.gameOver || this.board[row][col] !== 0) return

    // 2. 修改状态
    // - 棋盘
    this.board[row][col] = this.currentPlayer  // 修改二维没响应式
    this.board = [...this.board]               // 直接覆盖原数据响应式生效
    // - next 棋子
    this.currentPlayer = this.currentPlayer === 1 ? 2 : 1
  }

  build() {
    Column() {
      // 游戏标题和状态显示
      Row() {
        Text('五子棋').fontSize(24).fontWeight(FontWeight.Bold)
        Text(`当前:${this.currentPlayer==1?'黑':'白'}棋`).fontSize(16).margin({ left: 20 })
      }
      .width('100%')
        .margin({ bottom: 10 })
        .justifyContent(FlexAlign.Center)

      // 棋盘
      Column() {
        // 行
        ForEach(this.board, (row: number[], rowIndex:number) => {
          Row() {
            ForEach(row, (cell: number, colIndex:number) => {
              // 列
              Column() {
                if (cell) {
                  Text().width(18).height(18).backgroundColor(cell === 1 ? '#000' : '#fff').borderRadius(9)
                }
              }
              .width(20).height(20).backgroundColor('#DEB887')
                    .border({ width: 1, color: '#999' })
                    .onClick(() => this.playChess(rowIndex, colIndex))
                    })
          }
        })
      }.margin(10)

      // 操作按钮
      Row() {
        Button('悔棋').width(120)
        Button('重新开始').width(120).margin({ left: 10 })
      }
      .width('100%')
        .justifyContent(FlexAlign.Center)
    }.padding(20)
  }
}

3. 检查胜负

cke_8770.png

ets/pages/gobang/03-检查胜负.ets

@Entry
@Component
struct Study {

  // 一个15x15的二维数组,表示棋盘,初始值为0表格空位,1表示黑棋,2表示白棋。
  @State board: number[][] = Array(15).fill(null).map(() => Array(15).fill(0))
  @State currentPlayer: number = 1 // 1表示黑棋,2表示白棋。
  @State gameOver: boolean = false

  // 下棋
  playChess(row: number, col: number) {
    // 1. 过滤数据
    if (this.gameOver || this.board[row][col] !== 0) return

    // 2. 修改状态
    // - 棋盘
    this.board[row][col] = this.currentPlayer  // 修改二维没响应式
    this.board = [...this.board]               // 直接覆盖原数据响应式生效
    // - next 棋子
    // this.currentPlayer = this.currentPlayer === 1 ? 2 : 1

    // 3. 判断下棋后结果
    if (this.checkWin(row, col)) {
      this.gameOver = true
      AlertDialog.show({ message: `${this.currentPlayer === 1 ? '黑棋' : '白棋'}获胜!` })
    } else {
      this.currentPlayer = this.currentPlayer === 1 ? 2 : 1
    }
  }

  // 检查胜负
  checkWin(row: number, col: number): boolean {
    const directions = [
      [[-1, 0], [1, 0]],   // 垂直
      [[0, -1], [0, 1]],   // 水平
      [[-1, -1], [1, 1]],  // 主对角线
      [[-1, 1], [1, -1]]   // 副对角线
    ]

    for (let direction of directions) {
      let count = 1
      for (let i = 0; i < direction.length; i++) {
        let dx = direction[i][0]
        let dy = direction[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
  }


  build() {
    Column() {
      // 游戏标题和状态显示
      Row() {
        Text('五子棋').fontSize(24).fontWeight(FontWeight.Bold)
        Text(`当前:${this.currentPlayer==1?'黑':'白'}棋`).fontSize(16).margin({ left: 20 })
      }
      .width('100%')
        .margin({ bottom: 10 })
        .justifyContent(FlexAlign.Center)

      // 棋盘
      Column() {
        // 行
        ForEach(this.board, (row: number[], rowIndex:number) => {
          Row() {
            ForEach(row, (cell: number, colIndex:number) => {
              // 列
              Column() {
                if (cell) {
                  Text().width(18).height(18).backgroundColor(cell === 1 ? '#000' : '#fff').borderRadius(9)
                }
              }
              .width(20).height(20).backgroundColor('#DEB887')
                    .border({ width: 1, color: '#999' })
                    .onClick(() => this.playChess(rowIndex, colIndex))
                    })
          }
        })
      }.margin(10)

      // 操作按钮
      Row() {
        Button('悔棋').width(120)
        Button('重新开始').width(120).margin({ left: 10 })
      }
      .width('100%')
        .justifyContent(FlexAlign.Center)
    }.padding(20)
  }
}

4. 重新开始

ets/pages/gobang/04-重新开始.ets

// 重新开发
resetGame() {
  this.board = Array(15).fill(null).map(() => Array(15).fill(0))
  this.currentPlayer = 1
  this.gameOver = false
}

5. 悔棋 选写

  • 下棋playChess时,保存row、col索引
  • 点击悔棋修改改索引状态为空格
  • 并且把currentPlayer修改

6. 面向对象封装

ets/gobang/Control.ets

 
export class Control {

  private currentPlayer: number = 1 // 1表示黑棋,2表示白棋。
  private gameOver: boolean = false

  // 下棋
  playChess(board:number[][], row: number, col: number) {
    // 1. 过滤数据
    if (this.gameOver || board[row][col] !== 0) return

    // 2. 修改状态
    // - 棋盘
    board[row][col] = this.currentPlayer
    // - next 棋子
    // this.currentPlayer = this.currentPlayer === 1 ? 2 : 1

    // 3. 判断下棋后结果
    if (this.checkWin(board, row, col)) {
      this.gameOver = true
      AlertDialog.show({ message: `${this.currentPlayer === 1 ? '黑棋' : '白棋'}获胜!` })
    } else {
      this.currentPlayer = this.currentPlayer === 1 ? 2 : 1
    }

    return [...board]
  }

  // 检查胜负
  checkWin(board:number[][], row: number, col: number): boolean {
    const directions = [
      [[-1, 0], [1, 0]],   // 垂直
      [[0, -1], [0, 1]],   // 水平
      [[-1, -1], [1, 1]],  // 主对角线
      [[-1, 1], [1, -1]]   // 副对角线
    ]

    for (let direction of directions) {
      let count = 1
      for (let i = 0; i < direction.length; i++) {
        let dx = direction[i][0]
        let dy = direction[i][1]
        let x = row + dx
        let y = col + dy
        while (x >= 0 && x < 15 && y >= 0 && y < 15 &&
          board[x][y] === this.currentPlayer) {
          count++
          x += dx
          y += dy
        }
      }
      if (count >= 5) return true
    }
    return false
  }

  // 重新开始
  resetGame():number[][] {
    this.currentPlayer = 1
    this.gameOver = false
    return this.sourceData()
  }

  // 数据源
  sourceData():number[][] {
    return Array(15).fill(null).map(() => Array(15).fill(0))
  }
}

ets/pages/gobang/05-封装.ets

import { Control } from './Control'

@Entry
@Component
struct Study {

  private control1 = new Control()
  @State board1: number[][] = this.control1.sourceData()

  private control2 = new Control()
  @State board2: number[][] = this.control2.sourceData()

  build() {
    Column() {
      // 棋盘1
      Column() {
        // 行
        ForEach(this.board1, (row: number[], rowIndex:number) => {
          Row() {
            ForEach(row, (cell: number, colIndex:number) => {
              // 列
              Column() {
                if (cell) {
                  Text().width(18).height(18).backgroundColor(cell === 1 ? '#000' : '#fff').borderRadius(9)
                }
              }
              .width(20).height(20).backgroundColor('#DEB887')
              .border({ width: 1, color: '#999' })
              .onClick(() => {
                const data = this.control1.playChess(this.board1, rowIndex, colIndex)
                if (data) this.board1 = data
              })
            })
          }
        })
        Button('重新开始').onClick(() => this.board1 = this.control1.resetGame())
      }.margin(10)

      // 棋盘2
      Column() {
        // 行
        ForEach(this.board2, (row: number[], rowIndex:number) => {
          Row() {
            ForEach(row, (cell: number, colIndex:number) => {
              // 列
              Column() {
                if (cell) {
                  Text().width(18).height(18).backgroundColor(cell === 1 ? '#000' : '#fff').borderRadius(9)
                }
              }
              .width(20).height(20).backgroundColor('#DEB887')
              .border({ width: 1, color: '#999' })
              .onClick(() => {
                const data = this.control2.playChess(this.board2, rowIndex, colIndex)
                if (data) this.board2 = data
              })
            })
          }
        })
        Button('重新开始').onClick(() => this.board2 = this.control2.resetGame())
      }.margin(10)
    }.padding(20)
  }
}

在HarmonyOS Next中实现五子棋游戏,主要使用ArkTS开发。通过Canvas组件绘制棋盘和棋子,结合Touch事件处理落子交互。利用二维数组存储棋盘状态,实现胜负判断逻辑。可封装游戏逻辑与UI组件,通过状态管理更新界面。

在HarmonyOS Next中实现五子棋小游戏,主要涉及UI绘制、触摸交互和胜负判定三个核心模块。以下是关键实现步骤:

  1. UI绘制

    • 使用Canvas组件绘制棋盘(网格)和棋子。
    • 通过CanvasRenderingContext2DstrokeLine方法绘制棋盘线,fillCircle方法绘制黑白棋子。
    • 建议将棋盘数据(二维数组)与UI分离,数组存储落子状态(如0空、1黑、2白)。
  2. 触摸交互

    • Canvas添加onTouch事件监听,通过触摸坐标计算落子位置(行列索引)。
    • 将有效落子更新到棋盘数据中,并触发UI重绘。
  3. 胜负判定

    • 每次落子后,以该位置为中心,检测横、竖、斜四个方向是否存在连续五子。
    • 使用循环遍历相邻棋子状态,匹配相同颜色即计数,达到5则结束游戏。

示例代码片段(ArkTS):

// 棋盘数据
private board: number[][] = Array.from({ length: 15 }, () => new Array(15).fill(0));

// 绘制棋盘
private drawBoard(context: CanvasRenderingContext2D) {
  // 绘制网格线
  for (let i = 0; i <= 15; i++) {
    context.strokeLine(...计算坐标);
  }
  // 绘制已有棋子
  this.board.forEach((row, i) => {
    row.forEach((cell, j) => {
      if (cell !== 0) {
        context.fillCircle(...计算坐标, cell === 1 ? 'black' : 'white');
      }
    });
  });
}

// 触摸事件处理
onTouch(event: TouchEvent) {
  const { x, y } = 转换坐标(event);
  const row = Math.floor(y / 格子尺寸);
  const col = Math.floor(x / 格子尺寸);
  if (this.board[row][col] === 0) {
    this.board[row][col] = 当前玩家;
    this.checkWin(row, col);
  }
}

// 胜负判定
private checkWin(row: number, col: number) {
  const directions = [[1,0], [0,1], [1,1], [1,-1]];
  for (const [dx, dy] of directions) {
    let count = 1;
    // 正反方向计数
    count += this.countDirection(row, col, dx, dy);
    count += this.countDirection(row, col, -dx, -dy);
    if (count >= 5) {
      // 游戏结束处理
    }
  }
}

优化建议:

  • 使用Stack布局叠加Canvas和提示层。
  • 通过@State装饰器管理游戏状态(当前玩家、胜负状态)。
  • 可添加悔棋功能(用栈记录落子历史)。

注意:需在module.json5中声明ohos.permission.SYSTEM_FLOAT_WINDOW权限(若需要悬浮窗功能)。实际开发中应根据棋盘尺寸动态计算坐标,并处理触摸事件防抖。

回到顶部