鸿蒙NEXT开发案例:围住神经猫

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

鸿蒙NEXT开发案例:围住神经猫-鸿蒙开发者社区

【引言】
一个基于网格的游戏环境,其中包含了一个名为“猫咪”的角色。游戏中使用了一个9x9的网格,每个单元格可以是空闲的(值为0)或者被设置为墙壁(值为1)。游戏的目标是让“猫咪”在一个充满墙壁的迷宫中移动,避免被墙壁围困。
【环境准备】
电脑系统:windows 10
开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806
工程版本:API 12
真机:Mate 60 Pro
语言:ArkTS、ArkUI
【主要功能】
• 初始化棋盘并设置每个单元格的邻居关系。
• 开始游戏时随机放置墙壁,并将猫咪放置在指定位置。
• 当猫咪尝试移动时,寻找所有可移动的空邻居,并根据一定的策略选择下一步移动的方向。
• 计算启发式值(使用曼哈顿距离)来帮助决定移动方向。
• 构建用户界面,显示背景和猫咪的位置,并允许玩家通过点击放置墙壁并触发猫咪的移动。
【算法分析】

  1. 广度优先搜索(BFS):在 findNeighbors 方法中,通过遍历当前单元格的邻居来获取周围非墙壁且可以移动的单元格集合。这类似于广度优先搜索的思想,逐层遍历邻居单元格。
findNeighbors(cell: Cell): Cell[] {
    let neighbors: Cell[] = [];
    // 检查当前单元格的六个方向邻居,将非墙壁且可以移动的单元格加入集合
    // ...
    return neighbors;
}
  1. 启发式搜索:在 selectNextMove 方法中,根据一定的启发式函数选择下一个移动位置,以确保小猫朝着离边界最近的方向移动。这种启发式搜索可以帮助小猫更智能地选择下一步的移动位置。
selectNextMove(emptyNeighbors: Cell[]): Cell {
    // 根据启发式函数选择最优的移动位置
    // ...
    return closestToEdge || emptyNeighbors[0];
}
  1. 曼哈顿距离计算:在 computeHeuristic 方法中,使用曼哈顿距离计算启发式函数的值,以评估当前单元格到边界的距离。曼哈顿距离是在网格上两点之间的距离,沿着网格的边缘移动。
computeHeuristic(cell: Cell): number {
    // 计算曼哈顿距离作为启发式函数的值
    // ...
    return minDistanceX + minDistanceY;
}

【完整代码】

import { promptAction } from '@kit.ArkUI' // 导入提示框组件

@ObservedV2 // 观察者装饰器,监控状态变化
class Cell {
  @Trace value: number = 0; // 单元格值,0表示空位,1表示墙壁
  x: number = 0; // 单元格的x坐标
  y: number = 0; // 单元格的y坐标
  leftNeighborIndex: number | undefined = undefined; // 左邻居索引
  rightNeighborIndex: number | undefined = undefined; // 右邻居索引
  leftTopNeighborIndex: number | undefined = undefined; // 左上邻居索引
  rightTopNeighborIndex: number | undefined = undefined; // 右上邻居索引
  leftBottomNeighborIndex: number | undefined = undefined; // 左下邻居索引
  rightBottomNeighborIndex: number | undefined = undefined; // 右下邻居索引

  constructor(value: number, x: number, y: number) {
    this.value = value; // 初始化单元格值
    this.x = x; // 初始化x坐标
    this.y = y; // 初始化y坐标
  }
}

@Entry // 入口装饰器,标记组件的入口
@Component // 组件装饰器
struct Index {
  @State gridCells: Cell[] = []; // 保存所有单元格的数组
  cellWidth: number = 70; // 单元格宽度
  borderPieceWidth: number = -10; // 边缘件宽度
  pieceSize: number = 65; // 件大小
  @State catPositionIndex: number = 40; // 小猫位置索引

  aboutToAppear(): void {
    this.initializeBoard(); // 初始化棋盘
    this.startGame(); // 开始游戏
  }

  findNeighbors(cell: Cell): Cell[] { // 获取当前单元格周围的可移动邻居
    let neighbors: Cell[] = []; // 存储邻居单元格
    // 检查每个方向的邻居是否可移动
    if (cell.leftNeighborIndex !== undefined && this.gridCells[cell.leftNeighborIndex].value === 0) {
      neighbors.push(this.gridCells[cell.leftNeighborIndex]); // 左邻居
    }
    if (cell.rightNeighborIndex !== undefined && this.gridCells[cell.rightNeighborIndex].value === 0) {
      neighbors.push(this.gridCells[cell.rightNeighborIndex]); // 右邻居
    }
    if (cell.leftTopNeighborIndex !== undefined && this.gridCells[cell.leftTopNeighborIndex].value === 0) {
      neighbors.push(this.gridCells[cell.leftTopNeighborIndex]); // 左上邻居
    }
    if (cell.rightTopNeighborIndex !== undefined && this.gridCells[cell.rightTopNeighborIndex].value === 0) {
      neighbors.push(this.gridCells[cell.rightTopNeighborIndex]); // 右上邻居
    }
    if (cell.leftBottomNeighborIndex !== undefined && this.gridCells[cell.leftBottomNeighborIndex].value === 0) {
      neighbors.push(this.gridCells[cell.leftBottomNeighborIndex]); // 左下邻居
    }
    if (cell.rightBottomNeighborIndex !== undefined && this.gridCells[cell.rightBottomNeighborIndex].value === 0) {
      neighbors.push(this.gridCells[cell.rightBottomNeighborIndex]); // 右下邻居
    }
    return neighbors; // 返回可移动邻居
  }

