Skip to content

Commit 6b51676

Browse files
authored
Merge pull request #1734 from wichern/pre-improvecarriercost-dirty
Improve A* Pathfinding Efficiency by Addressing Final Segment Penalty
2 parents bd3a2ab + 7843b3c commit 6b51676

File tree

5 files changed

+57
-41
lines changed

5 files changed

+57
-41
lines changed

libs/s25main/Ware.cpp

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -338,27 +338,25 @@ unsigned Ware::CheckNewGoalForLostWare(const noBaseBuilding& newgoal) const
338338
Ware::RouteParams Ware::CalcPathToGoal(const noBaseBuilding& newgoal) const
339339
{
340340
RTTR_Assert(location);
341-
unsigned length = 0xFFFFFFFF;
341+
unsigned length;
342342
RoadPathDirection possibledir = world->FindPathForWareOnRoads(*location, newgoal, &length);
343343
if(possibledir != RoadPathDirection::None) // there is a valid path to the goal? -> ordered!
344344
{
345345
// in case the ware is right in front of the goal building the ware has to be moved away 1 flag and then back
346346
// because non-warehouses cannot just carry in new wares they need a helper to do this
347347
if(possibledir == RoadPathDirection::NorthWest && newgoal.GetFlagPos() == location->GetPos())
348348
{
349+
// Not executed for road from flag to the warehouse as that is handled directly by the warehouse
350+
RTTR_Assert(!BuildingProperties::IsWareHouse(newgoal.GetBuildingType()));
349351
for(const auto dir : helpers::EnumRange<Direction>{})
350352
{
353+
// Bounce of in this direction
351354
if(dir != Direction::NorthWest && location->GetRoute(dir))
352-
{
353-
possibledir = toRoadPathDirection(dir);
354-
break;
355-
}
355+
return {1, toRoadPathDirection(dir)};
356356
}
357-
if(possibledir == RoadPathDirection::NorthWest) // got no other route from the flag -> impossible
358-
return {0xFFFFFFFF, RoadPathDirection::None};
357+
// got no other route from the flag -> impossible
358+
return {0xFFFFFFFF, RoadPathDirection::None};
359359
}
360-
// at this point there either is a road to the goal
361-
// or we are at the flag of the goal and have a road to a different flag to bounce off of to get to the goal
362360
return {length, possibledir};
363361
}
364362
return {0xFFFFFFFF, RoadPathDirection::None};

libs/s25main/buildings/nobHarborBuilding.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -839,8 +839,8 @@ std::vector<nobHarborBuilding::ShipConnection> nobHarborBuilding::GetShipConnect
839839
{
840840
ShipConnection sc;
841841
sc.dest = harbor_building;
842-
// Als Kantengewicht nehmen wir die doppelte Entfernung (evtl muss ja das Schiff erst kommen)
843-
// plus einer Kopfpauschale (Ein/Ausladen usw. dauert ja alles)
842+
// Use twice the distance as cost (ship might need to arrive first) and a fixed value to represent
843+
// loading&unloading
844844
sc.way_costs = 2 * world->CalcHarborDistance(GetHarborPosID(), harbor_building->GetHarborPosID()) + 10;
845845
connections.push_back(sc);
846846
}

libs/s25main/nodeObjs/noFlag.cpp

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -228,13 +228,14 @@ unsigned noFlag::GetNumWaresForRoad(const Direction dir) const
228228
return helpers::count_if(wares, [roadDir](const auto& ware) { return ware->GetNextDir() == roadDir; });
229229
}
230230

