From f0971dfdd61c1ea7385d06a0d8c0c5b6deac852f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Fri, 14 Mar 2025 11:03:28 +0100 Subject: [PATCH] First implementation of random connections --- .../Content/config/rmg/symmetric/6lm10a.json | 86 +++++----- lib/rmg/CMapGenerator.cpp | 1 + lib/rmg/CZonePlacer.cpp | 157 ++++++++++++++++++ lib/rmg/CZonePlacer.h | 1 + lib/rmg/modificators/ConnectionsPlacer.cpp | 3 +- 5 files changed, 203 insertions(+), 45 deletions(-) diff --git a/Mods/vcmi/Content/config/rmg/symmetric/6lm10a.json b/Mods/vcmi/Content/config/rmg/symmetric/6lm10a.json index 1df537fd3..e135682ad 100644 --- a/Mods/vcmi/Content/config/rmg/symmetric/6lm10a.json +++ b/Mods/vcmi/Content/config/rmg/symmetric/6lm10a.json @@ -256,59 +256,59 @@ }, "connections" : [ - { "a" : "1", "b" : "9", "guard" : 6000 }, - { "a" : "1", "b" : "21", "guard" : 3000 }, - { "a" : "1", "b" : "21", "guard" : 3000 }, + { "a" : "1", "b" : "9", "guard" : 6000, "road" : "true" }, + { "a" : "1", "b" : "21", "guard" : 3000, "road" : "random" }, + { "a" : "1", "b" : "21", "guard" : 3000, "road" : "true" }, - { "a" : "2", "b" : "10", "guard" : 6000 }, - { "a" : "2", "b" : "22", "guard" : 3000 }, - { "a" : "2", "b" : "22", "guard" : 3000 }, + { "a" : "2", "b" : "10", "guard" : 6000, "road" : "random" }, + { "a" : "2", "b" : "22", "guard" : 3000, "road" : "random" }, + { "a" : "2", "b" : "22", "guard" : 3000, "road" : "random" }, - { "a" : "3", "b" : "21", "guard" : 3000 }, - { "a" : "3", "b" : "23", "guard" : 3000 }, - { "a" : "3", "b" : "24", "guard" : 3000 }, + { "a" : "3", "b" : "21", "guard" : 3000, "road" : "true" }, + { "a" : "3", "b" : "23", "guard" : 3000, "road" : "false" }, + { "a" : "3", "b" : "24", "guard" : 3000, "road" : "random" }, - { "a" : "4", "b" : "22", "guard" : 3000 }, - { "a" : "4", "b" : "23", "guard" : 3000 }, - { "a" : "4", "b" : "25", "guard" : 3000 }, + { "a" : "4", "b" : "22", "guard" : 3000, "road" : "random" }, + { "a" : "4", "b" : "23", "guard" : 3000, "road" : "false" }, + { "a" : "4", "b" : "25", "guard" : 3000, "road" : "true" }, - { "a" : "5", "b" : "13", "guard" : 6000 }, - { "a" : "5", "b" : "24", "guard" : 3000 }, - { "a" : "5", "b" : "24", "guard" : 3000 }, + { "a" : "5", "b" : "13", "guard" : 6000, "road" : "true" }, + { "a" : "5", "b" : "24", "guard" : 3000, "road" : "random" }, + { "a" : "5", "b" : "24", "guard" : 3000, "road" : "random" }, { "a" : "6", "b" : "14", "guard" : 6000 }, - { "a" : "6", "b" : "25", "guard" : 3000 }, - { "a" : "6", "b" : "25", "guard" : 3000 }, + { "a" : "6", "b" : "25", "guard" : 3000, "road" : "random" }, + { "a" : "6", "b" : "25", "guard" : 3000, "road" : "true" }, - { "a" : "7", "b" : "21", "guard" : 6000 }, - { "a" : "7", "b" : "21", "guard" : 6000 }, - { "a" : "8", "b" : "22", "guard" : 6000 }, - { "a" : "8", "b" : "22", "guard" : 6000 }, - { "a" : "15", "b" : "24", "guard" : 6000 }, - { "a" : "15", "b" : "24", "guard" : 6000 }, - { "a" : "16", "b" : "25", "guard" : 6000 }, - { "a" : "16", "b" : "25", "guard" : 6000 }, + { "a" : "7", "b" : "21", "guard" : 6000, "road" : "random" }, + { "a" : "7", "b" : "21", "guard" : 6000, "road" : "random" }, + { "a" : "8", "b" : "22", "guard" : 6000, "road" : "random" }, + { "a" : "8", "b" : "22", "guard" : 6000, "road" : "random" }, + { "a" : "15", "b" : "24", "guard" : 6000, "road" : "random" }, + { "a" : "15", "b" : "24", "guard" : 6000, "road" : "random" }, + { "a" : "16", "b" : "25", "guard" : 6000, "road" : "random" }, + { "a" : "16", "b" : "25", "guard" : 6000, "road" : "random" }, - { "a" : "9", "b" : "10", "guard" : 6000 }, - { "a" : "9", "b" : "21", "guard" : 6000 }, - { "a" : "9", "b" : "23", "guard" : 6000 }, - { "a" : "10", "b" : "22", "guard" : 6000 }, - { "a" : "10", "b" : "23", "guard" : 6000 }, - { "a" : "13", "b" : "23", "guard" : 6000 }, - { "a" : "13", "b" : "24", "guard" : 6000 }, - { "a" : "13", "b" : "14", "guard" : 6000 }, - { "a" : "14", "b" : "23", "guard" : 6000 }, - { "a" : "14", "b" : "25", "guard" : 6000 }, + { "a" : "9", "b" : "10", "guard" : 6000, "road" : "true" }, + { "a" : "9", "b" : "21", "guard" : 6000, "road" : "random" }, + { "a" : "9", "b" : "23", "guard" : 6000, "road" : "false" }, + { "a" : "10", "b" : "22", "guard" : 6000, "road" : "random" }, + { "a" : "10", "b" : "23", "guard" : 6000, "road" : "random" }, + { "a" : "13", "b" : "23", "guard" : 6000, "road" : "random" }, + { "a" : "13", "b" : "24", "guard" : 6000, "road" : "random" }, + { "a" : "13", "b" : "14", "guard" : 6000, "road" : "true" }, + { "a" : "14", "b" : "23", "guard" : 6000, "road" : "false" }, + { "a" : "14", "b" : "25", "guard" : 6000, "road" : "random" }, - { "a" : "11", "b" : "21", "guard" : 6000 }, - { "a" : "11", "b" : "24", "guard" : 6000 }, - { "a" : "12", "b" : "22", "guard" : 6000 }, - { "a" : "12", "b" : "25", "guard" : 6000 }, + { "a" : "11", "b" : "21", "guard" : 6000, "road" : "true" }, + { "a" : "11", "b" : "24", "guard" : 6000, "road" : "random" }, + { "a" : "12", "b" : "22", "guard" : 6000, "road" : "true" }, + { "a" : "12", "b" : "25", "guard" : 6000, "road" : "random" }, - { "a" : "17", "b" : "21", "guard" : 9000 }, - { "a" : "18", "b" : "22", "guard" : 9000 }, - { "a" : "19", "b" : "24", "guard" : 9000 }, - { "a" : "20", "b" : "25", "guard" : 9000 }, + { "a" : "17", "b" : "21", "guard" : 9000, "road" : "random" }, + { "a" : "18", "b" : "22", "guard" : 9000, "road" : "random" }, + { "a" : "19", "b" : "24", "guard" : 9000, "road" : "random" }, + { "a" : "20", "b" : "25", "guard" : 9000, "road" : "random" }, { "a" : "21", "b" : "22", "type": "wide" }, { "a" : "24", "b" : "25", "type": "wide" } diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index b846eb726..e17fd9c79 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -328,6 +328,7 @@ void CMapGenerator::genZones() { placer->placeZones(rand.get()); placer->assignZones(rand.get()); + placer->dropRandomRoads(rand.get()); logGlobal->info("Zones generated successfully"); } diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 99d02d99c..2714c9523 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -18,6 +18,7 @@ #include "../mapping/CMapEditManager.h" #include "../GameLibrary.h" #include "CMapGenOptions.h" +#include "CRmgTemplate.h" #include "RmgMap.h" #include "Zone.h" #include "Functions.h" @@ -1004,6 +1005,162 @@ void CZonePlacer::assignZones(vstd::RNG * rand) logGlobal->info("Finished zone colouring"); } +void CZonePlacer::dropRandomRoads(vstd::RNG * rand) +{ + auto zones = map.getZones(); + + //First, build a graph of road connections + std::map> roadGraph; + std::vector randomConnections; + std::vector fixedConnections; + + //Collect all road connections and build initial graph + for(const auto & zone : zones) + { + for(const auto & connection : zone.second->getConnections()) + { + if(connection.getRoadOption() == rmg::ERoadOption::ROAD_TRUE) + { + roadGraph[connection.getZoneA()].insert(connection.getZoneB()); + roadGraph[connection.getZoneB()].insert(connection.getZoneA()); + fixedConnections.push_back(connection); + } + else if(connection.getRoadOption() == rmg::ERoadOption::ROAD_RANDOM) + { + roadGraph[connection.getZoneA()].insert(connection.getZoneB()); + roadGraph[connection.getZoneB()].insert(connection.getZoneA()); + randomConnections.push_back(connection); + } + } + } + + //Find all connected components in the initial graph + std::map zoneToComponent; + int numComponents = 0; + + auto dfsComponent = [&](TRmgTemplateZoneId start, int component) + { + std::stack stack; + stack.push(start); + + while(!stack.empty()) + { + auto current = stack.top(); + stack.pop(); + + if(zoneToComponent.find(current) != zoneToComponent.end()) + continue; + + zoneToComponent[current] = component; + + for(auto neighbor : roadGraph[current]) + { + if(zoneToComponent.find(neighbor) == zoneToComponent.end()) + { + stack.push(neighbor); + } + } + } + }; + + //Find all components + for(const auto & zone : zones) + { + if(zoneToComponent.find(zone.first) == zoneToComponent.end() && !roadGraph[zone.first].empty()) + { + dfsComponent(zone.first, numComponents++); + } + } + + //Process each component separately + for(int component = 0; component < numComponents; component++) + { + //Get random connections for this component + std::vector componentRandomConnections; + for(const auto & conn : randomConnections) + { + if(zoneToComponent[conn.getZoneA()] == component) + { + componentRandomConnections.push_back(conn); + } + } + + //Shuffle random connections + RandomGeneratorUtil::randomShuffle(componentRandomConnections, *rand); + + //Try removing each random connection + for(const auto & conn : componentRandomConnections) + { + //Temporarily remove this connection + roadGraph[conn.getZoneA()].erase(conn.getZoneB()); + roadGraph[conn.getZoneB()].erase(conn.getZoneA()); + + //Check if graph remains connected + bool canRemove = true; + std::set visited; + + //Start DFS from any zone in this component + auto startZone = conn.getZoneA(); + std::stack stack; + stack.push(startZone); + + while(!stack.empty()) + { + auto current = stack.top(); + stack.pop(); + + if(visited.find(current) != visited.end()) + continue; + + visited.insert(current); + + for(auto neighbor : roadGraph[current]) + { + if(visited.find(neighbor) == visited.end()) + { + stack.push(neighbor); + } + } + } + + //Check if all zones in this component are still reachable + for(const auto & zone : zones) + { + if(zoneToComponent[zone.first] == component && !roadGraph[zone.first].empty()) + { + if(visited.find(zone.first) == visited.end()) + { + canRemove = false; + break; + } + } + } + + if(!canRemove) + { + //Restore connection if removing it would break connectivity + roadGraph[conn.getZoneA()].insert(conn.getZoneB()); + roadGraph[conn.getZoneB()].insert(conn.getZoneA()); + } + else + { + //Mark this connection as having no road + for(auto & zonePtr : zones) + { + for(auto & connection : zonePtr.second->getConnections()) + { + if(connection.getId() == conn.getId()) + { + const_cast(connection) = conn; //FIXME: avoid const_cast + break; + } + } + } + } + } + } +} + const TDistanceMap& CZonePlacer::getDistanceMap() { return distancesBetweenZones; diff --git a/lib/rmg/CZonePlacer.h b/lib/rmg/CZonePlacer.h index 43e3479df..effcd8345 100644 --- a/lib/rmg/CZonePlacer.h +++ b/lib/rmg/CZonePlacer.h @@ -46,6 +46,7 @@ public: void placeOnGrid(vstd::RNG* rand); float scaleForceBetweenZones(const std::shared_ptr zoneA, const std::shared_ptr zoneB) const; void assignZones(vstd::RNG * rand); + void dropRandomRoads(vstd::RNG * rand); const TDistanceMap & getDistanceMap(); diff --git a/lib/rmg/modificators/ConnectionsPlacer.cpp b/lib/rmg/modificators/ConnectionsPlacer.cpp index ea0d89a2f..612e3872a 100644 --- a/lib/rmg/modificators/ConnectionsPlacer.cpp +++ b/lib/rmg/modificators/ConnectionsPlacer.cpp @@ -477,8 +477,7 @@ void ConnectionsPlacer::collectNeighbourZones() bool ConnectionsPlacer::shouldGenerateRoad(const rmg::ZoneConnection& connection) const { - return connection.getRoadOption() == rmg::ERoadOption::ROAD_TRUE || - (connection.getRoadOption() == rmg::ERoadOption::ROAD_RANDOM && zone.getRand().nextDouble(0, 1) >= 0.5f); + return connection.getRoadOption() == rmg::ERoadOption::ROAD_TRUE; } void ConnectionsPlacer::createBorder()