/// Wegfinden ( A* ), O(v lg v) --> Wegfindung auf allgemeinen Terrain (ohne Straßen), für Wegbau und frei herumlaufende Berufe bool GameWorldBase::FindFreePathAlternatingConditions(const MapPoint start, const MapPoint dest, const bool random_route, const unsigned max_route, std::vector<unsigned char>* route, unsigned* length, unsigned char* first_dir, FP_Node_OK_Callback IsNodeOK, FP_Node_OK_Callback IsNodeOKAlternate, FP_Node_OK_Callback IsNodeToDestOk, const void* param, const bool record) const { // increase currentVisit, so we don't have to clear the visited-states at every run currentVisit++; //currentVisitEven++; // if the counter reaches its maxium, tidy up if (currentVisit == std::numeric_limits<unsigned>::max() - 1) { for (unsigned i = 0; i < (maxMapSize * maxMapSize); ++i) { pf_nodes[i].lastVisited = 0; pf_nodes[i].lastVisitedEven = 0; } currentVisit = 1; //currentVisitEven = 1; } std::list<PathfindingPoint> todo; bool prevstepEven=true; //flips between even and odd unsigned stepsTilSwitch=1; PathfindingPoint::Init(dest, this); // Anfangsknoten einfügen unsigned start_id = MakeCoordID(start); todo.push_back(PathfindingPoint(start, start_id)); // Und mit entsprechenden Werten füllen //pf_nodes[start_id].it_p = ret.first; pf_nodes[start_id].prevEven = INVALID_PREV; pf_nodes[start_id].lastVisitedEven = currentVisit; pf_nodes[start_id].wayEven = 0; pf_nodes[start_id].dirEven = 0; //LOG.lprintf("pf: from %i, %i to %i, %i \n", x_start, y_start, x_dest, y_dest); // TODO confirm random unsigned rand = GetIdx(start) * GAMECLIENT.GetGFNumber() % 6; //RANDOM.Rand(__FILE__, __LINE__, y_start * GetWidth() + x_start, 6); while(!todo.empty()) { if(!stepsTilSwitch) //counter for next step and switch condition { prevstepEven=!prevstepEven; stepsTilSwitch=todo.size(); //prevstepEven? LOG.lprintf("pf: even, to switch %i listsize %i ", stepsTilSwitch, todo.size()) : LOG.lprintf("pf: odd, to switch %i listsize %i ", stepsTilSwitch, todo.size()); } //else //prevstepEven? LOG.lprintf("pf: even, to switch %i listsize %i ", stepsTilSwitch, todo.size()) : LOG.lprintf("pf: odd, to switch %i listsize %i ", stepsTilSwitch, todo.size()); stepsTilSwitch--; // Knoten mit den geringsten Wegkosten auswählen PathfindingPoint best = *todo.begin(); // Knoten behandelt --> raus aus der todo Liste todo.erase(todo.begin()); //printf("x: %u y: %u\n", best.x, best.y); // ID des besten Punktes ausrechnen unsigned best_id = best.id; //LOG.lprintf(" now %i, %i id: %i \n", best.x, best.y, best_id); // Dieser Knoten wurde aus dem set entfernt, daher wird der entsprechende Iterator // auf das Ende (also nicht definiert) gesetzt, quasi als "NULL"-Ersatz //pf_nodes[best_id].it_p = todo.end(); // Ziel schon erreicht? Allerdings Null-Weg, wenn Start=Ende ist, verbieten if(dest == best.pt && ((prevstepEven && pf_nodes[best_id].wayEven) || (!prevstepEven && pf_nodes[best_id].way))) { // Ziel erreicht! // Jeweils die einzelnen Angaben zurückgeben, falls gewünscht (Pointer übergeben) if(length) *length = prevstepEven ? pf_nodes[best_id].wayEven : pf_nodes[best_id].way; if(route) prevstepEven? route->resize(pf_nodes[best_id].wayEven) : route->resize(pf_nodes[best_id].way); // Route rekonstruieren und ggf. die erste Richtung speichern, falls gewünscht bool alternate=prevstepEven; for(unsigned z = prevstepEven? pf_nodes[best_id].wayEven - 1 : pf_nodes[best_id].way - 1; best_id != start_id; --z, best_id = alternate? pf_nodes[best_id].prevEven : pf_nodes[best_id].prev, alternate=!alternate) { if(route) (*route)[z] = alternate? pf_nodes[best_id].dirEven : pf_nodes[best_id].dir; if(first_dir && z == 0) *first_dir = pf_nodes[best_id].dirEven; } // Fertig, es wurde ein Pfad gefunden return true; } // Maximaler Weg schon erreicht? In dem Fall brauchen wir keine weiteren Knoten von diesem aus bilden if((prevstepEven && pf_nodes[best_id].wayEven)==max_route || (!prevstepEven && pf_nodes[best_id].way == max_route)) continue; // Bei Zufälliger Richtung anfangen (damit man nicht immer denselben Weg geht, besonders für die Soldaten wichtig) unsigned startDir = random_route ? rand : 0; //LOG.lprintf("pf get neighbor nodes %i, %i id: %i \n", best.x, best.y, best_id); // Knoten in alle 6 Richtungen bilden for(unsigned z = startDir + 3; z < startDir + 9; ++z) { unsigned i = z % 6; // Koordinaten des entsprechenden umliegenden Punktes bilden MapPoint na = GetNeighbour(best.pt, i); // ID des umliegenden Knotens bilden unsigned xaid = MakeCoordID(na); // Knoten schon auf dem Feld gebildet? if ((prevstepEven && pf_nodes[xaid].lastVisited == currentVisit) || (!prevstepEven && pf_nodes[xaid].lastVisitedEven == currentVisit)) { continue; } // Das Ziel wollen wir auf jedenfall erreichen lassen, daher nur diese zusätzlichen // Bedingungen, wenn es nicht das Ziel ist if(na != dest && ((prevstepEven && IsNodeOK) || (!prevstepEven && IsNodeOKAlternate))) { if(prevstepEven) { if(!IsNodeOK(*this, na, i, param)) continue; } else { if (!IsNodeOKAlternate(*this, na, i, param)) continue; MapPoint p = best.pt; std::vector<MapPoint>evenlocationsonroute; bool alternate=prevstepEven; unsigned back_id=best_id; for(unsigned i=pf_nodes[best_id].way-1; i>1; i--, back_id = alternate? pf_nodes[back_id].prevEven : pf_nodes[back_id].prev, alternate=!alternate) // backtrack the plannend route and check if another "even" position is too close { unsigned char pdir = alternate? pf_nodes[back_id].dirEven : pf_nodes[back_id].dir; p = GetNeighbour(p, (pdir+3)%6); if(i%2==0) //even step { evenlocationsonroute.push_back(p); } } bool tooclose=false; //LOG.lprintf("pf from %i, %i to %i, %i now %i, %i ", x_start, y_start, x_dest, y_dest, xa, ya);//\n for(std::vector<MapPoint>::const_iterator it=evenlocationsonroute.begin();it!=evenlocationsonroute.end(); ++it) { //LOG.lprintf("dist to %i, %i ", temp, *it); if(CalcDistance(na, (*it))<2) { tooclose=true; break; } } //LOG.lprintf("\n"); if(CalcDistance(na, start)<2) continue; if(CalcDistance(na, dest)<2) continue; if(tooclose) continue; } } // Zusätzliche Bedingungen, auch die das letzte Stück zum Ziel betreffen if(IsNodeToDestOk) { if(!IsNodeToDestOk(*this, na, i, param)) continue; } // Alles in Ordnung, Knoten kann gebildet werden prevstepEven? pf_nodes[xaid].lastVisited = currentVisit : pf_nodes[xaid].lastVisitedEven = currentVisit; prevstepEven? pf_nodes[xaid].way = pf_nodes[best_id].wayEven + 1: pf_nodes[xaid].wayEven = pf_nodes[best_id].way + 1; prevstepEven? pf_nodes[xaid].dir = i : pf_nodes[xaid].dirEven = i; prevstepEven? pf_nodes[xaid].prev = best_id : pf_nodes[xaid].prevEven = best_id ; todo.push_back(PathfindingPoint(na, xaid)); //pf_nodes[xaid].it_p = ret.first; } } // Liste leer und kein Ziel erreicht --> kein Weg return false; }
/// Pathfinder ( A* ), O(v lg v) --> Normal terrain (ignoring roads) for road building and free walking jobs bool FreePathFinder::FindPathAlternatingConditions(const MapPoint start, const MapPoint dest, const bool randomRoute, const unsigned maxLength, std::vector<unsigned char>* route, unsigned* length, unsigned char* firstDir, FP_Node_OK_Callback IsNodeOK, FP_Node_OK_Callback IsNodeOKAlternate, FP_Node_OK_Callback IsNodeToDestOk, const void* param) { if(start == dest) { // Path where start==goal should never happen RTTR_Assert(false); LOG.write("WARNING: Bug detected (GF: %u). Please report this with the savegame and replay (Start==Dest in pathfinding %u,%u)\n") % gwb_.GetEvMgr().GetCurrentGF() % unsigned(start.x) % unsigned(start.y); // But for now we assume it to be valid and return (kind of) correct values if(route) route->clear(); if(length) *length = 0; if(firstDir) *firstDir = 0xff; return true; } // increase currentVisit, so we don't have to clear the visited-states at every run IncreaseCurrentVisit(); std::list<PathfindingPoint> todo; const unsigned destId = gwb_.GetIdx(dest); bool prevStepEven = true; //flips between even and odd unsigned stepsTilSwitch = 1; // Add start node unsigned startId = gwb_.GetIdx(start); todo.push_back(PathfindingPoint(startId, gwb_.CalcDistance(start, dest), 0)); // And init it nodes[startId].prevEven = INVALID_PREV; nodes[startId].lastVisitedEven = currentVisit; nodes[startId].wayEven = 0; nodes[startId].dirEven = 0; //LOG.write(("pf: from %i, %i to %i, %i \n", x_start, y_start, x_dest, y_dest); // Start at random dir (so different jobs may use different roads) const unsigned startDir = randomRoute ? (gwb_.GetIdx(start)) * gwb_.GetEvMgr().GetCurrentGF() % 6 : 0; while(!todo.empty()) { if(!stepsTilSwitch) //counter for next step and switch condition { prevStepEven = !prevStepEven; stepsTilSwitch = todo.size(); //prevstepEven ? LOG.write(("pf: even, to switch %i listsize %i ", stepsTilSwitch, todo.size()) : LOG.write(("pf: odd, to switch %i listsize %i ", stepsTilSwitch, todo.size()); } //else //prevstepEven ? LOG.write(("pf: even, to switch %i listsize %i ", stepsTilSwitch, todo.size()) : LOG.write(("pf: odd, to switch %i listsize %i ", stepsTilSwitch, todo.size()); stepsTilSwitch--; // Get node with lowest cost PathfindingPoint best = *todo.begin(); // Knoten behandelt --> raus aus der todo Liste todo.erase(todo.begin()); //printf("x: %u y: %u\n", best.x, best.y); // ID des besten Punktes ausrechnen unsigned bestId = best.id_; //LOG.write((" now %i, %i id: %i \n", best.x, best.y, best_id); // Dieser Knoten wurde aus dem set entfernt, daher wird der entsprechende Iterator // auf das Ende (also nicht definiert) gesetzt, quasi als "NULL"-Ersatz //pf_nodes[best_id].it_p = todo.end(); // Ziel schon erreicht? if(destId == bestId) { // Ziel erreicht! // Return the values if requested const unsigned routeLen = prevStepEven ? nodes[bestId].wayEven : nodes[bestId].way; if(length) *length = routeLen; if(route) route->resize(routeLen); // Reconstruct route and get first direction (if requested) bool alternate = prevStepEven; for(unsigned z = routeLen - 1; bestId != startId; --z) { if(route) (*route)[z] = alternate ? nodes[bestId].dirEven : nodes[bestId].dir; if(firstDir && z == 0) *firstDir = nodes[bestId].dirEven; bestId = alternate ? nodes[bestId].prevEven : nodes[bestId].prev; alternate = !alternate; } // Fertig, es wurde ein Pfad gefunden return true; } // Maximaler Weg schon erreicht ? In dem Fall brauchen wir keine weiteren Knoten von diesem aus bilden if((prevStepEven && nodes[bestId].wayEven == maxLength) || (!prevStepEven && nodes[bestId].way == maxLength)) continue; //LOG.write(("pf get neighbor nodes %i, %i id: %i \n", best.x, best.y, best_id); // Knoten in alle 6 Richtungen bilden for(unsigned z = startDir + 3; z < startDir + 9; ++z) { unsigned i = z % 6; // Koordinaten des entsprechenden umliegenden Punktes bilden MapPoint neighbourPos = gwb_.GetNeighbour(nodes[bestId].mapPt, i); // ID des umliegenden Knotens bilden unsigned nbId = gwb_.GetIdx(neighbourPos); // Knoten schon auf dem Feld gebildet ? if ((prevStepEven && nodes[nbId].lastVisited == currentVisit) || (!prevStepEven && nodes[nbId].lastVisitedEven == currentVisit)) { continue; } // Check additional constraints for non-destination points if(nbId != destId && ((prevStepEven && IsNodeOK) || (!prevStepEven && IsNodeOKAlternate))) { if(prevStepEven) { if(!IsNodeOK(gwb_, neighbourPos, i, param)) continue; } else { if (!IsNodeOKAlternate(gwb_, neighbourPos, i, param)) continue; MapPoint p = nodes[bestId].mapPt; std::vector<MapPoint> evenLocationsOnRoute; bool alternate = prevStepEven; unsigned back_id = bestId; for(unsigned i = nodes[bestId].way-1; i>1; i--) // backtrack the plannend route and check if another "even" position is too close { unsigned char pdir = alternate ? nodes[back_id].dirEven : nodes[back_id].dir; p = gwb_.GetNeighbour(p, (pdir+3) % 6); if(i%2 == 0) //even step { evenLocationsOnRoute.push_back(p); } back_id = alternate ? nodes[back_id].prevEven : nodes[back_id].prev; alternate = !alternate; } bool tooClose = false; //LOG.write(("pf from %i, %i to %i, %i now %i, %i ", x_start, y_start, x_dest, y_dest, xa, ya);//\n for(std::vector<MapPoint>::const_iterator it = evenLocationsOnRoute.begin();it!= evenLocationsOnRoute.end(); ++it) { //LOG.write(("dist to %i, %i ", temp, *it); if(gwb_.CalcDistance(neighbourPos, (*it)) < 2) { tooClose = true; break; } } //LOG.write(("\n"); if(tooClose) continue; if(gwb_.CalcDistance(neighbourPos, start) < 2) continue; if(gwb_.CalcDistance(neighbourPos, dest) < 2) continue; } } // Conditions for all nodes if(IsNodeToDestOk) { if(!IsNodeToDestOk(gwb_, neighbourPos, i, param)) continue; } // Alles in Ordnung, Knoten kann gebildet werden unsigned way; if(prevStepEven) { nodes[nbId].lastVisited = currentVisit; way = nodes[nbId].way = nodes[bestId].wayEven + 1; nodes[nbId].dir = i; nodes[nbId].prev = bestId; } else { nodes[nbId].lastVisitedEven = currentVisit; way = nodes[nbId].wayEven = nodes[bestId].way + 1; nodes[nbId].dirEven = i; nodes[nbId].prevEven = bestId; } todo.push_back(PathfindingPoint(nbId, gwb_.CalcDistance(neighbourPos, dest), way)); //pf_nodes[xaid].it_p = ret.first; } } // Liste leer und kein Ziel erreicht --> kein Weg return false; }
/// Wegfinden ( A* ), O(v lg v) --> Wegfindung auf allgemeinen Terrain (ohne Straßen), für Wegbau und frei herumlaufende Berufe bool GameWorldBase::FindFreePath(const MapPoint start, const MapPoint dest, const bool random_route, const unsigned max_route, std::vector<unsigned char>* route, unsigned* length, unsigned char* first_dir, FP_Node_OK_Callback IsNodeOK, FP_Node_OK_Callback IsNodeToDestOk, const void* param, const bool record) const { // increase currentVisit, so we don't have to clear the visited-states at every run currentVisit++; // if the counter reaches its maxium, tidy up if (currentVisit == std::numeric_limits<unsigned>::max() - 1) { for (unsigned i = 0; i < (maxMapSize * maxMapSize); ++i) { pf_nodes[i].lastVisited = 0; pf_nodes[i].lastVisitedEven = 0; } currentVisit = 1; } std::set<PathfindingPoint> todo; PathfindingPoint::Init(dest, this); // Anfangsknoten einfügen unsigned start_id = MakeCoordID(start); std::pair< std::set<PathfindingPoint>::iterator, bool > ret = todo.insert(PathfindingPoint(start, start_id)); // Und mit entsprechenden Werten füllen pf_nodes[start_id].it_p = ret.first; pf_nodes[start_id].prev = INVALID_PREV; pf_nodes[start_id].lastVisited = currentVisit; pf_nodes[start_id].way = 0; pf_nodes[start_id].dir = 0; // TODO confirm random unsigned rand = (GetIdx(start)) * GAMECLIENT.GetGFNumber() % 6; //RANDOM.Rand(__FILE__, __LINE__, y_start * GetWidth() + x_start, 6); while(!todo.empty()) { // Knoten mit den geringsten Wegkosten auswählen PathfindingPoint best = *todo.begin(); // Knoten behandelt --> raus aus der todo Liste todo.erase(todo.begin()); //printf("x: %u y: %u\n", best.x, best.y); // ID des besten Punktes ausrechnen unsigned best_id = best.id; // Dieser Knoten wurde aus dem set entfernt, daher wird der entsprechende Iterator // auf das Ende (also nicht definiert) gesetzt, quasi als "NULL"-Ersatz pf_nodes[best_id].it_p = todo.end(); // Ziel schon erreicht? Allerdings Null-Weg, wenn Start=Ende ist, verbieten if(dest == best.pt && pf_nodes[best_id].way) { // Ziel erreicht! // Jeweils die einzelnen Angaben zurückgeben, falls gewünscht (Pointer übergeben) if(length) *length = pf_nodes[best_id].way; if(route) route->resize(pf_nodes[best_id].way); // Route rekonstruieren und ggf. die erste Richtung speichern, falls gewünscht for(unsigned z = pf_nodes[best_id].way - 1; best_id != start_id; --z, best_id = pf_nodes[best_id].prev) { if(route) (*route)[z] = pf_nodes[best_id].dir; if(first_dir && z == 0) *first_dir = pf_nodes[best_id].dir; } // Fertig, es wurde ein Pfad gefunden return true; } // Maximaler Weg schon erreicht? In dem Fall brauchen wir keine weiteren Knoten von diesem aus bilden if(pf_nodes[best_id].way == max_route) continue; // Bei Zufälliger Richtung anfangen (damit man nicht immer denselben Weg geht, besonders für die Soldaten wichtig) unsigned start = random_route ? rand : 0; // Knoten in alle 6 Richtungen bilden for(unsigned z = start + 3; z < start + 9; ++z) { unsigned i = z % 6; // Koordinaten des entsprechenden umliegenden Punktes bilden MapPoint na = GetNeighbour(best.pt, i); // ID des umliegenden Knotens bilden unsigned xaid = MakeCoordID(na); // Knoten schon auf dem Feld gebildet? if (pf_nodes[xaid].lastVisited == currentVisit) { // Dann nur ggf. Weg und Vorgänger korrigieren, falls der Weg kürzer ist if(pf_nodes[xaid].it_p != todo.end() && pf_nodes[best_id].way + 1 < pf_nodes[xaid].way) { pf_nodes[xaid].way = pf_nodes[best_id].way + 1; pf_nodes[xaid].prev = best_id; todo.erase(pf_nodes[xaid].it_p); ret = todo.insert(PathfindingPoint(na, xaid)); pf_nodes[xaid].it_p = ret.first; pf_nodes[xaid].dir = i; } // Wir wollen nicht denselben Knoten noch einmal einfügen, daher Abbruch continue; } // Das Ziel wollen wir auf jedenfall erreichen lassen, daher nur diese zusätzlichen // Bedingungen, wenn es nicht das Ziel ist if(na != dest && IsNodeOK) { if(!IsNodeOK(*this, na, i, param)) continue; } // Zusätzliche Bedingungen, auch die das letzte Stück zum Ziel betreffen if(IsNodeToDestOk) { if(!IsNodeToDestOk(*this, na, i, param)) continue; } // Alles in Ordnung, Knoten kann gebildet werden pf_nodes[xaid].lastVisited = currentVisit; pf_nodes[xaid].way = pf_nodes[best_id].way + 1; pf_nodes[xaid].dir = i; pf_nodes[xaid].prev = best_id; ret = todo.insert(PathfindingPoint(na, xaid)); pf_nodes[xaid].it_p = ret.first; } } // Liste leer und kein Ziel erreicht --> kein Weg return false; }