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
428 lines
16 KiB
TypeScript
428 lines
16 KiB
TypeScript
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;
|
||
}
|
||
}
|
||
|