Skip to content

Commit

Permalink
convert to typescript
Browse files Browse the repository at this point in the history
  • Loading branch information
fenomas committed Oct 18, 2024
1 parent db33fae commit 9b5a8af
Show file tree
Hide file tree
Showing 13 changed files with 706 additions and 297 deletions.
10 changes: 9 additions & 1 deletion .gitignore
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


51 changes: 18 additions & 33 deletions README.md
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).
13 changes: 13 additions & 0 deletions dist/index.d.ts
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 {};
76 changes: 76 additions & 0 deletions dist/index.js
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;
25 changes: 0 additions & 25 deletions examples/example1.js

This file was deleted.

56 changes: 0 additions & 56 deletions examples/example2.js

This file was deleted.

67 changes: 0 additions & 67 deletions examples/test.js

This file was deleted.

Loading

0 comments on commit 9b5a8af

Please sign in to comment.