鸿蒙NEXT小游戏开发:区字棋

1. 引言

"区字棋"是一款简单而有趣的棋类游戏,玩家通过移动棋子,连接棋子之间的线条,最终实现胜利条件。游戏规则简单明了,但需要一定的策略和思考。

2. 环境准备

电脑系统:windows 10

工程版本:API 12

真机:Mate 60 Pro

语言:ArkTS、ArkUI

3. 技术实现

在本案例中,我们使用了鸿蒙系统提供的UI框架和特性,通过自定义棋子类Cell和连接关系类Connection,实现了棋子之间的移动和连接。同时,利用鸿蒙系统的动画功能,为棋子移动添加了流畅的动画效果,提升了用户体验。

4. 游戏流程

游戏加载时,初始化棋盘上的棋子和连接关系。

玩家点击棋子进行移动,判断移动是否合法,若合法则执行移动动画。

移动完成后,切换玩家回合,检查游戏是否结束。

若游戏未结束,AI自动走法,选择最佳移动策略。

游戏结束时,显示胜利者信息,并提供重新开始按钮。

5. 完整代码

kotlin 复制代码
// 导入提示框操作模块
import { promptAction } from '@kit.ArkUI';

// 使用框架提供的特性,如属性追踪等
@ObservedV2
class Cell {
  // 定义棋子类型,0为空,1为黑方,2为白方
  @Trace user: number = 0;
  // 棋子的名字,例如"A"、"B"
  name: string = "";
  // 棋子的位置坐标
  x: number = 0;
  y: number = 0;
  // 棋子的尺寸
  width: number = 100;
  height: number = 100;

  // 构造函数初始化棋子的状态
  constructor(name: string, x: number, y: number, user: number) {
    this.user = user; // 设置棋子的用户类型
    this.name = name; // 设置棋子的名字
    this.x = x; // 设置棋子的X坐标
    this.y = y; // 设置棋子的Y坐标
  }

  // 动画中的X轴偏移量
  @Trace animX: number = 0;
  // 动画中的Y轴偏移量
  @Trace animY: number = 0;

  // 获取棋子中心点的X坐标
  getCenterX() {
    return this.x - this.width / 2; // 计算并返回中心X坐标
  }

  // 获取棋子中心点的Y坐标
  getCenterY() {
    return this.y - this.height / 2; // 计算并返回中心Y坐标
  }

  // 执行棋子移动动画
  moveAnimation(animationTime: number, toCell: Cell, callback?: () => void) {
    // 设置动画参数
    animateToImmediately({
      duration: animationTime, // 动画持续时间
      iterations: 1, // 动画迭代次数
      curve: Curve.Linear, // 动画曲线类型
      // 在动画完成后的回调函数
      onFinish: () => {
        animateToImmediately({
          duration: 0, // 立即完成
          iterations: 1,
          curve: Curve.Linear,
          // 在动画完成后的内部回调函数
          onFinish: () => {
            if (callback) {
              callback(); // 调用外部回调函数
            }
          }
        }, () => {
          // 重置动画偏移量
          this.animX = 0; // X轴偏移量重置
          this.animY = 0; // Y轴偏移量重置
          // 交换棋子的位置信息
          let temp = this.user; // 临时存储当前棋子的用户
          this.user = toCell.user; // 将目标棋子的用户赋值给当前棋子
          toCell.user = temp; // 将临时存储的用户赋值给目标棋子
        });
      }
    }, () => {
      // 设置动画的目标偏移量
      this.animX = toCell.x - this.x; // 计算X轴目标偏移量
      this.animY = toCell.y - this.y; // 计算Y轴目标偏移量
    });
  }
}

// 定义棋子之间的连接关系
class Connection {
  // 开始和结束节点的名字
  startName: string;
  endName: string;
  // 开始和结束节点的坐标
  startX: number;
  startY: number;
  endX: number;
  endY: number;

  // 构造函数初始化连接关系
  constructor(start: Cell, end: Cell) {
    this.startName = start.name; // 设置连接的起始棋子名字
    this.endName = end.name; // 设置连接的结束棋子名字
    this.startX = start.x; // 设置起始棋子的X坐标
    this.startY = start.y; // 设置起始棋子的Y坐标
    this.endX = end.x; // 设置结束棋子的X坐标
    this.endY = end.y; // 设置结束棋子的Y坐标
  }
}

// 定义游戏结构
@Entry
@Component
struct TwoSonChessGame {
  // 游戏状态标志,用于控制动画执行
  @State isAnimationRunning: boolean = false;
  // 当前棋盘上的所有棋子
  @State cells: Cell[] = [];
  // 当前棋盘上的所有连接关系
  @State connections: Connection[] = [];
  // 当前玩家,1代表黑方,2代表白方
  @State currentPlayer: number = 1;

