移除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>;
|
groupBy(keySelector: Function): Array<T>;
|
||||||
sum(selector: Function): number;
|
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 {
|
declare module es {
|
||||||
abstract class Component {
|
abstract class Component {
|
||||||
entity: Entity;
|
entity: Entity;
|
||||||
@@ -1137,6 +863,39 @@ declare module es {
|
|||||||
reset(): void;
|
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 {
|
declare module es {
|
||||||
class Vector3 {
|
class Vector3 {
|
||||||
x: number;
|
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