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

devtools/2024/10/22 14:33:31/

一个基于网格的游戏环境,其中包含了一个名为“猫咪”的角色。游戏中使用了一个9x9的网格,每个单元格可以是空闲的(值为0)或者被设置为墙壁(值为1)。游戏的目标是让“猫咪”在一个充满墙壁的迷宫中移动,避免被墙壁围困。

【主要功能】

• 初始化棋盘并设置每个单元格的邻居关系。

• 开始游戏时随机放置墙壁,并将猫咪放置在指定位置。

• 当猫咪尝试移动时,寻找所有可移动的空邻居,并根据一定的策略选择下一步移动的方向。

• 计算启发式值(使用曼哈顿距离)来帮助决定移动方向。

• 构建用户界面,显示背景和猫咪的位置,并允许玩家通过点击放置墙壁并触发猫咪的移动。

【开发环境】

开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.814

工程API版本:12

【算法分析】

1. 广度优先搜索(BFS):在 findNeighbors 方法中,通过遍历当前单元格的邻居来获取周围非墙壁且可以移动的单元格集合。这类似于广度优先搜索的思想,逐层遍历邻居单元格。

findNeighbors(cell: Cell): Cell[] {let neighbors: Cell[] = [];// 检查当前单元格的六个方向邻居,将非墙壁且可以移动的单元格加入集合// ...return neighbors;
}

2. 启发式搜索:在 selectNextMove 方法中,根据一定的启发式函数选择下一个移动位置,以确保小猫朝着离边界最近的方向移动。这种启发式搜索可以帮助小猫更智能地选择下一步的移动位置。

selectNextMove(emptyNeighbors: Cell[]): Cell {// 根据启发式函数选择最优的移动位置// ...return closestToEdge || emptyNeighbors[0];
}

3. 曼哈顿距离计算:在 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;y: number = 0;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;this.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[] { // 获取当前单元格周围非 undefined 且可以移动的集合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 = [];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;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)));let minDistanceY = Math.min(...[0, 8].map(y => Math.abs(cell.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 })}
}


http://www.ppmy.cn/devtools/127858.html

相关文章

R语言机器学习算法实战系列(九)决策树分类算法 (Decision Trees Classifier)

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍教程下载数据加载R包导入数据数据预处理数据描述数据切割调节参数构建模型模型的决策树预测测试数据评估模型模型准确性混淆矩阵模型评估指标ROC CurvePRC Curve特征的重要性保存模…

爬虫逆向学习(十一):实战分享反爬机制快速定位与破解

此分享只用于学习用途&#xff0c;不作商业用途&#xff0c;若有冒犯&#xff0c;请联系处理 反爬前置信息 站点&#xff1a;aHR0cHM6Ly9jenFqZC5qc3p3ZncuZ292LmNuL2tkY3B1YXBvcnRhbC8jLw 接口&#xff1a;/kong/InnerGateway/kdcpua/lib/project/publicity 反爬机制定位 …

Mybatis--简略2

mybatis常用功能设置 开启日志 在<settings>标签下加入命令 <setting name"logImpl" value"STDOUT_LOGGING"/> 开启java属性驼峰格式与数据库下划线连接格式封装自动转换设置 <setting name"mapUnderscoreToCamelCase" value&…

vue与u3d互调

vue与u3d互调 u3d配置 给前端发送数据的方法中使用下面的方法给Window注册个事件 // eventname 方法名: 随意取, 前端用这个名判断是获取哪个事件的数据 // data 给vue 传递的参数 window.ReportReady(UTF8ToString(eventname), UTF8ToString(data));vue配置 将u3d导出好…

微信小程序实现计数器,微信小程序震动,微信小程序提示音

一、效果 通过微信小程序实现简单的计数器功能&#xff0c;点一下1的同时震动及发出提示音。 预览效果&#xff1a; 扫码进入小程序体验&#xff0c;或者微信搜索“即享工具箱”进入到“计数器”模块体验。 二、编码 wxml: <!--pages/camcelToUnderlane/count.wxml--&g…

前端学习---(1)HTML

一个后端狗, 在公司悄摸得学习前端技术 在公司上班时间看视频影响不太好 按照这个来吧: https://gitee.com/chinese-gitee/Web 前端基础要学: HTML, CSS,JS 浏览器 浏览器中最重要的是渲染引擎(浏览器内核),JS引擎(常见的V8引擎) 渲染引擎: 用来解析 HTML与CSS。渲染引擎决定了…

Unity Mirror NetworkManager初识

文章目录 Network Manager网络管理器什么是网络管理器&#xff1f;通过Transports进行定制化网络连接管理自定义连接地址和端口号Game State Management游戏状态管理Network Manager HUD玩家预制体及其生成控制Spawn Prefabs其他预制体注册Scene Management场景管理 Network Ma…

重新阅读《马说》,感悟“伯乐相马”背后的被选择与选择的大智慧

“初闻不识曲中意&#xff0c;再听已是曲终人”。世有伯乐&#xff0c;然后有千里马。千里马常有&#xff0c;而伯乐不常有。无论你是考研考公等考试大军中的一员&#xff0c;还是已步入社会的打工人或者领导&#xff0c;当你面临被人选择或者选择人时&#xff0c;皆可从《马说…