231-
/**
232-
* Gibt Wegstrafpunkte für das Pathfinden für Waren, die in eine bestimmte
233-
* Richtung noch transportiert werden müssen.
234-
*/
235231
unsigned noFlag::GetPunishmentPoints(const Direction dir) const
236232
{
237-
// Waren zählen, die in diese Richtung transportiert werden müssen
233+
constexpr auto PATHFINDING_PENALTY_CARRIER_ARRIVING = 50;
234+
constexpr auto PATHFINDING_PENALTY_NO_CARRIER = 500;
235+
// This must be the same as "NO_CARRIER" for replay compatibility
236+
// TODO(Replay): Move to `way_costs` in nobHarborBuilding::GetShipConnections
237+
constexpr auto PATHFINDING_PENALTY_START_SHIPPING = 500;
238+
// 2 Points per ware as carrier has to walk to other point and back for each ware
238239
unsigned points = GetNumWaresForRoad(dir) * 2;
239240

240241
const RoadSegment* routeInDir = GetRoute(dir);
@@ -243,9 +244,20 @@ unsigned noFlag::GetPunishmentPoints(const Direction dir) const
243244
{
244245
// normal carrier has been ordered from the warehouse but has not yet arrived and no donkey
245246
if(humanCarrier->GetCarrierState() == CarrierState::FigureWork && !routeInDir->hasCarrier(1))
246-
points += 50;
247+
points += PATHFINDING_PENALTY_CARRIER_ARRIVING;
247248
} else if(!routeInDir->hasCarrier(1))
248-
points += 500; // No carrier at all -> Large penalty
249+
{
250+
// Routes are either between flags or from a flag to a building, see the ctor of noBaseBuilding
251+
const bool isBuildingEntry = (dir == Direction::NorthWest) && (routeInDir->GetF2()->GetGOT() != GO_Type::Flag);
252+
// For entering a building no carrier is required
253+
// Only roads to buildings considered by path finding are those to harbors
254+
if(isBuildingEntry)
255+
{
256+
RTTR_Assert(routeInDir->GetF2()->GetGOT() == GO_Type::NobHarborbuilding);
257+
points += PATHFINDING_PENALTY_START_SHIPPING;
258+
} else
259+
points += PATHFINDING_PENALTY_NO_CARRIER;
260+
}
249261

250262
return points;
251263
}

libs/s25main/nodeObjs/noFlag.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ class noFlag : public noRoadNode
4646
std::unique_ptr<Ware> SelectWare(Direction roadDir, bool swap_wares, const noFigure* carrier);
4747
/// Prüft, ob es Waren gibt, die auf den Weg in Richtung dir getragen werden müssen.
4848
unsigned GetNumWaresForRoad(Direction dir) const;
49-
/// Gibt Wegstrafpunkte für das Pathfinden für Waren, die in eine bestimmte Richtung noch transportiert werden
50-
/// müssen.
49+
/// Penalty for transporting a ware in a specific direction
5150
unsigned GetPunishmentPoints(Direction dir) const override;
5251
/// Zerstört evtl. vorhandenes Gebäude bzw. Baustelle vor der Flagge.
5352
void DestroyAttachedBuilding();

libs/s25main/pathfinding/RoadPathFinder.cpp

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ struct RoadNodeComperatorGreater
2020
{
2121
if(lhs->estimate == rhs->estimate)
2222
{
23-
// Wenn die Wegkosten gleich sind, vergleichen wir die Koordinaten, da wir für std::set eine streng
24-
// monoton steigende Folge brauchen
23+
// Use a tie-breaker to get strict ordering
2524
return (lhs->GetObjId() > rhs->GetObjId());
2625
}
2726

@@ -100,7 +99,9 @@ struct And : private T_Func1, private T_Func2
10099
};
101100
} // namespace SegmentConstraints
102101