  initializeBoard() {
    this.gridCells = []; // 初始化单元格数组
    // 创建9x9的单元格
    for (let rowIndex = 0; rowIndex < 9; rowIndex++) {
      for (let columnIndex = 0; columnIndex < 9; columnIndex++) {
        this.gridCells.push(new Cell(0, rowIndex, columnIndex)); // 添加新单元格
      }
    }
    // 设置每个单元格的邻居索引
    for (let rowIndex = 0; rowIndex < 9; rowIndex++) {
      for (let columnIndex = 0; columnIndex < 9; columnIndex++) {
        let cellIndex: number = rowIndex * 9 + columnIndex; // 计算单元格索引
        const row = rowIndex; // 当前行
        const column = columnIndex; // 当前列
        let cell = this.gridCells[cellIndex]; // 当前单元格
        // 检查六个方向的邻居
        if (column > 0) {
          cell.leftNeighborIndex = cellIndex - 1; // 左邻居
        }
        if (column < 8) {
          cell.rightNeighborIndex = cellIndex + 1; // 右邻居
        }
        // 根据行数的奇偶性设置邻居
        if (row % 2 === 1) {
          let leftTopIndex = cellIndex - 9; // 左上邻居索引
          if (leftTopIndex >= 0) {
            cell.leftTopNeighborIndex = leftTopIndex; // 设置左上邻居
          }
          let leftBottomIndex = cellIndex + 9; // 左下邻居索引
          if (leftBottomIndex < this.gridCells.length) {
            cell.leftBottomNeighborIndex = leftBottomIndex; // 设置左下邻居
          }
          let rightTopIndex = cellIndex - 8; // 右上邻居索引
          if (rightTopIndex >= 0) {
            cell.rightTopNeighborIndex = rightTopIndex; // 设置右上邻居
          }
          let rightBottomIndex = cellIndex + 10; // 右下邻居索引
          if (rightBottomIndex < this.gridCells.length) {
            cell.rightBottomNeighborIndex = rightBottomIndex; // 设置右下邻居
          }
        } else {
          let leftTopIndex = cellIndex - 10; // 左上邻居索引
          if (leftTopIndex >= 0) {
            cell.leftTopNeighborIndex = leftTopIndex; // 设置左上邻居
          }
          let leftBottomIndex = cellIndex + 8; // 左下邻居索引
          if (leftBottomIndex < this.gridCells.length) {
            cell.leftBottomNeighborIndex = leftBottomIndex; // 设置左下邻居
          }
          let rightTopIndex = cellIndex - 9; // 右上邻居索引
          if (rightTopIndex >= 0) {
            cell.rightTopNeighborIndex = rightTopIndex; // 设置右上邻居
          }
          let rightBottomIndex = cellIndex + 9; // 右下邻居索引
          if (rightBottomIndex < this.gridCells.length) {
            cell.rightBottomNeighborIndex = rightBottomIndex; // 设置右下邻居
          }
        }
      }
    }
  }

  startGame() {
    let availableIndices: number[] = []; // 可用索引数组
    for (let i = 0; i < 81; i++) {
      this.gridCells[i].value = 0; // 初始化单元格为0
      if (i === 39 || i === 40 || i === 41) { // 排除中心点及左右两点
        continue; // 跳过
      }
      availableIndices.push(i); // 添加可用索引
    }
    // 随机生成墙壁
    for (let i = 0; i < 8; i++) {
      let randomIndex = Math.floor(Math.random() * availableIndices.length); // 随机索引
      let randomNeighbor = availableIndices[randomIndex]; // 随机邻居
      this.gridCells[randomNeighbor].value = 1; // 设置为墙壁
      availableIndices.splice(randomIndex, 1); // 移除已使用的索引
    }
    this.catPositionIndex = 40; // 重置小猫位置
  }

  moveCat(): void {
    let neighbors = this.findNeighbors(this.gridCells[this.catPositionIndex]); // 找到小猫的邻居
    let emptyNeighbors: Cell[] = neighbors.filter(neighbor => neighbor.value === 0); // 仅保留空位邻居
    if (emptyNeighbors.length === 0) {
      console.log('神经猫被围住了,游戏结束!'); // 游戏结束提示
      promptAction.showDialog({
        title: '游戏胜利!', // 对话框标题
        buttons: [{ text: '重新开始', color: '#ffa500' }] // 对话框按钮
      }).then(() => { // 对话框关闭后执行
        this.startGame(); // 重新开始游戏
      });
    } else {
      // 根据一定策略选择下一个移动位置
      let nextMove = this.selectNextMove(emptyNeighbors); // 选择下一个移动位置
      // 清除原来的位置,并且更新猫的位置和状态
      this.catPositionIndex = nextMove.x * 9 + nextMove.y; // 更新小猫位置索引
      // 检查小猫是否移动到边界
      if (nextMove.x === 0 || nextMove.x === 8 || nextMove.y === 0 || nextMove.y === 8) {
        console.log('小猫移动到了边界,游戏结束!'); // 游戏
        // 边界提示
        promptAction.showDialog({
          title: '游戏失败!', // 对话框标题
          buttons: [{ text: '重新开始', color: '#ffa500' }] // 对话框按钮
        }).then(() => { // 对话框关闭后执行
          this.startGame(); // 重新开始游戏
        });
      }
    }
  }

