-
Notifications
You must be signed in to change notification settings - Fork 69
/
Copy pathastar.js
125 lines (110 loc) · 3.63 KB
/
astar.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
const { performance } = require('perf_hooks')
const Heap = require('./heap.js')
class PathNode {
constructor () {
this.data = null
this.g = 0
this.h = 0
this.f = 0
this.parent = null
}
set (data, g, h, parent = null) {
this.data = data
this.g = g
this.h = h
this.f = g + h
this.parent = parent
return this
}
}
function reconstructPath (node) {
const path = []
while (node.parent) {
path.push(node.data)
node = node.parent
}
return path.reverse()
}
class AStar {
constructor (start, movements, goal, timeout, tickTimeout = 40, searchRadius = -1) {
this.startTime = performance.now()
this.movements = movements
this.goal = goal
this.timeout = timeout
this.tickTimeout = tickTimeout
this.closedDataSet = new Set()
this.openHeap = new Heap()
this.openDataMap = new Map()
const startNode = new PathNode().set(start, 0, goal.heuristic(start))
this.openHeap.push(startNode)
this.openDataMap.set(startNode.data.hash, startNode)
this.bestNode = startNode
this.maxCost = searchRadius < 0 ? -1 : startNode.h + searchRadius
this.visitedChunks = new Set()
}
makeResult (status, node) {
return {
status,
cost: node.g,
time: performance.now() - this.startTime,
visitedNodes: this.closedDataSet.size,
generatedNodes: this.closedDataSet.size + this.openHeap.size(),
path: reconstructPath(node),
context: this
}
}
compute () {
const computeStartTime = performance.now()
while (!this.openHeap.isEmpty()) {
if (performance.now() - computeStartTime > this.tickTimeout) { // compute time per tick
return this.makeResult('partial', this.bestNode)
}
if (performance.now() - this.startTime > this.timeout) { // total compute time
return this.makeResult('timeout', this.bestNode)
}
const node = this.openHeap.pop()
if (this.goal.isEnd(node.data)) {
return this.makeResult('success', node)
}
// not done yet
this.openDataMap.delete(node.data.hash)
this.closedDataSet.add(node.data.hash)
this.visitedChunks.add(`${node.data.x >> 4},${node.data.z >> 4}`)
const neighbors = this.movements.getNeighbors(node.data)
for (const neighborData of neighbors) {
if (this.closedDataSet.has(neighborData.hash)) {
continue // skip closed neighbors
}
const gFromThisNode = node.g + neighborData.cost
let neighborNode = this.openDataMap.get(neighborData.hash)
let update = false
const heuristic = this.goal.heuristic(neighborData)
if (this.maxCost > 0 && gFromThisNode + heuristic > this.maxCost) continue
if (neighborNode === undefined) {
// add neighbor to the open set
neighborNode = new PathNode()
// properties will be set later
this.openDataMap.set(neighborData.hash, neighborNode)
} else {
if (neighborNode.g < gFromThisNode) {
// skip this one because another route is faster
continue
}
update = true
}
// found a new or better route.
// update this neighbor with this node as its new parent
neighborNode.set(neighborData, gFromThisNode, heuristic, node)
if (neighborNode.h < this.bestNode.h) this.bestNode = neighborNode
if (update) {
this.openHeap.update(neighborNode)
} else {
this.openHeap.push(neighborNode)
}
}
}
// all the neighbors of every accessible node have been exhausted
return this.makeResult('noPath', this.bestNode)
}
}
module.exports = AStar