新增a*寻路

This commit is contained in:
yhh
2020-06-09 19:45:09 +08:00
parent 9e0d14da7c
commit 0fd33be5f9
14 changed files with 998 additions and 3 deletions

View File

@@ -0,0 +1,89 @@
///<reference path="./PriorityQueueNode.ts" />
/**
* 计算路径给定的IAstarGraph和开始/目标位置
*/
class AStarPathfinder {
public static search<T>(graph: IAstarGraph<T>, start: T, goal: T){
let foundPath = false;
let cameFrom = new Map<T, T>();
cameFrom.set(start, start);
let costSoFar = new Map<T, number>();
let frontier = new PriorityQueue<AStarNode<T>>(1000);
frontier.enqueue(new AStarNode<T>(start), 0);
costSoFar.set(start, 0);
while (frontier.count > 0){
let current = frontier.dequeue();
if (JSON.stringify(current.data) == JSON.stringify(goal)){
foundPath = true;
break;
}
graph.getNeighbors(current.data).forEach(next => {
let newCost = costSoFar.get(current.data) + graph.cost(current.data, next);
if (!this.hasKey(costSoFar, next) || newCost < costSoFar.get(next)){
costSoFar.set(next, newCost);
let priority = newCost + graph.heuristic(next, goal);
frontier.enqueue(new AStarNode<T>(next), priority);
cameFrom.set(next, current.data);
}
});
}
return foundPath ? this.recontructPath(cameFrom, start, goal) : null;
}
private static hasKey<T>(map: Map<T, number>, compareKey: T){
let iterator = map.keys();
let r: IteratorResult<T>;
while (r = iterator.next() , !r.done) {
if (JSON.stringify(r.value) == JSON.stringify(compareKey))
return true;
}
return false;
}
private static getKey<T>(map: Map<T, T>, compareKey: T){
let iterator = map.keys();
let valueIterator = map.values();
let r: IteratorResult<T>;
let v: IteratorResult<T>;
while (r = iterator.next(), v = valueIterator.next(), !r.done) {
if (JSON.stringify(r.value) == JSON.stringify(compareKey))
return v.value;
}
return null;
}
public static recontructPath<T>(cameFrom: Map<T, T>, start: T, goal: T): T[]{
let path = [];
let current = goal;
path.push(goal);
while (current != start){
current = this.getKey(cameFrom, current);
path.push(current);
}
path.reverse();
return path;
}
}
/**
* 使用PriorityQueue需要的额外字段将原始数据封装在一个小类中
*/
class AStarNode<T> extends PriorityQueueNode {
public data: T;
constructor(data: T){
super();
this.data = data;
}
}

View File

@@ -0,0 +1,67 @@
/**
* 基本静态网格图与A*一起使用
* 将walls添加到walls HashSet并将加权节点添加到weightedNodes
*/
class AstarGridGraph implements IAstarGraph<Point> {
public dirs: Point[] = [
new Point(1, 0),
new Point(0, -1),
new Point(-1, 0),
new Point(0, 1)
];
public walls: Point[] = [];
public weightedNodes: Point[] = [];
public defaultWeight: number = 1;
public weightedNodeWeight = 5;
private _width;
private _height;
private _neighbors: Point[] = new Array(4);
constructor(width: number, height: number){
this._width = width;
this._height = height;
}
/**
* 确保节点在网格图的边界内
* @param node
*/
public isNodeInBounds(node: Point): boolean {
return 0 <= node.x && node.x < this._width && 0 <= node.y && node.y < this._height;
}
/**
* 检查节点是否可以通过。墙壁是不可逾越的。
* @param node
*/
public isNodePassable(node: Point): boolean {
return !this.walls.contains(node);
}
public search(start: Point, goal: Point){
return AStarPathfinder.search(this, start, goal);
}
public getNeighbors(node: Point): Point[] {
this._neighbors.length = 0;
this.dirs.forEach(dir => {
let next = new Point(node.x + dir.x, node.y + dir.y);
if (this.isNodeInBounds(next) && this.isNodePassable(next))
this._neighbors.push(next);
});
return this._neighbors;
}
public cost(from: Point, to: Point): number {
return this.weightedNodes.find((p)=> JSON.stringify(p) == JSON.stringify(to)) ? this.weightedNodeWeight : this.defaultWeight;
}
public heuristic(node: Point, goal: Point) {
return Math.abs(node.x - goal.x) + Math.abs(node.y - goal.y);
}
}

View File

@@ -0,0 +1,5 @@
interface IAstarGraph<T> {
getNeighbors(node: T): Array<T>;
cost(from: T, to: T): number;
heuristic(node: T, goal: T);
}

View File