  selectNextMove(emptyNeighbors: Cell[]): Cell {
    let closestToEdge: Cell | null = null; // 最靠近边界的邻居
    let minDistanceToEdge: number = Number.MAX_VALUE; // 到边界的最小距离
    for (let neighbor of emptyNeighbors) {
      let distanceToEdge = Math.min(neighbor.x, 8 - neighbor.x, neighbor.y, 8 - neighbor.y); // 计算到边界的距离
      if (distanceToEdge < minDistanceToEdge) {
        minDistanceToEdge = distanceToEdge; // 更新最小距离
        closestToEdge = neighbor; // 更新最靠近边界的邻居
      } else if (distanceToEdge === minDistanceToEdge) {
        // 如果距离相同,根据启发式函数选择更靠近边界的邻居
        if (this.computeHeuristic(neighbor) < this.computeHeuristic(closestToEdge as Cell)) {
          closestToEdge = neighbor; // 根据启发式函数选择更靠近边界的邻居
        }
      }
    }
    return closestToEdge || emptyNeighbors[0]; // 返回最靠近边界的一个空位邻居,如果没有则返回第一个空位邻居
  }

  computeHeuristic(cell: Cell): number {
    // 曼哈顿距离
    let minDistanceX = Math.min(...[0, 8].map(x => Math.abs(cell.x - x))); // x轴最小距离
    let minDistanceY = Math.min(...[0, 8].map(y => Math.abs(cell.y - y))); // y轴最小距离
    return minDistanceX + minDistanceY; // 返回曼哈顿距离
  }

  build() {
    Column({ space: 20 }) { // 垂直布局
      Stack() { // 堆叠布局
        Flex({ wrap: FlexWrap.Wrap }) { // 弹性布局,自动换行
          // 背景单元格
          ForEach(this.gridCells, (item: Cell, index: number) => {
            Stack() { // 堆叠布局
              Text().width(`${this.pieceSize}lpx`).height(`${this.pieceSize}lpx`)
                .backgroundColor(item.value == 0 ? "#b4b4b4" : "#ff8d5a").borderRadius('50%') // 设置背景颜色
            }.width(`${this.cellWidth}lpx`).height(`${this.cellWidth}lpx`)
            .margin({
              left: `${(index + 9) % 18 == 0 ? this.cellWidth / 2 : 0}lpx`, // 设置左边距
              top: `${this.borderPieceWidth}lpx` // 设置上边距
            })
          })
        }.width(`${this.cellWidth * 9 + this.cellWidth / 2}lpx`) // 设置宽度

        Flex({ wrap: FlexWrap.Wrap }) { // 弹性布局,自动换行
          // 小猫
          ForEach(this.gridCells, (item: Cell, index: number) => {
            Stack() { // 堆叠布局
              Text('猫') // 文本显示"猫"
                .width(`${this.pieceSize}lpx`)
                .height(`${this.pieceSize}lpx`)
                .fontColor(Color.White) // 设置字体颜色
                .fontSize(`${this.cellWidth / 2}lpx`) // 设置字体大小
                .textAlign(TextAlign.Center) // 文本居中
                .backgroundColor(Color.Black) // 设置背景颜色
                .borderRadius('50%') // 设置圆角
                .visibility(this.catPositionIndex == index ? Visibility.Visible : Visibility.None) // 根据小猫位置显示或隐藏
            }
            .width(`${this.cellWidth}lpx`)
            .height(`${this.cellWidth}lpx`)
            .margin({
              left: `${(index + 9) % 18 == 0 ? this.cellWidth / 2 : 0}lpx`, // 设置左边距
              top: `${this.borderPieceWidth}lpx` // 设置上边距
            })
            .onClick(() => {
              if (item.value == 0 && index != this.catPositionIndex) {
                item.value = 1; // 放置墙壁
                this.moveCat(); // 移动神经猫
              }
            })
            .animation({ duration: 150 }) // 设置动画效果

          })
        }.width(`${this.cellWidth * 9 + this.cellWidth / 2}lpx`) // 设置宽度 
      }

      Button('重新开始').clickEffect({ scale: 0.5, level: ClickEffectLevel.LIGHT }).onClick(() => {
        this.startGame() // 点击按钮重新开始游戏
      })
    }.height('100%').width('100%') // 设置高度和宽度
    .backgroundColor("#666666") // 设置背景颜色
    .padding({ top: 20 }) // 设置内边距
  }
}

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