移除astar库转为插件

This commit is contained in:
yhh
2020-11-23 18:02:14 +08:00
parent 6505f31ad5
commit 5845128479
22 changed files with 192 additions and 2750 deletions

View File

@@ -17,280 +17,6 @@ declare interface Array<T> {
groupBy(keySelector: Function): Array<T>;
sum(selector: Function): number;
}
declare module es {
class PriorityQueueNode {
priority: number;
insertionIndex: number;
queueIndex: number;
}
}
declare module es {
class AStarPathfinder {
static search<T>(graph: IAstarGraph<T>, start: T, goal: T): T[];
static recontructPath<T>(cameFrom: Map<T, T>, start: T, goal: T): T[];
private static hasKey;
private static getKey;
}
}
declare module es {
class AstarGridGraph implements IAstarGraph<Vector2> {
dirs: Vector2[];
walls: Vector2[];
weightedNodes: Vector2[];
defaultWeight: number;
weightedNodeWeight: number;
private _width;
private _height;
private _neighbors;
constructor(width: number, height: number);
isNodeInBounds(node: Vector2): boolean;
isNodePassable(node: Vector2): boolean;
search(start: Vector2, goal: Vector2): Vector2[];
getNeighbors(node: Vector2): Vector2[];
cost(from: Vector2, to: Vector2): number;
heuristic(node: Vector2, goal: Vector2): number;
}
}
declare module es {
interface IAstarGraph<T> {
getNeighbors(node: T): Array<T>;
cost(from: T, to: T): number;
heuristic(node: T, goal: T): any;
}
}
declare module es {
class PriorityQueue<T extends PriorityQueueNode> {
private _numNodes;
private _nodes;
private _numNodesEverEnqueued;
constructor(maxNodes: number);
readonly count: number;
readonly maxSize: number;
clear(): void;
contains(node: T): boolean;
enqueue(node: T, priority: number): void;
dequeue(): T;
remove(node: T): void;
isValidQueue(): boolean;
private onNodeUpdated;
private cascadeDown;
private cascadeUp;
private swap;
private hasHigherPriority;
}
}
declare module es {
class BreadthFirstPathfinder {
static search<T>(graph: IUnweightedGraph<T>, start: T, goal: T): T[];
private static hasKey;
}
}
declare module es {
interface IUnweightedGraph<T> {
getNeighbors(node: T): T[];
}
}
declare module es {
class UnweightedGraph<T> implements IUnweightedGraph<T> {
edges: Map<T, T[]>;
addEdgesForNode(node: T, edges: T[]): this;
getNeighbors(node: T): T[];
}
}
declare module es {
class Vector2 implements IEquatable<Vector2> {
x: number;
y: number;
constructor(x?: number, y?: number);
static readonly zero: Vector2;
static readonly one: Vector2;
static readonly unitX: Vector2;
static readonly unitY: Vector2;
static add(value1: Vector2, value2: Vector2): Vector2;
static divide(value1: Vector2, value2: Vector2): Vector2;
static multiply(value1: Vector2, value2: Vector2): Vector2;
static subtract(value1: Vector2, value2: Vector2): Vector2;
static normalize(value: Vector2): Vector2;
static dot(value1: Vector2, value2: Vector2): number;
static distanceSquared(value1: Vector2, value2: Vector2): number;
static clamp(value1: Vector2, min: Vector2, max: Vector2): Vector2;
static lerp(value1: Vector2, value2: Vector2, amount: number): Vector2;
static transform(position: Vector2, matrix: Matrix2D): Vector2;
static distance(value1: Vector2, value2: Vector2): number;
static angle(from: Vector2, to: Vector2): number;
static negate(value: Vector2): Vector2;
add(value: Vector2): Vector2;
divide(value: Vector2): Vector2;
multiply(value: Vector2): Vector2;
subtract(value: Vector2): this;
normalize(): void;
length(): number;
lengthSquared(): number;
round(): Vector2;
equals(other: Vector2 | object): boolean;
}
}
declare module es {
class UnweightedGridGraph implements IUnweightedGraph<Vector2> {
private static readonly CARDINAL_DIRS;
private static readonly COMPASS_DIRS;
walls: Vector2[];
private _width;
private _hegiht;
private _dirs;
private _neighbors;
constructor(width: number, height: number, allowDiagonalSearch?: boolean);
isNodeInBounds(node: Vector2): boolean;
isNodePassable(node: Vector2): boolean;
getNeighbors(node: Vector2): Vector2[];
search(start: Vector2, goal: Vector2): Vector2[];
}
}
declare module es {
interface IWeightedGraph<T> {
getNeighbors(node: T): T[];
cost(from: T, to: T): number;
}
}
declare module es {
class WeightedGridGraph implements IWeightedGraph<Vector2> {
static readonly CARDINAL_DIRS: Vector2[];
private static readonly COMPASS_DIRS;
walls: Vector2[];
weightedNodes: Vector2[];
defaultWeight: number;
weightedNodeWeight: number;
private _width;
private _height;
private _dirs;
private _neighbors;
constructor(width: number, height: number, allowDiagonalSearch?: boolean);
isNodeInBounds(node: Vector2): boolean;
isNodePassable(node: Vector2): boolean;
search(start: Vector2, goal: Vector2): Vector2[];
getNeighbors(node: Vector2): Vector2[];
cost(from: Vector2, to: Vector2): number;
}
}
declare module es {
class WeightedNode<T> extends PriorityQueueNode {
data: T;
constructor(data: T);
}
class WeightedPathfinder {
static search<T>(graph: IWeightedGraph<T>, start: T, goal: T): T[];
static recontructPath<T>(cameFrom: Map<T, T>, start: T, goal: T): T[];
private static hasKey;
private static getKey;
}
}
declare module es {
class AStarStorage {
static readonly MAX_NODES: number;
_opened: AStarNode[];
_closed: AStarNode[];
_numOpened: number;
_numClosed: number;
_lastFoundOpened: number;
_lastFoundClosed: number;
constructor();
clear(): void;
findOpened(node: AStarNode): AStarNode;
findClosed(node: AStarNode): AStarNode;
hasOpened(): boolean;
removeOpened(node: AStarNode): void;
removeClosed(node: AStarNode): void;
isOpen(node: AStarNode): boolean;
isClosed(node: AStarNode): boolean;
addToOpenList(node: AStarNode): void;
addToClosedList(node: AStarNode): void;
removeCheapestOpenNode(): AStarNode;
}
}
declare module es {
class AStarNode implements IEquatable<AStarNode>, IPoolable {
worldState: WorldState;
costSoFar: number;
heuristicCost: number;
costSoFarAndHeuristicCost: number;
action: Action;
parent: AStarNode;
parentWorldState: WorldState;
depth: number;
equals(other: AStarNode): boolean;
compareTo(other: AStarNode): number;
reset(): void;
clone(): AStarNode;
toString(): string;
}
class AStar {
static storage: AStarStorage;
static plan(ap: ActionPlanner, start: WorldState, goal: WorldState, selectedNodes?: AStarNode[]): Action[];
static reconstructPlan(goalNode: AStarNode, selectedNodes: AStarNode[]): Action[];
static calculateHeuristic(fr: WorldState, to: WorldState): number;
}
}
declare module es {
class Action {
name: string;
cost: number;
_preConditions: Set<[string, boolean]>;
_postConditions: Set<[string, boolean]>;
constructor(name?: string, cost?: number);
setPrecondition(conditionName: string, value: boolean): void;
setPostcondition(conditionName: string, value: boolean): void;
validate(): boolean;
toString(): string;
}
}
declare module es {
class ActionPlanner {
static readonly MAX_CONDITIONS: number;
conditionNames: string[];
_actions: Action[];
_viableActions: Action[];
_preConditions: WorldState[];
_postConditions: WorldState[];
_numConditionNames: number;
constructor();
createWorldState(): WorldState;
addAction(action: Action): void;
plan(startState: WorldState, goalState: WorldState, selectedNode?: any): Action[];
getPossibleTransitions(fr: WorldState): AStarNode[];
applyPostConditions(ap: ActionPlanner, actionnr: number, fr: WorldState): WorldState;
findConditionNameIndex(conditionName: string): any;
findActionIndex(action: Action): number;
}
}
declare module es {
abstract class Agent {
actions: Action[];
_planner: ActionPlanner;
constructor();
plan(debugPlan?: boolean): boolean;
hasActionPlan(): boolean;
abstract getWorldState(): WorldState;
abstract getGoalState(): WorldState;
}
}
declare module es {
class WorldState implements IEquatable<WorldState> {
values: number;
dontCare: number;
planner: ActionPlanner;
static create(planner: ActionPlanner): WorldState;
constructor(planner: ActionPlanner, values: number, dontcare: number);
set(conditionId: number | string, value: boolean): boolean;
equals(other: WorldState): boolean;
describe(planner: ActionPlanner): string;
}
}
declare module es {
class DebugDefaults {
static verletParticle: number;
static verletConstraintEdge: number;
}
}
declare module es {
abstract class Component {
entity: Entity;
@@ -1137,6 +863,39 @@ declare module es {
reset(): void;
}
}
declare module es {
class Vector2 implements IEquatable<Vector2> {
x: number;
y: number;
constructor(x?: number, y?: number);
static readonly zero: Vector2;
static readonly one: Vector2;
static readonly unitX: Vector2;
static readonly unitY: Vector2;
static add(value1: Vector2, value2: Vector2): Vector2;
static divide(value1: Vector2, value2: Vector2): Vector2;
static multiply(value1: Vector2, value2: Vector2): Vector2;
static subtract(value1: Vector2, value2: Vector2): Vector2;
static normalize(value: Vector2): Vector2;
static dot(value1: Vector2, value2: Vector2): number;
static distanceSquared(value1: Vector2, value2: Vector2): number;
static clamp(value1: Vector2, min: Vector2, max: Vector2): Vector2;
static lerp(value1: Vector2, value2: Vector2, amount: number): Vector2;
static transform(position: Vector2, matrix: Matrix2D): Vector2;
static distance(value1: Vector2, value2: Vector2): number;
static angle(from: Vector2, to: Vector2): number;
static negate(value: Vector2): Vector2;
add(value: Vector2): Vector2;
divide(value: Vector2): Vector2;
multiply(value: Vector2): Vector2;
subtract(value: Vector2): this;
normalize(): void;
length(): number;
lengthSquared(): number;
round(): Vector2;
equals(other: Vector2 | object): boolean;
}
}
declare module es {
class Vector3 {
x: number;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,110 +0,0 @@
///<reference path="./PriorityQueueNode.ts" />
module es {
/**
* 计算路径给定的IAstarGraph和开始/目标位置
*/
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);
costSoFar.set(start, 0);
while (frontier.count > 0) {
let current = frontier.dequeue();
if (current.data instanceof Vector2 && goal instanceof Vector2 && current.data.equals(goal)) {
foundPath = true;
break;
} else if (current.data == 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;
}
/**
* 从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;
}
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 (r.value instanceof Vector2 && compareKey instanceof Vector2 && r.value.equals(compareKey))
return true;
else if (r.value == 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 (r.value instanceof Vector2 && compareKey instanceof Vector2 && r.value.equals(compareKey))
return v.value;
else if (r.value == compareKey)
return v.value;
}
return null;
}
}
/**
* 使用PriorityQueue需要的额外字段将原始数据封装在一个小类中
*/
class AStarNode<T> extends PriorityQueueNode {
public data: T;
constructor(data: T) {
super();
this.data = data;
}
}
}

View File

@@ -1,74 +0,0 @@
module es {
/**
* 基本静态网格图与A*一起使用
* 将walls添加到walls HashSet并将加权节点添加到weightedNodes
*/
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 => wall.equals(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) => p.equals(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,26 +0,0 @@
module es {
/**
* graph的接口可以提供给AstarPathfinder.search方法
*/
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,236 +0,0 @@
module es {
/**
* 使用堆实现最小优先级队列 O(1)复杂度
* 这种查找速度比使用字典快5-10倍
* 但是由于IPriorityQueue.contains()是许多寻路算法中调用最多的方法,因此尽可能快地实现它对于我们的应用程序非常重要。
*/
export class PriorityQueue<T extends PriorityQueueNode> {
private _numNodes: number;
private _nodes: T[];
private _numNodesEverEnqueued;
/**
* 实例化一个新的优先级队列
* @param maxNodes 允许加入队列的最大节点(执行此操作将导致undefined的行为)
*/
constructor(maxNodes: number) {
this._numNodes = 0;
this._nodes = new Array(maxNodes + 1);
this._numNodesEverEnqueued = 0;
}
/**
* 返回队列中的节点数。
* O(1)复杂度
*/
public get count() {
return this._numNodes;
}
/**
* 返回可同时进入此队列的最大项数。一旦你达到这个数字(即。一旦Count == MaxSize)尝试加入另一个项目将导致undefined的行为
* O(1)复杂度
*/
public get maxSize() {
return this._nodes.length - 1;
}
/**
* 从队列中删除每个节点。
* O(n)复杂度 所有尽可能少调用该方法
*/
public clear() {
this._nodes.splice(1, this._numNodes);
this._numNodes = 0;
}
/**
* 返回(在O(1)中)给定节点是否在队列中
* O (1)复杂度
* @param node
*/
public contains(node: T): boolean {
if (!node) {
console.error("node cannot be null");
return false;
}
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--;
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;
}
}
// 如果其中一个子节点具有更高(更小)的优先级,则交换并继续级联
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));
}
}
}

View File

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

View File

@@ -1,43 +0,0 @@
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);
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);
}
});
}
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;
}
return false;
}
}
}

