-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
706 additions
and
297 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,10 @@ | ||
|
||
# git ignore | ||
|
||
node_modules* | ||
npm-debug.log | ||
node_modules | ||
|
||
.~lock* | ||
*-error.log | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,51 +1,36 @@ | ||
# abstract-pathfinder | ||
|
||
Completely agnostic A* pathfinding. | ||
Type-agnostic A\* pathfinding. | ||
|
||
Doesn't care how your data is structured. Just implement a minimal set of accessors, | ||
and this module will apply [A*](https://en.wikipedia.org/wiki/A*_search_algorithm) | ||
Doesn't care how your data is structured - just implement a minimal set of accessors, | ||
and this module will apply [A\*](https://en.wikipedia.org/wiki/A*_search_algorithm) | ||
to whatever data or graph you're using. | ||
|
||
Now built in typescript! Takes any arbitrary type for the graph nodes. | ||
|
||
### Installation: | ||
|
||
```shell | ||
npm i --save abstract-pathfinder | ||
pnpm i abstract-pathfinder | ||
``` | ||
|
||
### Usage: | ||
|
||
```js | ||
var Pathfinder = require('abstract-pathfinder') | ||
var finder = new Pathfinder() | ||
|
||
// implement stuff | ||
finder.nodeToPrimitive = function (node) { ... } | ||
finder.getNeighbors = function (node) { ... } | ||
finder.getMovementCost = function (nodeA, nodeB) { ... } | ||
finder.getHeuristic = function (nodeA, nodeB) { ... } | ||
|
||
// specify two nodes and find a path | ||
var path = finder.findPath(startNode, endNode) | ||
``` | ||
```ts | ||
import { Pathfinder } from 'abstract-pathfinder' | ||
|
||
Note that `node` here means an whatever you're using to represent | ||
positions in the graph you're findind a path through. | ||
Nodes can be any type, as long as you implement the methods consistently. | ||
type MyNodeType = { x: number; y: number } // or whatever | ||
|
||
Details: | ||
const finder = new Pathfinder<MyNodeType>({ | ||
nodeToPrimitive: (node) => 'a', // unique key for each node | ||
getNeighbors: (node) => [], // array of neighboring nodes | ||
getMoveCost: (a, b) => 1 , // move cost between neighboring nodes | ||
getHeuristic: (a, b) => 1, // guess at move cost between arbitrary nodes | ||
}) | ||
|
||
* `nodeToPrimitive` should return a **string or number** (uniquely) for the given node | ||
* `getNeighbors` should return an **array of nodes** directly accessible from the given one | ||
* `getMovementCost` should return a **number** for the cost of moving between neighboring nodes. | ||
Negative costs mean that movement is impossible. | ||
* `getHeuristic` should return a [A* heuristic](http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html) **number**. | ||
Typically this means a lower limit of the total cost of moving between the two (not necessarily neighboring) nodes. | ||
* `findPath` returns an array of nodes, or `[]` if no path was found | ||
|
||
|
||
See the `examples` folder for some working demos. | ||
Example 1 uses strings for nodes; Example 2 uses objects. | ||
const path = finder.findPath(start, end) // array of nodes, or [] if no path found | ||
``` | ||
|
||
### By: | ||
|
||
Copyright Andy Hall, 2016. MIT license. | ||
Made with 🍺 by [fenomas](https://fenomas.com). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
interface Methods<T> { | ||
nodeToPrimitive: (node: T) => PropertyKey; | ||
getNeighbors: (node: T) => T[]; | ||
getMovementCost: (a: T, b: T) => number; | ||
getHeuristic: (a: T, b: T) => number; | ||
} | ||
export declare class Pathfinder<T> { | ||
methods: Methods<T>; | ||
maxIterations: number; | ||
constructor(params?: Partial<Methods<T>>); | ||
findPath(start: T, goal: T): T[]; | ||
} | ||
export {}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Pathfinder = void 0; | ||
const ts_priority_queue_1 = require("ts-priority-queue"); | ||
const wrapNode = (node, previous, f = 0, g = 0, open = true) => { | ||
return { node, previous, f, g, open }; | ||
}; | ||
class Pathfinder { | ||
methods; | ||
maxIterations = 1e6; | ||
constructor(params = {}) { | ||
this.methods = { | ||
nodeToPrimitive: (node) => node, | ||
getNeighbors: () => [], | ||
getMovementCost: () => 1, | ||
getHeuristic: () => 1, | ||
...params, | ||
}; | ||
} | ||
findPath(start, goal) { | ||
const startKey = this.methods.nodeToPrimitive(start); | ||
const goalKey = this.methods.nodeToPrimitive(goal); | ||
const open = new ts_priority_queue_1.default({ comparator: (a, b) => a.f - b.f }); | ||
const visited = {}; | ||
const startNode = wrapNode(start, null, 0, 0, true); | ||
open.queue(startNode); | ||
visited[startKey] = startNode; | ||
let ct = 0; | ||
while (open.length > 0) { | ||
if (ct++ > this.maxIterations) | ||
throw new Error('Infinite loop!'); | ||
const curr = open.dequeue(); | ||
if (!curr.open) | ||
continue; | ||
const currKey = this.methods.nodeToPrimitive(curr.node); | ||
if (currKey === goalKey) | ||
break; | ||
curr.open = false; | ||
this.methods.getNeighbors(curr.node).forEach((neighbor) => { | ||
const moveCost = this.methods.getMovementCost(curr.node, neighbor); | ||
if (moveCost < 0) | ||
return; | ||
const neighborKey = this.methods.nodeToPrimitive(neighbor); | ||
const nnode = visited[neighborKey]; | ||
const cost = curr.g + moveCost; | ||
if (nnode && nnode.g <= cost) | ||
return; | ||
const h = this.methods.getHeuristic(neighbor, goal); | ||
if (nnode) { | ||
nnode.open = true; | ||
nnode.g = cost; | ||
nnode.f = cost + h; | ||
nnode.previous = curr; | ||
open.queue(nnode); | ||
} | ||
else { | ||
const newnode = wrapNode(neighbor, curr, cost + h, cost, true); | ||
visited[neighborKey] = newnode; | ||
open.queue(newnode); | ||
} | ||
}); | ||
} | ||
open.clear(); | ||
const end = visited[goalKey]; | ||
if (!end) | ||
return []; | ||
const path = [end.node]; | ||
let p = end; | ||
while (p.previous) { | ||
path.push(p.previous.node); | ||
p = p.previous; | ||
} | ||
return path.reverse(); | ||
} | ||
} | ||
exports.Pathfinder = Pathfinder; |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.