Skip to content

Commit

Permalink
16.12.2018
Browse files Browse the repository at this point in the history
Initial README writeup
  • Loading branch information
Suficio committed Dec 16, 2018
1 parent e4079d3 commit 3ece51e
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 27 deletions.
1 change: 1 addition & 0 deletions DefaultConditions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#Default Condition
12 changes: 7 additions & 5 deletions Pathfinders/ASTAR.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ function State(p)
function ASTARReturnState(MainPromise)
{
const ReturnState = this;
this.on = function(Callback) {MainPromise.then(function() {Callback(ReturnState);});};
this.on = function(Callback)
{
MainPromise.then(function() {Callback(ReturnState);});
};

MainPromise.then(function(IntermediateObject)
{
Expand All @@ -25,8 +28,7 @@ function ASTARReturnState(MainPromise)
Path.push(State.p);
}
ReturnState.path = Path;
})
.catch(function() {return;});
}).catch(function() {return;});
}

module.exports = function(bot, sp, ep)
Expand Down Expand Up @@ -91,7 +93,7 @@ module.exports = function(bot, sp, ep)
O.push = O.add;
O.pop = O.poll;

const MainPromise = new Promise(function(resolve, reject)
const MainPromise = new Promise(function(resolve)
{
// Maintains the element with the best path
let closest = new State(sp.floored());
Expand Down Expand Up @@ -133,7 +135,7 @@ module.exports = function(bot, sp, ep)
if (current.f - current.g < closest.f - closest.g) closest = current;
};

console.log('WARNING: Did not find path in allowed MAX_EXPANSIONS, returned closest path');
console.log('WARNING Pathfinder: Did not find path in allowed MAX_EXPANSIONS, returned closest path');
return resolve({ENUMStatus: bot.pathfinder.ENUMStatus.Incomplete, State: closest});
});

Expand Down
32 changes: 17 additions & 15 deletions Pathfinders/DLITE/DLITEReturnState.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,22 +142,24 @@ function Initialize(bot, sp, ep, UpdateVertex, ComputeShortestPath)
this.on = function(Callback)
{
// ComputeShortestPath has to be run initially
this.ComputeShortestPath().then(function(ReturnState)
{
R.ENUMStatus = ReturnState.ENUMStatus;
// Should the path be incomplete, sets the start point to the closest point to the intended
// start point, to which a replan can be attempted using a different algorithm.
if (ReturnState.ENUMStatus === bot.pathfinder.ENUMStatus.Incomplete)
const MainPromise = this.ComputeShortestPath();
MainPromise
.then(function(ReturnState)
{
console.warn(
'WARNING: Did not find path in allowed MAX_EXPANSIONS, returned closest valid start point',
'Use another algorithm to reach the valid start point before attempting D*Lite'
);
R.S.start = ReturnState.State;
}

Callback(R);
});
R.ENUMStatus = ReturnState.ENUMStatus;
// Should the path be incomplete, sets the start point to the closest point to the intended
// start point, to which a replan can be attempted using a different algorithm.
if (ReturnState.ENUMStatus === bot.pathfinder.ENUMStatus.Incomplete)
{
console.warn(
'WARNING Pathfinder: Did not find path in allowed MAX_EXPANSIONS, returned closest valid start point',
'Use another algorithm to reach the valid start point before attempting D*Lite'
);
R.S.start = ReturnState.State;
}

Callback(R);
});
};
};

Expand Down
61 changes: 61 additions & 0 deletions Pathfinders/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Algorithm Documentation
## A* Pathfinding
Standard A* algorithim as per Peter E. Hart, Nils J. Nilsson, Bertram Raphael, 1968.
Should you want to learn how the algorithm works: https://en.wikipedia.org/wiki/A*_search_algorithm

### ASTARReturnState
Object with the following properties:
* `ENUMStatus` - Provides the respective `bot.pathfinder.ENUMStatus` based on the outcome of the path search.
* `path` - Array of points which form the entirety of the path.

#### ASTARReturnState.on( Callback)
Provides a function to be executed when the path search has completed

* `Callback` - Function to be executed once the path search has completed, `ASTARReturnState` passed as argument.


## D* Lite Pathfinding
D* Lite as per S. Koenig, 2002.
Before using the algorithm it is important to familiarize yourself with its function at: http://idm-lab.org/bib/abstracts/papers/aaai02b.pdf.