@@ -0,0 +1,150 @@
class PriorityQueue<T extends PriorityQueueNode> {
private _numNodes: number;
private _nodes: T[];
private _numNodesEverEnqueued;
constructor(maxNodes: number) {
this._numNodes = 0;
this._nodes = new Array(maxNodes + 1);
this._numNodesEverEnqueued = 0;
}
public clear() {
this._nodes.splice(1, this._numNodes);
this._numNodes = 0;
}
public get count() {
return this._numNodes;
}
public contains(node: T): boolean {
return (this._nodes[node.queueIndex] == node);
}
public enqueue(node: T, priority: number) {
node.priority = priority;
this._numNodes++;
this._nodes[this._numNodes] = node;
node.queueIndex = this._numNodes;
node.insertionIndex = this._numNodesEverEnqueued++;
this.cascadeUp(this._nodes[this._numNodes]);
}
public dequeue(): T {
let returnMe = this._nodes[1];
this.remove(returnMe);
return returnMe;
}
public remove(node: T) {
if (node.queueIndex == this._numNodes) {
this._nodes[this._numNodes] = null;
this._numNodes--;
return;
}
let formerLastNode = this._nodes[this._numNodes];
this.swap(node, formerLastNode);
delete this._nodes[this._numNodes];
this._numNodes--;
this.onNodeUpdated(formerLastNode);
}
public isValidQueue(): boolean {
for (let i = 1; i < this._nodes.length; i++) {
if (this._nodes[i]) {
let childLeftIndex = 2 * i;
if (childLeftIndex < this._nodes.length && this._nodes[childLeftIndex] &&
this.hasHigherPriority(this._nodes[childLeftIndex], this._nodes[i]))
return false;
let childRightIndex = childLeftIndex + 1;
if (childRightIndex < this._nodes.length && this._nodes[childRightIndex] &&
this.hasHigherPriority(this._nodes[childRightIndex], this._nodes[i]))
return false;
}
}
return true;
}
private onNodeUpdated(node: T) {
let parentIndex = Math.floor(node.queueIndex / 2);
let parentNode = this._nodes[parentIndex];
if (parentIndex > 0 && this.hasHigherPriority(node, parentNode)) {
this.cascadeUp(node);
} else {
this.cascadeDown(node);
}
}
private cascadeDown(node: T) {
let newParent: T;
let finalQueueIndex = node.queueIndex;
while (true) {
newParent = node;
let childLeftIndex = 2 * finalQueueIndex;
if (childLeftIndex > this._numNodes) {
node.queueIndex = finalQueueIndex;
this._nodes[finalQueueIndex] = node;
break;
}
let childLeft = this._nodes[childLeftIndex];
if (this.hasHigherPriority(childLeft, newParent)) {
newParent = childLeft;
}
let childRightIndex = childLeftIndex + 1;
if (childRightIndex <= this._numNodes) {
let childRight = this._nodes[childRightIndex];
if (this.hasHigherPriority(childRight, newParent)) {
newParent = childRight;
}
}
if (newParent != node) {
this._nodes[finalQueueIndex] = newParent;
let temp = newParent.queueIndex;
newParent.queueIndex = finalQueueIndex;
finalQueueIndex = temp;
} else {
node.queueIndex = finalQueueIndex;
this._nodes[finalQueueIndex] = node;
break;
}
}
}
private cascadeUp(node: T) {
let parent = Math.floor(node.queueIndex / 2);
while (parent >= 1) {
let parentNode = this._nodes[parent];
if (this.hasHigherPriority(parentNode, node))
break;
this.swap(node, parentNode);
parent = Math.floor(node.queueIndex / 2);
}
}
private swap(node1: T, node2: T) {
this._nodes[node1.queueIndex] = node2;
this._nodes[node2.queueIndex] = node1;
let temp = node1.queueIndex;
node1.queueIndex = node2.queueIndex;
node2.queueIndex = temp;
}
private hasHigherPriority(higher: T, lower: T) {
return (higher.priority < lower.priority ||
(higher.priority == lower.priority && higher.insertionIndex < lower.insertionIndex));
}
}

View File

@@ -0,0 +1,14 @@
class PriorityQueueNode {
/**
* 插入此节点的优先级。在将节点添加到队列之前必须设置
*/
public priority: number = 0;
/**
* 由优先级队列使用-不要编辑此值。表示插入节点的顺序
*/
public insertionIndex: number = 0;
/**
* 由优先级队列使用-不要编辑此值。表示队列中的当前位置
*/
public queueIndex: number = 0;
}

9
source/src/Math/Point.ts Normal file
View File

@@ -0,0 +1,9 @@
class Point {
public x: number;
public y: number;
constructor(x: number, y: number){
this.x = x;
this.y = y;
}
}