  // 游戏加载时初始化棋盘
  aboutToAppear(): void {
    // 创建五个棋子
    const cellA = new Cell("A", 180, 180, 2); // 创建棋子A
    const cellB = new Cell("B", 540, 180, 1); // 创建棋子B
    const cellC = new Cell("C", 360, 360, 0); // 创建棋子C
    const cellD = new Cell("D", 180, 540, 1); // 创建棋子D
    const cellE = new Cell("E", 540, 540, 2); // 创建棋子E
    // 将创建的棋子添加到棋盘上
    this.cells.push(cellA, cellB, cellC, cellD, cellE);
    // 初始化棋子间的连接关系
    this.connections.push(new Connection(cellA, cellB)); // A与B连接
    this.connections.push(new Connection(cellA, cellC)); // A与C连接
    this.connections.push(new Connection(cellA, cellD)); // A与D连接
    this.connections.push(new Connection(cellB, cellC)); // B与C连接
    this.connections.push(new Connection(cellC, cellD)); // C与D连接
    this.connections.push(new Connection(cellC, cellE)); // C与E连接
    this.connections.push(new Connection(cellD, cellE)); // D与E连接
  }

  // 重置游戏状态
  resetGame() {
    this.currentPlayer = 1; // 重置当前玩家为黑方
    this.cells[0].user = 2; // 设置棋子A为白方
    this.cells[1].user = 1; // 设置棋子B为黑方
    this.cells[2].user = 0; // 设置棋子C为空
    this.cells[3].user = 1; // 设置棋子D为黑方
    this.cells[4].user = 2; // 设置棋子E为白方
  }

  // 处理棋子移动
  move(cell: Cell) {
    // 判断棋子是否可移动
    if (this.isCellValid(cell)) {
      let targetIndex = this.checkValidMove(cell); // 检查目标位置是否合法
      // 如果目标位置合法,则启动动画
      if (targetIndex !== -1) {
        this.isAnimationRunning = true; // 设置动画正在运行
        cell.moveAnimation(300, this.cells[targetIndex], () => {
          this.isAnimationRunning = false; // 动画完成后设置为不运行
          this.moveCompleted(); // 调用移动完成处理
        });
      } else {
        console.info(`当前位置无法移动`); // 输出无法移动的信息
      }
    }
  }

  // 移动完成后处理
  moveCompleted() {
    // 切换玩家
    this.currentPlayer = this.currentPlayer === 1 ? 2 : 1; // 切换当前玩家
    // 检查游戏是否结束
    if (this.isGameOver()) {
      let winner = this.currentPlayer === 1 ? '白棋赢了' : '黑棋赢了'; // 判断赢家
      console.info(`${winner}`); // 输出赢家信息
      // 显示游戏结束提示
      promptAction.showDialog({
        title: '游戏结束', // 提示框标题
        message: `${winner}`, // 提示框内容
        buttons: [{ text: '重新开始', color: '#ffa500' }] // 提示框按钮
      }).then(() => {
        this.resetGame(); // 重新开始游戏
      });
    } else {
      // 如果是白方回合,则进行AI自动走法
      if (this.currentPlayer === 2) {
        this.aiMove(); // 调用AI走法
      }
    }
  }

  // AI走法
  aiMove() {
    let whiteCells = this.cells.filter(cell => cell.user === 2 && this.checkValidMove(cell) !== -1); // 获取可移动的白棋
    // 根据当前情况选择最优走法
    if (whiteCells.length === 1) {
      this.move(whiteCells[0]); // 只有一个可移动棋子,直接移动
    } else if (whiteCells.length === 2) {
      let moveIndex = this.chooseBestMove(whiteCells); // 选择最佳走法
      this.move(whiteCells[moveIndex]); // 移动最佳棋子
    }
  }

  // 选择最佳走法
  chooseBestMove(whiteCells: Cell[]): number {
    let emptyIndex = this.cells.findIndex(cell => cell.user === 0); // 找到空棋子的位置 
    let bestMoveIndex = -1; // 初始化最佳移动索引
    for (let i = 0; i < whiteCells.length; i++) {
      let tempUser = whiteCells[i].user; // 临时存储当前白棋的用户
      whiteCells[i].user = this.cells[emptyIndex].user; // 将空位置的用户赋值给当前白棋
      this.cells[emptyIndex].user = tempUser; // 将当前白棋的用户赋值给空位置
      this.currentPlayer = 1; // 设置当前玩家为黑方
      let isGameOver = this.isGameOver(); // 检查游戏是否结束
      tempUser = whiteCells[i].user; // 恢复当前白棋的用户
      whiteCells[i].user = this.cells[emptyIndex].user; // 恢复空位置的用户
      this.cells[emptyIndex].user = tempUser; // 恢复空位置的用户
      this.currentPlayer = 2; // 恢复当前玩家为白方
      if (isGameOver) {
        bestMoveIndex = i; // 如果游戏结束,记录最佳移动索引
        break; // 退出循环
      }
    }
    if (bestMoveIndex === -1) {
      bestMoveIndex = Math.floor(Math.random() * 2); // 如果没有找到最佳移动,随机选择一个
    }
    return bestMoveIndex; // 返回最佳移动索引
  }