View File

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

View File

@@ -1,18 +0,0 @@
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 getNeighbors(node: T) {
return this.edges.get(node);
}
}
}

View File

@@ -1,63 +0,0 @@
///<reference path="../../../Math/Vector2.ts" />
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),
];
public walls: Vector2[] = [];
private _width: number;
private _hegiht: number;
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;
}
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,16 +0,0 @@
module es {
export interface IWeightedGraph<T> {
/**
*
* @param node
*/
getNeighbors(node: T): T[];
/**
*
* @param from
* @param to
*/
cost(from: T, to: T): number;
}
}

View File

@@ -1,69 +0,0 @@
///<reference path="../../../Math/Vector2.ts" />
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),
];
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);
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;
}
}
}

View File

@@ -1,85 +0,0 @@
module es {
export class WeightedNode<T> extends PriorityQueueNode {
public data: T;
constructor(data: T) {
super();
this.data = data;
}
}
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 costSoFar = new Map<T, number>();
let frontier = new PriorityQueue<WeightedNode<T>>(1000);
frontier.enqueue(new WeightedNode<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 priprity = newCost;
frontier.enqueue(new WeightedNode<T>(next), priprity);
cameFrom.set(next, current.data);
}
});
}
return foundPath ? this.recontructPath(cameFrom, start, goal) : 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;
}
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;
}
}
}

