Using Python to "Solve" the Map

MrFeline

Chieftain
Joined
Sep 9, 2012
Messages
58
Hi all!


Full disclosure, I am not a software engineer. I had to dig up my college Intro the CS course knowledge to make this, so I'm sure there are many, many ways it could be optimized. But! I've always been fascinated by the map, and by trying to place the best cities. I finally did something about it and wrote a program that produces a heatmap of food production and uses that to calculate maximum city sizes. Here are some assumptions and then the result!
Spoiler Assumptions :

- All food-boosting technologies are unlocked (Chemistry, Genetics)

- No food-boosting wonders, buildings, civics, unique powers, or unique buildings are present (Tsukiji Fish Market, Delta Works, Apothecary, Incan mountains, supermarket, etc.)

- No food-providing specialists

- All farmland is irrigated, despite the fact that there are a few plains on the map that cannot be irrigated (what comes to mind are islands and the flatlands to the east of Lake Baikal, but I’m sure there are others)

- All cities work their entire BFC

- All food resources are improved with their correct improvement, and all other tiles are improved with farms or windmills (even when non-optimal, say on a coal hill)

Spoiler Odds and Ends :

- The map allows you to place cities where RFC may not let you - tundra and desert without water. I left that in, as these cities aren't very well supported anyways.

- I used the 1700 AD map, so there are some late resources missing (Rice in Italy, flood plains and rice in California, most things in Australia)

- Currently, cities don’t get bonuses for being founded on resources or flood plains. This is an error I need to fix, but I’m… very tired. I’ll update this document when I do :)

Spoiler Results! :


The brighter the color is, the more population the city can support

Twenty possible city locations had potential populations of 50 or over. These are:

(92, 38) 102 food, Ayodha 1W of Varanasi on the gems

(93, 38) 107 food, Varanasi on the hill

(94, 39) 100 food, Pataliputra on the dye

(105, 42) 102 food, Wuhan on the silk

(104, 43) 100 food, Kaifeng on the deer

(21, 47) 100 food, Kansas City 1S of the horse

(22, 47) 102 food, Kansas a city 1SE of the horse

(21, 48) 101 food, Omaha on the horse

(22, 48) 100 food, Omaha 1E of the horse

(54, 49) 103 food, Tours on the uranium

(55, 49) 104 food, Orleans 1S of standard Paris

(55, 50) 102 food, Paris (standard)

(57, 51) 101 food, Aix-la-Chapelle

(58, 52) 101 food, Cologne on the cow

(59, 52) 107 food, Munster 1S of the horse

(59, 53) 100 food, Goslar on the horse

(66, 53) 102 food, Brest-Litowsk on the horse

(67, 54) 100 food, Wilno 1E of the wheat

(67, 55) 102 food, Polock 1NE of the wheat

(68, 55) 101 food, Novgorod 1NW of the pig

(If someone could graciously help me embed an image... I used "[#IMG]" (no #) but it doesn't appear to be working.)
 

Attachments

  • osBlhzJ - Imgur.png
    osBlhzJ - Imgur.png
    114.6 KB · Views: 238
I actually did something similar for the new map, to identify supercity locations and clusters of cities that were too strong.
 
I actually did something similar for the new map, to identify supercity locations and clusters of cities that were too strong.

Great minds think alike, I suppose! Would you be willing to send me your code (if you did it in Python)? I'm sure yours is a lot more elegant, and I'd like to learn from it.
 
I made some different assumptions: I only considered natural tile yield, and food provided by improving all resources, plus the harbour effect. The goal was more to understand the impact of how resources are placed. Resources are often placed with the intent to support certain city sites but there may be unintended side effects where undesired sites are also supported. I mostly wanted to catch these sites, which is why I ignored the impact of e.g. other food improvements and techs.

The script will also place markers on the map for all "outstanding" sites, which is tiles that have more food than all adjacent tiles. This was written before the Core module existed, a lot of it could be written more elegantly with it:
Code:
from CvPythonExtensions import *

gc = CyGlobalContext()
map = gc.getMap()
engine = CyEngine()

def calculateBonusImprovementFood(iBonus):
   iBonusImprovement = -1
   for iImprovement in range(gc.getNumImprovementInfos()):
       improvement = gc.getImprovementInfo(iImprovement)
       if improvement.isImprovementBonusTrade(iBonus) and not improvement.isActsAsCity():
           return improvement.getImprovementBonusYield(iBonus, YieldTypes.YIELD_FOOD)

def calculateTileFood(plot):
   iFood = plot.getYield(YieldTypes.YIELD_FOOD)
   iBonus = plot.getBonusType(-1)
   if iBonus >= 0:
       iFood += calculateBonusImprovementFood(iBonus)

   return iFood

def calculateCityFood(site, tileFood):
   iFood = 0

   if site.isImpassable() or site.isWater():
       return 0

   for i in range(21):
       plot = plotCity(site.getX(), site.getY(), i)
       index = map.plotIndex(plot.getX(), plot.getY())

       if (plot.getX(), plot.getY()) == (site.getX(), site.getY()):
           continue

       iFood += tileFood[index]

       # coastal cities receive Harbor food
       if site.isCoastalLand() and plot.isWater():
           iFood += 1

   return iFood

def findOutstandingSites(sites):
   return [(index, food) for index, food in enumerate(sites) if isOutstandingSite(sites, map.plotByIndex(index), food)]

def isOutstandingSite(sites, site, food):
   if food == 0:
       return False

   for iDirection in range(8):
       plot = plotDirection(site.getX(), site.getY(), DirectionTypes(iDirection))
       index = map.plotIndex(plot.getX(), plot.getY())

       if sites[index] > food:
           return False

   return True


lTileFood = [calculateTileFood(map.plotByIndex(i)) for i in range(map.numPlots())]
lSiteFood = [calculateCityFood(map.plotByIndex(i), lTileFood) for i in range(map.numPlots())]

lOutstandingSites = findOutstandingSites(lSiteFood)

for index, food in lOutstandingSites:
   engine.addLandmark(map.plotByIndex(index), str(food))
 
Is it possible to use something like this to get which civ has the biggest core population potential? I'm guessing that with every late agriculture tech large cores like Russia's have the advantage?
 
Sure, but it depends on a lot of factors, like city placement. Because cities can be in core but reach tiles outside of core, so it isn't easy to determine which tiles would be included. Also, it remains to be questioned if you would actually optimise for population, which would mean farms and windmills everywhere.
 
You can fit 9 very strong cities in Russia's core, I don't think any other civ comes close to that... China and USA are definitely up there though. France, Spain, and Greece can get some super high core populations if you get a little creative.
 
You can fit 9 very strong cities in Russia's core, I don't think any other civ comes close to that... China and USA are definitely up there though. France, Spain, and Greece can get some super high core populations if you get a little creative.
I'd agree. Russia has an insane core, population wise. Mongolia's is also fairly strong after the China flip, and especially once genetics and chemistry make plains viable food sources. I've found France to be the best core of a "normal" size.

Interestingly enough, China's core is amazing early and mid-game, but it doesn't benefit nearly as much as other cores from Genetics and Chemistry as the majority of its tiles are either hill or non-farmable resources. So they start strong but don't scale like USA, Mongolia, and Russia do. Same goes for India, though it benefits from Microbiology adding +1 Food to about half the tiles.
 
Top Bottom