-
Notifications
You must be signed in to change notification settings - Fork 4
/
astar.go
150 lines (130 loc) · 3.83 KB
/
astar.go
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package pathing
// AStar implements an A* search pathfinding algorithm.
// You must use NewAStar() function to obtain an instance of this type.
//
// AStar is a bit slower than GreedyBFS, but its results can be more optimal.
// It also supports a proper weight/cost based pathfinding.
//
// Once created, you should re-use it to build paths.
// Do not throw the instance away after building the path once.
type AStar struct {
frontier *minheap[astarCoord]
costmap *coordMap
pathmap *coordMap
}
type AStarConfig struct {
// NumCols and NumRows are size hints for the AStar constructor.
// Grid.NumCols() and Grid.NumRows() methods will come in handy to initialize these.
// If you keep them at 0, the max amount of the working space will be allocated.
// It's like a size hint: the constructor may allocate a smaller working area
// if the grids you're going operate on are small.
NumCols uint
NumRows uint
}
type astarCoord struct {
Coord GridCoord
Weight int32
Cost int32
}
// NewAStar creates a ready-to-use AStar object.
func NewAStar(config AStarConfig) *AStar {
if config.NumCols == 0 {
config.NumCols = gridMapSide
}
if config.NumRows == 0 {
config.NumRows = gridMapSide
}
coordMapCols := gridMapSide
if int(config.NumCols) < coordMapCols {
coordMapCols = int(config.NumCols)
}
coordMapRows := gridMapSide
if int(config.NumRows) < coordMapRows {
coordMapRows = int(config.NumRows)
}
astar := &AStar{
frontier: newMinheap[astarCoord](32),
pathmap: newCoordMap(coordMapCols, coordMapRows),
costmap: newCoordMap(coordMapCols, coordMapRows),
}
return astar
}
// BuildPath attempts to find a path between the two coordinates.
// It will use a provided Grid in combination with a GridLayer.
// The Grid is expected to store the tile tags and the GridLayer is
// used to interpret these tags.
func (astar *AStar) BuildPath(g *Grid, from, to GridCoord, l GridLayer) BuildPathResult {
var result BuildPathResult
if from == to {
result.Finish = to
return result
}
origin := findPathOrigin(from)
localStart := from.Sub(origin)
localGoal := to.Sub(origin)
frontier := astar.frontier
frontier.Reset()
pathmap := astar.pathmap
pathmap.Reset()
costmap := astar.costmap
costmap.Reset()
frontier.Push(0, astarCoord{Coord: localStart})
shortestDist := 0xffffffff
var fallbackCoord GridCoord
var fallbackCost int
foundPath := false
for !frontier.IsEmpty() {
current := frontier.Pop()
if current.Coord == localGoal {
result.Steps = constructPath(localStart, localGoal, pathmap)
result.Finish = to
result.Cost = int(current.Cost)
foundPath = true
break
}
if current.Weight > gridPathMaxLen {
break
}
dist := localGoal.Dist(current.Coord)
if dist < shortestDist {
shortestDist = dist
fallbackCoord = current.Coord
fallbackCost = int(current.Cost)
}
currentCost, _ := costmap.Get(costmap.packCoord(current.Coord))
for dir, offset := range &neighborOffsets {
next := current.Coord.Add(offset)
cx := uint(next.X) + uint(origin.X)
cy := uint(next.Y) + uint(origin.Y)
if cx >= g.numCols || cy >= g.numRows {
continue
}
nextCellCost := g.getCellCost(cx, cy, l)
if nextCellCost == 0 {
continue
}
newNextCost := currentCost + uint32(nextCellCost)
k := costmap.packCoord(next)
oldNextCost, ok := costmap.Get(k)
if ok && newNextCost >= oldNextCost {
continue
}
costmap.Set(k, newNextCost)
priority := newNextCost + uint32(localGoal.Dist(next))
nextWeighted := astarCoord{
Coord: next,
Cost: int32(newNextCost),
Weight: int32(current.Weight + 1),
}
frontier.Push(int(priority), nextWeighted)
pathmap.Set(k, uint32(dir))
}
}
if !foundPath {
result.Steps = constructPath(localStart, fallbackCoord, pathmap)
result.Finish = fallbackCoord.Add(origin)
result.Cost = fallbackCost
result.Partial = true
}
return result
}