View File

@@ -1,200 +0,0 @@
///<reference path="./AStarStorage.ts" />
module es {
export class AStarNode implements IEquatable<AStarNode>, IPoolable {
/**
* 这个节点的世界状态
*/
public worldState: WorldState;
/**
* 到目前为止的花费
*/
public costSoFar: number;
/**
* 剩余成本的启发式(不要高估!)
*/
public heuristicCost: number;
/**
* costSoFar+heuristicCost(g+h)组合
*/
public costSoFarAndHeuristicCost: number;
/**
* 与此节点相关的动作
*/
public action: Action;
/**
* 父节点
*/
public parent: AStarNode;
/**
*
*/
public parentWorldState: WorldState;
/**
*
*/
public depth: number;
/**
*
* @param other
*/
public equals(other: AStarNode): boolean {
let care = this.worldState.dontCare ^ -1;
return (this.worldState.values & care) == (other.worldState.values & care);
}
public compareTo(other: AStarNode){
return this.costSoFarAndHeuristicCost - other.costSoFarAndHeuristicCost;
}
public reset(){
this.action = null;
this.parent = null;
}
public clone(): AStarNode{
let node = new AStarNode();
node.action = this.action;
node.costSoFar = this.costSoFar;
node.depth = this.depth;
node.parent = this.parent;
node.parentWorldState = this.parentWorldState;
node.heuristicCost = this.heuristicCost;
node.worldState = this.worldState;
return node;
}
public toString(): string{
return `[cost: ${this.costSoFar} | heuristic: ${this.heuristicCost}]: ${this.action}`;
}
}
export class AStar {
public static storage: AStarStorage = new AStarStorage();
/**
* 制定达到理想世界状态的行动计划
* @param ap
* @param start
* @param goal
* @param selectedNodes
*/
public static plan(ap: ActionPlanner, start: WorldState, goal: WorldState, selectedNodes: AStarNode[] = null){
this.storage.clear();
let currentNode = Pool.obtain<AStarNode>(AStarNode);
currentNode.worldState = start;
currentNode.parentWorldState = start;
currentNode.costSoFar = 0;
currentNode.heuristicCost = this.calculateHeuristic(start, goal);
currentNode.costSoFarAndHeuristicCost = currentNode.costSoFar + currentNode.heuristicCost;
currentNode.depth = 1;
this.storage.addToOpenList(currentNode);
while(true){
// 无路可走,无路可寻
if (!this.storage.hasOpened()){
this.storage.clear();
return null;
}
currentNode = this.storage.removeCheapestOpenNode();
this.storage.addToClosedList(currentNode);
// 全部完成。 我们达到了我们的目标
if (goal.equals(currentNode.worldState)){
let plan = this.reconstructPlan(currentNode, selectedNodes);
this.storage.clear();
return plan;
}
let neighbors = ap.getPossibleTransitions(currentNode.worldState);
for (let i = 0; i < neighbors.length; i++){
let cur = neighbors[i];
let opened = this.storage.findOpened(cur);
let closed = this.storage.findClosed(cur);
let cost = currentNode.costSoFar + cur.costSoFar;
// 如果neighbors处于open状态且成本小于g(neighbors)。
if (opened != null && cost < opened.costSoFar){
// 将neighbors从OPEN中移除因为新的路径更好。
this.storage.removeOpened(opened);
opened = null;
}
// 如果neighbors在CLOSED且成本小于g(neighbors)
if (closed != null && cost < closed.costSoFar){
// 从CLOSED中删除neighbors
this.storage.removeClosed(closed);
}
// 如果neighbors不在OPENneighbors不在CLOSED。
if (opened == null && closed == null){
let nb = Pool.obtain<AStarNode>(AStarNode);
nb.worldState = cur.worldState;
nb.costSoFar = cost;
nb.heuristicCost = this.calculateHeuristic(cur.worldState, goal);
nb.costSoFarAndHeuristicCost = nb.costSoFar + nb.heuristicCost;
nb.action = cur.action;
nb.parentWorldState = currentNode.worldState;
nb.parent = currentNode;
nb.depth = currentNode.depth + 1;
this.storage.addToOpenList(nb);
}
}
ListPool.free<AStarNode>(neighbors);
}
}
/**
* 内部函数,通过从最后一个节点到初始节点的追踪来重建计划。
* @param goalNode
* @param selectedNodes
*/
public static reconstructPlan(goalNode: AStarNode, selectedNodes: AStarNode[]){
let totalActionsInPlan = goalNode.depth - 1;
let plan: Action[] = new Array(totalActionsInPlan);
let curnode = goalNode;
for (let i = 0; i <= totalActionsInPlan - 1; i ++){
// 如果我们被传递了一个节点,可以选择将该节点添加到列表中
if (selectedNodes != null)
selectedNodes.push(curnode.clone());
plan.push(curnode.action);
curnode = curnode.parent;
}
// 我们的节点从目标回到了起点,所以把它们反过来。
if (selectedNodes != null)
selectedNodes.reverse();
return plan;
}
/**
*
* @param fr
* @param to
*/
public static calculateHeuristic(fr: WorldState, to: WorldState){
let care = (to.dontCare ^ -1);
let diff = (fr.values & care) ^ (to.values & care);
let dist = 0;
for (let i = 0; i < ActionPlanner.MAX_CONDITIONS; ++i)
if ((diff & (1 << i)) != 0)
dist ++;
return dist;
}
}
}

