Files
ZhouXiao 487c68994d feat: add builder configuration file
feat: add cocos-service configuration file
feat: add device configuration file
feat: add engine configuration file
feat: add information configuration file
feat: add program configuration file
feat: add project configuration file
feat: add TypeScript configuration file
2025-12-22 11:42:51 +08:00

428 lines
16 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

type index = {
x: number,
y: number
}
/**
* AStar类用于实现A*寻路算法。
*/
export class AStar {
private map: any[][] = []; // 地图数组
private weightMap: number[][] = null; // 权重地图数组
private openlist: index[] = []; // 开放列表,存储待评估的节点
private closelist: index[] = []; // 封闭列表,存储已评估的节点
private parentMap: Map<index, index>; // 父节点映射,用于回溯路径
private gcostMap: Map<index, number>; // g成本映射记录从起点到当前节点的成本
private hcostMap: Map<index, number>; // h成本映射记录从当前节点到终点的估算成本
private startValue: number = 0;
private endValue: number = 0;
private pathValue: number = 0;
private isEightDirections: boolean = false;
private neighborMap: Map<index, index[]>;
constructor() {
this.openlist = [];
this.closelist = [];
this.parentMap = new Map();
this.gcostMap = new Map();
this.hcostMap = new Map();
}
/**
* 设置地图。
* @param map 二维数组表示的地图
*/
setMap(map: number[][]) {
this.map = map;
}
/**
* 设置权重地图。
* @param weightMap 二维数组表示的权重地图
*/
setWeightMap(weightMap: number[][]) {
this.weightMap = weightMap;
}
setValue(startValue: number, endValue: number, pathValue: number) {
this.startValue = startValue;
this.endValue = endValue;
this.pathValue = pathValue;
}
setEightDirections(isEightDirections: boolean) {
this.isEightDirections = isEightDirections;
}
setNeighborMap(neighborMap: Map<index, index[]>) {
this.neighborMap = neighborMap;
}
/**
* 在给定范围内根据指定类型搜索从起点到终点的路径。
* @param start 起点索引
* @param end 终点索引
* @param type 搜索类型(可能影响启发式函数的选择)
* @returns 返回找到的路径数组,如果无路径则返回空数组
*/
search(start: index, end: index, type: number) {
this.init(); // 初始化搜索环境
if (this.map[start.y][start.x] !== this.startValue ||
this.map[end.y][end.x] !== this.endValue) {
console.log("Invalid start or end point");
return [];
}
// 初始化起点信息,并将其加入开放列表
this.openlist.push(start);
this.gcostMap.set(start, 0); // 起点到起点的距离为0
this.hcostMap.set(start, this.getHeuristicCost(start, end, type)); // 起点到终点的启发式成本
this.parentMap.set(start, null); // 起点的父节点设置为null
// 循环直到开放列表为空,即所有可能的路径都被考虑
while (this.openlist.length > 0) {
const current = this.getMinCostNode(this.openlist); // 从开放列表中选择成本最小的节点
if (!current) {
this.openlist.splice(this.openlist.indexOf(current), 1);
continue;
}
// 判断是否到达终点
if (this.isArrived(current, end)) {
return this.reconstructPath(end); // 如果到达终点,返回路径重构的结果
} else {
// 从开放列表移除当前节点,将其加入封闭列表
this.openlist.splice(this.openlist.indexOf(current), 1);
this.closelist.push(current);
// 遍历当前节点的所有邻居节点
const neighbors = this.getNeighbors(current);
for (const neighbor of neighbors) {
const hCost = this.getHeuristicCost(neighbor, end, type); // 计算邻居节点到终点的启发式成本
const gCost = this.gcostMap.get(current) + this.getGCost(current, neighbor, type); // 计算当前节点到邻居节点的实际成本
const totalCost = gCost + hCost; // 计算总成本
// 根据邻居节点的状态进行处理
if (!this.isInClosedList(neighbor)) {
if (!this.isInOpenList(neighbor)) {
this.openlist.push(neighbor); // 如果邻居未在开放列表中,将其加入
this.updateCost(neighbor, gCost, hCost); // 更新邻居节点的成本信息
this.updateParent(neighbor, current); // 更新邻居节点的父节点信息
} else {
// 如果邻居已在开放列表中,但通过当前节点发现更优路径
if (totalCost < this.getCost(neighbor)) {
this.updateCost(neighbor, gCost, hCost); // 更新成本信息
this.updateParent(neighbor, current); // 更新父节点信息
}
}
}
}
}
}
console.log("No path found.");
return []; // 如果无法找到路径,返回空数组
}
/**
* 初始化算法状态。
*/
private init() {
// 重置所有列表和映射
this.openlist = [];
this.closelist = [];
this.parentMap = new Map();
this.gcostMap = new Map();
this.hcostMap = new Map();
}
/**
* 获取当前节点的邻居。
* @param current 当前节点坐标
* @returns 邻居节点数组
*/
private getNeighbors(current: index) {
if (this.neighborMap.size === 0) {
const neighbors: index[] = [];
// 尝试获取四个方向的邻居节点
const upindex = { x: current.x, y: current.y + 1 };
const downindex = { x: current.x, y: current.y - 1 };
const leftindex = { x: current.x - 1, y: current.y };
const rightindex = { x: current.x + 1, y: current.y };
const upleftindex = { x: current.x - 1, y: current.y + 1 };
const uprightindex = { x: current.x + 1, y: current.y + 1 };
const downleftindex = { x: current.x - 1, y: current.y - 1 };
const downrightindex = { x: current.x + 1, y: current.y - 1 };
// 如果合法则加入邻居列表
if (this.isValidIndex(upindex) && (this.map[upindex.y][upindex.x] == this.pathValue ||
this.map[upindex.y][upindex.x] == this.endValue)) {
neighbors.push(upindex);
}
if (this.isValidIndex(downindex) && (this.map[downindex.y][downindex.x] == this.pathValue ||
this.map[downindex.y][downindex.x] == this.endValue)) {
neighbors.push(downindex);
}
if (this.isValidIndex(leftindex) && (this.map[leftindex.y][leftindex.x] == this.pathValue ||
this.map[leftindex.y][leftindex.x] == this.endValue)) {
neighbors.push(leftindex);
}
if (this.isValidIndex(rightindex) && (this.map[rightindex.y][rightindex.x] == this.pathValue ||
this.map[rightindex.y][rightindex.x] == this.endValue)) {
neighbors.push(rightindex);
}
if (this.isEightDirections) {
if (this.isValidIndex(upleftindex) && (this.map[upleftindex.y][upleftindex.x] == this.pathValue ||
this.map[upleftindex.y][upleftindex.x] == this.endValue)) {
neighbors.push(upleftindex);
}
if (this.isValidIndex(downleftindex) && (this.map[downleftindex.y][downleftindex.x] == this.pathValue ||
this.map[downleftindex.y][downleftindex.x] == this.endValue)) {
neighbors.push(downleftindex);
}
if (this.isValidIndex(downrightindex) && (this.map[downrightindex.y][downrightindex.x] == this.pathValue ||
this.map[downrightindex.y][downrightindex.x] == this.endValue)) {
neighbors.push(downrightindex);
}
if (this.isValidIndex(uprightindex) && (this.map[uprightindex.y][uprightindex.x] == this.pathValue ||
this.map[uprightindex.y][uprightindex.x] == this.endValue)) {
neighbors.push(uprightindex);
}
}
return neighbors;
} else {
const _neighbors = this.neighborMap.get(current);
const neighbors: index[] = [];
for (const neighbor of _neighbors) {
if (this.map[neighbor.y][neighbor.x] == this.pathValue ||
this.map[neighbor.y][neighbor.x] == this.endValue) {
neighbors.push(neighbor);
}
}
return neighbors;
}
}
/**
* 计算从当前节点到邻居节点的实际成本
* @param current 当前节点
* @param neighbor 邻居节点
* @param type 距离计算类型
* @returns 返回从当前节点到邻居节点的成本
*/
private getGCost(current: index, neighbor: index, type: number = 0) {
let baseCost = 0;
// 计算基础距离成本
switch (type) {
case 0: {
// 计算曼哈顿距离
baseCost = Math.abs(current.x - neighbor.x) + Math.abs(current.y - neighbor.y);
break;
}
case 1: {
// 计算欧几里得距离
baseCost = Math.sqrt(Math.pow(current.x - neighbor.x, 2) + Math.pow(current.y - neighbor.y, 2));
break;
}
case 2: {
// 计算切比雪夫距离
const x = Math.abs(current.x - neighbor.x);
const y = Math.abs(current.y - neighbor.y);
const minxy = Math.min(x, y);
baseCost = x + y + (Math.sqrt(2) - 2) * minxy;
break;
}
}
// 如果存在权重地图,应用权重
if (this.weightMap && this.isValidIndex(neighbor)) {
const weight = this.weightMap[neighbor.y][neighbor.x];
return baseCost * weight;
}
return baseCost;
}
/**
* 根据给定的起始点和目标点,以及类型参数,计算启发式成本。
* @param start 起始点的索引,包含 x 和 y 坐标。
* @param end 目标点的索引,包含 x 和 y 坐标。
* @param type 计算成本的类型0 表示曼哈顿距离1 表示欧几里得距离2 表示切比雪夫距离,默认为 0。
* @returns 返回根据指定类型计算出的启发式成本。
*/
private getHeuristicCost(start: index, end: index, type: number = 0) {
switch (type) {
case 0: {
// 计算曼哈顿距离
return Math.abs(start.x - end.x) + Math.abs(start.y - end.y);
}
case 1: {
// 计算欧几里得距离
return Math.sqrt(Math.pow(start.x - end.x, 2) + Math.pow(start.y - end.y, 2));
}
case 2: {
// 计算切比雪夫距离
const x = Math.abs(start.x - end.x);
const y = Math.abs(start.y - end.y);
const minxy = Math.min(x, y);
return x + y + (Math.sqrt(2) - 2) * minxy;
}
}
}
/**
* 检查节点是否在封闭列表中。
* @param node 节点坐标
* @returns {boolean} 如果节点在封闭列表中返回true否则返回false
*/
private isInClosedList(node: index): boolean {
// 遍历封闭列表查找节点
let _index = -1;
for (let i = 0; i < this.closelist.length; i++) {
if (this.closelist[i].x == node.x && this.closelist[i].y == node.y) {
_index = i;
break;
}
}
return _index != -1;
}
/**
* 检查节点是否在开放列表中。
* @param node 节点坐标
* @returns {boolean} 如果节点在开放列表中返回true否则返回false
*/
private isInOpenList(node: index): boolean {
// 遍历开放列表查找节点
let _index = -1;
for (let i = 0; i < this.openlist.length; i++) {
if (this.openlist[i].x == node.x && this.openlist[i].y == node.y) {
_index = i;
break;
}
}
return _index != -1;
}
/**
* 获取节点的成本。
* @param node 节点坐标
* @returns 节点的总成本
*/
private getCost(node: index): number {
let gcost = 0;
let hcost = 0;
this.gcostMap.forEach((value, key) => {
if (key.x == node.x && key.y == node.y) {
gcost = value;
}
});
this.hcostMap.forEach((value, key) => {
if (key.x == node.x && key.y == node.y) {
hcost = value;
}
});
return gcost + hcost;
}
/**
* 获取开放列表中成本最小的节点。
* @param list 开放列表
* @returns 成本最小的节点
*/
private getMinCostNode(list: index[]): index {
let _minindex = -1;
let _mincost = Number.MAX_SAFE_INTEGER;
// 寻找成本最小的节点
for (let i = 0; i < list.length; i++) {
const cost = this.getCost(list[i]);
if (cost < _mincost) {
_minindex = i;
_mincost = cost;
}
}
return list[_minindex];
}
/**
* 更新节点的成本和父节点信息。
* @param node 节点坐标
* @param gcost 从起点到当前节点的实际成本
* @param hcost 从当前节点到终点的估算成本
*/
private updateCost(node: index, gcost: number, hcost: number) {
this.gcostMap.set(node, gcost);
this.hcostMap.set(node, hcost);
}
/**
* 更新节点的父节点。
* @param node 节点坐标
* @param parent 父节点坐标
*/
private updateParent(node: index, parent: index) {
const existingparent = this.parentMap.get(node);
// 仅当新的父节点成本更低时更新
if (!existingparent || this.getCost(parent) < this.getCost(existingparent)) {
this.parentMap.set(node, parent);
}
}
/**
* 根据父节点映射重构路径。
* @param endPoint 终点坐标
* @returns 从起点到终点的路径
*/
private reconstructPath(endPoint: index) {
const path = [endPoint];
let currentnode = endPoint;
// 逆向回溯路径
while (currentnode) {
let parent = null;
this.parentMap.forEach((value, key) => {
if (currentnode.x == key.x && currentnode.y == key.y) {
parent = value;
}
})
if (parent) {
path.unshift(parent);
currentnode = parent;
} else {
currentnode = null;
}
}
return path;
}
/**
* 检查给定的索引是否为有效索引。
* @param index 一个包含x和y属性的对象代表要检查的索引位置。
* @returns 返回一个布尔值如果索引位置有效则为true否则为false。
*/
private isValidIndex(index: index): boolean {
return index.x >= 0 && index.x < this.map[0].length && index.y >= 0 && index.y < this.map.length;
}
/**
* 判断当前节点是否到达了目标节点。
* @param node 一个包含x和y属性的对象代表当前节点的位置。
* @param end 一个包含x和y属性的对象代表目标节点的位置。
* @returns 返回一个布尔值如果当前节点已到达目标节点则为true否则为false。
*/
private isArrived(node: index, end: index) {
// 比较当前节点和目标节点的坐标,判断是否已经到达目标位置
return node.x == end.x && node.y == end.y;
}
}