103-
/// Wegfinden ( A* ), O(v lg v) --> Wegfindung auf Stra�en
102+
/// Path finding on roads using A* O(n lg n)
103+
/// \tparam T_AdditionalCosts Cost for each road segment but the one to the goal building
104+
/// \tparam T_SegmentConstraints Predicate whether a road is allowed
104105
template<class T_AdditionalCosts, class T_SegmentConstraints>
105106
bool RoadPathFinder::FindPathImpl(const noRoadNode& start, const noRoadNode& goal, const unsigned max,
106107
const T_AdditionalCosts addCosts, const T_SegmentConstraints isSegmentAllowed,
@@ -124,9 +125,12 @@ bool RoadPathFinder::FindPathImpl(const noRoadNode& start, const noRoadNode& goa
124125
return true;
125126
}
126127

127-
// increase current_visit_on_roads, so we don't have to clear the visited-states at every run
128-
currentVisit++;
128+
// If the goal is a flag (unlikely) we have no goal building
129+
// TODO(Replay): Change RoadPathFinder::FindPath to target flag instead of building for wares
130+
const noRoadNode* goalBld = (goal.GetGOT() == GO_Type::Flag) ? nullptr : &goal;
129131

132+
// Use a counter for the visited-states so we don't have to reset them on every invocation
133+
currentVisit++;
130134
// if the counter reaches its maximum, tidy up
131135
if(currentVisit == std::numeric_limits<unsigned>::max())
132136
{
@@ -163,19 +167,20 @@ bool RoadPathFinder::FindPathImpl(const noRoadNode& start, const noRoadNode& goa
163167
if(length)
164168
*length = best.cost;
165169

166-
// Backtrace to get the last node that is not the start node (has a prev node) --> Next node from start on
167-
// path
168-
const noRoadNode* firstNode = &best;
169-
while(firstNode->prev != &start)
170+
// Backtrack to get the last node that is not the start node (has a prev node)
171+
// --> Next node from start on path
172+
if(firstDir || firstNodePos)
170173
{
171-
firstNode = firstNode->prev;
172-
}
174+
const noRoadNode* firstNode = &best;
175+
while(firstNode->prev != &start)
176+
firstNode = firstNode->prev;
173177

174-
if(firstDir)
175-
*firstDir = firstNode->dir_;
178+
if(firstDir)
179+
*firstDir = firstNode->dir_;
176180

177-
if(firstNodePos)
178-
*firstNodePos = firstNode->GetPos();
181+
if(firstNodePos)
182+
*firstNodePos = firstNode->GetPos();
183+
}
179184

180185
// Done, path found
181186
return true;
@@ -184,7 +189,7 @@ bool RoadPathFinder::FindPathImpl(const noRoadNode& start, const noRoadNode& goa
184189
const helpers::EnumArray<RoadSegment*, Direction> routes = best.getRoutes();
185190
const noRoadNode* prevNode = best.prev;
186191

187-
// Nachbarflagge bzw. Wege in allen 6 Richtungen verfolgen
192+
// Check paths in all directions
188193
for(const auto dir : helpers::EnumRange<Direction>{})
189194
{
190195
const auto* route = routes[dir];
@@ -209,12 +214,11 @@ bool RoadPathFinder::FindPathImpl(const noRoadNode& start, const noRoadNode& goa
209214
continue;
210215
}
211216

212-
// evtl verboten?
217+
// Check if route is forbidden
213218
if(!isSegmentAllowed(*route))
214219
continue;
215220

216-
unsigned cost = best.cost + route->GetLength();
217-
cost += addCosts(best, dir);
221+
const unsigned cost = best.cost + route->GetLength() + (neighbour != goalBld ? addCosts(best, dir) : 0);
218222

219223
if(cost > max)
220224
continue;
@@ -283,18 +287,19 @@ bool RoadPathFinder::FindPathImpl(const noRoadNode& start, const noRoadNode& goa
283287
}
284288
}
285289

286-
// Liste leer und kein Ziel erreicht --> kein Weg
290+
// Queue is empty without reaching the goal --> No allowed, existing path
287291
return false;
288292
}
289293

290294
bool RoadPathFinder::FindPath(const noRoadNode& start, const noRoadNode& goal, const bool wareMode, const unsigned max,
291295
const RoadSegment* const forbidden, unsigned* const length,
292296
RoadPathDirection* const firstDir, MapPoint* const firstNodePos)
293297
{
294-
RTTR_Assert(length || firstDir || firstNodePos); // If none of them is set use the \ref PathExist function!
298+
RTTR_Assert_Msg(length || firstDir || firstNodePos, "Use PathExists instead!");
295299

296300
if(wareMode)
297301
{
302+
// TODO(Replay): Change to target flag instead of its attached building
298303
if(forbidden)
299304
return FindPathImpl(start, goal, max, AdditonalCosts::Carrier(),
300305
SegmentConstraints::AvoidSegment(forbidden), length, firstDir, firstNodePos);
@@ -319,6 +324,8 @@ bool RoadPathFinder::PathExists(const noRoadNode& start, const noRoadNode& goal,
319324
{
320325
if(allowWaterRoads)
321326
{
327+
// TODO(Replay): Change to target flag instead of its attached building.
328+
// Likely combine with RoadPathFinder::FindPath
322329
if(forbidden)
323330
return FindPathImpl(start, goal, max, AdditonalCosts::None(), SegmentConstraints::AvoidSegment(forbidden));
324331
else

0 commit comments

Comments
 (0)