View File

@@ -1,107 +0,0 @@
module es {
export class AStarStorage {
/**
* 我们可以存储的最大节点数
*/
public static readonly MAX_NODES = 128;
public _opened: AStarNode[] = new Array(AStarStorage.MAX_NODES);
public _closed: AStarNode[] = new Array(AStarStorage.MAX_NODES);
public _numOpened: number;
public _numClosed: number;
public _lastFoundOpened: number;
public _lastFoundClosed: number;
constructor(){}
public clear(){
for (let i = 0; i < this._numOpened; i ++){
Pool.free<AStarNode>(this._opened[i]);
this._opened[i] = null;
}
for (let i = 0; i < this._numClosed; i ++){
Pool.free<AStarNode>(this._closed[i]);
this._closed[i] = null;
}
this._numOpened = this._numClosed = 0;
this._lastFoundClosed = this._lastFoundOpened = 0;
}
public findOpened(node: AStarNode): AStarNode {
for (let i = 0; i < this._numOpened; i ++){
let care = node.worldState.dontCare ^ -1;
if ((node.worldState.values & care) == (this._opened[i].worldState.values & care)){
this._lastFoundClosed = i;
return this._closed[i];
}
}
return null;
}
public findClosed(node: AStarNode): AStarNode {
for (let i = 0; i < this._numClosed; i ++){
let care = node.worldState.dontCare ^ -1;
if ((node.worldState.values & care) == (this._closed[i].worldState.values & care)){
this._lastFoundClosed = i;
return this._closed[i];
}
}
return null;
}
public hasOpened(): boolean {
return this._numOpened > 0;
}
public removeOpened(node: AStarNode){
if (this._numOpened > 0)
this._opened[this._lastFoundOpened] = this._opened[this._numOpened - 1];
this._numOpened --;
}
public removeClosed(node: AStarNode) {
if (this._numClosed > 0)
this._closed[this._lastFoundClosed] = this._closed[this._numClosed - 1];
this._numClosed--;
}
public isOpen(node: AStarNode): boolean{
return this._opened.indexOf(node) > -1;
}
public isClosed(node: AStarNode): boolean {
return this._closed.indexOf(node) > -1;
}
public addToOpenList(node: AStarNode){
this._opened[this._numOpened++] = node;
}
public addToClosedList(node: AStarNode){
this._closed[this._numClosed++] = node;
}
/**
*
*/
public removeCheapestOpenNode(): AStarNode {
let lowestVal = Number.MAX_VALUE;
this._lastFoundOpened = -1;
for (let i = 0; i < this._numOpened; i ++){
if (this._opened[i].costSoFarAndHeuristicCost < lowestVal){
lowestVal = this._opened[i].costSoFarAndHeuristicCost;
this._lastFoundOpened = i;
}
}
var val = this._opened[this._lastFoundOpened];
this.removeOpened(val);
return val;
}
}
}

