全部移动至es模块

This commit is contained in:
yhh
2020-07-23 11:00:46 +08:00
parent 814234ca61
commit 1b52bc5fd1
71 changed files with 19273 additions and 16907 deletions

View File

@@ -1,101 +1,103 @@
///<reference path="./PriorityQueueNode.ts" />
/**
* 计算路径给定的IAstarGraph和开始/目标位置
*/
class AStarPathfinder {
module es {
/**
* 尽可能从开始目标找到一条路径。如果没有找到路径则返回null。
* @param graph
* @param start
* @param goal
* 计算路径给定的IAstarGraph和开始/目标位置
*/
public static search<T>(graph: IAstarGraph<T>, start: T, goal: T){
let foundPath = false;
let cameFrom = new Map<T, T>();
cameFrom.set(start, start);
export class AStarPathfinder {
/**
* 尽可能从开始到目标找到一条路径。如果没有找到路径则返回null。
* @param graph
* @param start
* @param goal
*/
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);
let costSoFar = new Map<T, number>();
let frontier = new PriorityQueue<AStarNode<T>>(1000);
frontier.enqueue(new AStarNode<T>(start), 0);
costSoFar.set(start, 0);
costSoFar.set(start, 0);
while (frontier.count > 0){
let current = frontier.dequeue();
while (frontier.count > 0){
let current = frontier.dequeue();
if (JSON.stringify(current.data) == JSON.stringify(goal)){
foundPath = true;
break;
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);
}
});
}
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;
}
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;
}
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;
}
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;
}
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;
}
return null;
/**
* 从cameFrom字典重新构造路径
* @param cameFrom
* @param start
* @param goal
*/
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;
}
}
/**
* 从cameFrom字典重新构造路径
* @param cameFrom
* @param start
* @param goal
* 使用PriorityQueue需要的额外字段将原始数据封装在一个小类中
*/
public static recontructPath<T>(cameFrom: Map<T, T>, start: T, goal: T): T[]{
let path = [];
let current = goal;
path.push(goal);
export class AStarNode<T> extends PriorityQueueNode {
public data: T;
while (current != start){
current = this.getKey(cameFrom, current);
path.push(current);
constructor(data: T){
super();
this.data = data;
}
path.reverse();
return path;
}
}
/**
* 使用PriorityQueue需要的额外字段将原始数据封装在一个小类中
*/
class AStarNode<T> extends PriorityQueueNode {
public data: T;
constructor(data: T){
super();
this.data = data;
}
}

View File

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

View File

@@ -1,22 +1,24 @@
/**
* graph的接口可以提供给AstarPathfinder.search方法
*/
interface IAstarGraph<T> {
module es {
/**
* getNeighbors方法应该返回从传入的节点可以到达的任何相邻节点
* @param node
* graph的接口可以提供给AstarPathfinder.search方法
*/
getNeighbors(node: T): Array<T>;
/**
* 计算从从from到to的成本
* @param from
* @param to
*/
cost(from: T, to: T): number;
/**
* 计算从node到to的启发式。参见WeightedGridGraph了解常用的Manhatten方法。
* @param node
* @param goal
*/
heuristic(node: T, goal: T);
}
export interface IAstarGraph<T> {
/**
* getNeighbors方法应该返回从传入的节点可以到达的任何相邻节点
* @param node
*/
getNeighbors(node: T): Array<T>;
/**
* 计算从从from到to的成本
* @param from
* @param to
*/
cost(from: T, to: T): number;
/**
* 计算从node到to的启发式。参见WeightedGridGraph了解常用的Manhatten方法。
* @param node
* @param goal
*/
heuristic(node: T, goal: T);
}
}

View File

@@ -1,234 +1,236 @@
/**
* 使用堆实现最小优先级队列 O(1)复杂度
* 这种查找速度比使用字典快5-10倍
* 但是由于IPriorityQueue.contains()是许多寻路算法中调用最多的方法,因此尽可能快地实现它对于我们的应用程序非常重要。
*/
class PriorityQueue<T extends PriorityQueueNode> {
private _numNodes: number;
private _nodes: T[];
private _numNodesEverEnqueued;
module es {
/**
* 实例化一个新的优先级队列
* @param maxNodes 允许加入队列的最大节点(执行此操作将导致undefined的行为)
* 使用堆实现最小优先级队列 O(1)复杂度
* 这种查找速度比使用字典快5-10倍
* 但是由于IPriorityQueue.contains()是许多寻路算法中调用最多的方法,因此尽可能快地实现它对于我们的应用程序非常重要。
*/
constructor(maxNodes: number) {
this._numNodes = 0;
this._nodes = new Array(maxNodes + 1);
this._numNodesEverEnqueued = 0;
}
export class PriorityQueue<T extends PriorityQueueNode> {
private _numNodes: number;
private _nodes: T[];
private _numNodesEverEnqueued;
/**
* 从队列中删除每个节点。
* O(n)复杂度 所有尽可能少调用该方法
*/
public clear() {
this._nodes.splice(1, this._numNodes);
this._numNodes = 0;
}
/**
* 返回队列中的节点数。
* O(1)复杂度
*/
public get count() {
return this._numNodes;
}
/**
* 返回可同时进入此队列的最大项数。一旦你达到这个数字(即。一旦Count == MaxSize)尝试加入另一个项目将导致undefined的行为
* O(1)复杂度
*/
public get maxSize() {
return this._nodes.length - 1;
}
/**
* 返回(在O(1)中)给定节点是否在队列中
* O (1)复杂度
* @param node
*/
public contains(node: T): boolean {
if (!node){
console.error("node cannot be null");
return false;
/**
* 实例化一个新的优先级队列
* @param maxNodes 允许加入队列的最大节点(执行此操作将导致undefined的行为)
*/
constructor(maxNodes: number) {
this._numNodes = 0;
this._nodes = new Array(maxNodes + 1);
this._numNodesEverEnqueued = 0;
}
if (node.queueIndex < 0 || node.queueIndex >= this._nodes.length){
console.error("node.QueueIndex has been corrupted. Did you change it manually? Or add this node to another queue?");
return false;
/**
* 从队列中删除每个节点。
* O(n)复杂度 所有尽可能少调用该方法
*/
public clear() {
this._nodes.splice(1, this._numNodes);
this._numNodes = 0;
}
return (this._nodes[node.queueIndex] == node);
}
/**
* 返回队列中的节点数。
* O(1)复杂度
*/
public get count() {
return this._numNodes;
}
/**
* 将节点放入优先队列 较低的值放在前面 先入先出
* 如果队列已满则结果undefined。如果节点已经加入队列则结果undefined。
* O(log n)
* @param node
* @param priority
*/
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]);
}
/**
* 返回可同时进入此队列的最大项数。一旦你达到这个数字(即。一旦Count == MaxSize)尝试加入另一个项目将导致undefined的行为
* O(1)复杂度
*/
public get maxSize() {
return this._nodes.length - 1;
}
/**
* 删除队列头(具有最小优先级的节点;按插入顺序断开连接)并返回它。如果队列为空结果undefined
* O(log n)
*/
public dequeue(): T {
let returnMe = this._nodes[1];
this.remove(returnMe);
return returnMe;
}
/**
* 返回(在O(1)中)给定节点是否在队列中
* O (1)复杂度
* @param node
*/
public contains(node: T): boolean {
if (!node){
console.error("node cannot be null");
return false;
}
/**
* 从队列中删除一个节点。节点不需要是队列的头。如果节点不在队列中则结果未定义。如果不确定首先检查Contains()
* O(log n)
* @param node
*/
public remove(node: T) {
if (node.queueIndex == this._numNodes) {
this._nodes[this._numNodes] = null;
if (node.queueIndex < 0 || node.queueIndex >= this._nodes.length){
console.error("node.QueueIndex has been corrupted. Did you change it manually? Or add this node to another queue?");
return false;
}
return (this._nodes[node.queueIndex] == node);
}
/**
* 将节点放入优先队列 较低的值放在前面 先入先出
* 如果队列已满则结果undefined。如果节点已经加入队列则结果undefined。
* O(log n)
* @param node
* @param priority
*/
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]);
}
/**
* 删除队列头(具有最小优先级的节点;按插入顺序断开连接)并返回它。如果队列为空结果undefined
* O(log n)
*/
public dequeue(): T {
let returnMe = this._nodes[1];
this.remove(returnMe);
return returnMe;
}
/**
* 从队列中删除一个节点。节点不需要是队列的头。如果节点不在队列中则结果未定义。如果不确定首先检查Contains()
* O(log n)
* @param node
*/
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--;
return;
this.onNodeUpdated(formerLastNode);
}
let formerLastNode = this._nodes[this._numNodes];
this.swap(node, formerLastNode);
delete this._nodes[this._numNodes];
this._numNodes--;
/**
* 检查以确保队列仍然处于有效状态。用于测试/调试队列。
*/
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;
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 {
// 注意如果parentNode == node(即节点是根)则将调用CascadeDown。
this.cascadeDown(node);
}
}
private cascadeDown(node: T) {
// 又名Heapify-down
let newParent: T;
let finalQueueIndex = node.queueIndex;
while (true) {
newParent = node;
let childLeftIndex = 2 * finalQueueIndex;
// 检查左子节点的优先级是否高于当前节点
if (childLeftIndex > this._numNodes) {
// 这可以放在循环之外但是我们必须检查newParent != node两次
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;
let childRightIndex = childLeftIndex + 1;
if (childRightIndex < this._nodes.length && this._nodes[childRightIndex] &&
this.hasHigherPriority(this._nodes[childRightIndex], this._nodes[i]))
return false;
}
}
// 如果其中一个子节点具有更高(更小)的优先级,则交换并继续级联
if (newParent != node) {
// 将新的父节点移动到它的新索引
// 节点将被移动一次这样做比调用Swap()少一个赋值操作。
this._nodes[finalQueueIndex] = newParent;
return true;
}
let temp = newParent.queueIndex;
newParent.queueIndex = finalQueueIndex;
finalQueueIndex = temp;
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 {
// 参见上面的笔记
node.queueIndex = finalQueueIndex;
this._nodes[finalQueueIndex] = node;
break;
// 注意如果parentNode == node(即节点是根)则将调用CascadeDown。
this.cascadeDown(node);
}
}
}
/**
* 当没有内联时,性能会稍微好一些
* @param node
*/
private cascadeUp(node: T) {
// 又名Heapify-up
let parent = Math.floor(node.queueIndex / 2);
while (parent >= 1) {
let parentNode = this._nodes[parent];
if (this.hasHigherPriority(parentNode, node))
break;
private cascadeDown(node: T) {
// 又名Heapify-down
let newParent: T;
let finalQueueIndex = node.queueIndex;
while (true) {
newParent = node;
let childLeftIndex = 2 * finalQueueIndex;
// 节点具有较低的优先级值,因此将其向上移动到堆中
// 出于某种原因使用Swap()比使用单独的操作更快如CascadeDown()
this.swap(node, parentNode);
// 检查左子节点的优先级是否高于当前节点
if (childLeftIndex > this._numNodes) {
// 这可以放在循环之外但是我们必须检查newParent != node两次
node.queueIndex = finalQueueIndex;
this._nodes[finalQueueIndex] = node;
break;
}
parent = Math.floor(node.queueIndex / 2);
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) {
// 将新的父节点移动到它的新索引
// 节点将被移动一次这样做比调用Swap()少一个赋值操作。
this._nodes[finalQueueIndex] = newParent;
let temp = newParent.queueIndex;
newParent.queueIndex = finalQueueIndex;
finalQueueIndex = temp;
} else {
// 参见上面的笔记
node.queueIndex = finalQueueIndex;
this._nodes[finalQueueIndex] = node;
break;
}
}
}
/**
* 当没有内联时,性能会稍微好一些
* @param node
*/
private cascadeUp(node: T) {
// 又名Heapify-up
let parent = Math.floor(node.queueIndex / 2);
while (parent >= 1) {
let parentNode = this._nodes[parent];
if (this.hasHigherPriority(parentNode, node))
break;
// 节点具有较低的优先级值,因此将其向上移动到堆中
// 出于某种原因使用Swap()比使用单独的操作更快如CascadeDown()
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;
// 交换他们的indicies
let temp = node1.queueIndex;
node1.queueIndex = node2.queueIndex;
node2.queueIndex = temp;
}
/**
* 如果higher的优先级高于lower则返回true否则返回false。
* 注意调用HasHigherPriority(节点,节点)(即。两个参数为同一个节点)将返回false
* @param higher
* @param lower
*/
private hasHigherPriority(higher: T, lower: T) {
return (higher.priority < lower.priority ||
(higher.priority == lower.priority && higher.insertionIndex < lower.insertionIndex));
}
}
private swap(node1: T, node2: T) {
// 交换节点
this._nodes[node1.queueIndex] = node2;
this._nodes[node2.queueIndex] = node1;
// 交换他们的indicies
let temp = node1.queueIndex;
node1.queueIndex = node2.queueIndex;
node2.queueIndex = temp;
}
/**
* 如果higher的优先级高于lower则返回true否则返回false。
* 注意调用HasHigherPriority(节点,节点)(即。两个参数为同一个节点)将返回false
* @param higher
* @param lower
*/
private hasHigherPriority(higher: T, lower: T) {
return (higher.priority < lower.priority ||
(higher.priority == lower.priority && higher.insertionIndex < lower.insertionIndex));
}
}
}

