HarmonyOS游戏开发入门:用ArkTS打造经典五子棋

✨家人们记得点个账号关注,会持续发布大前端领域技术文章💕 🍃

引言

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

仓库地址(小游戏/五子棋目录)https://gitee.com/harmonyos-projects/codelabs

1. 棋盘

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

less 复制代码
@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. 棋子

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

less 复制代码
@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. 检查胜负

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

less 复制代码
@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

javascript 复制代码
// 重新开发
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

typescript 复制代码
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

scss 复制代码
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)
  }
}

鸿蒙开发者班级

✨家人们点个账号关注,会持续发布大前端领域技术文章💕 🍃

✨家人们点个账号关注,会持续发布大前端领域技术文章💕 🍃

✨家人们点个账号关注,会持续发布大前端领域技术文章💕 🍃

^_^ 点关注、不迷路、主播带你学技术 (๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤

相关推荐
Magic-ZYJ8 小时前
HarmonyOS Release 构建安全:ArkGuard 混淆、签名配置与秘密管理
安全·华为·harmonyos·移动应用开发·心晴手记
见山是山-见水是水10 小时前
拆解HarmonyOS网络请求适配:原生鸿蒙页面的实现路径与调试方法
华为·harmonyos
m0_7496902310 小时前
【寻迹校园 HarmonyOS NEXT 实战 36】深色模式不是反色:用语义 Token 管理品牌色与状态色
harmonyos·arkts·深色模式·ui设计·主题设计
lqj_本人11 小时前
鸿蒙跨端实战_QT后台切换导致OpenGLES上下文丢失:黑屏自救指南
qt·华为·harmonyos
OH_TPC13 小时前
HarmonyOS APP开发---“数据眼“健康追踪App,需要用到这个库
华为·harmonyos·鸿蒙
lilian23313 小时前
Harmony os 技术实战|拼豆制图49:五组分类视觉规则如何收口成一张配置表
华为·harmonyos
2501_9197490314 小时前
华为鸿蒙免费决策APP—小羊挠头
华为·harmonyos·鸿蒙
m0_7496902314 小时前
【寻迹校园 HarmonyOS NEXT 实战 37】一套 ArkUI 适配四档宽度:sm、md、lg、xl 响应式 Shell 实战
华为·harmonyos·arkui·navigation·响应式布局·多设备适配
RisunJan16 小时前
HarmonyOS 架构精读:从系统分层到应用模型
华为·架构·harmonyos
见山是山-见水是水16 小时前
从需求到页面:鸿蒙原生 Ability 与 ArkTS 交互的 ArkTS 原生实现
华为·harmonyos