View File

@@ -1,41 +0,0 @@
module es {
export class Action {
/**
* Action的可选名称。用于调试目的
*/
public name: string;
/**
* 执行动作的成本。 改变它将会影响到计划期间的行动/选择。
*/
public cost: number = 1;
public _preConditions: Set<[string, boolean]> = new Set<[string, boolean]>();
public _postConditions: Set<[string, boolean]> = new Set<[string, boolean]>();
constructor(name?: string, cost: number = 1){
this.name = name;
this.cost = cost;
}
public setPrecondition(conditionName: string, value: boolean){
this._preConditions.add([conditionName, value]);
}
public setPostcondition(conditionName: string, value: boolean){
this._preConditions.add([conditionName, value]);
}
/**
* 在ActionPlanner进行plan之前调用。让Action有机会设置它的分数或者在没有用的情况下选择退出。
* 例如如果Action是要拿起一把枪但世界上没有枪返回false将使Action不被ActionPlanner考虑
*/
public validate(): boolean{
return true;
}
public toString(): string{
return `[Action] ${this.name} - cost: ${this.cost}`;
}
}
}

View File

@@ -1,129 +0,0 @@
module es {
export class ActionPlanner {
public static readonly MAX_CONDITIONS: number = 64;
/**
* 与所有世界状态原子相关的名称
*/
public conditionNames: string[] = new Array(ActionPlanner.MAX_CONDITIONS);
public _actions: Action[] = [];
public _viableActions: Action[] = [];
/**
* 所有行动的前提条件
*/
public _preConditions: WorldState[] = new Array(ActionPlanner.MAX_CONDITIONS);
/**
* 所有行动的后置条件(行动效果)
*/
public _postConditions: WorldState[] = new Array(ActionPlanner.MAX_CONDITIONS);
/**
* 世界状态原子的数量
*/
public _numConditionNames: number;
constructor(){
this._numConditionNames = 0;
for (let i = 0; i < ActionPlanner.MAX_CONDITIONS; ++i){
this.conditionNames[i] = null;
this._preConditions[i] = WorldState.create(this);
this._postConditions[i] = WorldState.create(this);
}
}
/**
* 读取世界状态对象的便利方法
*/
public createWorldState(): WorldState {
return WorldState.create(this);
}
public addAction(action: Action){
let actionId = this.findActionIndex(action);
if (actionId == -1)
throw new Error("无法找到或创建行动");
action._preConditions.forEach((preCondition)=>{
let conditionId = this.findConditionNameIndex(preCondition[0]);
if (conditionId == -1)
throw new Error("无法找到或创建条件名称");
this._preConditions[actionId].set(conditionId, preCondition[1]);
});
action._postConditions.forEach((postCondition)=>{
let conditionId = this.findConditionNameIndex(postCondition[0]);
if (conditionId == -1)
throw new Error("找不到条件名称");
this._postConditions[actionId].set(conditionId, postCondition[1]);
});
}
public plan(startState: WorldState, goalState: WorldState, selectedNode = null){
this._viableActions.length = 0;
for (let i = 0; i < this._actions.length; i++){
if (this._actions[i].validate())
this._viableActions.push(this._actions[i]);
}
return AStar.plan(this, startState, goalState, selectedNode);
}
public getPossibleTransitions(fr: WorldState){
let result = ListPool.obtain<AStarNode>();
for (let i = 0; i < this._viableActions.length; ++i){
let pre = this._preConditions[i];
let care = (pre.dontCare ^ -1);
let met = ((pre.values & care) == (fr.values & care));
if (met){
let node = Pool.obtain<AStarNode>(AStarNode);
node.action = this._viableActions[i];
node.costSoFar = this._viableActions[i].cost;
node.worldState = this.applyPostConditions(this, i, fr);
result.push(node);
}
}
return result;
}
public applyPostConditions(ap: ActionPlanner, actionnr: number, fr: WorldState){
let pst = ap._postConditions[actionnr];
let unaffected = pst.dontCare;
let affected = (unaffected ^ -1);
fr.values = (fr.values & unaffected) | (pst.values & affected);
fr.dontCare &= pst.dontCare;
return fr;
}
public findConditionNameIndex(conditionName: string){
let idx;
for (idx = 0; idx < this._numConditionNames; ++idx){
if (this.conditionNames[idx] == conditionName)
return idx;
}
if (idx < ActionPlanner.MAX_CONDITIONS - 1){
this.conditionNames[idx] = conditionName;
this._numConditionNames ++;
return idx;
}
return -1;
}
public findActionIndex(action: Action): number{
let idx = this._actions.indexOf(action);
if (idx > -1)
return idx;
this._actions.push(action);
return this._actions.length - 1;
}
}
}

