In Chartered Waters, the map and the location of cities are procedurally generated. In this aspect, at least, it follows the roguelike tradition. However, items are not entirely procedurally generated and instead picked from a random list. This for atmospheric reasons; trading specific items that the player has schema of (say, Glassware or Jade or Clover or Mace) feels better than trading random, generic items. In this blog post, I shall discuss how the cities are placed.

Cities.

This is a populated section of a typical CW map. The orange faction and the green faction dominate this area, with blue and teal on the edges. The cities are not totally randomly placed: like-colored cities tend to be near other like-colored cities. Additionally, all cities are on coast. None are inland. This is done by using the following algorithm:

foreach group of cities:
    startingPoint = getRandomCoastCoordinate();
    for (numberOfCities) times:
        until (isNotInZoneOfControl(current)) and (numberOfTries < limit): # of another city
            current = getRandomOceanCoordinate();
            numberOfTries++;
        if (numberOfTries >= limit):
            continue;
        markCoordinateAsCity();

Or, in shorter terms, move an imaginary ‘settler boat’ either until a certain limit has been hit (in which case we give up) or the boat hits a piece of land that is not in a zone of control and is also a coast. The boat effectively shows Brownian motion.

One thing that must be explained is the zone of control.

Zone of controls for cities

Basically, a zone of control is a limit where other cities are not allowed to settle. This is to prevent all of the cities from clustering in a small region, which can easily happen because the Brownian motion of the boat doubles back on itself frequently.

The paths of the boat look roughly like this:

cities-brownian

One drawback of the visualization is that no info about how many times a certain tile was pathed to survives. However, given that a boat has a threshold of 50 to 100 in the current version of CW, many of the tiles were certainly stepped on multiple times.

Generating cities in this way has been sufficient so far: cities are not entirely randomly generated, instead clustering, and factions slightly overlap. There is an additional part to the generation logic that helps with the illusion of realistic cities:

# ...
foreach faction:
    genCities(number, random(50, 100));
foreach faction:
    genCities(number/2, random(50, 100));
foreach faction:
    genCities(number/4, random(50, 100));
# ...

CW generates the cities in three steps, reducing the number of placed cities each time. Looking at a world map, generally one country’s cities in the Imperialism age were all clustered, but there always are a few outliers. The genWorld() function weakly tries to simulate that behaviour.

The downsides of the random boat generation are mainly two. One, the cities are still too clustered for my tastes, but increasing the Zone of Control radius tends to make city placement fail frequently. Two, the world is still very sparse. The vast majority of a CW world is not settled at all, and ideally something should be done about it.

The actual code for this algorithm is a ~~bit~~ very cluttered (written by me nearly six months ago), but it is in worldMap.cpp file; class randomBoat, function WorldMapClass::setFactionsCity() along with randomBoat‘s member functions and miscellaneous helper functions in the repo here.