CRoadRandomizer.cpp 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  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 obligatory road connections 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. // Initialize Union-Find for all zones to track connectivity
  91. std::map<TRmgTemplateZoneId, TRmgTemplateZoneId> parent;
  92. // Track if a component (represented by its root) contains a town
  93. std::map<TRmgTemplateZoneId, bool> componentHasTown;
  94. for(const auto & zone : zones)
  95. {
  96. auto zoneId = zone.first;
  97. parent[zoneId] = zoneId;
  98. componentHasTown[zoneId] = vstd::contains(zonesWithTowns, zoneId);
  99. }
  100. // Process all connections that are already set to TRUE
  101. for(auto & zonePtr : zones)
  102. {
  103. for(auto & connection : zonePtr.second->getConnections())
  104. {
  105. if(connection.getRoadOption() == rmg::ERoadOption::ROAD_TRUE)
  106. {
  107. auto zoneA = connection.getZoneA();
  108. auto zoneB = connection.getZoneB();
  109. auto rootA = findSet(parent, zoneA);
  110. auto rootB = findSet(parent, zoneB);
  111. if(rootA != rootB)
  112. {
  113. bool hasTown = componentHasTown[rootA] || componentHasTown[rootB];
  114. parent[rootA] = rootB;
  115. componentHasTown[rootB] = hasTown;
  116. }
  117. }
  118. }
  119. }
  120. // Collect all RANDOM connections to be processed
  121. std::vector<rmg::ZoneConnection> randomRoads;
  122. std::map<int, bool> processedConnectionIds;
  123. for(auto & zonePtr : zones)
  124. {
  125. for(auto & connection : zonePtr.second->getConnections())
  126. {
  127. if(connection.getRoadOption() == rmg::ERoadOption::ROAD_RANDOM)
  128. {
  129. // Ensure we only add each connection once
  130. if(processedConnectionIds.find(connection.getId()) == processedConnectionIds.end())
  131. {
  132. randomRoads.push_back(connection);
  133. processedConnectionIds[connection.getId()] = true;
  134. }
  135. }
  136. }
  137. }
  138. RandomGeneratorUtil::randomShuffle(randomRoads, *rand);
  139. // Process random roads using Kruskal's-like algorithm to connect components
  140. for(const auto& connection : randomRoads)
  141. {
  142. auto zoneA = connection.getZoneA();
  143. auto zoneB = connection.getZoneB();
  144. auto rootA = findSet(parent, zoneA);
  145. auto rootB = findSet(parent, zoneB);
  146. bool roadSet = false;
  147. // Only build roads if they connect different components
  148. if(rootA != rootB)
  149. {
  150. bool townInA = componentHasTown[rootA];
  151. bool townInB = componentHasTown[rootB];
  152. // Connect components if at least one of them contains a town.
  153. // This ensures we connect all town components and extend them,
  154. // but never connect two non-town components together.
  155. if(townInA || townInB)
  156. {
  157. logGlobal->info("Setting RANDOM road to TRUE for connection %d between zones %d and %d to connect town components.", connection.getId(), zoneA, zoneB);
  158. setRoadOptionForConnection(connection.getId(), rmg::ERoadOption::ROAD_TRUE);
  159. // Union the sets
  160. bool hasTown = townInA || townInB;
  161. parent[rootA] = rootB;
  162. componentHasTown[rootB] = hasTown;
  163. roadSet = true;
  164. }
  165. }
  166. if(!roadSet)
  167. {
  168. // This road was not chosen, either because it creates a cycle or connects two non-town areas
  169. setRoadOptionForConnection(connection.getId(), rmg::ERoadOption::ROAD_FALSE);
  170. }
  171. }
  172. logGlobal->info("Finished road generation - created minimal spanning tree connecting all towns");
  173. }
  174. VCMI_LIB_NAMESPACE_END