From d5c7134d333e9e8c66276261243b7a174d415df6 Mon Sep 17 00:00:00 2001 From: oscuro Date: Thu, 21 Dec 2017 12:46:47 +0100 Subject: [PATCH] Done --- .gitignore | 3 + City.py | 216 +++++ Culture.py | 196 ++++ Flag.py | 66 ++ GenConfiguration.py | 3 + Influence.py | 43 + Landmass.py | 75 ++ Language.py | 70 ++ Map.py | 953 +++++++++++++++++++ Node.py | 239 +++++ Population.py | 12 + Region.py | 31 + ResourceRegion.py | 95 ++ River.py | 57 ++ Tools.py | 99 ++ Triangle.py | 100 ++ Values.py | 40 + bodyWater.py | 19 + mapgenmain.py | 2116 +------------------------------------------ noiseMaker.py | 30 + 20 files changed, 2353 insertions(+), 2110 deletions(-) create mode 100644 .gitignore create mode 100644 City.py create mode 100644 Culture.py create mode 100644 Flag.py create mode 100644 GenConfiguration.py create mode 100644 Influence.py create mode 100644 Landmass.py create mode 100644 Language.py create mode 100644 Map.py create mode 100644 Node.py create mode 100644 Population.py create mode 100644 Region.py create mode 100644 ResourceRegion.py create mode 100644 River.py create mode 100644 Tools.py create mode 100644 Triangle.py create mode 100644 Values.py create mode 100644 bodyWater.py mode change 100644 => 100755 mapgenmain.py create mode 100644 noiseMaker.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7666e7a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +__pycache__ +Tools.pyc diff --git a/City.py b/City.py new file mode 100644 index 0000000..c4f2370 --- /dev/null +++ b/City.py @@ -0,0 +1,216 @@ +from Tools import * +from ResourceRegion import ResourceRegion +from Culture import Culture + + +class City: + def __init__(self, n, pop=50, cltr=None, m=None): + self.myMap = m + self.myMap.cities.append(self) + self.node = n + self.node.city = self + self.population = pop + if cltr is None: + self.culture = Culture(self.node, m) + else: + self.culture = cltr + self.cityType = self.cType(self.population) + self.name = self.culture.language.genName() + self.region = self.node.region + self.node.culture = self.culture + self.node.allegiance = 1 / self.population + if self.culture.name not in self.node.region.culturalNames: + self.node.region.culturalNames[self.culture.name] = self.culture.language.genName() + if self.culture.name not in self.node.landmass.culturalNames: + self.node.landmass.culturalNames[self.culture.name] = self.culture.language.genName() + for q in self.node.neighbors: + if q.region.biome == "ocean" or q.region.biome == "lake" or q.region.biome == "sea": + if self.culture.name not in q.region.culturalNames: + q.region.culturalNames[self.culture.name] = self.culture.language.genName() + if self.node.river is not None: + if self.culture.name not in self.node.river.culturalNames: + self.node.river.culturalNames[self.culture.name] = self.culture.language.genName() + reg = ResourceRegion(self, self.myMap) + self.rawResources = [0, 0] + + def updateDemog(self): + rscShare = self.population / self.node.resourceRegion.totalPop + rscMax = [0, 0] + rscMax[0] = rscShare * self.node.resourceRegion.resources[0] + rscMax[1] = rscShare * self.node.resourceRegion.resources[1] + mpo = 0.1 # Maximum personal output (max resources production per person) + m = self.culture.value.mainValues + if "agriculture" in m: + mpo *= 1.1 + if "simplicity" in m: + mpo *= 0.8 + if "warriors" in m: + mpo *= 1.05 + if "builders" in m: + mpo *= 1.05 + if "metallurgists" in m: + mpo *= 1.05 + if "craftsmen" in m: + mpo *= 1.05 + self.foodProduction = min(self.population * mpo, rscMax[0]) + self.industrialProduction = min(self.population * mpo, rscMax[1]) + mpc = 0.080 # Maximum personal consumption (max food needed per person) + mpc -= 0.002 * (math.log10(clamp(self.population, 1, 1000000)) + 1) + # As the population grows, need less food per person due to economies of scale + mpc += 0.001 * math.log2(clamp(len(self.node.resourceRegion.nodes) - 16, 1, 1000000)) + # As the resource region grows, need more food per person due to transporation distance + if "collectivists" in m: + mpc -= 0.001 + if "freedom" in m: + mpc += 0.001 + if "simplicity" in m: + mpc -= 0.001 + if "greed" in m: + mpc += 0.001 + self.foodConsumption = mpc * self.population + diff = self.foodProduction - self.foodConsumption + growth = clamp(diff / mpc, -self.population * 0.1, self.population * 0.1) + self.population = math.ceil(self.population * 0.99) # Age related death + self.population = clamp(math.floor(self.population + growth + random.choice([-1, 0, 0, 0, 0, 1])), 1, 1000000) + self.cityType = self.cType(self.population) + + def cType(self, p): + if p <= 35: + c = random.choice(["bivouac", "camp", "camp", "encampment", "campsite"]) + elif p <= 200: + c = random.choice(["village", "village", "hamlet"]) + elif p <= 1000: + c = random.choice(["township", "settlement"]) + elif p <= 5000: + c = "town" + elif p <= 20000: + c = "city" + else: + c = "metropolis" + return c.capitalize() + + def cityInfo(self): + n = self.name + " (" + self.cityType + ")\n" + n += "Population: " + str(self.population) + return n + + def cultureInfo(self): + info = self.culture.information() + return info + + def drawTent(self, drawer, xx, yy, col, out): + p0 = (xx, yy - 6) + p1 = (xx - 4, yy + 2) + p2 = (xx + 4, yy + 2) + drawer.polygon([p0, p1, p2], outline=out, fill=col) + p0 = (xx, yy - 1) + p1 = (xx - 1, yy + 2) + p2 = (xx + 1, yy + 2) + drawer.polygon([p0, p1, p2], outline=out, fill=out) + p0 = (xx, yy - 6) + p1 = (xx - 2, yy - 8) + p2 = (xx + 2, yy - 8) + drawer.line([p0, p1], fill=out, width=1) + drawer.line([p0, p2], fill=out, width=1) + + def drawHut(self, drawer, xx, yy, col, out): + p0 = (xx + 3, yy + 2) + p1 = (xx - 3, yy + 2) + p2 = (xx - 3, yy - 3) + p3 = (xx, yy - 6) + p4 = (xx + 3, yy - 3) + drawer.polygon([p0, p1, p2, p3, p4], outline=out, fill=col) + p1 = (xx - 1, yy - 1) + p2 = (xx + 1, yy + 2) + drawer.rectangle([p1, p2], outline=out, fill=out) + p0 = (xx, yy - 6) + p1 = (xx + 5, yy - 1) + p2 = (xx - 5, yy - 1) + drawer.line([p0, p1], fill=out, width=1) + drawer.line([p0, p2], fill=out, width=1) + + def drawVillage(self, drawer, xx, yy, col, out): + p0 = (xx, yy - 5) + p1 = (xx - 2, yy - 7) + p2 = (xx - 4, yy - 5) + p3 = (xx - 4, yy + 3) + p4 = (xx, yy + 3) + drawer.polygon([p0, p1, p2, p3, p4], outline=out, fill=col) + p0 = (xx - 6, yy - 3) + p2 = (xx + 2, yy - 3) + drawer.line([p1, p0], fill=out, width=1) + drawer.line([p1, p2], fill=out, width=1) + p3 = (xx - 2, yy - 4) + p4 = (xx - 2, yy - 3) + drawer.line([p4, p3], fill=out, width=1) + p0 = (xx, yy - 1) + p1 = (xx + 2, yy - 3) + p2 = (xx + 4, yy - 1) + p3 = (xx + 4, yy + 3) + p4 = (xx, yy + 3) + drawer.polygon([p0, p1, p2, p3, p4], outline=out, fill=col) + p0 = (xx - 2, yy + 1) + p2 = (xx + 6, yy + 1) + drawer.line([p1, p0], fill=out, width=1) + drawer.line([p1, p2], fill=out, width=1) + p3 = (xx + 2, yy + 3) + p4 = (xx + 2, yy + 1) + drawer.line([p4, p3], fill=out, width=1) + + def drawTown(self, drawer, xx, yy, col, out): + p0 = (xx - 4, yy + 3) + p1 = (xx - 4, yy - 2) + p2 = (xx, yy - 5) + p3 = (xx + 4, yy - 2) + p4 = (xx + 4, yy + 3) + drawer.polygon([p0, p1, p3, p4], outline=out, fill=col) + drawer.polygon([p1, p2, p3], outline=out, fill=out) + p0 = (xx - 2, yy + 4) + p1 = (xx - 2, yy - 5) + p2 = (xx, yy - 7) + p3 = (xx + 2, yy - 5) + p4 = (xx + 2, yy + 4) + drawer.polygon([p0, p1, p3, p4], outline=out, fill=col) + drawer.polygon([p1, p2, p3], outline=out, fill=out) + p0 = (xx, yy) + p1 = (xx, yy + 3) + drawer.line([p1, p0], fill=out, width=1) + + def drawCity(self, drawer, xx, yy, col, out): + p0 = (xx - 5, yy - 3) + p1 = (xx - 5, yy + 1) + p2 = (xx - 3, yy + 1) + p3 = (xx - 3, yy - 5) + drawer.polygon([p0, p1, p2, p3], outline=out, fill=col) + p0 = (xx + 5, yy - 4) + p1 = (xx + 5, yy + 1) + p2 = (xx + 3, yy + 1) + p3 = (xx + 3, yy - 4) + drawer.polygon([p0, p1, p2, p3], outline=out, fill=col) + p0 = (xx - 1, yy - 7) + p1 = (xx - 1, yy + 2) + p2 = (xx + 1, yy + 2) + p3 = (xx + 1, yy - 7) + drawer.polygon([p0, p1, p2, p3], outline=out, fill=col) + p0 = (xx - 6, yy + 2) + p1 = (xx - 6, yy - 1) + p2 = (xx + 6, yy - 1) + p3 = (xx + 6, yy + 2) + drawer.polygon([p0, p1, p2, p3], outline=out, fill=col) + p0 = (xx, yy) + p1 = (xx, yy + 1) + drawer.line([p1, p0], fill=out, width=1) + + def drawSelf(self, drawer): + col = self.culture.bannerColor + out = (0, 0, 0) + if self.population <= 35: + self.drawTent(drawer, self.node.x, self.node.y, col, out) + elif self.population <= 200: + self.drawHut(drawer, self.node.x, self.node.y, col, out) + elif self.population <= 1000: + self.drawVillage(drawer, self.node.x, self.node.y, col, out) + elif self.population <= 5000: + self.drawTown(drawer, self.node.x, self.node.y, col, out) + elif self.population <= 20000: + self.drawCity(drawer, self.node.x, self.node.y, col, out) diff --git a/Culture.py b/Culture.py new file mode 100644 index 0000000..f8a4520 --- /dev/null +++ b/Culture.py @@ -0,0 +1,196 @@ +import random +from Influence import Influence +from Values import Values +from Population import Population +from Flag import Flag +from Language import Language + + +class Culture: + def __init__(self, n, m): + self.origin = n + self.myMap = m + self.myMap.cultures.append(self) + self.influences = Influence(self.myMap, self.origin, 1) + self.value = Values(self.myMap, self.influences) + self.society = self.setSociety() + self.language = Language(self) + self.name = self.language.name + self.title = self.setTitle() + self.flag = Flag(self) + self.bannerColor = self.flag.colors[0] + self.leaderTitle = self.setLeaderTitle() + self.leader = Population(self, t=self.leaderTitle) + + def setSociety(self): + m = self.value.mainValues + if "greed" in m and "worshippers" in m and "builders" in m: + return "Empire" + if "warriors" in m and "greed" in m and "builders" in m and ("travelers" in m or "sailors" in m): + return "Imperium" + if "warriors" in m and "collectivists" in m and "worshippers" in m: + return "Hegemony" + if "builders" in m and "collectivists" in m and "materialists" in m: + return "Socialists" + if "travelers" in m and "sailors" in m and "traders" in m: + return "Traders" + if "freedom" in m and "collectivists" in m and "simplicity" in m: + return "Paleolithic tribe" + if ("travelers" in m or "sailors" in m) and ("traders" in m or "greed" in m) and "freedom" in m: + return "Independent merchants" + if "collectivists" in m and "agriculture" in m and "materialists" in m: + return "Agricultural communists" + if "collectivists" in m and "agriculture" in m and "simplicity" in m: + return "Farming commune" + if "worshippers" in m and "warriors" in m and ("superstitious" in m or "collectivists" in m): + return "Religious zealots" + if "shamans" in m and ("naturalists" in m or "astrologists" in m) and "superstitious" in m: + return "Shamanic tribe" + if "shamans" in m and "warriors" in m and ("astrologists" in m or "superstitious" in m or "worshippers" in m): + return "Shamanistic warriors" + if "metallurgists" in m and "builders" in m and "craftsmen" in m and "materialists" in m: + return "Cooperative artisans" + if "metallurgists" in m and "builders" in m and "craftsmen" in m and "traders" in m: + return "Merchant artisans" + if "freedom" in m and "greed" in m and "traders" in m: + return "Liberal capitalists" + if "freedom" in m and "traders" in m and ("builders" in m or "craftsmen" in m or "metallurgists" in m): + return "Liberal merchant-artisans" + if "builders" in m and "agriculture" in m and "traders" in m: + return "Mercantile folk" + if "builders" in m and "agriculture" in m and ("travelers" in m or "sailors" in m): + return "Township builders" + if ("craftsmen" in m or "metallurgy" in m or "builders" in m) and "simplicity" in m and ( + "naturalists" in m or "shamans" in m): + return "Naturalist artisans" + if ("craftsmen" in m or "metallurgy" in m or "builders" in m) and "simplicity" in m and ( + "superstitious" in m or "astrologists" in m): + return "Traditionalist artisans" + if "greed" in m and "sailors" in m and "warriors" in m: + return "Pirates" + if ("travelers" in m or "sailors" in m) and "greed" in m and "warriors" in m: + return "Raiders" + if ("travelers" in m or "sailors" in m) and "greed" in m and "simplicity" in m: + return "Scavengers" + if "travelers" in m and "simplicity" in m and "freedom" in m: + return "Hunter-gatherer tribe" + if "astrologists" in m and "superstitious" in m and "worshippers" in m: + return "Religious sovereignty" + if "collectivists" in m and "agriculture" in m and "naturalists" in m: + return "Agriculturalists" + if "travelers" in m and "simplicity" in m and ("metallurgists" in m or "craftsmen" in m or "builders" in m): + return "Nomadic artisans" + if "travelers" in m and "simplicity" and ( + "naturalists" in m or "superstitious" in m or "astrologists" in m or "shamans" in m): + return "Nomadic tribe" + if "materialists" in m and "metallurgists" in m and ("craftsmen" in m or "builders" in m): + return "Blacksmiths" + if "warriors" in m and "collectivists" in m and ("travelers" in m or "sailors" in m): + return "Revolutionary commune" + if "builders" in m and "metallurgists" in m and "craftsmen" in m and "agriculture" in m: + return "Syndicalists" + if "warriors" in m and "builders" in m and ( + "worshippers" in m or "superstitious" in m) and "freedom" not in m and "traders" not in m: + return "Nationalists" + if "collectivists" in m and "freedom" in m: + return "Social Liberals" + if (("astrologists" in m and "superstitious" in m) or + ("superstitious" in m and "worshippers" in m) or + ("woshippers" in m and "astrologists" in m)): + return "Religious Sovereignty" + if "collectivists" in m: + return "Communalists" + if "freedom" in m: + return "Liberals" + if "shamans" in m: + return "Shamans" + if "simplicity" in m: + return "Tribe" + return "Mixed society" + + def setTitle(self): + if self.society == "Nationalists": + return "Nation" + if self.society == "Religious sovereignty" or self.society == "Religious zealots": + return random.choice(["Theocracy", "Ecclesiarchy", "Order"]) + if self.society == "Agriculturalists" or self.society == "Farming commune" or self.society == "Agricultural communists": + return random.choice(["Farmers", "Yeomen", "Peasants"]) + if self.society == "Imperium" or self.society == "Hegemony" or self.society == "Empire": + return "Empire" + if self.society == "Nomadic artisans" or self.society == "Nomadic peoples" or self.society == "Scavengers": + return "Nomads" + if ( + self.society == "Liberal capitalists" or self.society == "Liberal merchant-artisans" or self.society == "Merchant artisans" or + self.society == "Traders" or self.society == "Independent merchants" or self.society == "Mercantile folk"): + return "Caravans" + if (self.society == "Blacksmiths" or self.society == "Traditionalist artisans" or + self.society == "Naturalist artisans" or self.society == "Cooperative artisans"): + return "Artisans" + if (self.society == "Socialists" or self.society == "Syndicalists" or self.society == "Revolutionary commune" + or self.society == "Communalists"): + return random.choice(["People's Union", "Union", "Collective"]) + if (self.society == "Shamanistic warriors" or self.society == "Shamanic tribe" + or self.society == "shamans"): + return "Mystics" + if self.society == "Pirates" or self.society == "Raiders": + return "Brigands" + if self.society == "Social Liberals" or self.society == "Liberals": + return "Republic" + return "People" + + def setLeaderTitle(self): + if self.society == "Nationalists": + return (random.choice(["Master", "High", "Lord", ""]) + " " + + random.choice(["Commissioner", "Chancellor", "Harbinger"])) + if self.society == "Religious sovereignty" or self.society == "Religious zealots": + return (random.choice(["Grand", "High", "Supreme", "Holy"]) + " " + + random.choice(["Pontiff", "Priest", "Shepherd"])) + if self.society == "Agriculturalists" or self.society == "Farming commune" or self.society == "Agricultural communists": + return (random.choice(["Head", "Chief", "Master"]) + " " + + random.choice(["Farmer", "Agronomist", "Foreman"])) + if self.society == "Imperium" or self.society == "Hegemony" or self.society == "Empire": + return "Emperor" + if self.society == "Nomadic artisans" or self.society == "Nomadic peoples" or self.society == "Scavengers": + return (random.choice(["Chief", "Head", "Elder"]) + " " + + random.choice(["Captain", "Dignitary", "Herald"])) + if ( + self.society == "Liberal capitalists" or self.society == "Liberal merchant-artisans" or self.society == "Merchant artisans" or + self.society == "Traders" or self.society == "Independent merchants" or self.society == "Mercantile folk"): + return (random.choice(["Primary", "Head", "Chief", ""]) + " " + + random.choice(["Executive", "Director", "Superintendent"])) + if (self.society == "Blacksmiths" or self.society == "Traditionalist artisans" or + self.society == "Naturalist artisans" or self.society == "Cooperative artisans"): + return (random.choice(["Master", "Elder", "Grandmaster"]) + " " + + random.choice(["Atificer", "Builder", "Craftsman", ""])) + if (self.society == "Socialists" or self.society == "Syndicalists" or self.society == "Revolutionary commune" + or self.society == "Communalists"): + return (random.choice(["Prime", "Chief", "Central", ""]) + " " + + random.choice(["Director", "Governer", "Speaker"])) + if (self.society == "Shamanistic warriors" or self.society == "Shamanic tribe" + or self.society == "shamans"): + return (random.choice(["Elder", "High", "Grand", "Ancestral"]) + " " + + random.choice(["Medicine Man", "Seer", "Shaman"])) + if self.society == "Pirates" or self.society == "Raiders": + return (random.choice(["Chief", "Head", ""]) + " " + + random.choice(["Captain", "Commander", ""])) + if self.society == "Social Liberals" or self.society == "Liberals": + return random.choice(["President", "Speaker", "Minister"]) + return "Chief" + + def shortName(self): + name = "" + name += self.name + " " + self.title + return name + + def information(self): + info = "" + info += self.name + " " + self.title + "\n" + info += "(" + self.society + ")" + "\n" + return info + + def cultureNotes(self): + s = self.name + " " + self.title + "\n\n" + s += "Society type: " + self.society + "\n\n" + s += "Leader: " + self.leader.nameFull() + "\n\n" + s += "Capital: " + self.origin.city.name + "\n\n" + return s diff --git a/Flag.py b/Flag.py new file mode 100644 index 0000000..2c51189 --- /dev/null +++ b/Flag.py @@ -0,0 +1,66 @@ +from Tools import * +from PIL import Image, ImageDraw, ImageTk + + +class Flag: + def __init__(self, c): + self.culture = c + self.xDim = 384 + self.yDim = 192 + self.colors = [] + self.newColor() + self.genFlag() + + def newColor(self): + h = random.randint(0, 255) + s = random.randint(128, 255) + v = random.randint(0, 255) + col = (h, s, v) + self.colors.append(col) + return col + + def randPt(self): + pt = (random.randint(0, self.xDim), random.randint(0, self.yDim)) + return pt + + def center(self): + return (self.xDim / 2, self.yDim / 2) + + def addTri(self, drawer): + col = self.newColor() + p0 = random.choice(self.corners) + p1 = random.choice([(abs(self.xDim - p0[0]), p0[1]), (p0[0], abs(self.yDim - p0[1]))]) + p2 = random.choice([self.center(), random.choice(self.corners)]) + drawer.polygon([p0, p1, p2], fill=col, outline=col) + + def addRect(self, drawer): + col = self.newColor() + p0 = random.choice([(random.randint(0, self.xDim), random.choice([0, self.yDim, self.yDim / 2])), + (random.choice([0, self.xDim, self.xDim / 2]), random.randint(0, self.yDim))]) + p1 = random.choice(self.corners) + drawer.rectangle([p0, p1], fill=col, outline=col) + + def addCirc(self, drawer): + col = self.newColor() + p0 = random.choice([(random.randint(0, self.xDim), random.choice([0, self.yDim, self.yDim / 2])), + (random.choice([0, self.xDim, self.xDim / 2]), random.randint(0, self.yDim))]) + rad = random.randint(1, self.yDim) + drawCircle(drawer, p0[0], p0[1], rad, col) + + def genFlag(self): + img = Image.new('HSV', (self.xDim, self.yDim), self.colors[0]) + drawer = ImageDraw.Draw(img) + numElements = random.randint(1, 4) + self.corners = [(0, 0), (self.xDim, 0), (0, self.yDim), (self.xDim, self.yDim)] + for i in range(numElements): + element = random.choice(["tri", "rect", "circ"]) + if element == "tri": + self.addTri(drawer) + if element == "rect": + self.addRect(drawer) + if element == "circ": + self.addCirc(drawer) + + self.filename = "flag_" + self.culture.name + ".gif" + img = img.convert('RGB') + img.save(self.filename, "GIF") diff --git a/GenConfiguration.py b/GenConfiguration.py new file mode 100644 index 0000000..991d352 --- /dev/null +++ b/GenConfiguration.py @@ -0,0 +1,3 @@ +numNodes = 2**14 +mapDimX = 960 +mapDimY = 960 \ No newline at end of file diff --git a/Influence.py b/Influence.py new file mode 100644 index 0000000..54624a1 --- /dev/null +++ b/Influence.py @@ -0,0 +1,43 @@ +class Influence: + def __init__(self, mMap, myNode, root): + self.node = myNode + self.rootNode = root + self.myMap = mMap + self.influenceOutput = {} + for o in self.myMap.influenceOutputs.keys(): + self.influenceOutput[o] = 0 + self.translateInfluence() + + def setOutput(self, influenceType, strength): + for o in self.myMap.influenceOutputs: + if o in self.myMap.influences[influenceType]: + output = strength * self.myMap.influences[influenceType][o] + self.influenceOutput[o] += output + + def translateInfluence(self): + if self.rootNode == 1: + for p in self.node.neighbors: + ni = Influence(self.myMap, p, 0) + for j in ni.influenceOutput.keys(): + self.influenceOutput[j] += ni.influenceOutput[j] / len(self.node.neighbors) + strength = 1 + inf = self.node.biome + self.setOutput(inf, strength) + strength = self.node.herbivores + inf = "carnivores" + self.setOutput(inf, strength) + strength = self.node.carnivores + inf = "herbivores" + self.setOutput(inf, strength) + strength = self.node.temp + inf = "temperature" + self.setOutput(inf, strength) + strength = self.node.elevation + inf = "elevation" + self.setOutput(inf, strength) + strength = self.node.slope + inf = "slope" + self.setOutput(inf, strength) + strength = self.node.vegetation + inf = "vegetation" + self.setOutput(inf, strength) diff --git a/Landmass.py b/Landmass.py new file mode 100644 index 0000000..a8bbd7a --- /dev/null +++ b/Landmass.py @@ -0,0 +1,75 @@ +from Tools import * +from Node import Node +from River import River + + +class Landmass: + def __init__(self, rootNode, sLevel): + self.sealevel = sLevel + self.color = self.landmassColor() + self.root = rootNode + self.nodes = [] + self.addNode(rootNode) + self.boundary = [] + self.rivers = [] + self.fill() + self.size = len(self.nodes) + self.centermass() + self.culturalNames = {} + self.landmassType = self.lType(self.size) + self.centroid = None + + def centermass(self): + xTotal = sum([p.x for p in self.nodes]) + yTotal = sum([p.y for p in self.nodes]) + xx = xTotal / self.size + yy = yTotal / self.size + self.centroid = Node(xx, yy) + + def lType(self, s): + if s <= 3: + t = "isle" + elif s <= 16: + t = "atoll" + elif s <= 512: + t = "island" + elif s <= 2048: + t = "land" + else: + t = "continent" + return t + + def landmassColor(self): + h = math.floor(random.random() * 255) + s = 128 + math.floor(random.random() * 128) + v = 128 + math.floor(random.random() * 128) + return (h, s, v) + + def addNode(self, p): + if p not in self.nodes: + self.nodes.append(p) + p.landmass = self + + def addBoundary(self, p): + if p not in self.boundary: + self.boundary.append(p) + + def fill(self): + for p in self.nodes: + for k in p.neighbors: + if k.elevation < self.sealevel: + self.addBoundary(p) + while k not in self.nodes and k.elevation >= self.sealevel: + self.addNode(k) + + def addRiver(self, length): + root = self.boundary[int(math.floor(random.random() * (len(self.boundary) - 1)))] + riverLen = random.randint(length / 4, length) + newRiver = River(root, riverLen, self) + self.rivers.append(newRiver) + + def cullStreams(self, minLen): + for r in self.rivers: + if len(r.nodes) < minLen: + self.rivers.remove(r) + r.removeRiver() diff --git a/Language.py b/Language.py new file mode 100644 index 0000000..2a70551 --- /dev/null +++ b/Language.py @@ -0,0 +1,70 @@ +from Tools import * + + +class Language: + def __init__(self, c): + self.culture = c + self.characters() + self.lengthPref = random.choice([3, 5, 9]) + self.name = self.genName() + + def characters(self): + c = ['b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z'] + v = ['a', 'e', 'i', 'o', 'u'] + self.langConsonants = [] + self.langVowels = [] + count = 32 + n = 1 + while len(c) > 0: + cons = random.choice(c) + for l in range(int(math.floor(count))): + self.langConsonants.append(cons) + c.remove(cons) + n += 1 + count = 32 * (1 / n) * random.uniform(0.8, 1.25) + count = 32 + while len(v) > 0: + vow = random.choice(v) + for l in range(int(math.floor(count))): + self.langVowels.append(vow) + v.remove(vow) + n += 1 + count = 32 * (1 / n) * random.uniform(0.8, 1.25) + self.preferredStart = random.choice(self.langConsonants + self.langVowels) + + def genName(self): + length = random.randint(3, 9) + length = math.floor((length + self.lengthPref) / 2) + n = "" + con = 0 + vow = 0 + lastchar = '1' + for k in range(int(length)): + ctype = random.choice(["con", "vow"]) + if vow >= 2: + ctype = "con" + if con >= 2: + ctype = "vow" + c = lastchar + if ctype == "con": + c = random.choice(self.langConsonants) + vow = 0 + con += 1 + if ctype == "vow": + c = random.choice(self.langVowels) + con = 0 + vow += 1 + while c == lastchar and (random.random() > 0.2): + if ctype == "con": + c = random.choice(self.langConsonants) + vow = 0 + con += 1 + if ctype == "vow": + c = random.choice(self.langVowels) + con = 0 + vow += 1 + if k == 0 and random.random() < 0.35: + c = self.preferredStart + n += c + lastchar = c + return n.capitalize() diff --git a/Map.py b/Map.py new file mode 100644 index 0000000..17c5e01 --- /dev/null +++ b/Map.py @@ -0,0 +1,953 @@ +from Tools import * +from tkinter import * +from Node import Node +from noiseMaker import noiseMaker +from Landmass import Landmass +from bodyWater import bodyWater +from Region import Region +from City import City +from GenConfiguration import * +from PIL import Image, ImageDraw, ImageTk + + +class Map: + def __init__(self, aAtlas, numNodes, mapDimX, mapDimY): + self.triangles = None + self.atlas = aAtlas + self.n = numNodes + self.xDim = mapDimX + self.yDim = mapDimY + self.landmasses = [] + self.waterBodies = [] + self.regions = [] + self.cities = [] + self.cultures = [] + self.resourceRegions = [] + self.resourceScale = 100 + self.sealevel = 0.4 + self.setNorth() + self.biomeColors() + self.displayNo = None + self.infoGui = None + + def setNorth(self): + nx = math.floor(random.random() * self.xDim) + ny = math.floor(random.random() * self.yDim) + side = random.randint(0, 3) + if side == 0: + nx = 0 + elif side == 1: + nx = self.xDim + elif side == 2: + ny = 0 + else: + ny = self.yDim + self.north = Node(nx, ny) + + def nodeLat(self, n): + return "Latitude: " + str(round(n.y / self.distScale)) + + def nodeLong(self, n): + return "Longitude: " + str(round(n.x / self.distScale)) + + def nodeElevation(self, n): + return "Elevation: " + str(round((n.elevation - self.sealevel) * self.eScale, 1)) + "m" + + def nodeTemp(self, n): + if n.biome != "water": + return "Temperature: " + str(round((n.temp * self.tempScale) - 30, 1)) + " degrees" + else: + return "Temperature: " + str(round((n.temp * self.tempScale * 0.3), 1)) + " degrees" + + def nodeRain(self, n): + if n.biome != "water": + rain = "Rainfall: " + str(round(((n.rainfall) ** 2) * self.rainfallScale, 1)) + "cm/yr" + else: + rain = "Rainfall: " + str(round((n.rainfall) * self.rainfallScale * 0.03, 1)) + "cm/yr" + return rain + + def nodeBiome(self, n): + return n.biome + + def nodeRegion(self, n): + if n.region.culturalNames == {}: + return "Unnamed " + n.region.biome + "\n" + else: + names = "" + for f in n.region.culturalNames.keys(): + q = n.region.culturalNames[f] + " " + q += n.region.biome + q += " (" + f + " language)" + names += q + "\n" + return names + + def nodeLandmass(self, n): + if n.landmass.culturalNames == {}: + return "Unnamed " + n.landmass.landmassType + "\n" + else: + names = "" + for f in n.landmass.culturalNames.keys(): + q = n.landmass.culturalNames[f] + " " + q += n.landmass.landmassType + q += " (" + f + " language)" + names += q + "\n" + return names + + def nodeRiver(self, n): + if n.river.culturalNames == {}: + return "Unnamed " + "river" + "\n" + else: + names = "" + for f in n.river.culturalNames.keys(): + q = n.river.culturalNames[f] + " " + q += "river" + q += " (" + f + " language)" + names += q + "\n" + return names + + def nodeFertility(self, n): + return "Soil fertility: " + str(math.floor(n.fertility * self.fertScale)) + "%" + + def nodeMetallicity(self, n): + return "Ground metallicity: " + str(math.floor(n.metallicity * self.metalScale)) + "ppm" + + def nodeVegetation(self, n): + return "Vegetation: " + str(math.floor(n.vegetation * self.vegScale)) + "p/km" + chr(0x00B2) + + def nodeHerbivores(self, n): + return "Herbivores: " + str(math.floor(n.herbivores * self.wildlifeScale)) + "/km" + chr(0x00B2) + + def nodeCarnivores(self, n): + return "Carnivores: " + str(math.floor(n.carnivores * self.wildlifeScale)) + "/km" + chr(0x00B2) + + def nodeCityInfo(self, n): + cityInfo = n.city.cityInfo() + "\n" + "\n" + cityInfo += n.city.cultureInfo() + return cityInfo + + def nodeTerritory(self, n): + territory = "" + if n.culture is not None: + territory = n.culture.shortName() + " territory" + return territory + + def nodeResReg(self, n): + reg = "" + if n.resourceRegion is not None: + reg = "Inside " + n.resourceRegion.rootCity.name + " outskirts" + "\n" + reg += "Total food available: " + str( + math.floor(n.resourceRegion.resources[0] * self.resourceScale)) + " t/year \n" + reg += "Total industrial resources available: " + str( + math.floor(n.resourceRegion.resources[1] * self.resourceScale)) + " t/year \n" + return reg + + def infoScales(self): + self.distScale = 12 + self.eScale = 2000 + self.tempScale = 105 + self.rainfallScale = 7628 + self.fertScale = 100 + self.metalScale = 140000 + self.vegScale = 10295 + self.wildlifeScale = 500 + self.resourceScale = 1 + + def nodeInfo(self, n): + self.divWidth = 48 + info = "" + if n.city is not None: + info += strDivider(self.divWidth) + "\n" + info += self.nodeCityInfo(n) + "\n" + if n.resourceRegion is not None: + info += strDivider(self.divWidth) + "\n" + info += self.nodeResReg(n) + "\n" + info += self.nodeTerritory(n) + "\n" + info += strDivider(self.divWidth) + "\n" + info += self.nodeRegion(n) + "\n" + if n.landmass is not None: + info += self.nodeLandmass(n) + "\n" + if n.river is not None: + info += self.nodeRiver(n) + "\n" + info += self.nodeElevation(n) + "\n" + info += self.nodeTemp(n) + "\n" + info += self.nodeRain(n) + "\n" + if n.biome != "water": + info += strDivider(self.divWidth) + "\n" + info += self.nodeFertility(n) + "\n" + info += self.nodeMetallicity(n) + "\n" + info += self.nodeVegetation(n) + "\n" + info += self.nodeHerbivores(n) + "\n" + info += self.nodeCarnivores(n) + "\n" + return info + + def nearestNode(self, xx, yy): + n = self.atlas[0] + minDist = 1000000 + search = Node(xx, yy) + for p in self.atlas: + dist = p.dist(search) + if dist < minDist: + minDist = dist + n = p + return n + + def nearestCity(self, xx, yy): + if len(self.cities) == 0: + return self.atlas[0] + n = self.cities[0] + minDist = 1000000 + search = Node(xx, yy) + for p in self.cities: + dist = p.node.dist(search) + if dist < minDist: + minDist = dist + n = p + return n + + def setSeaLevel(self, n): + self.sealevel = n + + def perlinElevation(self, octaves, scale=1): + noiseThing = noiseMaker(math.floor(self.xDim / scale), math.floor(self.yDim / scale)) + for p in self.atlas: + p.elevation = noiseThing.turbulence(math.floor(p.x), math.floor(p.y), 2 ** octaves) + + def flatten(self): + for p in self.atlas: + p.elevation = 0.5 + + def elevationAdd(self, amount): + for p in self.atlas: + p.elevation = clamp(p.elevation + amount, 0, 1) + + def randomizeElevation(self, anchor, rng): + for p in self.atlas: + p.elevation = anchor - (random.random() * (rng / 2)) + + def clampElevation(self): + for p in self.atlas: + p.elevation = clamp(p.elevation, 0, 1) + + def smooth(self, strength): + for l in range(strength): + for p in self.atlas: + p.smooth() + + def cullDots(self): + for p in self.atlas: + count = len(p.neighbors) + for n in p.neighbors: + if n.elevation < self.sealevel: + count -= 1 + if count <= 1: + p = clamp(p.elevation, self.sealevel - 0.01, 255) + + def buildLandmass(self, root): + if root.landmass is not None or root.elevation < self.sealevel: + return -1 + root.landmass = Landmass(root, self.sealevel) + self.landmasses.append(root.landmass) + + def buildAllLand(self): + print("Building landmasses...") + for p in self.atlas: + self.buildLandmass(p) + + def buildBodyWater(self, root): + if root.landmass is not None or root.bodyWater is not None: + return -1 + root.bodyWater = bodyWater(root, self.sealevel) + self.waterBodies.append(root.bodyWater) + + def buildAllWater(self): + print("Building lakes/oceans...") + for p in self.atlas: + self.buildBodyWater(p) + + def buildRegions(self): + print("Building world regions...") + for p in self.atlas: + if p.region is None: + newReg = Region(p) + self.regions.append(newReg) + + def addRiver(self, length): + c = 0 + landmass = None + while c < len(self.landmasses): + landmass = self.landmasses[int(math.floor(random.random() * len(self.landmasses)))] + if landmass.size > length * 8: + c = 100000000 + c += 1 + landmass.addRiver(length) + + def addMinorRiver(self, count): + for i in range(count): + self.addRiver(self.n / 512) + + def addMajorRiver(self, count): + for i in range(count): + self.addRiver(self.n / 128) + + def cullStreams(self): + for l in self.landmasses: + l.cullStreams(self.n / 32) + + def addSineHill(self, xx, yy, maximum=0.25, radius=128): + hillCenter = Node(xx, yy) + for p in self.atlas: + dist = p.dist(hillCenter) + if dist <= radius: + multiplier = distMod(dist, radius) + if p.elevation > 0.75: + multiplier = multiplier * ((1 - p.elevation) ** 2) + p.elevation = p.elevation + (maximum * multiplier) + + def addHill(self, xx, yy, maximum=0.25, radius=128): + hillCenter = Node(xx, yy) + for p in self.atlas: + dist = p.dist(hillCenter) + if dist <= radius: + multiplier = random.uniform(0.99, 1.01) + p.elevation = maximum * multiplier + + def addMountains(self, num=5, height=0.2): + avgRad = self.xDim / 3.5 + for i in range(num): + xx = math.floor(random.random() * self.xDim) + yy = math.floor(random.random() * self.yDim) + hillRad = avgRad * random.uniform(0.8, 1.25) + hillHeight = height * random.uniform(0.8, 1.25) + self.addSineHill(xx, yy, hillHeight, hillRad) + + def addHills(self, num=8, height=0.1): + avgRad = self.xDim / 6 + for i in range(num): + xx = math.floor(random.random() * self.xDim) + yy = math.floor(random.random() * self.yDim) + hillRad = avgRad * random.uniform(0.8, 1.25) + hillHeight = height * random.uniform(0.8, 1.25) + self.addSineHill(xx, yy, hillHeight, hillRad) + + def addShape(self, shape): + if shape == "island" or shape == "volcanic": + self.addSineHill(self.xDim / 2, self.yDim / 2, 0.4, radius=self.xDim / 1.5) + if shape == "volcanic": + self.smooth(4) + self.elevationAdd(-0.1) + self.addSineHill(self.xDim / 2, self.yDim / 2, 0.25, radius=self.xDim * 1.5) + self.smooth(4) + self.addHill(self.xDim / 2, self.yDim / 2, 0.45, radius=self.xDim / 11) + self.addSineHill(self.xDim / 2, self.yDim / 2, 0. - 0.1, radius=self.xDim / 11) + if shape == "shore" or shape == "highlands": + corner = random.randint(0, 3) + if corner == 0: + xx = 0 + yy = 0 + elif corner == 1: + xx = self.xDim + yy = 0 + elif corner == 2: + xx = self.xDim + yy = self.yDim + else: + xx = 0 + yy = self.yDim + self.addSineHill(xx, yy, 0.45, radius=self.xDim * 1.5) + if shape == "highlands": + self.addSineHill(xx, yy, 0.3, radius=self.xDim * 2) + self.addMountains() + if shape == "archipelago": + self.addHills(16, 0.25) + if shape == "plain": + self.addSineHill(self.xDim / 2, self.yDim / 2, 0.4, radius=self.xDim * 5) + if shape != "volcanic": + self.addMountains() + self.addHills() + self.erode(3) + self.smooth(3) + + def addRandomShape(self): + shp = random.choice(["highlands", "plain", "volcanic", "shore", "archipelago", "island"]) + self.addShape(shp) + + def erode(self, strength=3): + for j in range(strength): + for p in self.atlas: + if p.elevation < self.sealevel * 1.02 and p.elevation > self.sealevel * 0.9: + if p.hasWaterNeighbor(self.sealevel): + p.elevation = p.elevation * 0.94 + for n in p.neighbors: + n.elevation = n.elevation * 0.98 + + def nodeSlopes(self): + for p in self.atlas: + p.getSlope() + + def rainfall(self): + for p in self.atlas: + rainfall = 0.4 * random.uniform(0.95, 1.05) + rainfall = ((rainfall * (0.4 - (p.temp + p.elevation))) + rainfall) / 2 + q = p + distance = self.n / 256 + diff = 0 + for i in range(int(math.floor(distance))): + q = p.westMostNeighbor() + diff += q.elevation - p.elevation + diff = diff / distance + if diff > 0.2 and 0: + rainfall = clamp(rainfall * (0.5 - diff), 0, 1) + if p.river is not None: + rainfall = rainfall * 1.5 + for l in p.neighbors: + if l.elevation < self.sealevel: + rainfall *= 1.25 + p.rainfall = clamp(rainfall, 0, 1) + + def temperature(self): + for p in self.atlas: + latTemp = 0.375 * (p.dist(self.north) / self.xDim) + elevationTemp = 0.575 * (1 - p.elevation) + p.temp = clamp(elevationTemp + latTemp, 0, 1) + + def soilProperties(self): + for p in self.atlas: + p.metallicity = 0.5 * p.elevation * random.uniform(0.666, 1.5) + if p.river is not None: + p.metallicity *= 1.25 + p.metallicity = clamp(p.metallicity, 0, 1) + fertilityBase = abs(p.elevation - (self.sealevel * 1.2)) * random.uniform(0.8, 1.25) + if fertilityBase == 0: + p.fertility = 1 + else: + p.fertility = 0.07 / fertilityBase + if p.river is not None: + p.fertility *= 2 + p.fertility = clamp(p.fertility, 0, 1) + + def vegetation(self): + self.temperature() + self.soilProperties() + self.rainfall() + for p in self.atlas: + p.setVegetation() + + def setBiomes(self): + self.nodeSlopes() + self.vegetation() + for p in self.atlas: + p.setBiome(self.sealevel) + p.biomeColor = self.biomeColors[p.biome] + slope = clamp(p.realSlope() * (2 ** 16), -48, 48) + shade = math.floor((-16) + p.biomeColor[2] + slope + (((p.elevation + 1) ** 2) * 16)) + p.biomeColor = (p.biomeColor[0], p.biomeColor[1], shade) + + def setWildlife(self): + for p in self.atlas: + p.herbivores = clamp((p.vegetation * random.uniform(0.8, 1.25)) ** 2, 0, 1) + p.carnivores = clamp(((p.herbivores * 1.5) ** 2), 0, 1) + + def biomeColors(self): + bColors = {} + bColors["desert"] = (16, 64, 142) + bColors["savanna"] = (48, 64, 136) + bColors["tundra"] = (128, 48, 142) + bColors["shrubland"] = (72, 96, 128) + bColors["boreal"] = (110, 86, 108) + bColors["forest"] = (96, 128, 128) + bColors["tropical"] = (78, 162, 96) + bColors["frozen"] = (134, 32, 206) + bColors["mountain"] = (134, 0, 136) + bColors["water"] = (142, 64, 64) + self.biomeColors = bColors + + def values(self): + self.valuesOutputs = {"travelers": 0, + "craftsmen": 0, + "traders": 0, + "superstitious": 0, + "metallurgists": 0, + "worshippers": 0, + "freedom": 0, + "shamans": 0, + "astrologists": 0, + "materialists": 0, + "agriculture": 0, + "collectivists": 0, + "builders": 0, + "naturalists": 0, + "simplicity": 0, + "greed": 0, + "sailors": 0, + "warriors": 0} + self.values = {} + self.values["swimming"] = {"simplicity": 0.2, + "sailors": 0.8, + "astrologists": 0.15, + "freedom": 0.35, + "warriors": 0.05} + self.values["food"] = {"agriculture": -0.1, + "greed": -0.25, + "materialists": 0.55, + "collectivists": 0.25, + "simplicity": 0.45, + "worshippers": -0.2, + "superstitious": -0.2, + "traders": 0.2, + "warriors": 0.2, + "builders": 0.25} + self.values["darkness"] = {"travelers": 0.2, + "collectivists": -0.4, + "superstitious": 0.7, + "greed": 0.4, + "astrologists": 0.25, + "materialists": -0.15, + "shamans": 0.15, + "freedom": -0.2, + "warriors": 0.5} + self.values["movement"] = {"travelers": 0.8, + "sailors": 0.2, + "traders": 0.55, + "astrologists": 0.15, + "builders": -0.4, + "simplicity": 0.4, + "materialists": -0.15, + "freedom": 0.85, + "naturalists": 0.2, + "collectivists": -0.2, + "warriors": 0.2} + self.values["plantlife"] = {"agriculture": 0.25, + "greed": 0.15, + "naturalists": 0.25, + "shamans": 0.05, + "craftsmen": 0.4, + "traders": 0.15, + "builders": 0.4, + "simplicity": -0.25, + "collectivists": 0.15} + self.values["nature"] = {"naturalists": 0.35, + "shamans": 0.2, + "agriculture": 0.25, + "freedom": 0.25, + "travelers": 0.15, + "simplicity": 0.5, + "collectivists": 0.15, + "superstitious": 0.3, + "astrologists": 0.1, + "metallurgists": 0.4, + "warriors": 0.05} + self.values["growth"] = {"shamans": 0.1, + "agriculture": 0.55, + "naturalists": 0.45, + "metallurgists": -0.2, + "freedom": 0.15, + "astrologists": -0.3, + "collectivists": 0.35, + "materialists": -0.1, + "warriors": 0.05, + "builders": 0.15} + self.values["sky"] = {"travelers": 0.55, + "craftsmen": 0.3, + "traders": 0.25, + "superstitious": 0.5, + "metallurgists": 0.1, + "worshippers": 0.5, + "freedom": 0.55, + "shamans": 0.1, + "astrologists": 0.7, + "simplicity": 0.4, + "builders": 0.1} + self.values["earth"] = {"metallurgists": 1.2, + "craftsmen": 0.9, + "traders": 0.45, + "materialists": 0.75, + "agriculture": 0.25, + "collectivists": 0.4, + "builders": 0.6, + "freedom": -0.25, + "worshippers": -0.15, + "greed": 0.6, + "superstitious": -0.25, + "warriors": 0.05, + "astrologists": -0.2} + self.values["fields"] = {"agriculture": 0.75, + "builders": 0.3, + "materialists": 0.5, + "naturalists": 0.35, + "superstitious": -0.3, + "simplicity": -0.3, + "collectivists": 0.25, + "warriors": 0.05} + self.values["sunlight"] = {"worshippers": 0.9, + "astrologists": 0.6, + "naturalists": 0.15, + "travelers": 0.25, + "simplicity": 0.75, + "freedom": 0.6, + "materialists": -0.2, + "warriors": 0.3, + "builders": -0.1} + self.values["ice"] = {"superstitious": 0.3, + "simplicity": -0.4, + "freedom": -0.4, + "travelers": -0.45, + "materialists": 0.4, + "sailors": 0.1, + "shamans": 0.45, + "greed": 0.7, + "metallurgists": 0.2, + "warriors": 0.4, + "agriculture": -0.2} + self.values["fear"] = {"superstitious": 0.85, + "worshippers": 0.3, + "shamans": 0.8, + "freedom": -0.55, + "collectivists": 0.4, + "simplicity": -0.3, + "builders": 0.25, + "greed": 0.8, + "materialists": -0.25, + "warriors": 0.65} + self.values["water"] = {"sailors": 2, + "simplicity": 0.3, + "freedom": 0.75, + "travelers": 0.6, + "builders": 0.15, + "craftsmen": 0.1, + "astrologists": 0.45, + "traders": 0.75, + "warriors": 0.1} + + def influences(self): + self.influenceOutputs = {"sky": 0, + "sunlight": 0, + "fields": 0, + "earth": 0, + "ice": 0, + "fear": 0, + "water": 0, + "growth": 0, + "nature": 0, + "plantlife": 0, + "movement": 0, + "darkness": 0, + "swimming": 0, + "food": 0} + self.influences = {} + self.influences["elevation"] = {"sky": 0.3, + "sunlight": 0.15, + "fields": 0.1, + "earth": 0.1, + "ice": 0.1, + "fear": 0.05} + self.influences["slope"] = {"sky": 0.1, + "sunlight": 0.1, + "earth": 0.1, + "fear": 0.05} + self.influences["temperature"] = {"sunlight": 0.2, + "darkness": -0.1, + "sky": 0.1, + "movement": 0.2, + "fear": 0.15, + "water": 0.05, + "food": 0.1} + self.influences["vegetation"] = {"nature": 0.15, + "food": 0.15, + "darkness": 0.05, + "plantlife": 0.2, + "sunlight": -0.1, + "fields": -0.1} + self.influences["rainfall"] = {"water": 0.5, + "growth": 0.2, + "nature": 0.1, + "sky": 0.25, + "movement": 0.1, + "darkness": 0.1, + "plantlife": 0.1, + "swimming": 0.15} + self.influences["vegetation"] = {"plantlife": 0.6, + "growth": 0.15, + "nature": 0.35, + "earth": 0.2, + "fields": 0.15} + self.influences["fertility"] = {"plantlife": 0.35, + "growth": 0.25, + "nature": 0.15, + "earth": 0.3, + "fields": 0.25} + self.influences["metallicity"] = {"earth": 0.7, + "darkness": 0.15, + "nature": 0.1} + self.influences["carnivores"] = {"darkness": 0.35, + "food": 0.25, + "fear": 0.4, + "nature": 0.15} + self.influences["herbivores"] = {"nature": 0.35, + "food": 0.65, + "growth": 0.3, + "earth": 0.1, + "plantlife": -0.1} + self.influences["river"] = {"water": 2.5, + "swimming": 1.0, + "nature": 0.1, + "food": 0.35, + "growth": 0.1, + "movement": 0.3} + self.influences["water"] = {"water": 4.0, + "swimming": 1.5, + "sky": 0.15, + "fear": 0.2, + "darkness": 0.35, + "food": 0.25, + "movement": 0.15, + "ice": 0.1} + self.influences["forest"] = {"plantlife": 0.6, + "darkness": 0.25, + "nature": 0.4, + "growth": 0.2, + "earth": 0.1, + "food": 0.3, + "fields": 0.1} + self.influences["desert"] = {"earth": 0.4, + "sky": 0.3, + "sunlight": 0.7, + "food": -0.3, + "fields": 0.35} + self.influences["shrubland"] = {"food": 0.25, + "fields": 0.4, + "sky": 0.35, + "sunlight": 0.2, + "earth": 0.25, + "nature": 0.1} + self.influences["savanna"] = {"food": 0.2, + "fields": 0.6, + "sky": 0.4, + "sunlight": 0.3, + "nature": 0.2, + "earth": 0.1} + self.influences["tundra"] = {"food": -0.35, + "fields": 0.35, + "fear": 0.2, + "darkness": 0.4, + "sky": 0.2, + "growth": -0.1, + "earth": 0.3, + "ice": 0.5} + self.influences["mountain"] = {"food": -0.3, + "fields": 0.1, + "sky": 0.7, + "sunlight": 0.35, + "earth": 0.45, + "fear": 0.2, + "nature": 0.15, + "ice": 0.5} + self.influences["tropical"] = {"food": 0.35, + "fear": 0.4, + "darkness": 0.35, + "fields": -0.25, + "nature": 0.5, + "plantlife": 0.6, + "growth": 0.45, + "earth": 0.1} + self.influences["boreal"] = {"food": 0.1, + "nature": 0.25, + "growth": 0.1, + "fields": 0.2, + "darkness": 0.2, + "plantlife": 0.25, + "earth": 0.15, + "ice": 0.15} + self.influences["frozen"] = {"ice": 0.75, + "earth": 0.2, + "food": -0.3, + "plantlife": -0.2, + "fear": 0.2, + "darkness": 0.2, + "fields": 0.15, + "water": 0.1} + + def placeCity(self, xx, yy, pop=50, culture=None, node=None): + if node is not None: + cityNode = self.nearestNode(xx, yy) + else: + cityNode = node + if cityNode.biome != "water" and cityNode.city is None: + newCity = City(cityNode, pop, culture, self) + return 1 + else: + return -1 + + def randomCity(self): + cityNode = self.atlas[math.floor(random.random() * len(self.atlas))] + while (cityNode.biome == "water" or cityNode.city is not None or + cityNode.x < 32 or cityNode.x > self.xDim - 32 or cityNode.y < 32 or cityNode.y > self.yDim - 32): + if len(self.cities) >= 1: + cityNode = self.atlas[math.floor(random.random() * len(self.atlas))] + while cityNode.dist(self.nearestCity(cityNode.x, cityNode.y).node) < 32: + cityNode = self.atlas[math.floor(random.random() * len(self.atlas))] + else: + cityNode = self.atlas[math.floor(random.random() * len(self.atlas))] + newCity = City(cityNode, pop=random.randint(12, 136), m=self) + + def scatterCities(self, n): + for i in range(n): + self.randomCity() + + def drawGraph(self, gui=None): + visualAtlas = Image.new("HSV", (mapDimX, mapDimY), "white") + graphDraw = ImageDraw.Draw(visualAtlas) + for tri in self.triangles: + tri.drawGraph(graphDraw) + for n in self.atlas: + n.drawPoint(graphDraw, 1, "red") + visualAtlas.show() + + def drawElevation(self, pts=0, sea=1, gui=None): + visualAtlas = Image.new("HSV", (mapDimX, mapDimY), "white") + graphDraw = ImageDraw.Draw(visualAtlas) + for tri in self.triangles: + tri.drawElevation(graphDraw, self.sealevel * sea) + for n in self.atlas: + n.drawElevation(graphDraw) + for l in self.landmasses: + for r in l.rivers: + r.drawRiver(graphDraw) + visualAtlas.show() + + def drawLandmass(self, pts=0, nbrs=0, gui=None): + visualAtlas = Image.new("HSV", (mapDimX, mapDimY), "white") + graphDraw = ImageDraw.Draw(visualAtlas) + for tri in self.triangles: + tri.drawLandmass(graphDraw, self.sealevel) + for n in self.atlas: + n.drawLandmass(graphDraw, pts, nbrs) + for l in self.landmasses: + for r in l.rivers: + r.drawPath(graphDraw) + visualAtlas.show() + + def drawWildlife(self, gui=None): + visualAtlas = Image.new("HSV", (mapDimX, mapDimY), "white") + graphDraw = ImageDraw.Draw(visualAtlas) + for tri in self.triangles: + tri.drawWildlife(graphDraw, self.sealevel) + for l in self.landmasses: + for r in l.rivers: + r.drawRiver(graphDraw, self.xDim) + visualAtlas.show() + + def displayNode(self, event): + clickedNode = self.nearestNode(event.x, event.y) + self.displayString.set(self.nodeInfo(clickedNode)) + self.displayNo = clickedNode + cityNode = self.nearestCity(event.x, event.y) + if cityNode.node.dist(Node(event.x, event.y)) < 8: + self.displayString.set(self.nodeInfo(cityNode.node)) + self.displayNo = cityNode.node + + def redraw(self): + visualAtlas = Image.new("HSV", (mapDimX, mapDimY), "white") + graphDraw = ImageDraw.Draw(visualAtlas) + for tri in self.triangles: + tri.drawReal(graphDraw, self.sealevel) + for n in self.atlas: + n.drawReal(graphDraw, self.sealevel) + for l in self.landmasses: + for r in l.rivers: + r.drawRiver(graphDraw, self.xDim) + for c in self.cities: + c.drawSelf(graphDraw) + visualAtlas = visualAtlas.convert("RGB") + visualAtlas.save(self.mapname, "GIF") + photo = Image.open(self.mapname) + self.img = ImageTk.PhotoImage(photo) + self.lbl.configure(image=self.img) + self.lbl.image = self.img + if self.displayNo is not None: + self.displayString.set(self.nodeInfo(self.displayNo)) + + def updateResources(self): + for r in self.resourceRegions: + r.updateReg() + + def updateTerritory(self): + for c in self.cities: + c.node.culture = c.culture + c.node.allegiance = 1 / c.population + for p in self.atlas: + p.updateAllegiance() + for c in self.cities: + c.node.culture = c.culture + c.node.allegiance = 1 / c.population + + def updateDemogs(self): + for c in self.cities: + c.updateDemog() + + def nextTurn(self): + self.updateResources() + self.updateDemogs() + self.updateTerritory() + self.redraw() + + def cultureInfo(self): + if self.displayNo is None: + return -1 + if self.displayNo.culture is None: + return -1 + self.displayCulture = self.displayNo.culture + if self.infoGui is not None: + self.infoGui.destroy() + self.infoGui = Toplevel() + photo = Image.open(self.displayCulture.flag.filename) + self.flagImg = ImageTk.PhotoImage(photo) + self.flagLbl = Label(self.infoGui, image=self.flagImg) + self.flagLbl.config(borderwidth=32) + self.flagLbl.photo = photo + self.flagLbl.pack() + self.cultureString = StringVar() + self.cultureString.set(self.displayCulture.cultureNotes()) + cdsc = Label(self.infoGui, textvariable=self.cultureString) + cdsc.pack() + + def drawReal(self, gui=None): + visualAtlas = Image.new("HSV", (mapDimX, mapDimY), "white") + graphDraw = ImageDraw.Draw(visualAtlas) + for tri in self.triangles: + tri.drawReal(graphDraw, self.sealevel) + for n in self.atlas: + n.drawReal(graphDraw, self.sealevel) + for l in self.landmasses: + for r in l.rivers: + r.drawRiver(graphDraw, self.xDim) + for c in self.cities: + c.drawSelf(graphDraw) + if gui is None: + visualAtlas = visualAtlas.convert("RGB") + visualAtlas.save("map00.png", "PNG") + visualAtlas.show() + else: + self.gui = gui + self.displayString = StringVar() + self.displayString.set("No node selected") + self.infoScales() + desc = Label(gui, textvariable=self.displayString) + desc.pack(side=RIGHT, fill=Y) + visualAtlas = visualAtlas.convert("RGB") + self.mapname = self.cultures[0].language.genName() + ".gif" + visualAtlas.save(self.mapname, "GIF") + photo = Image.open(self.mapname) + self.img = ImageTk.PhotoImage(photo) + self.lbl = Label(gui, image=self.img) + self.lbl.pack() + self.lbl.bind("", self.displayNode) + b0 = Button(gui, text="Next Turn", command=self.nextTurn) + b0.pack(anchor=S, side=RIGHT) + c1 = "orange red" + b0.config(bg=c1, activebackground=c1, activeforeground=c1) + b1 = Button(gui, text="Society Info", command=self.cultureInfo) + b1.pack(anchor=S, side=RIGHT) + c1 = "medium aquamarine" + b1.config(bg=c1, activebackground=c1, activeforeground=c1) + self.nextTurn() + gui.mainloop() diff --git a/Node.py b/Node.py new file mode 100644 index 0000000..40f223c --- /dev/null +++ b/Node.py @@ -0,0 +1,239 @@ +from Tools import * + + +class Node: + def __init__(self, xx, yy): + self.biomeColor = None + self.rainfall = None + self.temp = None + self.x = xx + self.y = yy + self.neighbors = [] + self.landmass = None + self.bodyWater = None + self.river = None + self.region = None + self.city = None + self.culture = None + self.allegiance = 0 + self.resourceRegion = None + self.resourceDist = 0 + self.slope = None + + def coords(self): + tupleVert = (self.x, self.y) + return tupleVert + + def dist(self, n): + distX = abs(self.x - n.x) + distY = abs(self.y - n.y) + dist = math.sqrt((distX ** 2) + (distY ** 2)) + return dist + + def isLinked(self, nNode): + if nNode in self.neighbors: + return 1 + else: + return 0 + + def link(self, newNeighbor): + self.neighbors.append(newNeighbor) + newNeighbor.neighbors.append(self) + + def hasWaterNeighbor(self, sealevel): + for n in self.neighbors: + if n.elevation < sealevel: + return 1 + return 0 + + def nearestNeighbor(self, n): + distance = 100000000000 + nbr = self + for p in self.neighbors: + newDist = p.dist(n) + if newDist < distance: + nbr = p + distance = newDist + return nbr + + def westMostNeighbor(self): + n = self + for p in self.neighbors: + if p.x < n.x: + n = p + self.westMostNbr = n + return n + + def minNeighbor(self): + n = self + ne = self.elevation + for p in self.neighbors: + pe = p.elevation + if pe < ne: + ne = pe + n = p + return n + + def maxNeighbor(self): + n = self + ne = self.elevation + for p in self.neighbors: + pe = p.elevation + if pe > ne: + ne = pe + n = p + return n + + def getSlope(self): + minimum = self.minNeighbor() + maximum = self.maxNeighbor() + rise = maximum.elevation - minimum.elevation + run = self.dist(minimum) + self.dist(maximum) + if run != 0: + m = rise / run + else: + m = 0.1 + self.slope = m + return m + + def slopeDirection(self): + self.westMostNeighbor() + if self.westMostNbr.elevation > self.elevation: + self.slopeDir = -1 + else: + self.slopeDir = 1 + + def realSlope(self): + self.getSlope() + self.slopeDirection() + self.rSlope = self.slope * self.slopeDir + return self.rSlope + + def smooth(self): + nbrs = [] + nbrs.append(self.elevation) + for i in self.neighbors: + nbrs.append(i.elevation) + self.elevation = sum(nbrs) / len(nbrs) + + def setVegetation(self): + tempFitness = 1 - abs(0.3 - self.temp) + elevationFitness = 1 - abs(0.45 - self.elevation) + fertilityFitness = self.fertility + (self.metallicity * 0.25) + rainFitness = 1 - abs(self.rainfall - 0.8) + vegFitness = ((tempFitness + 1) * (elevationFitness + 0.5) * (fertilityFitness + 0.5) * (rainFitness + 2)) + self.vegetation = clamp(vegFitness / 16, 0, 1) + + def setBiome(self, sl): + if self.elevation > 0.9: + self.biome = "mountain" + elif self.temp < 0.2: + if self.rainfall < 0.08: + self.biome = "frozen" + elif self.rainfall < 0.25: + self.biome = "tundra" + else: + self.biome = "shrubland" + elif self.temp < 0.3: + if self.rainfall < 0.03: + self.biome = "tundra" + elif self.rainfall < 0.05: + self.biome = "shrubland" + elif self.rainfall < 0.3: + self.biome = "boreal" + elif self.rainfall < 0.4: + self.biome = "forest" + else: + self.biome = "tropical" + elif self.temp < 0.5: + if self.rainfall < 0.04: + self.biome = "desert" + elif self.rainfall < 0.06: + self.biome = "savanna" + elif self.rainfall < 0.09: + self.biome = "shrubland" + elif self.rainfall < 0.2: + self.biome = "forest" + else: + self.biome = "tropical" + else: + if self.rainfall < 0.05: + self.biome = "desert" + else: + self.biome = "tropical" + if self.elevation < sl: + self.biome = "water" + + def claim(self, n): + n.culture = self.culture + n.allegiance = self.allegiance + 1 + if self.culture.name not in n.region.culturalNames: + n.region.culturalNames[self.culture.name] = self.culture.language.genName() + if n.landmass != None: + if self.culture.name not in n.landmass.culturalNames: + n.landmass.culturalNames[self.culture.name] = self.culture.language.genName() + if n.river != None: + if self.culture.name not in n.river.culturalNames: + n.river.culturalNames[self.culture.name] = self.culture.language.genName() + + def updateAllegiance(self): + if self.culture is not None: + chance = 1 / clamp(self.allegiance, 0.00001, 512) + for n in self.neighbors: + roll = random.random() + if roll <= chance: + self.claim(n) + + # NOTE: you can override the __str__() inbuilt method, which is the equivalent of what you are doing here + # Advantage of overriding __str__(): + # You can write print(someNodeVariable) instead of print(someNodeVariable.toString()) + # As python automatically calls the __str__() class method when you try to print an object instance + # This is why, when you do print(someObject) without overriding __str__(), + # you get something like <__main__.someObject object at 0x[RAM address]> + def toString(self): + self.selfString = "(" + self.selfString += str(self.x) + self.selfString += ", " + self.selfString += str(self.y) + self.selfString += ")" + return self.selfString + + def printSelf(self): + print(self.toString()) + + def drawPoint(self, drawer, radius, color): + drawCircle(drawer, self.x, self.y, radius, color) + + def drawElevation(self, drawer, pts=0): + col = math.floor(self.elevation * 255) + dCol = (0, 0, col) + if self.elevation >= 1: + dCol = "red" + if self.landmass is not None: + for n in self.neighbors: + if n.landmass == self.landmass: + drawer.line([self.coords(), n.coords()], dCol, 3) + if pts == 1: + self.drawPoint(drawer, 1, dCol) + + def drawLandmass(self, drawer, pts=0, drawNeighbors=0): + if self.landmass is None: + dCol = "black" + else: + dCol = self.landmass.color + if self.landmass is not None: + for n in self.neighbors: + if n.landmass == self.landmass: + drawer.line([self.coords(), n.coords()], dCol, 3) + if self.landmass is not None and drawNeighbors == 1: + for n in self.neighbors: + drawer.line([self.coords(), n.coords()], dCol, 1) + if pts == 1: + self.drawPoint(drawer, 1, "black") + + def drawReal(self, drawer, sl): + dCol = self.biomeColor + if self.landmass is not None and 0: + for n in self.neighbors: + if n.landmass == self.landmass: + drawer.line([self.coords(), n.coords()], dCol, 3) diff --git a/Population.py b/Population.py new file mode 100644 index 0000000..d3150b6 --- /dev/null +++ b/Population.py @@ -0,0 +1,12 @@ +class Population: + def __init__(self, c, n=None, t=""): + self.culture = c + if n is None: + self.name = (self.culture.language.genName(), self.culture.language.genName()) + else: + self.name = n + self.title = t + " " + self.fullName = self.nameFull() + + def nameFull(self): + return self.title + self.name[0] + " " + self.name[1] diff --git a/Region.py b/Region.py new file mode 100644 index 0000000..6920a13 --- /dev/null +++ b/Region.py @@ -0,0 +1,31 @@ +class Region: + def __init__(self, rootNode): + self.root = rootNode + self.nodes = [] + self.culturalNames = {} + self.addNode(self.root) + self.biome = self.root.biome + self.fill() + if self.biome == "boreal" or self.biome == "tropical": + self.biome += " forest" + if self.biome == "frozen": + self.biome += " tundra" + if self.biome == "water": + bodysize = len(self.nodes) + if bodysize > 1024: + self.biome = "ocean" + elif bodysize > 512: + self.biome = "sea" + else: + self.biome = "lake" + + def addNode(self, p): + if p not in self.nodes: + self.nodes.append(p) + p.region = self + + def fill(self): + for p in self.nodes: + for k in p.neighbors: + if k.biome == self.biome: + self.addNode(k) diff --git a/ResourceRegion.py b/ResourceRegion.py new file mode 100644 index 0000000..92fefa2 --- /dev/null +++ b/ResourceRegion.py @@ -0,0 +1,95 @@ +import math + + +class ResourceRegion: + def __init__(self, c, m): + self.rootCity = c + self.myMap = m + self.culture = self.rootCity.culture + self.nodes = [] + self.addNode(self.rootCity.node) + self.resources = [0, 0] + self.updateReg() + + def expungeReg(self): + self.myMap.resourceRegions.remove(self) + + def addNode(self, node): + if node.resourceRegion is not None: + node.resourceRegion.nodes.remove(node) + node.resourceRegion = self + self.nodes.append(node) + + def cityCount(self): + c = 0 + q = 0 + for p in self.nodes: + if p.city is not None: + c += 1 + q += p.city.population + self.totalCities = c + self.totalPop = q + + def sumResources(self): + self.resources = [0, 0] # [Food resources, industrial resources] + rawPlant = 0 + rawMetal = 0 + rawAnimal = 0 + for p in self.nodes: + if p.landmass is not None: + rawPlant += p.vegetation + rawMetal += p.metallicity + rawAnimal += p.herbivores + (p.carnivores / 3) + else: + rawAnimal += 0.1 + rawPlant += 0.1 + m = self.culture.value.mainValues + if "metallurgists" in m: + rawMetal = rawMetal * 1.25 + if "agriculture" in m: + rawPlant *= 1.15 + if "warriors" in m: + rawAnimal *= 1.2 + if "simplicity" in m: + rawMetal *= 0.75 + rawAnimal *= 0.75 + rawPlant *= 0.75 + scale = self.myMap.resourceScale + self.resources[0] = scale * (rawPlant + (rawAnimal * 0.7)) + self.resources[1] = scale * (rawMetal + (rawAnimal * 0.3) + (rawPlant * 0.4)) + if "craftsmen" in m: + self.resources[1] *= 1.1 + if "builders" in m: + self.resources[1] *= 1.1 + if "collectivists" in m: + self.resources[0] *= 1.05 + + def updateReg(self): + for p in self.nodes: + if p.city is not None: + p.resourceDist = math.log2(p.city.population) + for p in self.nodes: + for k in p.neighbors: + if k.resourceRegion != self and k.resourceRegion is not None: + # In this case, it's a different region OTHER than noRegion. + if k.resourceRegion.culture != self.culture: + # Interact with a different region of a different society... + # This is a placeholder. In the future there will probably be wars here. + k.resourceRegion = k.resourceRegion + elif k.resourceRegion.culture == self.culture: + # Interacting with a different region of the same society... + if k.resourceDist < p.resourceDist: + self.addNode(k) + k.resourceDist = (p.resourceDist - 1) / 2 + elif k.resourceRegion is None: + if k.resourceDist < p.resourceDist: + self.addNode(k) + k.resourceDist = (p.resourceDist - 1) / 2 + elif k.resourceRegion == self: + if k.resourceDist < p.resourceDist: + k.resourceDist = (p.resourceDist - 1) / 2 + self.cityCount() + self.sumResources() + if len(self.nodes) == 0: + self.expungeReg() + return -1 diff --git a/River.py b/River.py new file mode 100644 index 0000000..7ad9ce9 --- /dev/null +++ b/River.py @@ -0,0 +1,57 @@ +from Tools import * + + +class River: + def __init__(self, root, length, landms): + self.nodes = [] + self.addNode(root) + self.landmass = landms + current = root + j = 0 + while j < length: + choice = current + for m in current.neighbors: + if (m.elevation > choice.elevation + and m.river is None and m not in self.nodes): + choice = m + for k in current.neighbors: + if (k.elevation < choice.elevation and k.elevation >= current.elevation + and k.river is None and k not in self.nodes): + choice = k + if current == choice: + j = length + else: + self.addNode(choice) + j += 1 + current = choice + self.culturalNames = {} + + def removeRiver(self): + for n in self.nodes: + n.river = None + self.nodes.remove(n) + + def addNode(self, newNode): + self.nodes.append(newNode) + newNode.river = self + + def drawPath(self, drawer): + dCol = (142, 64, 64) + drawNodes = [] + for n in self.nodes: + drawNodes.append(n.coords()) + drawer.line(drawNodes, dCol, 2) + + def drawRiver(self, drawer, xDim): + for l in self.nodes: + l.getSlope() + dCol = (142, 128, 76) + for i in range(len(self.nodes) - 1): + n = self.nodes[i] + n1 = self.nodes[i + 1] + scale = xDim / 2 + w = clamp(1 / n.slope, 0, 2048) / scale + w1 = clamp(1 / n1.slope, 0, 2048) / scale + drawCircle(drawer, n.x, n.y, w, dCol) + drawCircle(drawer, n1.x, n1.y, w1, dCol) + drawTrapezoid(drawer, n.x, n.y, n1.x, n1.y, w, w1, dCol) diff --git a/Tools.py b/Tools.py new file mode 100644 index 0000000..a410904 --- /dev/null +++ b/Tools.py @@ -0,0 +1,99 @@ +import math +import numpy as np +import random +from scipy.spatial import Voronoi + + +def lerp(t, a, b): + return a + t * (b - a) + + +def smoothcurve(t): + return t * t * (3. - 2. * t) + + +def lengthDirX(length, angle): + radian_angle = math.radians(angle) + return length * math.cos(radian_angle) + + +def lengthDirY(length, angle): + radian_angle = math.radians(angle) + return length * math.sin(radian_angle) + + +def A(dx, dy): + return math.degrees(math.atan2(dy, dx)) + + +def drawCircle(drawer, x, y, radius, color): + x1 = x - radius + x2 = x + radius + y1 = y - radius + y2 = y + radius + drawer.ellipse([(x1, y1), (x2, y2)], color) + + +def drawTrapezoid(drawer, x1, y1, x2, y2, r1, r2, color): + directAngle = A(x2 - x1, y2 - y1) + pAngle = directAngle - 90 + pAngle2 = pAngle - 180 + p1 = (x1 + lengthDirX(r1, pAngle), y1 + lengthDirY(r1, pAngle)) + p2 = (x1 + lengthDirX(r1, pAngle2), y1 + lengthDirY(r1, pAngle2)) + p3 = (x2 + lengthDirX(r2, pAngle2), y2 + lengthDirY(r2, pAngle2)) + p4 = (x2 + lengthDirX(r2, pAngle), y2 + lengthDirY(r2, pAngle)) + drawer.polygon([p1, p2, p3, p4], color, color) + + +def centroidnp(arr): + length = arr.shape[0] + sum_x = np.sum(arr[:, 0]) + sum_y = np.sum(arr[:, 1]) + return sum_x / length, sum_y / length + + +def unitCos(x): + return (math.cos(x * math.pi) + 1) / 2 + + +def distMod(x, maxDist): + return unitCos(x / maxDist) + + +def stick(val, minimum, maximum): + if abs(val - minimum) < abs(val - maximum): + return minimum + else: + return maximum + + +def clamp(x, minimum, maximum): + if x < minimum: + return minimum + elif x > maximum: + return maximum + else: + return x + + +def strDivider(length): + n = "" + for l in range(length): + n += "_" + return n + + +def relaxLloyd(pts, strength): + for i in range(strength): + vor = Voronoi(pts) + newpts = [] + for idx in range(len(vor.points)): + pt = vor.points[idx, :] + region = vor.regions[vor.point_region[idx]] + if -1 in region: + newpts.append(pt) + else: + vxs = np.asarray([vor.vertices[i, :] for i in region]) + newpt = centroidnp(vxs) + newpts.append(newpt) + pts = np.array(newpts) diff --git a/Triangle.py b/Triangle.py new file mode 100644 index 0000000..13eddc4 --- /dev/null +++ b/Triangle.py @@ -0,0 +1,100 @@ +from Tools import * + + +class Triangle: + def __init__(self, aa, bb, cc): + self.verts = [None, None, None] + self.verts[0] = aa + self.verts[1] = bb + self.verts[2] = cc + self.neighbors = [] + + def sharesNeighbors(self, other): + shares = 0 + if self.verts[0] in other.verts: + shares += 1 + if self.verts[1] in other.verts: + shares += 1 + if self.verts[2] in other.verts: + shares += 1 + return shares + + def toString(self): + self.selfString = "(" + self.selfString += self.verts[0].toString() + self.selfString += ", " + self.selfString += self.verts[1].toString() + self.selfString += ", " + self.selfString += self.verts[2].toString() + self.selfString += ")" + return self.selfString + + def printSelf(self): + print(self.toString()) + + def drawGraph(self, drawer): + drawer.line([self.verts[0].coords(), self.verts[1].coords()], "black", 1) + drawer.line([self.verts[1].coords(), self.verts[2].coords()], "black", 1) + drawer.line([self.verts[2].coords(), self.verts[0].coords()], "black", 1) + + def drawElevation(self, drawer, sealevel=0): + elevationList = [self.verts[f].elevation for f in range(len(self.verts))] + elevation = sum(elevationList) / 3 + col = math.floor(elevation * 255) + dCol = (0, 0, col) + underwater = 0 + for f in self.verts: + if f.elevation < sealevel: + underwater = 1 + if underwater == 1: + dCol = (142, 128, clamp(col, 64, 76)) + drawer.polygon([self.verts[0].coords(), self.verts[1].coords(), self.verts[2].coords()], fill=dCol, + outline=dCol) + + def drawWildlife(self, drawer, sealevel=0): + carnivores = math.floor(128 * sum([self.verts[f].carnivores for f in range(len(self.verts))]) / 3) + herbivores = math.floor(128 * sum([self.verts[f].herbivores for f in range(len(self.verts))]) / 3) + dCol = (128 + (carnivores) - (herbivores), (carnivores + herbivores) * 2, 128) + underwater = 0 + for f in self.verts: + if f.elevation < sealevel: + underwater = 1 + if underwater == 1: + dCol = (142, 128, 70) + drawer.polygon([self.verts[0].coords(), self.verts[1].coords(), self.verts[2].coords()], fill=dCol, + outline=dCol) + + def drawLandmass(self, drawer, sealevel=0): + landmass = None + for p in self.verts: + if p.landmass is not None: + landmass = p.landmass + if landmass is not None: + dCol = landmass.color + else: + dCol = "black" + underwater = 0 + for f in self.verts: + if f.elevation < sealevel: + underwater = 1 + if underwater == 1: + dCol = (142, 64, 64) + drawer.polygon([self.verts[0].coords(), self.verts[1].coords(), self.verts[2].coords()], fill=dCol, + outline=dCol) + + def drawReal(self, drawer, sl): + elevationList = [self.verts[f].elevation for f in range(len(self.verts))] + elevation = sum(elevationList) / 3 + col = math.floor(elevation * 255) + underwater = 0 + avgHue = math.floor(sum([self.verts[f].biomeColor[0] for f in range(len(self.verts))]) / 3) + avgSat = math.floor(sum([self.verts[f].biomeColor[1] for f in range(len(self.verts))]) / 3) + avgVal = math.floor(sum([self.verts[f].biomeColor[2] for f in range(len(self.verts))]) / 3) + dCol = (avgHue, avgSat, avgVal) + for f in self.verts: + if f.elevation < sl: + underwater = 1 + if underwater == 1: + dCol = (142, 128, clamp(col, 64, 76)) + drawer.polygon([self.verts[0].coords(), self.verts[1].coords(), self.verts[2].coords()], fill=dCol, + outline=dCol) diff --git a/Values.py b/Values.py new file mode 100644 index 0000000..c0b4425 --- /dev/null +++ b/Values.py @@ -0,0 +1,40 @@ +from Tools import random + + +class Values: + def __init__(self, m, inf): + self.myMap = m + self.influences = inf + self.valuesOutput = {} + for v in self.myMap.valuesOutputs.keys(): + self.valuesOutput[v] = 0 + self.translateValues() + self.mainValues = self.valuesMain(5) + + def maxval(self): + maxkey = 0 + for k in self.valuesOutput.keys(): + maxkey = k + for k in self.valuesOutput.keys(): + if self.valuesOutput[k] > self.valuesOutput[maxkey]: + maxkey = k + return maxkey + + def valuesMain(self, n): + mVals = {} + for f in range(n): + maxkey = self.maxval() + mVals[maxkey] = self.valuesOutput[maxkey] + del self.valuesOutput[maxkey] + return mVals + + def translateValues(self): + for q in self.influences.influenceOutput.keys(): + modifier = self.influences.influenceOutput[q] + roll = random.random() + if roll > 0.99: + modifier = 8 + elif roll < 0.01: + modifier = 0.01 + for v in self.myMap.values[q].keys(): + self.valuesOutput[v] += self.myMap.values[q][v] * modifier * random.uniform(0.8, 1.25) diff --git a/bodyWater.py b/bodyWater.py new file mode 100644 index 0000000..3778ab0 --- /dev/null +++ b/bodyWater.py @@ -0,0 +1,19 @@ +class bodyWater: + def __init__(self, rootNode, sLevel): + self.sealevel = sLevel + self.root = rootNode + self.nodes = [] + self.addNode(rootNode) + self.fill() + self.culturalNames = {} + + def addNode(self, p): + if p not in self.nodes: + self.nodes.append(p) + p.bodyWater = self + + def fill(self): + for p in self.nodes: + for k in p.neighbors: + while k not in self.nodes and k.elevation < self.sealevel: + self.addNode(k) diff --git a/mapgenmain.py b/mapgenmain.py old mode 100644 new mode 100755 index 4d2c090..f033bd4 --- a/mapgenmain.py +++ b/mapgenmain.py @@ -5,2121 +5,18 @@ @author: Bri """ -import numpy as np -import random -import math +from Tools import * from tkinter import * -from PIL import Image, ImageDraw, ImageTk - -def lerp(t,a,b): - return a + t * (b - a) - -def smoothcurve(t): - return t * t * (3. - 2. * t) - -def lengthDirX(length, angle): - radian_angle = math.radians(angle) - return length * math.cos(radian_angle) - -def lengthDirY(length, angle): - radian_angle = math.radians(angle) - return length * math.sin(radian_angle) - -def A(dx, dy): - return math.degrees( math.atan2(dy, dx) ) - -def drawCircle(drawer,x,y,radius,color): - x1 = x-radius - x2 = x+radius - y1 = y-radius - y2 = y+radius - drawer.ellipse([(x1,y1),(x2,y2)],color) - -def drawTrapezoid(drawer,x1,y1,x2,y2,r1,r2,color): - directAngle = A(x2-x1,y2-y1) - pAngle = directAngle-90 - pAngle2 = pAngle-180 - p1 = (x1+lengthDirX(r1,pAngle),y1+lengthDirY(r1,pAngle)) - p2 = (x1+lengthDirX(r1,pAngle2),y1+lengthDirY(r1,pAngle2)) - p3 = (x2+lengthDirX(r2,pAngle2),y2+lengthDirY(r2,pAngle2)) - p4 = (x2+lengthDirX(r2,pAngle),y2+lengthDirY(r2,pAngle)) - drawer.polygon([p1,p2,p3,p4],color,color) - -def centroidnp(arr): - length = arr.shape[0] - sum_x = np.sum(arr[:, 0]) - sum_y = np.sum(arr[:, 1]) - return sum_x/length, sum_y/length - -def unitCos(x): - return (math.cos(x*math.pi)+1)/2 - -def distMod(x,maxDist): - return unitCos(x/maxDist) - -def stick(val,minimum,maximum): - if abs(val-minimum) < abs(val-maximum): - return minimum - else: - return maximum - -def clamp(x,minimum,maximum): - if x < minimum: - return minimum - elif x > maximum: - return maximum - else: - return x - -def strDivider(length): - n = "" - for l in range(length): - n += "_" - return n - -from scipy.spatial import Voronoi -def relaxLloyd(pts,strength): - for i in range(strength): - vor = Voronoi(pts) - newpts = [] - for idx in range(len(vor.points)): - pt = vor.points[idx,:] - region = vor.regions[vor.point_region[idx]] - if -1 in region: - newpts.append(pt) - else: - vxs = np.asarray([vor.vertices[i,:] for i in region]) - newpt = centroidnp(vxs) - newpts.append(newpt) - pts = np.array(newpts) - -class noiseMaker: - def __init__(self,w,h): - self.noise = np.random.rand(w,h) - self.width = w - self.height = h - def smoothNoise(self,xx,yy): - fracX = xx % 1 - fracY = yy % 1 - x1 = (math.floor(xx)+self.width) % self.width - y1 = (math.floor(yy)+self.height) % self.height - x2 = (x1+self.width-1) % self.width - y2 = (y1+self.height-1) % self.height - tileVal = 0 - tileVal += fracX*fracY*self.noise[x1,y1] - tileVal += (1-fracX)*fracY*self.noise[x2,y1] - tileVal += fracX*(1-fracY)*self.noise[x1,y2] - tileVal += (1-fracX)*(1-fracY)*self.noise[x2,y2] - return tileVal - def turbulence(self,xx,yy,size): - tileVal = 0 - initialSize = size - while size >= 1: - tileVal += self.smoothNoise(xx/size,yy/size)*size - size = size/2 - return tileVal/(initialSize*2) - -class Node: - def __init__(self,xx,yy): - self.x = xx - self.y = yy - self.neighbors = [] - self.landmass = None - self.bodyWater = None - self.river = None - self.region = None - self.city = None - self.culture = None - self.allegiance = 0 - self.resourceRegion = None - self.resourceDist = 0 - def coords(self): - tupleVert = (self.x,self.y) - return tupleVert - def dist(self,n): - distX = abs(self.x-n.x) - distY = abs(self.y-n.y) - dist = math.sqrt((distX**2)+(distY**2)) - return dist - def isLinked(self,nNode): - if nNode in self.neighbors: - return 1 - else: - return 0 - def link(self,newNeighbor): - self.neighbors.append(newNeighbor) - newNeighbor.neighbors.append(self) - def hasWaterNeighbor(self,sealevel): - for n in self.neighbors: - if n.elevation < sealevel: - return 1 - return 0 - def nearestNeighbor(self,n): - distance = 100000000000 - nbr = self - for p in self.neighbors: - newDist = p.dist(n) - if newDist < distance: - nbr = p - distance = newDist - return nbr - def westMostNeighbor(self): - n = self - for p in self.neighbors: - if p.x < n.x: - n = p - self.westMostNbr = n - return n - def minNeighbor(self): - n = self - ne = self.elevation - for p in self.neighbors: - pe = p.elevation - if pe < ne: - ne = pe - n = p - return n - def maxNeighbor(self): - n = self - ne = self.elevation - for p in self.neighbors: - pe = p.elevation - if pe > ne: - ne = pe - n = p - return n - def getSlope(self): - minimum = self.minNeighbor() - maximum = self.maxNeighbor() - rise = maximum.elevation-minimum.elevation - run = self.dist(minimum)+self.dist(maximum) - if run != 0: - m = rise/run - else: - m = 0.1 - self.slope = m - return m - def slopeDirection(self): - self.westMostNeighbor() - if self.westMostNbr.elevation > self.elevation: - self.slopeDir = -1 - else: - self.slopeDir = 1 - def realSlope(self): - self.getSlope() - self.slopeDirection() - self.rSlope = self.slope*self.slopeDir - return self.rSlope - def smooth(self): - nbrs = [] - nbrs.append(self.elevation) - for i in self.neighbors: - nbrs.append(i.elevation) - self.elevation = sum(nbrs)/len(nbrs) - def setVegetation(self): - tempFitness = 1-abs(0.3-self.temp) - elevationFitness = 1-abs(0.45-self.elevation) - fertilityFitness = self.fertility+(self.metallicity*0.25) - rainFitness = 1-abs(self.rainfall-0.8) - vegFitness = ((tempFitness+1)*(elevationFitness+0.5)*(fertilityFitness+0.5)*(rainFitness+2)) - self.vegetation = clamp(vegFitness/16,0,1) - def setBiome(self,sl): - if self.elevation > 0.9: - self.biome = "mountain" - elif self.temp < 0.2: - if self.rainfall < 0.08: - self.biome = "frozen" - elif self.rainfall < 0.25: - self.biome = "tundra" - else: - self.biome = "shrubland" - elif self.temp < 0.3: - if self.rainfall < 0.03: - self.biome = "tundra" - elif self.rainfall < 0.05: - self.biome = "shrubland" - elif self.rainfall < 0.3: - self.biome = "boreal" - elif self.rainfall < 0.4: - self.biome = "forest" - else: - self.biome = "tropical" - elif self.temp < 0.5: - if self.rainfall < 0.04: - self.biome = "desert" - elif self.rainfall < 0.06: - self.biome = "savanna" - elif self.rainfall < 0.09: - self.biome = "shrubland" - elif self.rainfall < 0.2: - self.biome = "forest" - else: - self.biome = "tropical" - else: - if self.rainfall < 0.05: - self.biome = "desert" - else: - self.biome = "tropical" - if self.elevation < sl: - self.biome = "water" - def claim(self,n): - n.culture = self.culture - n.allegiance = self.allegiance+1 - if self.culture.name not in n.region.culturalNames: - n.region.culturalNames[self.culture.name] = self.culture.language.genName() - if n.landmass != None: - if self.culture.name not in n.landmass.culturalNames: - n.landmass.culturalNames[self.culture.name] = self.culture.language.genName() - if n.river != None: - if self.culture.name not in n.river.culturalNames: - n.river.culturalNames[self.culture.name] = self.culture.language.genName() - def updateAllegiance(self): - if self.culture != None: - chance = 1/clamp(self.allegiance,0.00001,512) - for n in self.neighbors: - roll = random.random() - if roll <= chance: - self.claim(n) - def toString(self): - self.selfString = "(" - self.selfString += str(self.x) - self.selfString += ", " - self.selfString += str(self.y) - self.selfString += ")" - return self.selfString - def printSelf(self): - print(self.toString()) - def drawPoint(self,drawer,radius,color): - drawCircle(drawer,self.x,self.y,radius,color) - def drawElevation(self,drawer,pts=0): - col = math.floor(self.elevation*255) - dCol = (0,0,col) - if self.elevation >= 1: - dCol = "red" - if self.landmass != None: - for n in self.neighbors: - if n.landmass == self.landmass: - drawer.line([self.coords(),n.coords()],dCol,3) - if pts == 1: - self.drawPoint(drawer,1,dCol) - def drawLandmass(self,drawer,pts=0,drawNeighbors=0): - if self.landmass == None: - dCol = "black" - else: - dCol = self.landmass.color - if self.landmass != None: - for n in self.neighbors: - if n.landmass == self.landmass: - drawer.line([self.coords(),n.coords()],dCol,3) - if self.landmass != None and drawNeighbors == 1: - for n in self.neighbors: - drawer.line([self.coords(),n.coords()],dCol,1) - if pts == 1: - self.drawPoint(drawer,1,"black") - def drawReal(self,drawer,sl): - dCol = self.biomeColor - if self.landmass != None and 0: - for n in self.neighbors: - if n.landmass == self.landmass: - drawer.line([self.coords(),n.coords()],dCol,3) - - -class Triangle: - def __init__(self,aa,bb,cc): - self.verts = [None,None,None] - self.verts[0] = aa - self.verts[1] = bb - self.verts[2] = cc - self.neighbors = [] - def sharesNeighbors(self,other): - shares = 0 - if self.verts[0] in other.verts: - shares += 1 - if self.verts[1] in other.verts: - shares += 1 - if self.verts[2] in other.verts: - shares += 1 - return shares - def toString(self): - self.selfString = "(" - self.selfString += self.verts[0].toString() - self.selfString += ", " - self.selfString += self.verts[1].toString() - self.selfString += ", " - self.selfString += self.verts[2].toString() - self.selfString += ")" - return self.selfString - def printSelf(self): - print(self.toString()) - def drawGraph(self,drawer): - drawer.line([self.verts[0].coords(),self.verts[1].coords()],"black",1) - drawer.line([self.verts[1].coords(),self.verts[2].coords()],"black",1) - drawer.line([self.verts[2].coords(),self.verts[0].coords()],"black",1) - def drawElevation(self,drawer,sealevel=0): - elevationList = [self.verts[f].elevation for f in range(len(self.verts))] - elevation = sum(elevationList)/3 - col = math.floor(elevation*255) - dCol = (0,0,col) - underwater = 0 - for f in self.verts: - if f.elevation < sealevel: - underwater = 1 - if underwater == 1: - dCol = (142,128,clamp(col,64,76)) - drawer.polygon([self.verts[0].coords(),self.verts[1].coords(),self.verts[2].coords()],fill=dCol,outline=dCol) - def drawWildlife(self,drawer,sealevel=0): - carnivores = math.floor(128*sum([self.verts[f].carnivores for f in range(len(self.verts))])/3) - herbivores = math.floor(128*sum([self.verts[f].herbivores for f in range(len(self.verts))])/3) - dCol = (128+(carnivores)-(herbivores),(carnivores+herbivores)*2,128) - underwater = 0 - for f in self.verts: - if f.elevation < sealevel: - underwater = 1 - if underwater == 1: - dCol = (142,128,70) - drawer.polygon([self.verts[0].coords(),self.verts[1].coords(),self.verts[2].coords()],fill=dCol,outline=dCol) - def drawLandmass(self,drawer,sealevel=0): - landmass = None - for p in self.verts: - if p.landmass != None: - landmass = p.landmass - if landmass != None: - dCol = landmass.color - else: - dCol = "black" - underwater = 0 - for f in self.verts: - if f.elevation < sealevel: - underwater = 1 - if underwater == 1: - dCol = (142,64,64) - drawer.polygon([self.verts[0].coords(),self.verts[1].coords(),self.verts[2].coords()],fill=dCol,outline=dCol) - def drawReal(self,drawer,sl): - elevationList = [self.verts[f].elevation for f in range(len(self.verts))] - elevation = sum(elevationList)/3 - col = math.floor(elevation*255) - underwater = 0 - avgHue = math.floor(sum([self.verts[f].biomeColor[0] for f in range(len(self.verts))])/3) - avgSat = math.floor(sum([self.verts[f].biomeColor[1] for f in range(len(self.verts))])/3) - avgVal = math.floor(sum([self.verts[f].biomeColor[2] for f in range(len(self.verts))])/3) - dCol = (avgHue,avgSat,avgVal) - for f in self.verts: - if f.elevation < sl: - underwater = 1 - if underwater == 1: - dCol = (142,128,clamp(col,64,76)) - drawer.polygon([self.verts[0].coords(),self.verts[1].coords(),self.verts[2].coords()],fill=dCol,outline=dCol) - -class River: - def __init__(self,root,length,landms): - self.nodes = [] - self.addNode(root) - self.landmass = landms - current = root - j = 0 - while j < length: - choice = current - for m in current.neighbors: - if (m.elevation > choice.elevation - and m.river == None and m not in self.nodes): - choice = m - for k in current.neighbors: - if (k.elevation < choice.elevation and k.elevation >= current.elevation - and k.river == None and k not in self.nodes): - choice = k - if current == choice: - j = length - else: - self.addNode(choice) - j+=1 - current = choice - self.culturalNames = {} - def removeRiver(self): - for n in self.nodes: - n.river = None - self.nodes.remove(n) - def addNode(self,newNode): - self.nodes.append(newNode) - newNode.river = self - def drawPath(self,drawer): - dCol = (142,64,64) - drawNodes = [] - for n in self.nodes: - drawNodes.append(n.coords()) - drawer.line(drawNodes,dCol,2) - def drawRiver(self,drawer,xDim): - for l in self.nodes: - l.getSlope() - dCol = (142,128,76) - for i in range(len(self.nodes)-1): - n = self.nodes[i] - n1 = self.nodes[i+1] - scale = xDim/2 - w = clamp(1/n.slope,0,2048)/scale - w1 = clamp(1/n1.slope,0,2048)/scale - drawCircle(drawer,n.x,n.y,w,dCol) - drawCircle(drawer,n1.x,n1.y,w1,dCol) - drawTrapezoid(drawer,n.x,n.y,n1.x,n1.y,w,w1,dCol) - -class bodyWater: - def __init__(self,rootNode,sLevel): - self.sealevel = sLevel - self.root = rootNode - self.nodes = [] - self.addNode(rootNode) - self.fill() - self.culturalNames = {} - def addNode(self,p): - if p not in self.nodes: - self.nodes.append(p) - p.bodyWater = self - def fill(self): - for p in self.nodes: - for k in p.neighbors: - while k not in self.nodes and k.elevation < self.sealevel: - self.addNode(k) - -class Landmass: - def __init__(self,rootNode,sLevel): - self.sealevel = sLevel - self.color = self.landmassColor() - self.root = rootNode - self.nodes = [] - self.addNode(rootNode) - self.boundary = [] - self.rivers = [] - self.fill() - self.size = len(self.nodes) - self.centermass() - self.culturalNames = {} - self.landmassType = self.lType(self.size) - def centermass(self): - xTotal = sum([p.x for p in self.nodes]) - yTotal = sum([p.y for p in self.nodes]) - xx = xTotal/self.size - yy = yTotal/self.size - self.centroid = Node(xx,yy) - def lType(self,s): - if s <= 3: - t = "isle" - elif s <= 16: - t = "atoll" - elif s <= 512: - t = "island" - elif s <= 2048: - t = "land" - else: - t = "continent" - return t - def landmassColor(self): - h = math.floor(random.random()*255) - s = 128+math.floor(random.random()*128) - v = 128+math.floor(random.random()*128) - return (h,s,v) - def addNode(self,p): - if p not in self.nodes: - self.nodes.append(p) - p.landmass = self - def addBoundary(self,p): - if p not in self.boundary: - self.boundary.append(p) - def fill(self): - for p in self.nodes: - for k in p.neighbors: - if k.elevation < self.sealevel: - self.addBoundary(p) - while k not in self.nodes and k.elevation >= self.sealevel: - self.addNode(k) - def addRiver(self,length): - root = self.boundary[math.floor(random.random()*(len(self.boundary)-1))] - riverLen = random.randint(length/4,length) - newRiver = River(root,riverLen,self) - self.rivers.append(newRiver) - def cullStreams(self,minLen): - for r in self.rivers: - if len(r.nodes) < minLen: - self.rivers.remove(r) - r.removeRiver() - -class Region: - def __init__(self,rootNode): - self.root = rootNode - self.nodes = [] - self.culturalNames = {} - self.addNode(self.root) - self.biome = self.root.biome - self.fill() - if self.biome == "boreal" or self.biome == "tropical": - self.biome += " forest" - if self.biome == "frozen": - self.biome += " tundra" - if self.biome == "water": - bodysize = len(self.nodes) - if bodysize > 1024: - self.biome = "ocean" - elif bodysize > 512: - self.biome = "sea" - else: - self.biome = "lake" - def addNode(self,p): - if p not in self.nodes: - self.nodes.append(p) - p.region = self - def fill(self): - for p in self.nodes: - for k in p.neighbors: - if k.biome == self.biome: - self.addNode(k) - -class Influence: - def __init__(self,mMap,myNode,root): - self.node = myNode - self.rootNode = root - self.myMap = mMap - self.influenceOutput = {} - for o in self.myMap.influenceOutputs.keys(): - self.influenceOutput[o] = 0 - self.translateInfluence() - def setOutput(self,influenceType,strength): - for o in self.myMap.influenceOutputs: - if o in self.myMap.influences[influenceType]: - output = strength*self.myMap.influences[influenceType][o] - self.influenceOutput[o] += output - def translateInfluence(self): - if self.rootNode == 1: - for p in self.node.neighbors: - ni = Influence(self.myMap,p,0) - for j in ni.influenceOutput.keys(): - self.influenceOutput[j] += ni.influenceOutput[j]/len(self.node.neighbors) - strength = 1 - inf = self.node.biome - self.setOutput(inf,strength) - strength = self.node.herbivores - inf = "carnivores" - self.setOutput(inf,strength) - strength = self.node.carnivores - inf = "herbivores" - self.setOutput(inf,strength) - strength = self.node.temp - inf = "temperature" - self.setOutput(inf,strength) - strength = self.node.elevation - inf = "elevation" - self.setOutput(inf,strength) - strength = self.node.slope - inf = "slope" - self.setOutput(inf,strength) - strength = self.node.vegetation - inf = "vegetation" - self.setOutput(inf,strength) - -class Values: - def __init__(self,m,inf): - self.myMap = m - self.influences = inf - self.valuesOutput = {} - for v in self.myMap.valuesOutputs.keys(): - self.valuesOutput[v] = 0 - self.translateValues() - self.mainValues = self.valuesMain(5) - def maxval(self): - for k in self.valuesOutput.keys(): - maxkey = k - for k in self.valuesOutput.keys(): - if self.valuesOutput[k] > self.valuesOutput[maxkey]: - maxkey = k - return maxkey - def valuesMain(self,n): - mVals = {} - for f in range(n): - maxkey = self.maxval() - mVals[maxkey] = self.valuesOutput[maxkey] - del self.valuesOutput[maxkey] - return mVals - def translateValues(self): - for q in self.influences.influenceOutput.keys(): - modifier = self.influences.influenceOutput[q] - roll = random.random() - if roll > 0.99: - modifier = 8 - elif roll < 0.01: - modifier = 0.01 - for v in self.myMap.values[q].keys(): - self.valuesOutput[v] += self.myMap.values[q][v]*modifier*random.uniform(0.8,1.25) - -class City: - def __init__(self,n,pop=50,cltr=None,m=None): - self.myMap = m - self.myMap.cities.append(self) - self.node = n - self.node.city = self - self.population = pop - if cltr == None: - self.culture = Culture(self.node,m) - else: - self.culture = cltr - self.cityType = self.cType(self.population) - self.name = self.culture.language.genName() - self.region = self.node.region - self.node.culture = self.culture - self.node.allegiance = 1/self.population - if self.culture.name not in self.node.region.culturalNames: - self.node.region.culturalNames[self.culture.name] = self.culture.language.genName() - if self.culture.name not in self.node.landmass.culturalNames: - self.node.landmass.culturalNames[self.culture.name] = self.culture.language.genName() - for q in self.node.neighbors: - if q.region.biome == "ocean" or q.region.biome == "lake" or q.region.biome == "sea": - if self.culture.name not in q.region.culturalNames: - q.region.culturalNames[self.culture.name] = self.culture.language.genName() - if self.node.river != None: - if self.culture.name not in self.node.river.culturalNames: - self.node.river.culturalNames[self.culture.name] = self.culture.language.genName() - reg = ResourceRegion(self,self.myMap) - self.rawResources = [0,0] - def updateDemog(self): - rscShare = self.population/self.node.resourceRegion.totalPop - rscMax = [0,0] - rscMax[0] = rscShare*self.node.resourceRegion.resources[0] - rscMax[1] = rscShare*self.node.resourceRegion.resources[1] - mpo = 0.1 # Maximum personal output (max resources production per person) - m = self.culture.value.mainValues - if "agriculture" in m: - mpo *= 1.1 - if "simplicity" in m: - mpo *= 0.8 - if "warriors" in m: - mpo *= 1.05 - if "builders" in m: - mpo *= 1.05 - if "metallurgists" in m: - mpo *= 1.05 - if "craftsmen" in m: - mpo *= 1.05 - self.foodProduction = min(self.population*mpo,rscMax[0]) - self.industrialProduction = min(self.population*mpo,rscMax[1]) - mpc = 0.080 # Maximum personal consumption (max food needed per person) - mpc -= 0.002*(math.log10(clamp(self.population,1,1000000))+1) - # As the population grows, need less food per person due to economies of scale - mpc += 0.001*math.log2(clamp(len(self.node.resourceRegion.nodes)-16,1,1000000)) - # As the resource region grows, need more food per person due to transporation distance - if "collectivists" in m: - mpc -= 0.001 - if "freedom" in m: - mpc += 0.001 - if "simplicity" in m: - mpc -= 0.001 - if "greed" in m: - mpc += 0.001 - self.foodConsumption = mpc*self.population - diff = self.foodProduction-self.foodConsumption - growth = clamp(diff/mpc,-self.population*0.1,self.population*0.1) - self.population = math.ceil(self.population*0.99) # Age related death - self.population = clamp(math.floor(self.population+growth+random.choice([-1,0,0,0,0,1])),1,1000000) - self.cityType = self.cType(self.population) - def cType(self,p): - if p <= 35: - c = random.choice(["bivouac","camp","camp","encampment","campsite"]) - elif p <= 200: - c = random.choice(["village","village","hamlet"]) - elif p <= 1000: - c = random.choice(["township","settlement"]) - elif p <= 5000: - c = "town" - elif p <= 20000: - c = "city" - else: - c = "metropolis" - return c.capitalize() - def cityInfo(self): - n = self.name + " (" + self.cityType + ")\n" - n += "Population: " + str(self.population) - return n - def cultureInfo(self): - info = self.culture.information() - return info - def drawTent(self,drawer,xx,yy,col,out): - p0 = (xx,yy-6) - p1 = (xx-4,yy+2) - p2 = (xx+4,yy+2) - drawer.polygon([p0,p1,p2],outline=out,fill=col) - p0 = (xx,yy-1) - p1 = (xx-1,yy+2) - p2 = (xx+1,yy+2) - drawer.polygon([p0,p1,p2],outline=out,fill=out) - p0 = (xx,yy-6) - p1 = (xx-2,yy-8) - p2 = (xx+2,yy-8) - drawer.line([p0,p1],fill=out,width=1) - drawer.line([p0,p2],fill=out,width=1) - def drawHut(self,drawer,xx,yy,col,out): - p0 = (xx+3,yy+2) - p1 = (xx-3,yy+2) - p2 = (xx-3,yy-3) - p3 = (xx,yy-6) - p4 = (xx+3,yy-3) - drawer.polygon([p0,p1,p2,p3,p4],outline=out,fill=col) - p1 = (xx-1,yy-1) - p2 = (xx+1,yy+2) - drawer.rectangle([p1,p2],outline=out,fill=out) - p0 = (xx,yy-6) - p1 = (xx+5,yy-1) - p2 = (xx-5,yy-1) - drawer.line([p0,p1],fill=out,width=1) - drawer.line([p0,p2],fill=out,width=1) - def drawVillage(self,drawer,xx,yy,col,out): - p0 = (xx,yy-5) - p1 = (xx-2,yy-7) - p2 = (xx-4,yy-5) - p3 = (xx-4,yy+3) - p4 = (xx,yy+3) - drawer.polygon([p0,p1,p2,p3,p4],outline=out,fill=col) - p0 = (xx-6,yy-3) - p2 = (xx+2,yy-3) - drawer.line([p1,p0],fill=out,width=1) - drawer.line([p1,p2],fill=out,width=1) - p3 = (xx-2,yy-4) - p4 = (xx-2,yy-3) - drawer.line([p4,p3],fill=out,width=1) - p0 = (xx,yy-1) - p1 = (xx+2,yy-3) - p2 = (xx+4,yy-1) - p3 = (xx+4,yy+3) - p4 = (xx,yy+3) - drawer.polygon([p0,p1,p2,p3,p4],outline=out,fill=col) - p0 = (xx-2,yy+1) - p2 = (xx+6,yy+1) - drawer.line([p1,p0],fill=out,width=1) - drawer.line([p1,p2],fill=out,width=1) - p3 = (xx+2,yy+3) - p4 = (xx+2,yy+1) - drawer.line([p4,p3],fill=out,width=1) - def drawTown(self,drawer,xx,yy,col,out): - p0 = (xx-4,yy+3) - p1 = (xx-4,yy-2) - p2 = (xx,yy-5) - p3 = (xx+4,yy-2) - p4 = (xx+4,yy+3) - drawer.polygon([p0,p1,p3,p4],outline=out,fill=col) - drawer.polygon([p1,p2,p3],outline=out,fill=out) - p0 = (xx-2,yy+4) - p1 = (xx-2,yy-5) - p2 = (xx,yy-7) - p3 = (xx+2,yy-5) - p4 = (xx+2,yy+4) - drawer.polygon([p0,p1,p3,p4],outline=out,fill=col) - drawer.polygon([p1,p2,p3],outline=out,fill=out) - p0 = (xx,yy) - p1 = (xx,yy+3) - drawer.line([p1,p0],fill=out,width=1) - def drawCity(self,drawer,xx,yy,col,out): - p0 = (xx-5,yy-3) - p1 = (xx-5,yy+1) - p2 = (xx-3,yy+1) - p3 = (xx-3,yy-5) - drawer.polygon([p0,p1,p2,p3],outline=out,fill=col) - p0 = (xx+5,yy-4) - p1 = (xx+5,yy+1) - p2 = (xx+3,yy+1) - p3 = (xx+3,yy-4) - drawer.polygon([p0,p1,p2,p3],outline=out,fill=col) - p0 = (xx-1,yy-7) - p1 = (xx-1,yy+2) - p2 = (xx+1,yy+2) - p3 = (xx+1,yy-7) - drawer.polygon([p0,p1,p2,p3],outline=out,fill=col) - p0 = (xx-6,yy+2) - p1 = (xx-6,yy-1) - p2 = (xx+6,yy-1) - p3 = (xx+6,yy+2) - drawer.polygon([p0,p1,p2,p3],outline=out,fill=col) - p0 = (xx,yy) - p1 = (xx,yy+1) - drawer.line([p1,p0],fill=out,width=1) - def drawSelf(self,drawer): - col = self.culture.bannerColor - out = (0,0,0) - if self.population <= 35: - self.drawTent(drawer,self.node.x,self.node.y,col,out) - elif self.population <= 200: - self.drawHut(drawer,self.node.x,self.node.y,col,out) - elif self.population <= 1000: - self.drawVillage(drawer,self.node.x,self.node.y,col,out) - elif self.population <= 5000: - self.drawTown(drawer,self.node.x,self.node.y,col,out) - elif self.population <= 20000: - self.drawCity(drawer,self.node.x,self.node.y,col,out) - -class ResourceRegion: - def __init__(self,c,m): - self.rootCity = c - self.myMap = m - self.culture = self.rootCity.culture - self.nodes = [] - self.addNode(self.rootCity.node) - self.resources = [0,0] - self.updateReg() - def expungeReg(self): - self.myMap.resourceRegions.remove(self) - def addNode(self,node): - if node.resourceRegion != None: - node.resourceRegion.nodes.remove(node) - node.resourceRegion = self - self.nodes.append(node) - def cityCount(self): - c = 0 - q = 0 - for p in self.nodes: - if p.city != None: - c += 1 - q += p.city.population - self.totalCities = c - self.totalPop = q - def sumResources(self): - self.resources = [0,0] # [Food resources, industrial resources] - rawPlant = 0 - rawMetal = 0 - rawAnimal = 0 - for p in self.nodes: - if p.landmass != None: - rawPlant += p.vegetation - rawMetal += p.metallicity - rawAnimal += p.herbivores + (p.carnivores/3) - else: - rawAnimal += 0.1 - rawPlant += 0.1 - m = self.culture.value.mainValues - if "metallurgists" in m: - rawMetal = rawMetal*1.25 - if "agriculture" in m: - rawPlant *= 1.15 - if "warriors" in m: - rawAnimal *= 1.2 - if "simplicity" in m: - rawMetal *= 0.75 - rawAnimal *= 0.75 - rawPlant *= 0.75 - scale = self.myMap.resourceScale - self.resources[0] = scale*(rawPlant + (rawAnimal*0.7)) - self.resources[1] = scale*(rawMetal + (rawAnimal*0.3) + (rawPlant*0.4)) - if "craftsmen" in m: - self.resources[1] *= 1.1 - if "builders" in m: - self.resources[1] *= 1.1 - if "collectivists" in m: - self.resources[0] *= 1.05 - def updateReg(self): - for p in self.nodes: - if p.city != None: - p.resourceDist = math.log2(p.city.population) - for p in self.nodes: - for k in p.neighbors: - if k.resourceRegion != self and k.resourceRegion != None: - # In this case, it's a different region OTHER than noRegion. - if k.resourceRegion.culture != self.culture: - # Interact with a different region of a different society... - # This is a placeholder. In the future there will probably be wars here. - k.resourceRegion = k.resourceRegion - elif k.resourceRegion.culture == self.culture: - # Interacting with a different region of the same society... - if k.resourceDist < p.resourceDist: - self.addNode(k) - k.resourceDist = (p.resourceDist-1)/2 - elif k.resourceRegion == None: - if k.resourceDist < p.resourceDist: - self.addNode(k) - k.resourceDist = (p.resourceDist-1)/2 - elif k.resourceRegion == self: - if k.resourceDist < p.resourceDist: - k.resourceDist = (p.resourceDist-1)/2 - self.cityCount() - self.sumResources() - if len(self.nodes) == 0: - self.expungeReg() - return -1 - -class Culture: - def __init__(self,n,m): - self.origin = n - self.myMap = m - self.myMap.cultures.append(self) - self.influences = Influence(self.myMap,self.origin,1) - self.value = Values(self.myMap,self.influences) - self.society = self.setSociety() - self.language = Language(self) - self.name = self.language.name - self.title = self.setTitle() - self.flag = Flag(self) - self.bannerColor = self.flag.colors[0] - self.leaderTitle = self.setLeaderTitle() - self.leader = Population(self,t=self.leaderTitle) - def setSociety(self): - m = self.value.mainValues - if "greed" in m and "worshippers" in m and "builders" in m: - return "Empire" - if "warriors" in m and "greed" in m and "builders" in m and ("travelers" in m or "sailors" in m): - return "Imperium" - if "warriors" in m and "collectivists" in m and "worshippers" in m: - return "Hegemony" - if "builders" in m and "collectivists" in m and "materialists" in m: - return "Socialists" - if "travelers" in m and "sailors" in m and "traders" in m: - return "Traders" - if "freedom" in m and "collectivists" in m and "simplicity" in m: - return "Paleolithic tribe" - if ("travelers" in m or "sailors" in m) and ("traders" in m or "greed" in m) and "freedom" in m: - return "Independent merchants" - if "collectivists" in m and "agriculture" in m and "materialists" in m: - return "Agricultural communists" - if "collectivists" in m and "agriculture" in m and "simplicity" in m: - return "Farming commune" - if "worshippers" in m and "warriors" in m and ("superstitious" in m or "collectivists" in m): - return "Religious zealots" - if "shamans" in m and ("naturalists" in m or "astrologists" in m) and "superstitious" in m: - return "Shamanic tribe" - if "shamans" in m and "warriors" in m and ("astrologists" in m or "superstitious" in m or "worshippers" in m): - return "Shamanistic warriors" - if "metallurgists" in m and "builders" in m and "craftsmen" in m and "materialists" in m: - return "Cooperative artisans" - if "metallurgists" in m and "builders" in m and "craftsmen" in m and "traders" in m: - return "Merchant artisans" - if "freedom" in m and "greed" in m and "traders" in m: - return "Liberal capitalists" - if "freedom" in m and "traders" in m and ("builders" in m or "craftsmen" in m or "metallurgists" in m): - return "Liberal merchant-artisans" - if "builders" in m and "agriculture" in m and "traders" in m: - return "Mercantile folk" - if "builders" in m and "agriculture" in m and ("travelers" in m or "sailors" in m): - return "Township builders" - if ("craftsmen" in m or "metallurgy" in m or "builders" in m) and "simplicity" in m and ("naturalists" in m or "shamans" in m): - return "Naturalist artisans" - if ("craftsmen" in m or "metallurgy" in m or "builders" in m) and "simplicity" in m and ("superstitious" in m or "astrologists" in m): - return "Traditionalist artisans" - if "greed" in m and "sailors" in m and "warriors" in m: - return "Pirates" - if ("travelers" in m or "sailors" in m) and "greed" in m and "warriors" in m: - return "Raiders" - if ("travelers" in m or "sailors" in m) and "greed" in m and "simplicity" in m: - return "Scavengers" - if "travelers" in m and "simplicity" in m and "freedom" in m: - return "Hunter-gatherer tribe" - if "astrologists" in m and "superstitious" in m and "worshippers" in m: - return "Religious sovereignty" - if "collectivists" in m and "agriculture" in m and "naturalists" in m: - return "Agriculturalists" - if "travelers" in m and "simplicity" in m and ("metallurgists" in m or "craftsmen" in m or "builders" in m): - return "Nomadic artisans" - if "travelers" in m and "simplicity" and ("naturalists" in m or "superstitious" in m or "astrologists" in m or "shamans" in m): - return "Nomadic tribe" - if "materialists" in m and "metallurgists" in m and ("craftsmen" in m or "builders" in m): - return "Blacksmiths" - if "warriors" in m and "collectivists" in m and ("travelers" in m or "sailors" in m): - return "Revolutionary commune" - if "builders" in m and "metallurgists" in m and "craftsmen" in m and "agriculture" in m: - return "Syndicalists" - if "warriors" in m and "builders" in m and ("worshippers" in m or "superstitious" in m) and "freedom" not in m and "traders" not in m: - return "Nationalists" - if "collectivists" in m and "freedom" in m: - return "Social Liberals" - if (("astrologists" in m and "superstitious" in m) or - ("superstitious" in m and "worshippers" in m) or - ("woshippers" in m and "astrologists" in m)): - return "Religious Sovereignty" - if "collectivists" in m: - return "Communalists" - if "freedom" in m: - return "Liberals" - if "shamans" in m: - return "Shamans" - if "simplicity" in m: - return "Tribe" - return "Mixed society" - def setTitle(self): - if self.society == "Nationalists": - return "Nation" - if self.society == "Religious sovereignty" or self.society == "Religious zealots": - return random.choice(["Theocracy","Ecclesiarchy","Order"]) - if self.society == "Agriculturalists" or self.society == "Farming commune" or self.society == "Agricultural communists": - return random.choice(["Farmers","Yeomen","Peasants"]) - if self.society == "Imperium" or self.society == "Hegemony" or self.society == "Empire": - return "Empire" - if self.society == "Nomadic artisans" or self.society == "Nomadic peoples" or self.society == "Scavengers": - return "Nomads" - if (self.society == "Liberal capitalists" or self.society == "Liberal merchant-artisans" or self.society == "Merchant artisans" or - self.society == "Traders" or self.society == "Independent merchants" or self.society == "Mercantile folk"): - return "Caravans" - if (self.society == "Blacksmiths" or self.society == "Traditionalist artisans" or - self.society == "Naturalist artisans" or self.society == "Cooperative artisans"): - return "Artisans" - if (self.society == "Socialists" or self.society == "Syndicalists" or self.society == "Revolutionary commune" - or self.society == "Communalists"): - return random.choice(["People's Union","Union","Collective"]) - if (self.society == "Shamanistic warriors" or self.society == "Shamanic tribe" - or self.society == "shamans"): - return "Mystics" - if self.society == "Pirates" or self.society == "Raiders": - return "Brigands" - if self.society == "Social Liberals" or self.society == "Liberals": - return "Republic" - return "People" - def setLeaderTitle(self): - if self.society == "Nationalists": - return (random.choice(["Master","High","Lord",""])+ " " + - random.choice(["Commissioner","Chancellor","Harbinger"])) - if self.society == "Religious sovereignty" or self.society == "Religious zealots": - return (random.choice(["Grand","High","Supreme","Holy"]) + " " + - random.choice(["Pontiff","Priest","Shepherd"])) - if self.society == "Agriculturalists" or self.society == "Farming commune" or self.society == "Agricultural communists": - return (random.choice(["Head","Chief","Master"])+ " " + - random.choice(["Farmer","Agronomist","Foreman"])) - if self.society == "Imperium" or self.society == "Hegemony" or self.society == "Empire": - return "Emperor" - if self.society == "Nomadic artisans" or self.society == "Nomadic peoples" or self.society == "Scavengers": - return (random.choice(["Chief","Head","Elder"])+ " " + - random.choice(["Captain","Dignitary","Herald"])) - if (self.society == "Liberal capitalists" or self.society == "Liberal merchant-artisans" or self.society == "Merchant artisans" or - self.society == "Traders" or self.society == "Independent merchants" or self.society == "Mercantile folk"): - return (random.choice(["Primary","Head","Chief",""])+ " " + - random.choice(["Executive","Director","Superintendent"])) - if (self.society == "Blacksmiths" or self.society == "Traditionalist artisans" or - self.society == "Naturalist artisans" or self.society == "Cooperative artisans"): - return (random.choice(["Master","Elder","Grandmaster"])+ " " + - random.choice(["Atificer","Builder","Craftsman",""])) - if (self.society == "Socialists" or self.society == "Syndicalists" or self.society == "Revolutionary commune" - or self.society == "Communalists"): - return (random.choice(["Prime","Chief","Central",""])+ " " + - random.choice(["Director","Governer","Speaker"])) - if (self.society == "Shamanistic warriors" or self.society == "Shamanic tribe" - or self.society == "shamans"): - return (random.choice(["Elder","High","Grand","Ancestral"])+ " " + - random.choice(["Medicine Man","Seer","Shaman"])) - if self.society == "Pirates" or self.society == "Raiders": - return (random.choice(["Chief","Head",""])+ " " + - random.choice(["Captain","Commander",""])) - if self.society == "Social Liberals" or self.society == "Liberals": - return random.choice(["President","Speaker","Minister"]) - return "Chief" - def shortName(self): - name = "" - name += self.name + " " + self.title - return name - def information(self): - info = "" - info += self.name +" "+ self.title + "\n" - info += "("+self.society+")" + "\n" - return info - def cultureNotes(self): - s = self.name + " " + self.title + "\n\n" - s += "Society type: " + self.society + "\n\n" - s += "Leader: " + self.leader.nameFull() + "\n\n" - s += "Capital: " + self.origin.city.name + "\n\n" - return s - -class Population: - def __init__(self,c,n=None,t=""): - self.culture = c - if n == None: - self.name = (self.culture.language.genName(),self.culture.language.genName()) - else: - self.name = n - self.title = t + " " - self.fullName = self.nameFull() - def nameFull(self): - return self.title + self.name[0] + " " + self.name[1] - -class Flag: - def __init__(self,c): - self.culture = c - self.xDim = 384 - self.yDim = 192 - self.colors = [] - self.newColor() - self.genFlag() - def newColor(self): - h = random.randint(0,255) - s = random.randint(128,255) - v = random.randint(0,255) - col = (h,s,v) - self.colors.append(col) - return col - def randPt(self): - pt = (random.randint(0,self.xDim),random.randint(0,self.yDim)) - return pt - def center(self): - return (self.xDim/2,self.yDim/2) - def addTri(self,drawer): - col = self.newColor() - p0 = random.choice(self.corners) - p1 = random.choice([(abs(self.xDim-p0[0]),p0[1]),(p0[0],abs(self.yDim-p0[1]))]) - p2 = random.choice([self.center(),random.choice(self.corners)]) - drawer.polygon([p0,p1,p2],fill=col,outline=col) - def addRect(self,drawer): - col = self.newColor() - p0 = random.choice([(random.randint(0,self.xDim),random.choice([0,self.yDim,self.yDim/2])), - (random.choice([0,self.xDim,self.xDim/2]),random.randint(0,self.yDim))]) - p1 = random.choice(self.corners) - drawer.rectangle([p0,p1],fill=col,outline=col) - def addCirc(self,drawer): - col = self.newColor() - p0 = random.choice([(random.randint(0,self.xDim),random.choice([0,self.yDim,self.yDim/2])), - (random.choice([0,self.xDim,self.xDim/2]),random.randint(0,self.yDim))]) - rad = random.randint(1,self.yDim) - drawCircle(drawer,p0[0],p0[1],rad,col) - def genFlag(self): - img = Image.new('HSV',(self.xDim,self.yDim),self.colors[0]) - drawer = ImageDraw.Draw(img) - numElements = random.randint(1,4) - self.corners = [(0,0),(self.xDim,0),(0,self.yDim),(self.xDim,self.yDim)] - for i in range(numElements): - element = random.choice(["tri","rect","circ"]) - if element == "tri": - self.addTri(drawer) - if element == "rect": - self.addRect(drawer) - if element == "circ": - self.addCirc(drawer) - - self.filename = "flag_" + self.culture.name + ".gif" - img = img.convert('RGB') - img.save(self.filename,"GIF") - -class Language: - def __init__(self,c): - self.culture = c - self.characters() - self.lengthPref = random.choice([3,5,9]) - self.name = self.genName() - def characters(self): - c = ['b','c','d','f','g','h','j','k','l','m','n','p','q','r','s','t','v','w','x','y','z'] - v = ['a','e','i','o','u'] - self.langConsonants = [] - self.langVowels = [] - count = 32 - n = 1 - while len(c) > 0: - cons = random.choice(c) - for l in range(math.floor(count)): - self.langConsonants.append(cons) - c.remove(cons) - n += 1 - count = 32*(1/n)*random.uniform(0.8,1.25) - count = 32 - while len(v) > 0: - vow = random.choice(v) - for l in range(math.floor(count)): - self.langVowels.append(vow) - v.remove(vow) - n += 1 - count = 32*(1/n)*random.uniform(0.8,1.25) - self.preferredStart = random.choice(self.langConsonants+self.langVowels) - def genName(self): - length = random.randint(3,9) - length = math.floor((length+self.lengthPref)/2) - n = "" - con = 0 - vow = 0 - lastchar = '1' - for k in range(length): - ctype = random.choice(["con","vow"]) - if vow >= 2: - ctype = "con" - if con >= 2: - ctype = "vow" - c = lastchar - if ctype == "con": - c = random.choice(self.langConsonants) - vow = 0 - con += 1 - if ctype == "vow": - c = random.choice(self.langVowels) - con = 0 - vow += 1 - while c == lastchar and (random.random() > 0.2): - if ctype == "con": - c = random.choice(self.langConsonants) - vow = 0 - con += 1 - if ctype == "vow": - c = random.choice(self.langVowels) - con = 0 - vow += 1 - if k == 0 and random.random() < 0.35: - c = self.preferredStart - n += c - lastchar = c - return n.capitalize() +from scipy.spatial import Delaunay -class Map: - def __init__(self,aAtlas,numNodes,mapDimX,mapDimY): - self.atlas = aAtlas - self.n = numNodes - self.xDim = mapDimX - self.yDim = mapDimY - self.landmasses = [] - self.waterBodies = [] - self.regions = [] - self.cities = [] - self.cultures = [] - self.resourceRegions = [] - self.resourceScale = 100 - self.sealevel = 0.4 - self.setNorth() - self.biomeColors() - self.displayNo = None - self.infoGui = None - def setNorth(self): - nx = math.floor(random.random()*self.xDim) - ny = math.floor(random.random()*self.yDim) - side = random.randint(0,3) - if side == 0: - nx = 0 - elif side == 1: - nx = self.xDim - elif side == 2: - ny = 0 - else: - ny = self.yDim - self.north = Node(nx,ny) - def nodeLat(self,n): - return "Latitude: " + str(round(n.y/self.distScale)) - def nodeLong(self,n): - return "Longitude: " + str(round(n.x/self.distScale)) - def nodeElevation(self,n): - return "Elevation: " + str(round((n.elevation-self.sealevel)*self.eScale,1)) + "m" - def nodeTemp(self,n): - if n.biome != "water": - return "Temperature: " + str(round((n.temp*self.tempScale)-30,1)) + " degrees" - else: - return "Temperature: " + str(round((n.temp*self.tempScale*0.3),1)) + " degrees" - def nodeRain(self,n): - if n.biome != "water": - rain = "Rainfall: " + str(round(((n.rainfall)**2)*self.rainfallScale,1)) + "cm/yr" - else: - rain = "Rainfall: " + str(round((n.rainfall)*self.rainfallScale*0.03,1)) + "cm/yr" - return rain - def nodeBiome(self,n): - return n.biome - def nodeRegion(self,n): - if n.region.culturalNames == {}: - return "Unnamed " + n.region.biome + "\n" - else: - names = "" - for f in n.region.culturalNames.keys(): - q = n.region.culturalNames[f] + " " - q += n.region.biome - q += " (" + f + " language)" - names += q + "\n" - return names - def nodeLandmass(self,n): - if n.landmass.culturalNames == {}: - return "Unnamed " + n.landmass.landmassType + "\n" - else: - names = "" - for f in n.landmass.culturalNames.keys(): - q = n.landmass.culturalNames[f] + " " - q += n.landmass.landmassType - q += " (" + f + " language)" - names += q + "\n" - return names - def nodeRiver(self,n): - if n.river.culturalNames == {}: - return "Unnamed " + "river" + "\n" - else: - names = "" - for f in n.river.culturalNames.keys(): - q = n.river.culturalNames[f] + " " - q += "river" - q += " (" + f + " language)" - names += q + "\n" - return names - def nodeFertility(self,n): - return "Soil fertility: "+str(math.floor(n.fertility*self.fertScale))+"%" - def nodeMetallicity(self,n): - return "Ground metallicity: "+str(math.floor(n.metallicity*self.metalScale))+"ppm" - def nodeVegetation(self,n): - return "Vegetation: " + str(math.floor(n.vegetation*self.vegScale)) + "p/km"+chr(0x00B2) - def nodeHerbivores(self,n): - return "Herbivores: " + str(math.floor(n.herbivores*self.wildlifeScale)) + "/km"+chr(0x00B2) - def nodeCarnivores(self,n): - return "Carnivores: " + str(math.floor(n.carnivores*self.wildlifeScale)) + "/km"+chr(0x00B2) - def nodeCityInfo(self,n): - cityInfo = n.city.cityInfo() + "\n" + "\n" - cityInfo += n.city.cultureInfo() - return cityInfo - def nodeTerritory(self,n): - territory = "" - if n.culture != None: - territory = n.culture.shortName() + " territory" - return territory - def nodeResReg(self,n): - if n.resourceRegion != None: - reg = "Inside " + n.resourceRegion.rootCity.name + " outskirts" + "\n" - reg += "Total food available: " + str(math.floor(n.resourceRegion.resources[0]*self.resourceScale)) + " t/year \n" - reg += "Total industrial resources available: " + str(math.floor(n.resourceRegion.resources[1]*self.resourceScale)) + " t/year \n" - return reg - def infoScales(self): - self.distScale = 12 - self.eScale = 2000 - self.tempScale = 105 - self.rainfallScale = 7628 - self.fertScale = 100 - self.metalScale = 140000 - self.vegScale = 10295 - self.wildlifeScale = 500 - self.resourceScale = 1 - def nodeInfo(self,n): - self.divWidth = 48 - info = "" - if n.city != None: - info += strDivider(self.divWidth)+"\n" - info += self.nodeCityInfo(n) + "\n" - if n.resourceRegion != None: - info += strDivider(self.divWidth)+"\n" - info += self.nodeResReg(n)+"\n" - info += self.nodeTerritory(n) + "\n" - info += strDivider(self.divWidth)+"\n" - info += self.nodeRegion(n) + "\n" - if n.landmass != None: - info += self.nodeLandmass(n) + "\n" - if n.river != None: - info += self.nodeRiver(n) + "\n" - info += self.nodeElevation(n) + "\n" - info += self.nodeTemp(n) + "\n" - info += self.nodeRain(n) + "\n" - if n.biome != "water": - info += strDivider(self.divWidth)+"\n" - info += self.nodeFertility(n) + "\n" - info += self.nodeMetallicity(n) + "\n" - info += self.nodeVegetation(n) + "\n" - info += self.nodeHerbivores(n) + "\n" - info += self.nodeCarnivores(n) + "\n" - return info - def nearestNode(self,xx,yy): - n = self.atlas[0] - minDist = 1000000 - search = Node(xx,yy) - for p in self.atlas: - dist = p.dist(search) - if dist < minDist: - minDist = dist - n = p - return n - def nearestCity(self,xx,yy): - if len(self.cities) == 0: - return self.atlas[0] - n = self.cities[0] - minDist = 1000000 - search = Node(xx,yy) - for p in self.cities: - dist = p.node.dist(search) - if dist < minDist: - minDist = dist - n = p - return n - def setSeaLevel(self,n): - self.sealevel = n - def perlinElevation(self,octaves,scale=1): - noiseThing = noiseMaker(math.floor(self.xDim/scale),math.floor(self.yDim/scale)) - for p in self.atlas: - p.elevation = noiseThing.turbulence(math.floor(p.x),math.floor(p.y),2**octaves) - def flatten(self): - for p in self.atlas: - p.elevation = 0.5 - def elevationAdd(self,amount): - for p in self.atlas: - p.elevation = clamp(p.elevation+amount,0,1) - def randomizeElevation(self,anchor,rng): - for p in self.atlas: - p.elevation = anchor-(random.random()*(rng/2)) - def clampElevation(self): - for p in self.atlas: - p.elevation = clamp(p.elevation,0,1) - def smooth(self,strength): - for l in range(strength): - for p in self.atlas: - p.smooth() - def cullDots(self): - for p in self.atlas: - count = len(p.neighbors) - for n in p.neighbors: - if n.elevation < self.sealevel: - count -= 1 - if count <= 1: - p = clamp(p.elevation,self.sealevel-0.01,255) - def buildLandmass(self,root): - if root.landmass != None or root.elevation < self.sealevel: - return -1 - root.landmass = Landmass(root,self.sealevel) - self.landmasses.append(root.landmass) - def buildAllLand(self): - print("Building landmasses...") - for p in self.atlas: - self.buildLandmass(p) - def buildBodyWater(self,root): - if root.landmass != None or root.bodyWater != None: - return -1 - root.bodyWater = bodyWater(root,self.sealevel) - self.waterBodies.append(root.bodyWater) - def buildAllWater(self): - print("Building lakes/oceans...") - for p in self.atlas: - self.buildBodyWater(p) - def buildRegions(self): - print("Building world regions...") - for p in self.atlas: - if p.region == None: - newReg = Region(p) - self.regions.append(newReg) - def addRiver(self,length): - c = 0 - while c < len(self.landmasses): - landmass = self.landmasses[math.floor(random.random()*len(self.landmasses))] - if landmass.size > length*8: - c = 100000000 - c += 1 - landmass.addRiver(length) - def addMinorRiver(self,count): - for i in range(count): - self.addRiver(self.n/512) - def addMajorRiver(self,count): - for i in range(count): - self.addRiver(self.n/128) - def cullStreams(self): - for l in self.landmasses: - l.cullStreams(self.n/32) - def addSineHill(self,xx,yy,maximum=0.25,radius=128): - hillCenter = Node(xx,yy) - for p in self.atlas: - dist = p.dist(hillCenter) - if dist <= radius: - multiplier = distMod(dist,radius) - if p.elevation > 0.75: - multiplier = multiplier*((1-p.elevation)**2) - p.elevation = p.elevation+(maximum*multiplier) - def addHill(self,xx,yy,maximum=0.25,radius=128): - hillCenter = Node(xx,yy) - for p in self.atlas: - dist = p.dist(hillCenter) - if dist <= radius: - multiplier = random.uniform(0.99,1.01) - p.elevation = maximum*multiplier - def addMountains(self,num=5,height=0.2): - avgRad = self.xDim/3.5 - for i in range(num): - xx = math.floor(random.random()*self.xDim) - yy = math.floor(random.random()*self.yDim) - hillRad = avgRad*random.uniform(0.8,1.25) - hillHeight = height*random.uniform(0.8,1.25) - self.addSineHill(xx,yy,hillHeight,hillRad) - def addHills(self,num=8,height=0.1): - avgRad = self.xDim/6 - for i in range(num): - xx = math.floor(random.random()*self.xDim) - yy = math.floor(random.random()*self.yDim) - hillRad = avgRad*random.uniform(0.8,1.25) - hillHeight = height*random.uniform(0.8,1.25) - self.addSineHill(xx,yy,hillHeight,hillRad) - def addShape(self,shape): - if shape == "island" or shape == "volcanic": - self.addSineHill(self.xDim/2,self.yDim/2,0.4,radius=self.xDim/1.5) - if shape == "volcanic": - self.smooth(4) - self.elevationAdd(-0.1) - self.addSineHill(self.xDim/2,self.yDim/2,0.25,radius=self.xDim*1.5) - self.smooth(4) - self.addHill(self.xDim/2,self.yDim/2,0.45,radius=self.xDim/11) - self.addSineHill(self.xDim/2,self.yDim/2,0.-0.1,radius=self.xDim/11) - if shape == "shore" or shape == "highlands": - corner = random.randint(0,3) - if corner == 0: - xx = 0 - yy = 0 - elif corner == 1: - xx = self.xDim - yy = 0 - elif corner == 2: - xx = self.xDim - yy = self.yDim - else: - xx = 0 - yy = self.yDim - self.addSineHill(xx,yy,0.45,radius=self.xDim*1.5) - if shape == "highlands": - self.addSineHill(xx,yy,0.3,radius=self.xDim*2) - self.addMountains() - if shape == "archipelago": - self.addHills(16,0.25) - if shape == "plain": - self.addSineHill(self.xDim/2,self.yDim/2,0.4,radius=self.xDim*5) - if shape != "volcanic": - self.addMountains() - self.addHills() - self.erode(3) - self.smooth(3) - def addRandomShape(self): - shp = random.choice(["highlands","plain","volcanic","shore","archipelago","island"]) - self.addShape(shp) - def erode(self,strength=3): - for j in range(strength): - for p in self.atlas: - if p.elevation < self.sealevel*1.02 and p.elevation > self.sealevel*0.9: - if p.hasWaterNeighbor(self.sealevel): - p.elevation = p.elevation*0.94 - for n in p.neighbors: - n.elevation = n.elevation*0.98 - def nodeSlopes(self): - for p in self.atlas: - p.getSlope() - def rainfall(self): - for p in self.atlas: - rainfall = 0.4*random.uniform(0.95,1.05) - rainfall = ((rainfall*(0.4-(p.temp+p.elevation)))+rainfall)/2 - q = p - distance = self.n/256 - diff = 0 - for i in range(math.floor(distance)): - q = p.westMostNeighbor() - diff += q.elevation-p.elevation - diff = diff/distance - if diff > 0.2 and 0: - rainfall = clamp(rainfall*(0.5-diff),0,1) - if p.river != None: - rainfall = rainfall*1.5 - for l in p.neighbors: - if l.elevation < self.sealevel: - rainfall *= 1.25 - p.rainfall = clamp(rainfall,0,1) - def temperature(self): - for p in self.atlas: - latTemp = 0.375*(p.dist(self.north)/self.xDim) - elevationTemp = 0.575*(1-p.elevation) - p.temp = clamp(elevationTemp+latTemp,0,1) - def soilProperties(self): - for p in self.atlas: - p.metallicity = 0.5*p.elevation*random.uniform(0.666,1.5) - if p.river != None: - p.metallicity *= 1.25 - p.metallicity = clamp(p.metallicity,0,1) - fertilityBase = abs(p.elevation-(self.sealevel*1.2))*random.uniform(0.8,1.25) - if fertilityBase == 0: - p.fertility = 1 - else: - p.fertility = 0.07/fertilityBase - if p.river != None: - p.fertility *= 2 - p.fertility = clamp(p.fertility,0,1) - def vegetation(self): - self.temperature() - self.soilProperties() - self.rainfall() - for p in self.atlas: - p.setVegetation() - def setBiomes(self): - self.nodeSlopes() - self.vegetation() - for p in self.atlas: - p.setBiome(self.sealevel) - p.biomeColor = self.biomeColors[p.biome] - slope = clamp(p.realSlope()*(2**16),-48,48) - shade = math.floor((-16)+p.biomeColor[2]+slope+(((p.elevation+1)**2)*16)) - p.biomeColor = (p.biomeColor[0],p.biomeColor[1],shade) - def setWildlife(self): - for p in self.atlas: - p.herbivores = clamp((p.vegetation*random.uniform(0.8,1.25))**2,0,1) - p.carnivores = clamp(((p.herbivores*1.5)**2),0,1) - def biomeColors(self): - bColors = {} - bColors["desert"] = (16,64,142) - bColors["savanna"] = (48,64,136) - bColors["tundra"] = (128,48,142) - bColors["shrubland"] = (72,96,128) - bColors["boreal"] = (110,86,108) - bColors["forest"] = (96,128,128) - bColors["tropical"] = (78,162,96) - bColors["frozen"] = (134,32,206) - bColors["mountain"] = (134,0,136) - bColors["water"] = (142,64,64) - self.biomeColors = bColors - def values(self): - self.valuesOutputs = {"travelers":0, - "craftsmen":0, - "traders":0, - "superstitious":0, - "metallurgists":0, - "worshippers":0, - "freedom":0, - "shamans":0, - "astrologists":0, - "materialists":0, - "agriculture":0, - "collectivists":0, - "builders":0, - "naturalists":0, - "simplicity":0, - "greed":0, - "sailors":0, - "warriors":0} - self.values = {} - self.values["swimming"] = {"simplicity":0.2, - "sailors":0.8, - "astrologists":0.15, - "freedom":0.35, - "warriors":0.05} - self.values["food"] = {"agriculture":-0.1, - "greed":-0.25, - "materialists":0.55, - "collectivists":0.25, - "simplicity":0.45, - "worshippers":-0.2, - "superstitious":-0.2, - "traders":0.2, - "warriors":0.2, - "builders":0.25} - self.values["darkness"] = {"travelers":0.2, - "collectivists":-0.4, - "superstitious":0.7, - "greed":0.4, - "astrologists":0.25, - "materialists":-0.15, - "shamans":0.15, - "freedom":-0.2, - "warriors":0.5} - self.values["movement"] = {"travelers":0.8, - "sailors":0.2, - "traders":0.55, - "astrologists":0.15, - "builders":-0.4, - "simplicity":0.4, - "materialists":-0.15, - "freedom":0.85, - "naturalists":0.2, - "collectivists":-0.2, - "warriors":0.2} - self.values["plantlife"] = {"agriculture":0.25, - "greed":0.15, - "naturalists":0.25, - "shamans":0.05, - "craftsmen":0.4, - "traders":0.15, - "builders":0.4, - "simplicity":-0.25, - "collectivists":0.15} - self.values["nature"] = {"naturalists":0.35, - "shamans":0.2, - "agriculture":0.25, - "freedom":0.25, - "travelers":0.15, - "simplicity":0.5, - "collectivists":0.15, - "superstitious":0.3, - "astrologists":0.1, - "metallurgists":0.4, - "warriors":0.05} - self.values["growth"] = {"shamans":0.1, - "agriculture":0.55, - "naturalists":0.45, - "metallurgists":-0.2, - "freedom":0.15, - "astrologists":-0.3, - "collectivists":0.35, - "materialists":-0.1, - "warriors":0.05, - "builders":0.15} - self.values["sky"] = {"travelers":0.55, - "craftsmen":0.3, - "traders":0.25, - "superstitious":0.5, - "metallurgists":0.1, - "worshippers":0.5, - "freedom":0.55, - "shamans":0.1, - "astrologists":0.7, - "simplicity":0.4, - "builders":0.1} - self.values["earth"] = {"metallurgists":1.2, - "craftsmen":0.9, - "traders":0.45, - "materialists":0.75, - "agriculture":0.25, - "collectivists":0.4, - "builders":0.6, - "freedom":-0.25, - "worshippers":-0.15, - "greed":0.6, - "superstitious":-0.25, - "warriors":0.05, - "astrologists":-0.2} - self.values["fields"] = {"agriculture":0.75, - "builders":0.3, - "materialists":0.5, - "naturalists":0.35, - "superstitious":-0.3, - "simplicity":-0.3, - "collectivists":0.25, - "warriors":0.05} - self.values["sunlight"] = {"worshippers":0.9, - "astrologists":0.6, - "naturalists":0.15, - "travelers":0.25, - "simplicity":0.75, - "freedom":0.6, - "materialists":-0.2, - "warriors":0.3, - "builders":-0.1} - self.values["ice"] = {"superstitious":0.3, - "simplicity":-0.4, - "freedom":-0.4, - "travelers":-0.45, - "materialists":0.4, - "sailors":0.1, - "shamans":0.45, - "greed":0.7, - "metallurgists":0.2, - "warriors":0.4, - "agriculture":-0.2} - self.values["fear"] = {"superstitious":0.85, - "worshippers":0.3, - "shamans":0.8, - "freedom":-0.55, - "collectivists":0.4, - "simplicity":-0.3, - "builders":0.25, - "greed":0.8, - "materialists":-0.25, - "warriors":0.65} - self.values["water"] = {"sailors":2, - "simplicity":0.3, - "freedom":0.75, - "travelers":0.6, - "builders":0.15, - "craftsmen":0.1, - "astrologists":0.45, - "traders":0.75, - "warriors":0.1} - def influences(self): - self.influenceOutputs = {"sky":0, - "sunlight":0, - "fields":0, - "earth":0, - "ice":0, - "fear":0, - "water":0, - "growth":0, - "nature":0, - "plantlife":0, - "movement":0, - "darkness":0, - "swimming":0, - "food":0} - self.influences = {} - self.influences["elevation"] = {"sky":0.3, - "sunlight":0.15, - "fields":0.1, - "earth":0.1, - "ice":0.1, - "fear":0.05} - self.influences["slope"] = {"sky":0.1, - "sunlight":0.1, - "earth":0.1, - "fear":0.05} - self.influences["temperature"] = {"sunlight":0.2, - "darkness":-0.1, - "sky":0.1, - "movement":0.2, - "fear":0.15, - "water":0.05, - "food":0.1} - self.influences["vegetation"] = {"nature":0.15, - "food":0.15, - "darkness":0.05, - "plantlife":0.2, - "sunlight":-0.1, - "fields":-0.1} - self.influences["rainfall"] = {"water":0.5, - "growth":0.2, - "nature":0.1, - "sky":0.25, - "movement":0.1, - "darkness":0.1, - "plantlife":0.1, - "swimming":0.15} - self.influences["vegetation"] = {"plantlife":0.6, - "growth":0.15, - "nature":0.35, - "earth":0.2, - "fields":0.15} - self.influences["fertility"] = {"plantlife":0.35, - "growth":0.25, - "nature":0.15, - "earth":0.3, - "fields":0.25} - self.influences["metallicity"] = {"earth":0.7, - "darkness":0.15, - "nature":0.1} - self.influences["carnivores"] = {"darkness":0.35, - "food":0.25, - "fear":0.4, - "nature":0.15} - self.influences["herbivores"] = {"nature":0.35, - "food":0.65, - "growth":0.3, - "earth":0.1, - "plantlife":-0.1} - self.influences["river"] = {"water":2.5, - "swimming":1.0, - "nature":0.1, - "food":0.35, - "growth":0.1, - "movement":0.3} - self.influences["water"] = {"water":4.0, - "swimming":1.5, - "sky":0.15, - "fear":0.2, - "darkness":0.35, - "food":0.25, - "movement":0.15, - "ice":0.1} - self.influences["forest"] = {"plantlife":0.6, - "darkness":0.25, - "nature":0.4, - "growth":0.2, - "earth":0.1, - "food":0.3, - "fields":0.1} - self.influences["desert"] = {"earth":0.4, - "sky":0.3, - "sunlight":0.7, - "food":-0.3, - "fields":0.35} - self.influences["shrubland"] = {"food":0.25, - "fields":0.4, - "sky":0.35, - "sunlight":0.2, - "earth":0.25, - "nature":0.1} - self.influences["savanna"] = {"food":0.2, - "fields":0.6, - "sky":0.4, - "sunlight":0.3, - "nature":0.2, - "earth":0.1} - self.influences["tundra"] = {"food":-0.35, - "fields":0.35, - "fear":0.2, - "darkness":0.4, - "sky":0.2, - "growth":-0.1, - "earth":0.3, - "ice":0.5} - self.influences["mountain"] = {"food":-0.3, - "fields":0.1, - "sky":0.7, - "sunlight":0.35, - "earth":0.45, - "fear":0.2, - "nature":0.15, - "ice":0.5} - self.influences["tropical"] = {"food":0.35, - "fear":0.4, - "darkness":0.35, - "fields":-0.25, - "nature":0.5, - "plantlife":0.6, - "growth":0.45, - "earth":0.1} - self.influences["boreal"] = {"food":0.1, - "nature":0.25, - "growth":0.1, - "fields":0.2, - "darkness":0.2, - "plantlife":0.25, - "earth":0.15, - "ice":0.15} - self.influences["frozen"] = {"ice":0.75, - "earth":0.2, - "food":-0.3, - "plantlife":-0.2, - "fear":0.2, - "darkness":0.2, - "fields":0.15, - "water":0.1} - def placeCity(self,xx,yy,pop=50,culture=None,node=None): - if node != None: - cityNode = self.nearestNode(xx,yy) - else: - cityNode = node - if cityNode.biome != "water" and cityNode.city == None: - newCity = City(cityNode,pop,culture,self) - return 1 - else: - return -1 - def randomCity(self): - cityNode = self.atlas[math.floor(random.random()*len(self.atlas))] - while (cityNode.biome == "water" or cityNode.city != None or - cityNode.x < 32 or cityNode.x > self.xDim-32 or cityNode.y < 32 or cityNode.y > self.yDim-32): - if len(self.cities) >= 1: - cityNode = self.atlas[math.floor(random.random()*len(self.atlas))] - while cityNode.dist(self.nearestCity(cityNode.x,cityNode.y).node) < 32: - cityNode = self.atlas[math.floor(random.random()*len(self.atlas))] - else: - cityNode = self.atlas[math.floor(random.random()*len(self.atlas))] - newCity = City(cityNode,pop=random.randint(12,136),m=self) - def scatterCities(self,n): - for i in range(n): - self.randomCity() - def drawGraph(self,gui=None): - visualAtlas = Image.new("HSV",(mapDimX,mapDimY),"white") - graphDraw = ImageDraw.Draw(visualAtlas) - for tri in self.triangles: - tri.drawGraph(graphDraw) - for n in self.atlas: - n.drawPoint(graphDraw,1,"red") - visualAtlas.show() - def drawElevation(self,pts=0,sea=1,gui=None): - visualAtlas = Image.new("HSV",(mapDimX,mapDimY),"white") - graphDraw = ImageDraw.Draw(visualAtlas) - for tri in self.triangles: - tri.drawElevation(graphDraw,self.sealevel*sea) - for n in self.atlas: - n.drawElevation(graphDraw) - for l in self.landmasses: - for r in l.rivers: - r.drawRiver(graphDraw) - visualAtlas.show() - def drawLandmass(self,pts=0,nbrs=0,gui=None): - visualAtlas = Image.new("HSV",(mapDimX,mapDimY),"white") - graphDraw = ImageDraw.Draw(visualAtlas) - for tri in self.triangles: - tri.drawLandmass(graphDraw,self.sealevel) - for n in self.atlas: - n.drawLandmass(graphDraw,pts,nbrs) - for l in self.landmasses: - for r in l.rivers: - r.drawPath(graphDraw) - visualAtlas.show() - def drawWildlife(self,gui=None): - visualAtlas = Image.new("HSV",(mapDimX,mapDimY),"white") - graphDraw = ImageDraw.Draw(visualAtlas) - for tri in self.triangles: - tri.drawWildlife(graphDraw,self.sealevel) - for l in self.landmasses: - for r in l.rivers: - r.drawRiver(graphDraw,self.xDim) - visualAtlas.show() - def displayNode(self,event): - clickedNode = self.nearestNode(event.x,event.y) - self.displayString.set(self.nodeInfo(clickedNode)) - self.displayNo = clickedNode - cityNode = self.nearestCity(event.x,event.y) - if cityNode.node.dist(Node(event.x,event.y)) < 8: - self.displayString.set(self.nodeInfo(cityNode.node)) - self.displayNo = cityNode.node - def redraw(self): - visualAtlas = Image.new("HSV",(mapDimX,mapDimY),"white") - graphDraw = ImageDraw.Draw(visualAtlas) - for tri in self.triangles: - tri.drawReal(graphDraw,self.sealevel) - for n in self.atlas: - n.drawReal(graphDraw,self.sealevel) - for l in self.landmasses: - for r in l.rivers: - r.drawRiver(graphDraw,self.xDim) - for c in self.cities: - c.drawSelf(graphDraw) - visualAtlas = visualAtlas.convert("RGB") - visualAtlas.save(self.mapname,"GIF") - photo = Image.open(self.mapname) - self.img = ImageTk.PhotoImage(photo) - self.lbl.configure(image = self.img) - self.lbl.image = self.img - if self.displayNo != None: - self.displayString.set(self.nodeInfo(self.displayNo)) - def updateResources(self): - for r in self.resourceRegions: - r.updateReg() - def updateTerritory(self): - for c in self.cities: - c.node.culture = c.culture - c.node.allegiance = 1/c.population - for p in self.atlas: - p.updateAllegiance() - for c in self.cities: - c.node.culture = c.culture - c.node.allegiance = 1/c.population - def updateDemogs(self): - for c in self.cities: - c.updateDemog() - def nextTurn(self): - self.updateResources() - self.updateDemogs() - self.updateTerritory() - self.redraw() - def cultureInfo(self): - if self.displayNo == None: - return -1 - if self.displayNo.culture == None: - return -1 - self.displayCulture = self.displayNo.culture - if self.infoGui != None: - self.infoGui.destroy() - self.infoGui = Toplevel() - photo = Image.open(self.displayCulture.flag.filename) - self.flagImg = ImageTk.PhotoImage(photo) - self.flagLbl = Label(self.infoGui,image=self.flagImg) - self.flagLbl.config(borderwidth=32) - self.flagLbl.photo = photo - self.flagLbl.pack() - self.cultureString = StringVar() - self.cultureString.set(self.displayCulture.cultureNotes()) - cdsc = Label(self.infoGui,textvariable=self.cultureString) - cdsc.pack() - def drawReal(self,gui=None): - visualAtlas = Image.new("HSV",(mapDimX,mapDimY),"white") - graphDraw = ImageDraw.Draw(visualAtlas) - for tri in self.triangles: - tri.drawReal(graphDraw,self.sealevel) - for n in self.atlas: - n.drawReal(graphDraw,self.sealevel) - for l in self.landmasses: - for r in l.rivers: - r.drawRiver(graphDraw,self.xDim) - for c in self.cities: - c.drawSelf(graphDraw) - if gui == None: - visualAtlas = visualAtlas.convert("RGB") - visualAtlas.save("map00.png","PNG") - visualAtlas.show() - else: - self.gui = gui - self.displayString = StringVar() - self.displayString.set("No node selected") - self.infoScales() - desc = Label(gui,textvariable=self.displayString) - desc.pack(side=RIGHT,fill=Y) - visualAtlas = visualAtlas.convert("RGB") - self.mapname = self.cultures[0].language.genName() + ".gif" - visualAtlas.save(self.mapname,"GIF") - photo = Image.open(self.mapname) - self.img = ImageTk.PhotoImage(photo) - self.lbl = Label(gui,image=self.img) - self.lbl.pack() - self.lbl.bind("",self.displayNode) - b0 = Button(gui,text="Next Turn",command=self.nextTurn) - b0.pack(anchor=S,side=RIGHT) - c1 = "orange red" - b0.config(bg=c1,activebackground=c1,activeforeground=c1) - b1 = Button(gui,text="Society Info",command=self.cultureInfo) - b1.pack(anchor=S,side=RIGHT) - c1 = "medium aquamarine" - b1.config(bg=c1,activebackground=c1,activeforeground=c1) - self.nextTurn() - gui.mainloop() +from Node import Node +from Map import Map +from Triangle import Triangle +from GenConfiguration import * #----------------------------------------------------------------------# # Let's generate a map -numNodes = 2**14 -mapDimX = 960 -mapDimY = 960 atlas = [Node(-1,-1),Node(mapDimX+1,-1),Node(mapDimY+1,mapDimY+1),Node(-1,mapDimY+1)] world = Map(atlas,numNodes,mapDimX,mapDimY) @@ -2137,7 +34,6 @@ def drawReal(self,gui=None): npFloatAtlas[q] = [nodeX,nodeY] print("Triangulating...") -from scipy.spatial import Delaunay triangulation = Delaunay(npFloatAtlas) trisList = triangulation.vertices diff --git a/noiseMaker.py b/noiseMaker.py new file mode 100644 index 0000000..8edc357 --- /dev/null +++ b/noiseMaker.py @@ -0,0 +1,30 @@ +from Tools import * + + +class noiseMaker: + def __init__(self,w,h): + self.noise = np.random.rand(w,h) + self.width = w + self.height = h + + def smoothNoise(self,xx,yy): + fracX = xx % 1 + fracY = yy % 1 + x1 = (math.floor(xx)+self.width) % self.width + y1 = (math.floor(yy)+self.height) % self.height + x2 = (x1+self.width-1) % self.width + y2 = (y1+self.height-1) % self.height + tileVal = 0 + tileVal += fracX*fracY*self.noise[x1,y1] + tileVal += (1-fracX)*fracY*self.noise[x2,y1] + tileVal += fracX*(1-fracY)*self.noise[x1,y2] + tileVal += (1-fracX)*(1-fracY)*self.noise[x2,y2] + return tileVal + + def turbulence(self,xx,yy,size): + tileVal = 0 + initialSize = size + while size >= 1: + tileVal += self.smoothNoise(xx/size,yy/size)*size + size = size/2 + return tileVal/(initialSize*2)