  // 判断棋子是否有效
  isCellValid(cell: Cell): boolean {
    return (cell.user === 1 && this.currentPlayer === 1) || (cell.user === 2 && this.currentPlayer === 2); // 判断棋子是否属于当前玩家
  }

  // 判断游戏是否结束
  isGameOver(): boolean {
    for (let i = 0; i < this.cells.length; i++) {
      if (this.currentPlayer == this.cells[i].user && this.checkValidMove(this.cells[i]) != -1) {
        return false; // 如果当前玩家还有可移动的棋子,游戏未结束
      }
    }
    return true; // 否则游戏结束
  }

  // 检查是否为有效走法
  checkValidMove(cell: Cell): number {
    for (let i = 0; i < this.connections.length; i++) {
      if (cell.name === this.connections[i].startName) { // 如果棋子是连接的起始点
        for (let j = 0; j < this.cells.length; j++) {
          if (this.cells[j].name === this.connections[i].endName && this.cells[j].user === 0) {
            return j; // 返回目标位置的索引
          }
        }
      } else if (cell.name === this.connections[i].endName) { // 如果棋子是连接的结束点
        for (let j = 0; j < this.cells.length; j++) {
          if (this.cells[j].name === this.connections[i].startName && this.cells[j].user === 0) {
            return j; // 返回目标位置的索引
          }
        }
      }
    }
    return -1; // 如果没有找到有效走法,返回-1
  }

  // 构建棋盘界面
  build() {
    Column({ space: 10 }) { // 创建一个垂直排列的列
      Stack() { // 创建一个堆叠布局
        ForEach(this.connections, (connection: Connection, _index) => { // 遍历所有连接关系
          Line() // 绘制连接线
            .width(5) // 设置线宽
            .height(5) // 设置线高
            .startPoint([`${connection.startX}lpx`, `${connection.startY}lpx`]) // 设置起始点
            .endPoint([`${connection.endX}lpx`, `${connection.endY}lpx`]) // 设置结束点
            .stroke(Color.Black) // 设置线条颜色
            .fill(Color.Green); // 设置填充颜色
        });

        ForEach(this.cells, (cell: Cell, _index) => { // 遍历所有棋子
          Text() // 绘制棋子
            .width(`${cell.width}lpx`) // 设置棋子宽度
            .height(`${cell.height}lpx`) // 设置棋子高度
            .margin({ left: `${cell.getCenterX()}lpx`, top: `${cell.getCenterY()}lpx` }) // 设置棋子位置
            .translate({ x: `${cell.animX}lpx`, y: `${cell.animY}lpx` }) // 设置动画偏移
            .backgroundColor(cell.user === 0 ? Color.Transparent : // 设置背景颜色
              (cell.user === 1 ? Color.Black : Color.White)) // 根据用户类型设置颜色
            .borderRadius('50%') // 设置圆角
            .onClick(() => { // 设置点击事件
              if (this.isAnimationRunning) { // 如果动画正在运行
                console.info(`动画执行中`); // 输出信息
                return; // 退出
              }
              this.move(cell); // 调用移动函数
            });
        });
      }
      .align(Alignment.TopStart) // 设置对齐方式
      .width('720lpx') // 设置宽度
      .height('720lpx') // 设置高度
      .backgroundColor(Color.Orange); // 设置背景颜色

      Button('重新开始').onClick(() => { // 创建重新开始按钮
        if (this.isAnimationRunning) { // 如果动画正在运行
          console.info(`动画执行中`); // 输出信息
          return; // 退出
        }
        this.resetGame(); // 调用重置游戏函数
      });
    }
  }
}

相关推荐
zhanshuo4 小时前
在鸿蒙里优雅地处理网络错误:从 Demo 到实战案例
harmonyos
zhanshuo4 小时前
在鸿蒙中实现深色/浅色模式切换:从原理到可运行 Demo
harmonyos
whysqwhw10 小时前
鸿蒙分布式投屏
harmonyos
whysqwhw11 小时前
鸿蒙AVSession Kit
harmonyos
whysqwhw13 小时前
鸿蒙各种生命周期
harmonyos
whysqwhw14 小时前
鸿蒙音频编码
harmonyos
whysqwhw14 小时前
鸿蒙音频解码
harmonyos
whysqwhw14 小时前
鸿蒙视频解码
harmonyos
whysqwhw14 小时前
鸿蒙视频编码
harmonyos
ajassi200014 小时前
开源 Arkts 鸿蒙应用 开发(十八)通讯--Ble低功耗蓝牙服务器
华为·开源·harmonyos