CRoadRandomizer.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. /*
  2. * CRoadRandomizer.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "CRoadRandomizer.h"
  12. #include "RmgMap.h"
  13. #include "Zone.h"
  14. #include "Functions.h"
  15. #include "CRmgTemplate.h"
  16. #include <vstd/RNG.h>
  17. VCMI_LIB_NAMESPACE_BEGIN
  18. CRoadRandomizer::CRoadRandomizer(RmgMap & map)
  19. : map(map)
  20. {
  21. }
  22. TRmgTemplateZoneId findSet(std::map<TRmgTemplateZoneId, TRmgTemplateZoneId> & parent, TRmgTemplateZoneId x)
  23. {
  24. if(parent[x] != x)
  25. parent[x] = findSet(parent, parent[x]);
  26. return parent[x];
  27. }
  28. void unionSets(std::map<TRmgTemplateZoneId, TRmgTemplateZoneId> & parent, TRmgTemplateZoneId x, TRmgTemplateZoneId y)
  29. {
  30. TRmgTemplateZoneId rx = findSet(parent, x);
  31. TRmgTemplateZoneId ry = findSet(parent, y);
  32. if(rx != ry)
  33. parent[rx] = ry;
  34. }
  35. /*
  36. Random road generation requirements:
  37. - Every town should be connected via road
  38. - There should be exactly one road betwen any two towns (connected MST)
  39. - This excludes cases when there are multiple road connetions betwween two zones
  40. - Road cannot end in a zone without town
  41. - Wide connections should have no road
  42. */
  43. void CRoadRandomizer::dropRandomRoads(vstd::RNG * rand)
  44. {
  45. logGlobal->info("Starting road randomization");
  46. auto zones = map.getZones();
  47. // Helper lambda to set road option for all instances of a connection
  48. auto setRoadOptionForConnection = [&zones](int connectionId, rmg::ERoadOption roadOption)
  49. {
  50. // Update all instances of this connection (A→B and B→A) to have the same road option
  51. for(auto & zonePtr : zones)
  52. {
  53. for(auto & connection : zonePtr.second->getConnections())
  54. {
  55. if(connection.getId() == connectionId)
  56. {
  57. zonePtr.second->setRoadOption(connectionId, roadOption);
  58. }
  59. }
  60. }
  61. };
  62. // Identify zones with towns
  63. std::set<TRmgTemplateZoneId> zonesWithTowns;
  64. for(const auto & zone : zones)
  65. {
  66. if(zone.second->getPlayerTowns().getTownCount() ||
  67. zone.second->getPlayerTowns().getCastleCount() ||
  68. zone.second->getNeutralTowns().getTownCount() ||
  69. zone.second->getNeutralTowns().getCastleCount())
  70. {
  71. zonesWithTowns.insert(zone.first);
  72. }
  73. }
  74. logGlobal->info("Found %d zones with towns", zonesWithTowns.size());
  75. if(zonesWithTowns.empty())
  76. {
  77. // No towns, no roads needed - mark all RANDOM roads as FALSE
  78. for(auto & zonePtr : zones)
  79. {
  80. for(auto & connection : zonePtr.second->getConnections())
  81. {
  82. if(connection.getRoadOption() == rmg::ERoadOption::ROAD_RANDOM)
  83. {
  84. setRoadOptionForConnection(connection.getId(), rmg::ERoadOption::ROAD_FALSE);
  85. }
  86. }
  87. }
  88. return;
  89. }
  90. // Track direct connections between zones (both TRUE and RANDOM)
  91. std::map<TRmgTemplateZoneId, std::map<TRmgTemplateZoneId, bool>> directConnections;
  92. // First pass: find all TRUE connections
  93. for(auto & zonePtr : zones)
  94. {
  95. for(auto & connection : zonePtr.second->getConnections())
  96. {
  97. auto zoneA = connection.getZoneA();
  98. auto zoneB = connection.getZoneB();
  99. if(connection.getRoadOption() == rmg::ERoadOption::ROAD_TRUE)
  100. {
  101. // Mark that these zones are directly connected by a TRUE road
  102. directConnections[zoneA][zoneB] = true;
  103. directConnections[zoneB][zoneA] = true;
  104. }
  105. }
  106. }
  107. // Track all available connections in the template (for graph connectivity analysis)
  108. std::map<TRmgTemplateZoneId, std::set<TRmgTemplateZoneId>> allConnections;
  109. std::map<std::pair<TRmgTemplateZoneId, TRmgTemplateZoneId>, int> connectionIds;
  110. // Build adjacency list for available connections and track connection IDs
  111. for(auto & zonePtr : zones)
  112. {
  113. for(auto & connection : zonePtr.second->getConnections())
  114. {
  115. auto zoneA = connection.getZoneA();
  116. auto zoneB = connection.getZoneB();
  117. if(connection.getRoadOption() == rmg::ERoadOption::ROAD_TRUE ||
  118. connection.getRoadOption() == rmg::ERoadOption::ROAD_RANDOM)
  119. {
  120. // Only include TRUE and RANDOM roads in connectivity analysis
  121. allConnections[zoneA].insert(zoneB);
  122. allConnections[zoneB].insert(zoneA);
  123. // Track connection ID for later use
  124. connectionIds[{std::min(zoneA, zoneB), std::max(zoneA, zoneB)}] = connection.getId();
  125. }
  126. }
  127. }
  128. // Check if there's a path between all town zones using all available connections
  129. // This is to verify if global connectivity is even possible
  130. std::map<TRmgTemplateZoneId, std::set<TRmgTemplateZoneId>> reachableTowns;
  131. for(auto startTown : zonesWithTowns)
  132. {
  133. std::queue<TRmgTemplateZoneId> q;
  134. std::set<TRmgTemplateZoneId> visited;
  135. q.push(startTown);
  136. visited.insert(startTown);
  137. while(!q.empty())
  138. {
  139. auto current = q.front();
  140. q.pop();
  141. for(auto neighbor : allConnections[current])
  142. {
  143. if(!vstd::contains(visited, neighbor))
  144. {
  145. visited.insert(neighbor);
  146. q.push(neighbor);
  147. if(vstd::contains(zonesWithTowns, neighbor))
  148. {
  149. reachableTowns[startTown].insert(neighbor);
  150. }
  151. }
  152. }
  153. }
  154. }
  155. // Initialize Union-Find for MST tracking
  156. std::map<TRmgTemplateZoneId, TRmgTemplateZoneId> parent;
  157. for(auto townZone : zonesWithTowns)
  158. {
  159. parent[townZone] = townZone;
  160. }
  161. // Add all TRUE roads connecting town zones to MST
  162. for(auto & zonePtr : zones)
  163. {
  164. for(auto & connection : zonePtr.second->getConnections())
  165. {
  166. if(connection.getRoadOption() == rmg::ERoadOption::ROAD_TRUE)
  167. {
  168. auto zoneA = connection.getZoneA();
  169. auto zoneB = connection.getZoneB();
  170. // If both zones have towns, add to MST
  171. if(vstd::contains(zonesWithTowns, zoneA) && vstd::contains(zonesWithTowns, zoneB))
  172. {
  173. unionSets(parent, zoneA, zoneB);
  174. }
  175. }
  176. }
  177. }
  178. // Process all paths through true roads (BFS)
  179. for(auto townZone : zonesWithTowns)
  180. {
  181. std::queue<TRmgTemplateZoneId> q;
  182. std::set<TRmgTemplateZoneId> visited;
  183. q.push(townZone);
  184. visited.insert(townZone);
  185. while(!q.empty())
  186. {
  187. auto current = q.front();
  188. q.pop();
  189. for(auto & otherZone : directConnections[current])
  190. {
  191. if(otherZone.second && !vstd::contains(visited, otherZone.first))
  192. {
  193. visited.insert(otherZone.first);
  194. q.push(otherZone.first);
  195. // If this is another town, update MST
  196. if(vstd::contains(zonesWithTowns, otherZone.first))
  197. {
  198. unionSets(parent, townZone, otherZone.first);
  199. }
  200. }
  201. }
  202. }
  203. }
  204. // Process RANDOM connections
  205. // First, ensure each town has at least one connection
  206. for(auto townZone : zonesWithTowns)
  207. {
  208. // Skip if already has a TRUE road
  209. if(!directConnections[townZone].empty())
  210. continue;
  211. // Find a random connection to upgrade to TRUE
  212. for(auto & zonePtr : zones)
  213. {
  214. for(auto & connection : zonePtr.second->getConnections())
  215. {
  216. if(connection.getRoadOption() == rmg::ERoadOption::ROAD_RANDOM &&
  217. (connection.getZoneA() == townZone || connection.getZoneB() == townZone))
  218. {
  219. auto zoneA = connection.getZoneA();
  220. auto zoneB = connection.getZoneB();
  221. // Don't upgrade if these zones are already directly connected by a TRUE road
  222. if(vstd::contains(directConnections[zoneA], zoneB) && directConnections[zoneA][zoneB])
  223. continue;
  224. // Upgrade to TRUE
  225. setRoadOptionForConnection(connection.getId(), rmg::ERoadOption::ROAD_TRUE);
  226. directConnections[zoneA][zoneB] = true;
  227. directConnections[zoneB][zoneA] = true;
  228. auto otherZone = (zoneA == townZone) ? zoneB : zoneA;
  229. logGlobal->info("Setting RANDOM road to TRUE for connection %d to ensure town zone %d has at least one connection (to zone %d)",
  230. connection.getId(), townZone, otherZone);
  231. break;
  232. }
  233. }
  234. // Stop if we've found a connection
  235. if(!directConnections[townZone].empty())
  236. break;
  237. }
  238. }
  239. // Process remaining RANDOM roads - prioritize town connectivity
  240. // First collect all RANDOM roads
  241. std::vector<std::pair<int, std::pair<TRmgTemplateZoneId, TRmgTemplateZoneId>>> randomRoads;
  242. for(auto & zonePtr : zones)
  243. {
  244. for(auto & connection : zonePtr.second->getConnections())
  245. {
  246. if(connection.getRoadOption() == rmg::ERoadOption::ROAD_RANDOM)
  247. {
  248. auto id = connection.getId();
  249. auto zoneA = connection.getZoneA();
  250. auto zoneB = connection.getZoneB();
  251. // Skip if these zones are already directly connected by a TRUE road
  252. if(vstd::contains(directConnections[zoneA], zoneB) && directConnections[zoneA][zoneB])
  253. {
  254. setRoadOptionForConnection(id, rmg::ERoadOption::ROAD_FALSE);
  255. logGlobal->info("Setting RANDOM road to FALSE for connection %d - duplicate of TRUE road between zones %d and %d",
  256. id, zoneA, zoneB);
  257. continue;
  258. }
  259. randomRoads.push_back(std::make_pair(id, std::make_pair(zoneA, zoneB)));
  260. }
  261. }
  262. }
  263. RandomGeneratorUtil::randomShuffle(randomRoads, *rand);
  264. // Process random roads - first connect town zones
  265. for(auto& road : randomRoads)
  266. {
  267. auto id = road.first;
  268. auto zoneA = road.second.first;
  269. auto zoneB = road.second.second;
  270. bool setToTrue = false;
  271. // If both zones have towns, check if they're already connected in the MST
  272. if(vstd::contains(zonesWithTowns, zoneA) && vstd::contains(zonesWithTowns, zoneB))
  273. {
  274. if(findSet(parent, zoneA) != findSet(parent, zoneB))
  275. {
  276. // Not connected, add this road to MST
  277. unionSets(parent, zoneA, zoneB);
  278. setToTrue = true;
  279. logGlobal->info("Setting RANDOM road to TRUE for connection %d between town zones %d and %d",
  280. id, zoneA, zoneB);
  281. }
  282. }
  283. // If one zone has a town and one doesn't
  284. else if(vstd::contains(zonesWithTowns, zoneA) || vstd::contains(zonesWithTowns, zoneB))
  285. {
  286. TRmgTemplateZoneId townZone = vstd::contains(zonesWithTowns, zoneA) ? zoneA : zoneB;
  287. TRmgTemplateZoneId nonTownZone = vstd::contains(zonesWithTowns, zoneA) ? zoneB : zoneA;
  288. // Check if this town already has at least one TRUE connection
  289. if(directConnections[townZone].empty())
  290. {
  291. setToTrue = true;
  292. logGlobal->info("Setting RANDOM road to TRUE for connection %d - only connection for town zone %d",
  293. id, townZone);
  294. }
  295. else
  296. {
  297. // See if this non-town zone connects to another town zone
  298. // This could be a potential bridge zone to connect towns
  299. bool connectsToOtherTown = false;
  300. TRmgTemplateZoneId otherTownZone = 0;
  301. for(auto connectedZone : allConnections[nonTownZone])
  302. {
  303. if(vstd::contains(zonesWithTowns, connectedZone) && connectedZone != townZone)
  304. {
  305. otherTownZone = connectedZone;
  306. connectsToOtherTown = true;
  307. break;
  308. }
  309. }
  310. if(connectsToOtherTown && findSet(parent, townZone) != findSet(parent, otherTownZone))
  311. {
  312. // This non-town zone can help connect two town zones that are not yet connected
  313. setToTrue = true;
  314. logGlobal->info("Setting RANDOM road to TRUE for connection %d - bridge through non-town zone %d to connect towns %d and %d",
  315. id, nonTownZone, townZone, otherTownZone);
  316. }
  317. }
  318. }
  319. // Update all zones with this connection
  320. setRoadOptionForConnection(id, setToTrue ? rmg::ERoadOption::ROAD_TRUE : rmg::ERoadOption::ROAD_FALSE);
  321. if(setToTrue)
  322. {
  323. directConnections[zoneA][zoneB] = true;
  324. directConnections[zoneB][zoneA] = true;
  325. }
  326. }
  327. // Check if we have a connected graph for town zones after initial MST
  328. std::map<TRmgTemplateZoneId, std::set<TRmgTemplateZoneId>> connectedComponents;
  329. std::set<TRmgTemplateZoneId> processedTowns;
  330. for(auto townZone : zonesWithTowns)
  331. {
  332. if(vstd::contains(processedTowns, townZone))
  333. continue;
  334. std::set<TRmgTemplateZoneId> component;
  335. for(auto otherTown : zonesWithTowns)
  336. {
  337. if(findSet(parent, townZone) == findSet(parent, otherTown))
  338. {
  339. component.insert(otherTown);
  340. processedTowns.insert(otherTown);
  341. }
  342. }
  343. if(!component.empty())
  344. connectedComponents[townZone] = component;
  345. }
  346. // If we have more than one component, try to connect them if possible
  347. if(connectedComponents.size() > 1)
  348. {
  349. logGlobal->warn("Found %d disconnected town components, trying to connect them", connectedComponents.size());
  350. // Create a list of components
  351. std::vector<std::set<TRmgTemplateZoneId>> components;
  352. for(auto & component : connectedComponents)
  353. {
  354. components.push_back(component.second);
  355. }
  356. // For each pair of components, try to find a path between them
  357. for(size_t i = 0; i < components.size() - 1; i++)
  358. {
  359. bool foundBridge = false;
  360. for(size_t j = i + 1; j < components.size() && !foundBridge; j++)
  361. {
  362. // Try to find a path between any two towns in different components
  363. for(auto townA : components[i])
  364. {
  365. if(foundBridge) break;
  366. for(auto townB : components[j])
  367. {
  368. // Check if there's a path between townA and townB in the original template
  369. if(vstd::contains(reachableTowns[townA], townB))
  370. {
  371. // There's a path, now find the specific path to enable roads on
  372. std::queue<TRmgTemplateZoneId> q;
  373. std::map<TRmgTemplateZoneId, TRmgTemplateZoneId> prev;
  374. q.push(townA);
  375. prev[townA] = 0; // Mark as visited with no predecessor
  376. bool found = false;
  377. while(!q.empty() && !found)
  378. {
  379. auto current = q.front();
  380. q.pop();
  381. for(auto next : allConnections[current])
  382. {
  383. if(!vstd::contains(prev, next))
  384. {
  385. prev[next] = current;
  386. q.push(next);
  387. if(next == townB)
  388. {
  389. found = true;
  390. break;
  391. }
  392. }
  393. }
  394. }
  395. // Now reconstruct the path and set all roads on this path to TRUE
  396. if(found)
  397. {
  398. std::vector<TRmgTemplateZoneId> path;
  399. TRmgTemplateZoneId current = townB;
  400. while(current != townA)
  401. {
  402. path.push_back(current);
  403. current = prev[current];
  404. }
  405. path.push_back(townA);
  406. // Reverse to get path from townA to townB
  407. std::reverse(path.begin(), path.end());
  408. logGlobal->info("Found path between town zones %d and %d, enabling all roads on this path", townA, townB);
  409. // Enable all roads on this path
  410. for(size_t k = 0; k < path.size() - 1; k++)
  411. {
  412. auto zoneA = path[k];
  413. auto zoneB = path[k+1];
  414. auto minZone = std::min(zoneA, zoneB);
  415. auto maxZone = std::max(zoneA, zoneB);
  416. if(vstd::contains(connectionIds, std::make_pair(minZone, maxZone)))
  417. {
  418. auto connectionId = connectionIds[std::make_pair(minZone, maxZone)];
  419. // Enable this road if it's not already TRUE
  420. for(auto & zonePtr : zones)
  421. {
  422. for(auto & connection : zonePtr.second->getConnections())
  423. {
  424. if(connection.getId() == connectionId &&
  425. connection.getRoadOption() == rmg::ERoadOption::ROAD_RANDOM)
  426. {
  427. setRoadOptionForConnection(connectionId, rmg::ERoadOption::ROAD_TRUE);
  428. directConnections[zoneA][zoneB] = true;
  429. directConnections[zoneB][zoneA] = true;
  430. logGlobal->info("Setting RANDOM road to TRUE for connection %d to connect components - part of path between towns %d and %d",
  431. connectionId, townA, townB);
  432. break;
  433. }
  434. }
  435. }
  436. }
  437. }
  438. // Update Union-Find to merge components
  439. unionSets(parent, townA, townB);
  440. foundBridge = true;
  441. break;
  442. }
  443. }
  444. }
  445. }
  446. }
  447. if(!foundBridge)
  448. {
  449. logGlobal->warn("Could not find a path between component with towns [%s] and other components",
  450. [&components, i]() {
  451. std::string result;
  452. for(auto town : components[i])
  453. {
  454. if(!result.empty()) result += ", ";
  455. result += std::to_string(town);
  456. }
  457. return result;
  458. }().c_str());
  459. }
  460. }
  461. }
  462. // Final check for connectivity between town zones
  463. std::set<TRmgTemplateZoneId> connectedTowns;
  464. if(!zonesWithTowns.empty())
  465. {
  466. auto firstTown = *zonesWithTowns.begin();
  467. for(auto town : zonesWithTowns)
  468. {
  469. if(findSet(parent, firstTown) == findSet(parent, town))
  470. {
  471. connectedTowns.insert(town);
  472. }
  473. }
  474. }
  475. logGlobal->info("Final town connectivity: %d connected out of %d total town zones",
  476. connectedTowns.size(), zonesWithTowns.size());
  477. logGlobal->info("Finished road generation - created minimal spanning tree connecting all towns");
  478. }
  479. VCMI_LIB_NAMESPACE_END