View File

@@ -1,41 +0,0 @@
module es {
/**
* Agent提供了一个简单明了的方式来使用计划书。
* 它根本不需要使用因为它只是ActionPlanner的一个方便的封装器使其更容易获得计划和存储结果。
*/
export abstract class Agent {
public actions: Action[];
public _planner: ActionPlanner;
constructor(){
this._planner = new ActionPlanner();
}
public plan(debugPlan: boolean = false){
let nodes: AStarNode[] = null;
if (debugPlan)
nodes = [];
this.actions = this._planner.plan(this.getWorldState(), this.getGoalState(), nodes);
if (nodes != null && nodes.length > 0){
console.log("---- ActionPlanner plan ----");
console.log(`plan cost = ${nodes[nodes.length - 1].costSoFar}`);
console.log(`${" start"}\t${this.getWorldState().describe(this._planner)}`);
for (let i = 0; i < nodes.length; i++){
console.log(`${i}: ${nodes[i].action.name}\t${nodes[i].worldState.describe(this._planner)}`);
Pool.free<AStarNode>(nodes[i]);
}
}
return this.hasActionPlan();
}
public hasActionPlan(){
return this.actions != null && this.actions.length > 0;
}
public abstract getWorldState(): WorldState;
public abstract getGoalState(): WorldState;
}
}

