Generates path from start to end using A* pathfinding algorithm.
IF YOU WANT A PATH TO AN INTERACTABLE UNIT (Constructing a geyser, mining minerals, etc.), SET isInteractableEndpoint AS TRUE
Using the A* algorithm, this function steps through BWAPI::TilePositions on the map to find an optimal path to the target position.
This A* algorithm makes use of the precached paths between areas and chokepoints to optimize pathfinding.
If the start and end positions are in different areas, it will first find the path between the areas using the cached paths, then generate subpaths between the chokepoints in the area path, and finally generate subpaths from the start position to the first chokepoint and from the last chokepoint to the end position.
Heuristic: BWAPI's getApproxDistance() multiplied by a heuristic weight. This is because BWAPI's getApproxDistance() is optimized and runs in O(1) time, so we can afford to use it as a heuristic even if it sacrifices some accuracy.
Walkability: Checks tile walkability using tileWalkable() function. This function uses the unitType to get the unit's size and checks if any objects on the tile will collide with the unit.
21 {
22 Timer totalTimer = Timer();
23 totalTimer.start();
24#ifdef DEBUG_PATH
25 rectCoordinates.clear();
26 closedTiles.clear();
27 earlyExpansionTiles.clear();
28#endif
29
30 vector<BWAPI::Position> positions = vector<BWAPI::Position>();
31 int finalDistance = 0;
32
33
34 if (!_start.isValid() || !_end.isValid()) {
35 cout << "ERROR: Starting point or ending point not valid" << endl;
36 return Path();
37 }
38
39
40 if (unitType.isFlyer()) {
41 positions.push_back(_end);
42 finalDistance = _start.getApproxDistance(_end);
43 return Path(positions, finalDistance);
44 }
45
46 BWAPI::TilePosition start = BWAPI::TilePosition(_start);
47 const BWAPI::TilePosition end = BWAPI::TilePosition(_end);
48
49
50
51
52 priority_queue<Node, vector<Node>, greater<Node>> openSet;
53
54 unordered_set<BWAPI::TilePosition, TilePositionHash> openSetNodes;
55
56
57
58 const int mapWidth = BWAPI::Broodwar->mapWidth();
59 const int mapHeight = BWAPI::Broodwar->mapHeight();
60 vector<bool> closedSet(mapWidth * mapHeight, false);
61 vector<int> gCostMap(mapWidth * mapHeight, INT_MAX);
62
63
64 unordered_map<int, BWAPI::TilePosition> parent;
65
66
67 Node firstNode = Node(start, start, 0, 0, 0);
68 openSet.push(firstNode);
69 openSetNodes.insert(start);
71 Node currentNode = Node();
72
73 bool earlyExpansion = false;
74 while (openSet.size() > 0) {
75
76 if (TIME_LIMIT_ENABLED && totalTimer.getElapsedTimeInMilliSec() > TIME_LIMIT_MS) {
77#ifdef DEBUG_PATH
78 cout << "TIME LIMIT EXCEEDED in main path: " << totalTimer.getElapsedTimeInMilliSec() << endl;
79#endif
80 totalTimer.stop();
81 return Path();
82 }
83
84 if (!earlyExpansion) {
85 currentNode = openSet.top();
86 openSet.pop();
87 openSetNodes.erase(currentNode.tile);
88 }
90 closedTiles.push_back(std::make_pair(currentNode.tile, currentNode.fCost));
91
92
93 if (currentNode.tile == end) {
94 BWAPI::Position prevPos = BWAPI::Position(currentNode.tile);
95 BWAPI::Position dir;
96 BWAPI::Position currentWaypoint;
97 BWAPI::Position prevWaypoint;
98
99 while (currentNode.tile != start) {
100 if (isInteractableEndpoint && currentNode.tile == end) {
101 positions.push_back(BWAPI::Position(currentNode.tile));
102 }
103 else {
104 positions.push_back(BWAPI::Position(currentNode.tile) + BWAPI::Position(16, 16));
105 }
106 currentNode.tile = parent[
TileToIndex(currentNode.tile)];
107 const BWAPI::Position currentPos = BWAPI::Position(currentNode.tile);
108
109 if (positions.size() == 2) {
110 dir = positions.at(1) - positions.at(0);
111 finalDistance += positions.at(1).getApproxDistance(positions.at(0));
112 }
113 else if (positions.size() > 2) {
114 BWAPI::Position currentWaypoint = positions.at(positions.size() - 1);
115 BWAPI::Position prevWaypoint = positions.at(positions.size() - 2);
116 const BWAPI::Position newDir = currentWaypoint - prevWaypoint;
117 const double currentDistance = currentWaypoint.getApproxDistance(prevWaypoint);
118
119 if (dir == newDir) {
120 finalDistance += currentWaypoint.getApproxDistance(positions.at(positions.size() - 3));
121 positions.erase(positions.end() - 2);
122 }
123 else {
124 dir = newDir;
125 finalDistance += currentDistance;
126 }
127 }
128
129 const BWAPI::Unit closestObstacle = BWAPI::Broodwar->getClosestUnit(currentWaypoint, (BWAPI::Filter::IsBuilding || BWAPI::Filter::IsSpecialBuilding), unitType.width() / 2 + 5);
130 if (closestObstacle != nullptr && closestObstacle->exists()) {
131 const BWAPI::Position buildingDir = closestObstacle->getPosition() - currentWaypoint;
132 int xOffset = 0;
133 int yOffset = 0;
134
135 if (buildingDir.x < 0) {
136 xOffset += 5;
137 }
138 if (buildingDir.y < 0) {
139 yOffset += 5;
140 }
141
142 if (buildingDir.x > 0) {
143 xOffset -= 5;
144 }
145 if (buildingDir.y > 0) {
146 yOffset -= 5;
147 }
148
149 const BWAPI::Position newWaypoint = BWAPI::Position(currentWaypoint.x + xOffset, currentWaypoint.y + yOffset);
150 dir = newWaypoint - prevWaypoint;
151 positions.erase(positions.end() - 1);
152 positions.push_back(newWaypoint);
153 }
154
155 prevPos = currentPos;
156 }
157
158
159
160 reverse(positions.begin(), positions.end());
161
162#ifdef DEBUG_PATH
163 cout << "Milliseconds spent generating path: " << totalTimer.getElapsedTimeInMilliSec() << " ms" << endl;
164#endif
165 totalTimer.stop();
166 return Path(positions, finalDistance);
167 }
168
169
170 const BWEM::Area* area1 = bwem_map.GetArea(currentNode.tile);
171 const BWEM::Area* area2 = bwem_map.GetArea(end);
172
173 Path precachedPath = Path();
174 if (area1 != nullptr && area2 != nullptr && area1->Id() != area2->Id()) {
175 if ((AreaPathCache.contains(make_pair(area1->Id(), area2->Id())) || AreaPathCache.contains(make_pair(area2->Id(), area1->Id()))) && area1->AccessibleFrom(area2)) {
176
177
178
179
180
181 if (!contains(area1->AccessibleNeighbours(), area2)) {
182 pair<const BWEM::Area*, const BWEM::Area*> pair = make_pair(area1, area2);
183 precachedPath = AreaPathCache[make_pair(area1->Id(), area2->Id())];
184
185
186 if (area1->Id() > area2->Id()) {
187 precachedPath.
flip();
188 }
189
190 if (precachedPath.
positions.size() > 0) {
191 vector<BWAPI::Position> currentPositions;
192 int currentDistance = 0;
193 while (currentNode.tile != start) {
194 if (isInteractableEndpoint && currentNode.tile == end) {
195 currentPositions.push_back(BWAPI::Position(currentNode.tile));
196 }
197 else {
198 currentPositions.push_back(BWAPI::Position(currentNode.tile) + BWAPI::Position(16, 16));
199 }
200 currentDistance += currentNode.tile.getApproxDistance(parent[
TileToIndex(currentNode.tile)]);
201 currentNode.tile = parent[
TileToIndex(currentNode.tile)];
202 }
203
204 Path currentPath;
205 Path toStartOfPrecache;
206 Path toEnd;
207 Path finalPath;
208
209 try {
210 currentPath = Path(currentPositions, currentDistance);
211
212 toStartOfPrecache;
214 toStartOfPrecache =
generateSubPath(currentPositions.at(currentPositions.size() - 1), unitType, precachedPath.
positions.at(0));
215 }
216
218
219 finalPath = currentPath + toStartOfPrecache + precachedPath + toEnd;
220#ifdef DEBUG_PATH
221 cout <<
"Current node found cached path: " <<
"start size: " << currentPath.
positions.size() + toStartOfPrecache.
positions.size() <<
" | " <<
"precache size: " << precachedPath.
positions.size() <<
" | " <<
"end size: " << toEnd.positions.size() << endl;
222#endif
223 }
224 catch (const runtime_error& e) {
225#ifdef DEBUG_PATH
226 cout << e.what() << endl;
227#endif
228 totalTimer.stop();
229 return Path();
230 }
231
232 totalTimer.stop();
233 return finalPath;
234 }
235 else {
236 totalTimer.stop();
237 return Path();
238 }
239 }
240
241 else {
243 if (subPath == Path()) {
244 totalTimer.stop();
245 return Path();
246 }
247
248 Path startPath;
249 Path endPath;
250 Path finalPath;
251
252 try {
255 }
256 catch (const runtime_error& e) {
257#ifdef DEBUG_PATH
258 cout << e.what() << endl;
259#endif
260 totalTimer.stop();
261 finalPath = Path();
262 }
263
264 finalPath = startPath + subPath + endPath;
265
266#ifdef DEBUG_PATH
267 cout <<
"Connected to nearby cached path: " << startPath.
positions.size() <<
" | " << precachedPath.
positions.size() <<
" | " << endPath.positions.size() << endl;
268#endif
269 totalTimer.stop();
271 totalTimer.stop();
272 return finalPath;
273 }
274 }
275 }
276 }
277
278
279
280 Timer neighbourTimer = Timer();
281 neighbourTimer.start();
282 for (int x = -1; x <= 1; x++) {
283 for (int y = -1; y <= 1; y++) {
284
285
286 if (x == 0 && y == 0) {
287 continue;
288 }
289
290
291 if (x != 0 && y != 0) {
292 BWAPI::TilePosition a(currentNode.tile.x + x, currentNode.tile.y);
293 BWAPI::TilePosition b(currentNode.tile.x, currentNode.tile.y + y);
294 if (!
tileWalkable(unitType, a, end, isInteractableEndpoint) || !
tileWalkable(unitType, b, end, isInteractableEndpoint))
295 continue;
296 }
297
298 const BWAPI::TilePosition neighbourTile = BWAPI::TilePosition(currentNode.tile.x + x, currentNode.tile.y + y);
299
300 if (
tileWalkable(unitType, neighbourTile, end, isInteractableEndpoint)) {
301
302
303
304 const int gCost = currentNode.gCost + currentNode.tile.getApproxDistance(neighbourTile) * ((x != 0 && y != 0) ? 14 : 10);
305
307 continue;
308 }
309
310 if (!openSetNodes.contains(neighbourTile) || gCost < gCostMap[
TileToIndex(neighbourTile)]) {
311 const int hCost = neighbourTile.getApproxDistance(end) * ((x != 0 && y != 0) ? 14 : 10);
312 const double fCost = gCost + (HEURISTIC_WEIGHT * hCost);
313
314
315 const Node neighbourNode = Node(neighbourTile, currentNode.tile, gCost, hCost, fCost);
316
317
318
319
320
321
322
323
324
325
326
327 if (!openSetNodes.contains(neighbourTile)) {
328 openSet.push(neighbourNode);
329 openSetNodes.insert(neighbourTile);
330 }
331 parent[
TileToIndex(neighbourTile)] = currentNode.tile;
333 }
334 }
335 }
336 }
337 neighbourTimer.stop();
338 }
339
340 totalTimer.stop();
341 return Path();
342}
static Path closestCachedPath(BWAPI::Position start, BWAPI::Position end)
Loops through the precached paths between areas and chokepoints to find the closest path from start t...
static int TileToIndex(BWAPI::TilePosition tile)
Used for indexing BWAPI::TilePositions in arrays. Converts a BWAPI::TilePosition to an integer index ...
static bool tileWalkable(BWAPI::UnitType unitType, BWAPI::TilePosition tile, BWAPI::TilePosition end, bool isInteractableEndpoint)
Using the unit's unit type, checks if the tile is walkable. This is done by using the unit's dimens...
void flip()
Reverses the vector of positions in the path. Used when we want to flip a path from A to B into a pat...