View File

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

View File

@@ -1,41 +1,43 @@
/**
* 计算路径给定的IUnweightedGraph和开始/目标位置
*/
class BreadthFirstPathfinder {
public static search<T>(graph: IUnweightedGraph<T>, start: T, goal: T): T[]{
let foundPath = false;
let frontier = [];
frontier.unshift(start);
module es {
/**
* 计算路径给定的IUnweightedGraph和开始/目标位置
*/
export class BreadthFirstPathfinder {
public static search<T>(graph: IUnweightedGraph<T>, start: T, goal: T): T[]{
let foundPath = false;
let frontier = [];
frontier.unshift(start);
let cameFrom = new Map<T, T>();
cameFrom.set(start, start);
let cameFrom = new Map<T, T>();
cameFrom.set(start, start);
while (frontier.length > 0){
let current = frontier.shift();
if (JSON.stringify(current) == JSON.stringify(goal)){
foundPath = true;
break;
while (frontier.length > 0){
let current = frontier.shift();
if (JSON.stringify(current) == JSON.stringify(goal)){
foundPath = true;
break;
}
graph.getNeighbors(current).forEach(next => {
if (!this.hasKey(cameFrom, next)){
frontier.unshift(next);
cameFrom.set(next, current);
}
});
}
graph.getNeighbors(current).forEach(next => {
if (!this.hasKey(cameFrom, next)){
frontier.unshift(next);
cameFrom.set(next, current);
}
});
return foundPath ? AStarPathfinder.recontructPath(cameFrom, start, goal) : null;
}
return foundPath ? AStarPathfinder.recontructPath(cameFrom, start, goal) : null;
}
private static hasKey<T>(map: Map<T, T>, 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;
}
private static hasKey<T>(map: Map<T, T>, 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;
}
return false;
}
}
}

View File

@@ -1,7 +1,9 @@
interface IUnweightedGraph<T>{
/**
* getNeighbors方法应该返回从传入的节点可以到达的任何相邻节点。
* @param node
*/
getNeighbors(node: T): T[];
}
module es {
export interface IUnweightedGraph<T>{
/**
* getNeighbors方法应该返回从传入的节点可以到达的任何相邻节点。
* @param node
*/
getNeighbors(node: T): T[];
}
}

View File

@@ -1,16 +1,18 @@
/**
* 一个未加权图的基本实现。所有的边都被缓存。这种类型的图最适合于非基于网格的图。
* 作为边添加的任何节点都必须在边字典中有一个条目作为键
*/
class UnweightedGraph<T> implements IUnweightedGraph<T> {
public edges: Map<T, T[]> = new Map<T, T[]>();
module es {
/**
* 一个未加权图的基本实现。所有的边都被缓存。这种类型的图最适合于非基于网格的图
* 作为边添加的任何节点都必须在边字典中有一个条目作为键。
*/
export class UnweightedGraph<T> implements IUnweightedGraph<T> {
public edges: Map<T, T[]> = new Map<T, T[]>();
public addEdgesForNode(node: T, edges: T[]){
this.edges.set(node, edges);
return this;
}
public addEdgesForNode(node: T, edges: T[]){
this.edges.set(node, edges);
return this;
}
public getNeighbors(node: T){
return this.edges.get(node);
public getNeighbors(node: T){
return this.edges.get(node);
}
}
}
}

View File

@@ -1,61 +1,63 @@
///<reference path="../../../Math/Vector2.ts" />
/**
* 基本的未加权网格图形用于BreadthFirstPathfinder
*/
class UnweightedGridGraph implements IUnweightedGraph<Vector2> {
private static readonly CARDINAL_DIRS: Vector2[] = [
new Vector2(1, 0),
new Vector2(0, -1),
new Vector2(-1, 0),
new Vector2(0, -1)
];
module es {
/**
* 基本的未加权网格图形用于BreadthFirstPathfinder
*/
export class UnweightedGridGraph implements IUnweightedGraph<Vector2> {
private static readonly CARDINAL_DIRS: Vector2[] = [
new Vector2(1, 0),
new Vector2(0, -1),
new Vector2(-1, 0),
new Vector2(0, -1)
];
private static readonly COMPASS_DIRS = [
new Vector2(1, 0),
new Vector2(1, -1),
new Vector2(0, -1),
new Vector2(-1, -1),
new Vector2(-1, 0),
new Vector2(-1, 1),
new Vector2(0, 1),
new Vector2(1, 1),
];
private static readonly COMPASS_DIRS = [
new Vector2(1, 0),
new Vector2(1, -1),
new Vector2(0, -1),
new Vector2(-1, -1),
new Vector2(-1, 0),
new Vector2(-1, 1),
new Vector2(0, 1),
new Vector2(1, 1),
];
public walls: Vector2[] = [];
public walls: Vector2[] = [];
private _width: number;
private _hegiht: number;
private _width: number;
private _hegiht: number;
private _dirs: Vector2[];
private _neighbors: Vector2[] = new Array(4);
private _dirs: Vector2[];
private _neighbors: Vector2[] = new Array(4);
constructor(width: number, height: number, allowDiagonalSearch: boolean = false) {
this._width = width;
this._hegiht = height;
this._dirs = allowDiagonalSearch ? UnweightedGridGraph.COMPASS_DIRS : UnweightedGridGraph.CARDINAL_DIRS;
constructor(width: number, height: number, allowDiagonalSearch: boolean = false) {
this._width = width;
this._hegiht = height;
this._dirs = allowDiagonalSearch ? UnweightedGridGraph.COMPASS_DIRS : UnweightedGridGraph.CARDINAL_DIRS;
}
public isNodeInBounds(node: Vector2): boolean {
return 0 <= node.x && node.x < this._width && 0 <= node.y && node.y < this._hegiht;
}
public isNodePassable(node: Vector2): boolean {
return !this.walls.firstOrDefault(wall => JSON.stringify(wall) == JSON.stringify(node));
}
public getNeighbors(node: Vector2) {
this._neighbors.length = 0;
this._dirs.forEach(dir => {
let next = new Vector2(node.x + dir.x, node.y + dir.y);
if (this.isNodeInBounds(next) && this.isNodePassable(next))
this._neighbors.push(next);
});
return this._neighbors;
}
public search(start: Vector2, goal: Vector2): Vector2[] {
return BreadthFirstPathfinder.search(this, start, goal);
}
}
public isNodeInBounds(node: Vector2): boolean {
return 0 <= node.x && node.x < this._width && 0 <= node.y && node.y < this._hegiht;
}
public isNodePassable(node: Vector2): boolean {
return !this.walls.firstOrDefault(wall => JSON.stringify(wall) == JSON.stringify(node));
}
public getNeighbors(node: Vector2) {
this._neighbors.length = 0;
this._dirs.forEach(dir => {
let next = new Vector2(node.x + dir.x, node.y + dir.y);
if (this.isNodeInBounds(next) && this.isNodePassable(next))
this._neighbors.push(next);
});
return this._neighbors;
}
public search(start: Vector2, goal: Vector2): Vector2[] {
return BreadthFirstPathfinder.search(this, start, goal);
}
}
}

View File

@@ -1,14 +1,16 @@
interface IWeightedGraph<T>{
/**
*
* @param node
*/
getNeighbors(node: T): T[];
module es {
export interface IWeightedGraph<T>{
/**
*
* @param node
*/
getNeighbors(node: T): T[];
/**
*
* @param from
* @param to
*/
cost(from: T, to: T): number;
}
/**
*
* @param from
* @param to
*/
cost(from: T, to: T): number;
}
}

View File

@@ -1,67 +1,69 @@
///<reference path="../../../Math/Vector2.ts" />
/**
* 支持一种加权节点的基本网格图
*/
class WeightedGridGraph implements IWeightedGraph<Vector2> {
public static readonly CARDINAL_DIRS = [
new Vector2(1, 0),
new Vector2(0, -1),
new Vector2(-1, 0),
new Vector2(0, 1)
];
module es {
/**
* 支持一种加权节点的基本网格图
*/
export class WeightedGridGraph implements IWeightedGraph<Vector2> {
public static readonly CARDINAL_DIRS = [
new Vector2(1, 0),
new Vector2(0, -1),
new Vector2(-1, 0),
new Vector2(0, 1)
];
private static readonly COMPASS_DIRS = [
new Vector2(1, 0),
new Vector2(1, -1),
new Vector2(0, -1),
new Vector2(-1, -1),
new Vector2(-1, 0),
new Vector2(-1, 1),
new Vector2(0, 1),
new Vector2(1, 1),
];
private static readonly COMPASS_DIRS = [
new Vector2(1, 0),
new Vector2(1, -1),
new Vector2(0, -1),
new Vector2(-1, -1),
new Vector2(-1, 0),
new Vector2(-1, 1),
new Vector2(0, 1),
new Vector2(1, 1),
];
public walls: Vector2[] = [];
public weightedNodes: Vector2[] = [];
public defaultWeight = 1;
public weightedNodeWeight = 5;
public walls: Vector2[] = [];
public weightedNodes: Vector2[] = [];
public defaultWeight = 1;
public weightedNodeWeight = 5;
private _width: number;
private _height: number;
private _dirs: Vector2[];
private _neighbors: Vector2[] = new Array(4);
private _width: number;
private _height: number;
private _dirs: Vector2[];
private _neighbors: Vector2[] = new Array(4);
constructor(width: number, height: number, allowDiagonalSearch: boolean = false){
this._width = width;
this._height = height;
this._dirs = allowDiagonalSearch ? WeightedGridGraph.COMPASS_DIRS : WeightedGridGraph.CARDINAL_DIRS;
constructor(width: number, height: number, allowDiagonalSearch: boolean = false){
this._width = width;
this._height = height;
this._dirs = allowDiagonalSearch ? WeightedGridGraph.COMPASS_DIRS : WeightedGridGraph.CARDINAL_DIRS;
}
public isNodeInBounds(node: Vector2){
return 0 <= node.x && node.x < this._width && 0 <= node.y && node.y < this._height;
}
public isNodePassable(node: Vector2): boolean {
return !this.walls.firstOrDefault(wall => JSON.stringify(wall) == JSON.stringify(node));
}
public search(start: Vector2, goal: Vector2){
return WeightedPathfinder.search(this, start, goal);
}
public getNeighbors(node: Vector2): Vector2[]{
this._neighbors.length = 0;
this._dirs.forEach(dir => {
let next = new Vector2(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: Vector2, to: Vector2): number{
return this.weightedNodes.find(t => JSON.stringify(t) == JSON.stringify(to)) ? this.weightedNodeWeight : this.defaultWeight;
}
}
public isNodeInBounds(node: Vector2){
return 0 <= node.x && node.x < this._width && 0 <= node.y && node.y < this._height;
}
public isNodePassable(node: Vector2): boolean {
return !this.walls.firstOrDefault(wall => JSON.stringify(wall) == JSON.stringify(node));
}
public search(start: Vector2, goal: Vector2){
return WeightedPathfinder.search(this, start, goal);
}
public getNeighbors(node: Vector2): Vector2[]{
this._neighbors.length = 0;
this._dirs.forEach(dir => {
let next = new Vector2(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: Vector2, to: Vector2): number{
return this.weightedNodes.find(t => JSON.stringify(t) == JSON.stringify(to)) ? this.weightedNodeWeight : this.defaultWeight;
}
}
}

View File

@@ -1,83 +1,85 @@
class WeightedNode<T> extends PriorityQueueNode {
public data: T;
module es {
export class WeightedNode<T> extends PriorityQueueNode {
public data: T;
constructor(data: T){
super();
this.data = data;
constructor(data: T){
super();
this.data = data;
}
}
}
class WeightedPathfinder {
public static search<T>(graph: IWeightedGraph<T>, start: T, goal: T){
let foundPath = false;
export class WeightedPathfinder {
public static search<T>(graph: IWeightedGraph<T>, start: T, goal: T){
let foundPath = false;
let cameFrom = new Map<T, T>();
cameFrom.set(start, start);
let cameFrom = new Map<T, T>();
cameFrom.set(start, start);
let costSoFar = new Map<T, number>();
let frontier = new PriorityQueue<WeightedNode<T>>(1000);
frontier.enqueue(new WeightedNode<T>(start), 0);
let costSoFar = new Map<T, number>();
let frontier = new PriorityQueue<WeightedNode<T>>(1000);
frontier.enqueue(new WeightedNode<T>(start), 0);
costSoFar.set(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;
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 priprity = newCost;
frontier.enqueue(new WeightedNode<T>(next), priprity);
cameFrom.set(next, current.data);
}
});
}
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 priprity = newCost;
frontier.enqueue(new WeightedNode<T>(next), priprity);
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 foundPath ? this.recontructPath(cameFrom, start, goal) : null;
}
return false;
}
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;
}
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 false;
}
return null;
}
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;
}
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);
return null;
}
path.reverse();
public static recontructPath<T>(cameFrom: Map<T, T>, start: T, goal: T): T[]{
let path = [];
let current = goal;
path.push(goal);
return path;
while (current != start){
current = this.getKey(cameFrom, current);
path.push(current);
}
path.reverse();
return path;
}
}
}
}