View File

@@ -1,80 +0,0 @@
module es {
export class WorldState implements IEquatable<WorldState> {
/**
* 我们使用条件索引上的位掩码移位来翻转位。
*/
public values: number;
/**
* 比特掩码用于明确表示false。
* 我们需要一个单独的负值存储空间,因为一个值的缺失并不一定意味着它是假的
*/
public dontCare: number;
/**
* 是必需的,这样我们就可以从字符串名称中获取条件索引
*/
public planner: ActionPlanner;
/**
*
* @param planner
*/
public static create(planner: ActionPlanner): WorldState {
return new WorldState(planner, 0, -1);
}
/**
*
* @param planner
* @param values
* @param dontcare
*/
constructor(planner: ActionPlanner, values: number, dontcare: number){
this.planner = planner;
this.values = values;
this.dontCare = dontcare;
}
public set(conditionId: number | string, value: boolean): boolean {
if (typeof conditionId == "string"){
return this.set(this.planner.findConditionNameIndex(conditionId), value);
}
this.values = value ? (this.values | (1 << conditionId)) : (this.values & ~(1 << conditionId));
this.dontCare ^= (1 << conditionId);
return true;
}
/**
*
* @param other
*/
public equals(other: WorldState): boolean {
let care = this.dontCare ^ -1;
return (this.values & care) == (other.values & care);
}
/**
* 用于调试目的。提供一个包含所有前提条件的可读字符串
* @param planner
*/
public describe(planner: ActionPlanner): string {
let s = "";
for (let i = 0; i < ActionPlanner.MAX_CONDITIONS; i ++){
if ((this.dontCare & (1 << i)) == 0){
let val = planner.conditionNames[i];
if (val == null)
continue;
let set = ((this.values & (1 << i)) != 0);
if (s.length > 0)
s += ", ";
s += (set ? val.toUpperCase() : val);
}
}
return s;
}
}
}

View File

@@ -1,6 +0,0 @@
module es {
export class DebugDefaults {
public static verletParticle = 0xDC345E;
public static verletConstraintEdge = 0x433E36;
}
}