The pathfinder module exposes two different D* Lite implementations. One which is standard D* Lite implementation as presented in Figure 3 of the paper, and the other which is an optimized variant, presented in Figure 4.
I have left the unoptimized variant in the code should anyone wish to familiarize themselves with how it works.

The advantage of using D* Lite is it precomputes the global state between the start and end point, this allows for convenient changes of costs at runtime, which can be used for entity avoidance.

However it requires the use of the `getPredecessors` function. When supplying your own `getSuccessors` and `getPredecessors` functions, ensure that both functions always return the exact same set of neighbours respectively, especially when using the optimized variant of D* Lite. The unoptimized version has some more leeway in handling any inconsistencies between `getSuccessors` and `getPredecessors`.

### DLITEReturnState
Object with the following properties:
* `ENUMStatus` - Provides the respective `bot.pathfinder.ENUMStatus` based on the outcome of the path search.
* `path` - See `DLITEReturnState.path`

#### ASTARReturnState.on( Callback)
Provides a function to be executed when the path search has completed

* `Callback` - Function to be executed once the path search has completed, `DLITEReturnState` passed as argument.

#### ASTARReturnState.path.pop()
As the path is determined while the bot moves across it, pop must be used to determine the next location to move to.

Returns position vector.

#### ASTARReturnState.path.peek()
Determines which path element will be popped next.

Returns position vector.

#### ASTARReturnState.path.replan([startPoint, endPoint]) /[Not implemented]


## JPS A* Pathfinding \[Not implemented]
Jump Point Search as per Daniel Harabor, Alban Grastien, 2011.
Should you want to learn how the algorithm works: http://users.cecs.anu.edu.au/~dharabor/data/papers/harabor-grastien-aaai11.pdf.

