鸿蒙NEXT开发案例:区字棋

zhongcx
发布于 2024-12-1 09:24
浏览
0收藏

鸿蒙NEXT开发案例:区字棋-鸿蒙开发者社区

【引言】
"区字棋"是一款简单而有趣的棋类游戏,玩家通过移动棋子,连接棋子之间的线条,最终实现胜利条件。游戏规则简单明了,但需要一定的策略和思考。
【环境准备】
电脑系统:windows 10
开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806
工程版本:API 12
真机:Mate 60 Pro
语言:ArkTS、ArkUI
【技术实现】
在本案例中,我们使用了鸿蒙系统提供的UI框架和特性,通过自定义棋子类Cell和连接关系类Connection,实现了棋子之间的移动和连接。同时,利用鸿蒙系统的动画功能,为棋子移动添加了流畅的动画效果,提升了用户体验。
【游戏流程】
游戏加载时,初始化棋盘上的棋子和连接关系。
玩家点击棋子进行移动,判断移动是否合法,若合法则执行移动动画。
移动完成后,切换玩家回合,检查游戏是否结束。
若游戏未结束,AI自动走法,选择最佳移动策略。
游戏结束时,显示胜利者信息,并提供重新开始按钮。
【结语】
【完整代码】

// 导入提示框操作模块
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(); // 调用重置游戏函数
      });
    }
  }
}

分类
标签
收藏
回复
举报
回复
    相关推荐