移除astar库转为插件
This commit is contained in:
307
source/bin/framework.d.ts
vendored
307
source/bin/framework.d.ts
vendored
@@ -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
2
source/bin/framework.min.js
vendored
2
source/bin/framework.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
module es {
|
||||
export class PriorityQueueNode {
|
||||
/**
|
||||
* 插入此节点的优先级。在将节点添加到队列之前必须设置
|
||||
*/
|
||||
public priority: number = 0;
|
||||
/**
|
||||
* 由优先级队列使用-不要编辑此值。表示插入节点的顺序
|
||||
*/
|
||||
public insertionIndex: number = 0;
|
||||
/**
|
||||
* 由优先级队列使用-不要编辑此值。表示队列中的当前位置
|
||||
*/
|
||||
public queueIndex: number = 0;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
module es {
|
||||
export interface IUnweightedGraph<T> {
|
||||
/**
|
||||
* getNeighbors方法应该返回从传入的节点可以到达的任何相邻节点。
|
||||
* @param node
|
||||
*/
|
||||
getNeighbors(node: T): T[];
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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不在OPEN,neighbors不在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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
module es {
|
||||
export class DebugDefaults {
|
||||
public static verletParticle = 0xDC345E;
|
||||
public static verletConstraintEdge = 0x433E36;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user