I am including this here as there was some conversation about using JPS in the past (PrismarineJS/mineflayer-navigate#20). It should be noted that JPS is a method to replace the `getSuccessors` function, and then chooses which neighbour to go to using A*.

It is also important to note that most of the movement in minecraft is done on the horizontal plane, so in effect 2D. This can be used to generalize the algorithim to a 2D version with additional checks whether the bot can change its y-level at a point.

The significant speed improvement of JPS comes from quickly checking which blocks it can move to, however it is based on the assumption that you can check what kind of block exists at a coordinate very quickly. To achieve this i wrote `bot.pathfinder.getBlock`, however I was not able to get JPS running faster than normal A*, hence why it is not included.

Should anyone be inclined to attempt to implement JPS, you can do so by replacing `bot.pathfinder.getSuccessors` with your JPS function for obtaining neighbours.
92 changes: 91 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,91 @@
# Mineflayer-Navigate
# Mineflayer-Pathfinder
Fast, promise based, 3D pathfinding library using A* and D*Lite algorithms, for Mineflayer found under: [https://github.com/superjoe30/mineflayer/](https://github.com/superjoe30/mineflayer/)

## Features

* Provides high level API for determining paths between two points
* Multiple algorithms for pathfinding, A* and D*Lite
* Exposed internal functions to allow easier user replacement
* Based solely on a promise based API

## Table of Contents

## Basic Usage
To get started just paste this code into your bot:
```js
const mineflayer = require('mineflayer');
const pathfinder = require('mineflayer-pathfinder');

// Install pathfinder
pathfinder(bot);

bot.on('chat', function(username, message)
{
// Find path to whoever talked
if (message === 'come')
{
bot.pathfinder
.to(
bot.entity.position,
bot.players[username].entity.position
)
.on(function(ReturnState)
{
const path = ReturnState.path;
// Move bot along path and youre done!
});
}
}
```
## Advanced Usage
## Documentation
### bot.pathfinder.to( startPoint, endPoint [, ENUMPathfinder])
Attempts a path search from the start point to the end point using the provided pathfinder.
* `startPoint` - the point from which you want to find the path
* `endPoint` - the end point of the path
* `ENUMPathfinder` - specifies which pathfinding algorithim to use, see `bot.pathfinder.ENUMPathfinder`
* Defaults to `bot.pathfinder.ENUMPathfinder.ASTAR`
Returns a return object based on the `ENUMPathfinder` provided, see Algorithim Documentation.
### bot.pathfinder.getSuccessors( position)
Determines positions to which the bot can move from the given position. There is some discussion about how the default function for `getSuccessors` works at [Default Conditions](https://github.com/CheezBarger/Mineflayer-Pathfinder/tree/master/DefaultConditions)
`position` - coordinates of the position you want to move from
### bot.pathfinder.getPredecessors( position)
Determines positions from which the bot could have moved to the given position.
`position` - coordinates of the position you want to move to
### bot.pathfinder.getBlock( position)
Slightly faster version of `bot.blockAt`
`position` - coordinates of the block from which you want to get data
### bot.pathfinder.MAX_EXPANSIONS
Integer values which determines the maximum ammount of positions an algorithim will inspect, defaults to 80000.
### bot.pathfinder.HEURISTIC( startPoint, EndPoint)
Determines the heuristic value from the `startPoint` to the `endPoint`. Defaults to euclidean distance.
### bot.pathfinder.COST
Determines the cost value from the `startPoint` to the `endPoint`. Defaults to `bot.pathfinder.HEURISTIC`.
### bot.pathfinder.ENUMPathfinder
Object with the following properties:
* `ASTAR` - Refers to the standard A* algorithm, see A* Pathfinding
* `DLITE` - Refers to the optimized D* lite algorithm, see D* Lite Pathfinding
* `UDLITE` - Refers to the unoptimized D* lite algorithm, see D* Lite Pathfinding
### bot.pathfinder.ENUMStatus
Object with the following properties:
* `Complete` - Occurs when the path could be successfully computed
* `Incomplete` - Occurs when the pathfinder encountered an error or could not compute the complete path
## Algorithm Documentation
Detailed overview of the algorithms used avaliable at [Algorithm Documentation](https://github.com/CheezBarger/Mineflayer-Pathfinder/tree/master/Pathfinders)
11 changes: 6 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ const Path = require('path');

module.exports = function(bot)
{
const mcData = require('minecraft-data')(bot.version);
blocks = mcData.blocks;
blocksByStateId = mcData.blocksByStateId;

// The greatest speedup that can be achieved at this point would be by optimizing the CheckBlockConditions function. It was purposefully built to be dynamic however
// a hardcoded implementation could work so much much faster. That being said the successorconditions.json is not optimized either, one could implement a top
// to bottom search considering the fact that once a block at the top is found, none of the blocks at the bottom will be accessible.
Expand All @@ -25,6 +21,7 @@ module.exports = function(bot)
bot.pathfinder.getPredecessors = gMS.bind(undefined, require(Path.resolve(__dirname, 'DefaultConditions/predecessorConditions.json')));

// Native getBlock implementation too slow for this case
let blocks;
bot.pathfinder.getBlock = function(absolutePoint)
{
// Get block cannot correctly identify the ammount of layers of snow at any block
Expand All @@ -37,6 +34,11 @@ module.exports = function(bot)
// Main function to interact
bot.pathfinder.to = function(Start, End, ENUMPathfinder)
{
if (bot.version) // bot.version may not be yet initialized when run
blocks = require('minecraft-data')(bot.version).blocks;
else
return console.error('ERROR Pathfinder: Bot not yet initialized when pathfinder was run, ensure that bot.version is initialized');

if (!ENUMPathfinder || ENUMPathfinder === bot.pathfinder.ENUMPathfinder.ASTAR)
return require(Path.resolve(__dirname, 'Pathfinders/ASTAR.js'))(bot, Start.floored(), End.floored());

Expand All @@ -47,7 +49,6 @@ module.exports = function(bot)
return require(Path.resolve(__dirname, 'Pathfinders/DLITE/UDLITE.js'))(bot, Start.floored(), End.floored());
};

// bot.pathfind.SCAFFOLD = false;
bot.pathfinder.MAX_EXPANSIONS = 80000; // 80000
bot.pathfinder.HEURISTIC = function(p1, p2) {return p1.distanceTo(p2);};
bot.pathfinder.COST = bot.pathfinder.HEURISTIC;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "mineflayer-pathfinder",
"version": "0.0.1",
"description": "Library for 3D pathfinding through mineflayer bot using A* and D*Lite algorithms.",
"description": "Fast, promise based, 3D pathfinding library for Mineflayer using A* and D*Lite algorithms.",
"main": "index.js",
"scripts": {
},
Expand Down

0 comments on commit 3ece51e

Please sign in to comment.