Adding Town Generator

This commit is contained in:
howlingsails 2022-01-03 19:48:50 -08:00
parent 74fb543101
commit 44be43d1cf
6 changed files with 102 additions and 47 deletions

View file

@ -2042,7 +2042,11 @@
<iframe id="mfcgPreview" sandbox="allow-scripts allow-same-origin"></iframe> <iframe id="mfcgPreview" sandbox="allow-scripts allow-same-origin"></iframe>
</div> </div>
</div> </div>
<div id="burgDetails">
<a id="burgGenerator" target="_blank" >
City Details by dnd5e-town-generator
</a>
<hr>
<div id="burgBottom"> <div id="burgBottom">
<button id="burgGroupShow" data-tip="Show group change section" class="icon-tags"></button> <button id="burgGroupShow" data-tip="Show group change section" class="icon-tags"></button>
<div id="burgGroupSection" style="display: none"> <div id="burgGroupSection" style="display: none">

View file

@ -405,7 +405,7 @@ window.BurgsAndStates = (function () {
if (type === "Lake" && f.type === "lake") return 10; // low lake crossing penalty for Lake cultures if (type === "Lake" && f.type === "lake") return 10; // low lake crossing penalty for Lake cultures
if (type === "Naval" && h < 20) return 300; // low sea crossing penalty for Navals if (type === "Naval" && h < 20) return 300; // low sea crossing penalty for Navals
if (type === "Nomadic" && h < 20) return 10000; // giant sea crossing penalty for Nomads if (type === "Nomadic" && h < 20) return 10000; // giant sea crossing penalty for Nomads
if (h < 20) return 1000; // general sea crossing penalty if (h < 20) return 5000; // general sea crossing penalty
if (type === "Highland" && h < 62) return 1100; // penalty for highlanders on lowlands if (type === "Highland" && h < 62) return 1100; // penalty for highlanders on lowlands
if (type === "Highland") return 0; // no penalty for highlanders on highlands if (type === "Highland") return 0; // no penalty for highlanders on highlands
if (h >= 67) return 2200; // general mountains crossing penalty if (h >= 67) return 2200; // general mountains crossing penalty

View file

@ -186,31 +186,31 @@ window.Cultures = (function () {
if (culturesSet.value === "darkFantasy") { if (culturesSet.value === "darkFantasy") {
return [ return [
{name: "Castien (Elvish)", base: 33, odd: 1, sort: i => (normalizedCellScore(i) / biomeGoals(i, [6, 7, 8, 9], 10)) * temperature[i], shield: "gondor"}, // Elves {name: "Castien (Elf)", base: 33, odd: 1, sort: i => (normalizedCellScore(i) / biomeGoals(i, [6, 7, 8, 9], 10)) * temperature[i], shield: "gondor"}, // Elves
{name: "Hagluin (Elvish)", base: 33, odd: 1, sort: i => (normalizedCellScore(i) / biomeGoals(i, [6, 7, 8, 9], 10)) * temperature[i], shield: "noldor"}, // Elves {name: "Hagluin (Elf)", base: 33, odd: 0.5, sort: i => (normalizedCellScore(i) / biomeGoals(i, [6, 7, 8, 9], 10)) * temperature[i], shield: "noldor"}, // Elves
{name: "Lothian (Elvish)", base: 33, odd: 1, sort: i => (normalizedCellScore(i) / biomeGoals(i, [7, 8, 9, 12], 10)) * temperature[i], shield: "wedged"}, {name: "Lothian (Elf)", base: 33, odd: 1, sort: i => (normalizedCellScore(i) / biomeGoals(i, [7, 8, 9, 12], 10)) * temperature[i], shield: "wedged"},
{name: "Dunirr (Dwarven)", base: 35, odd: 1, sort: i => normalizedCellScore(i) + (height[i]*3), shield: "ironHills"}, // Dwarfs {name: "Dunirr (Dwarf)", base: 35, odd: 0.7, sort: i => normalizedCellScore(i) + (height[i]*3), shield: "ironHills"}, // Dwarfs
{name: "Khazadur (Dwarven)", base: 35, odd: 1, sort: i => normalizedCellScore(i) + (height[i]*4), shield: "erebor"}, // Dwarfs {name: "Khazadur (Dwarf)", base: 35, odd: 1, sort: i => normalizedCellScore(i) + (height[i]*4), shield: "erebor"}, // Dwarfs
{name: "Dhommeam (Dwarven)", base: 35, odd: 1, sort: i => normalizedCellScore(i) + height[i], shield: "erebor"}, // Dwarfs {name: "Dhommeam (Dwarf)", base: 35, odd: 0.2, sort: i => normalizedCellScore(i) + height[i], shield: "erebor"}, // Dwarfs
{name: "Mudtoe (Dwarven)", base: 35, odd: 1, sort: i => normalizedCellScore(i) + height[i], shield: "erebor"}, // Dwarfs {name: "Mudtoe (Dwarf)", base: 35, odd: 1, sort: i => normalizedCellScore(i) + height[i], shield: "erebor"}, // Dwarfs
{name: "Gongrem (Dwarven)", base: 35, odd: 1, sort: i => normalizedCellScore(i) + height[i], shield: "erebor"}, // Dwarfs {name: "Gongrem (Dwarf)", base: 35, odd: 1, sort: i => normalizedCellScore(i) + height[i], shield: "erebor"}, // Dwarfs
{name: "Pabolk (Goblin)", base: 36, odd: 1, sort: i => temperature[i] - s[i], shield: "moriaOrc"}, // Goblin {name: "Pabolk (Goblin)", base: 36, odd: 2, sort: i => temperature[i] - s[i], shield: "moriaOrc"}, // Goblin
{name: "Lagakh (Orkish)", base: 37, odd: 1, sort: i => (height[i] * normalizedCellScore(i) * biomeGoals(i,[2,3,4], 100)), shield: "urukHai"}, // Orc {name: "Lagakh (Orc)", base: 37, odd: 1, sort: i => (height[i] * normalizedCellScore(i) * biomeGoals(i,[2,3,4], 200)), shield: "urukHai"}, // Orc
{name: "Mogak (Orkish)", base: 37, odd: 1, sort: i => (height[i]* normalizedCellScore(i) * biomeGoals(i,[2,3,4], 100)), shield: "urukHai"}, // Orc {name: "Mogak (Orc)", base: 37, odd: 1, sort: i => (height[i]* normalizedCellScore(i) * biomeGoals(i,[2,3,4], 300)), shield: "urukHai"}, // Orc
{name: "Xugarf (Orkish)", base: 37, odd: 1, sort: i => ((height[i] * normalizedCellScore(i) * temperature[i]) / biomeGoals(i, [2, 10, 11],100)), shield: "moriaOrc"}, // Orc {name: "Xugarf (Orc)", base: 37, odd: 1, sort: i => ((height[i] * normalizedCellScore(i) * temperature[i]) / biomeGoals(i, [2, 10, 11],300)), shield: "moriaOrc"}, // Orc
{name: "Zildud (Orkish)", base: 37, odd: 1, sort: i => ((height[i]* normalizedCellScore(i) * temperature[i]) / biomeGoals(i, [2, 10, 11],100)), shield: "moriaOrc"}, // Orc {name: "Zildud (Orc)", base: 37, odd: 1, sort: i => ((height[i]* normalizedCellScore(i) * temperature[i]) / biomeGoals(i, [2, 10, 11],300)), shield: "moriaOrc"}, // Orc
{name: "Shazgob (Orkish)", base: 37, odd: 1, sort: i => ((height[i]* normalizedCellScore(i) * temperature[i]) / biomeGoals(i, [2, 10, 11],100)), shield: "moriaOrc"}, // Orc {name: "Shazgob (Orc)", base: 37, odd: 1, sort: i => ((height[i]* normalizedCellScore(i) * temperature[i]) / biomeGoals(i, [2, 10, 11],300)), shield: "moriaOrc"}, // Orc
{name: "Mazoga (Orkish)", base: 37, odd: 1, sort: i => ((height[i] * temperature[i]) / biomeGoals(i, [2, 10, 11],100)), shield: "moriaOrc"}, // Orc {name: "Mazoga (Orc)", base: 37, odd: 1, sort: i => ((height[i] * temperature[i]) / biomeGoals(i, [2, 10, 11],100)), shield: "moriaOrc"}, // Orc
{name: "Gul (Orkish)", base: 37, odd: 1, sort: i => ((height[i] * temperature[i]) / biomeGoals(i, [2, 10, 11],100)), shield: "moriaOrc"}, // Orc {name: "Gul (Orc)", base: 37, odd: 1, sort: i => ((height[i] * temperature[i]) / biomeGoals(i, [2, 10, 11],100)), shield: "moriaOrc"}, // Orc
{name: "Goren (Elvish}", base: 33, odd: 0.5, sort: i => (normalizedCellScore(i) / biomeGoals(i, [6, 7, 8, 9], 10)) * temperature[i], shield: "fantasy5"}, // Elves {name: "Goren (Elf}", base: 33, odd: 0.5, sort: i => (normalizedCellScore(i) / biomeGoals(i, [6, 7, 8, 9], 50)) * temperature[i], shield: "fantasy5"}, // Elves
{name: "Ginikirr {Dwarven)", base: 35, odd: 0.8, sort: i => normalizedCellScore(i) + height[i], shield: "erebor"}, // Dwarven {name: "Ginikirr (Dwarf)", base: 35, odd: 0.8, sort: i => normalizedCellScore(i) + height[i], shield: "erebor"}, // Dwarf
{name: "Heenzurm (Goblin)", base: 36, odd: 1.8, sort: i => temperature[i] - s[i], shield: "moriaOrc"}, // Goblin {name: "Heenzurm (Goblin)", base: 36, odd: 1.8, sort: i => temperature[i] - s[i], shield: "moriaOrc"}, // Goblin
{name: "Yotunn (Giant)", base: 38, odd: 1.2, sort: i => tempDiff(i, -5), shield: "pavise"}, // Giant {name: "Yotunn (Giant)", base: 38, odd: 1.2, sort: i => tempDiff(i, -5), shield: "pavise"}, // Giant
{name: "Zakaos (Giant)", base: 38, odd: 1.0, sort: i => tempDiff(i, -5), shield: "pavise"}, // Giant {name: "Zakaos (Giant)", base: 38, odd: 1.0, sort: i => tempDiff(i, -5), shield: "pavise"}, // Giant
{name: "Fruthos (Human)", base: 32, odd: 1, sort: i => normalizedCellScore(i) / tempDiff(i, 10), shield: "fantasy5"}, {name: "Fruthos (Human)", base: 32, odd: 1, sort: i => normalizedCellScore(i) / tempDiff(i, 10), shield: "fantasy5"},
{name: "Rulor (Human)", base: 32, odd: 1, sort: i => normalizedCellScore(i) / tempDiff(i, 13), shield: "roman"}, {name: "Rulor (Human)", base: 32, odd: 0.5, sort: i => normalizedCellScore(i) / tempDiff(i, 13), shield: "roman"},
{name: "Gralcek (Human)", base: 16, odd: 1, sort: i => normalizedCellScore(i) / tempDiff(i, 16), shield: "round"}, {name: "Gralcek (Human)", base: 16, odd: 0.3, sort: i => normalizedCellScore(i) / tempDiff(i, 16), shield: "round"},
{name: "Llekkolk (Human)", base: 31, odd: 1, sort: i => (normalizedCellScore(i) / tempDiff(i, 5) / biomeGoals(i, [2, 4, 10], 7)) * temperature[i], shield: "easterling"} {name: "Llekkolk (Human)", base: 31, odd: 0.2, sort: i => (normalizedCellScore(i) / tempDiff(i, 5) / biomeGoals(i, [2, 4, 10], 7)) * temperature[i], shield: "easterling"}
]; ];
@ -303,11 +303,11 @@ window.Cultures = (function () {
{name: "Eldar (Elfish)", base: 33, odd: 1, sort: i => (normalizedCellScore(i) / biomeGoals(i, [6, 7, 8, 9], 10)) * temperature[i], shield: "noldor"}, // Elves {name: "Eldar (Elfish)", base: 33, odd: 1, sort: i => (normalizedCellScore(i) / biomeGoals(i, [6, 7, 8, 9], 10)) * temperature[i], shield: "noldor"}, // Elves
{name: "Trow (Dark Elfish)", base: 34, odd: 0.9, sort: i => (normalizedCellScore(i) / biomeGoals(i, [7, 8, 9, 12], 10)) * temperature[i], shield: "hessen"}, // Dark Elves {name: "Trow (Dark Elfish)", base: 34, odd: 0.9, sort: i => (normalizedCellScore(i) / biomeGoals(i, [7, 8, 9, 12], 10)) * temperature[i], shield: "hessen"}, // Dark Elves
{name: "Lothian (Dark Elfish)", base: 34, odd: 0.3, sort: i => (normalizedCellScore(i) / biomeGoals(i, [7, 8, 9, 12], 10)) * temperature[i], shield: "wedged"}, // Dark Elves {name: "Lothian (Dark Elfish)", base: 34, odd: 0.3, sort: i => (normalizedCellScore(i) / biomeGoals(i, [7, 8, 9, 12], 10)) * temperature[i], shield: "wedged"}, // Dark Elves
{name: "Dunirr (Dwarven)", base: 35, odd: 1, sort: i => normalizedCellScore(i) + height[i], shield: "ironHills"}, // Dwarfs {name: "Dunirr (Dwarf)", base: 35, odd: 1, sort: i => normalizedCellScore(i) + height[i], shield: "ironHills"}, // Dwarfs
{name: "Khazadur (Dwarven)", base: 35, odd: 1, sort: i => normalizedCellScore(i) + height[i], shield: "erebor"}, // Dwarfs {name: "Khazadur (Dwarf)", base: 35, odd: 1, sort: i => normalizedCellScore(i) + height[i], shield: "erebor"}, // Dwarfs
{name: "Kobold (Goblin)", base: 36, odd: 1, sort: i => temperature[i] - s[i], shield: "moriaOrc"}, // Goblin {name: "Kobold (Goblin)", base: 36, odd: 1, sort: i => temperature[i] - s[i], shield: "moriaOrc"}, // Goblin
{name: "Uruk (Orkish)", base: 37, odd: 1, sort: i => height[i] * temperature[i], shield: "urukHai"}, // Orc {name: "Uruk (Orc)", base: 37, odd: 1, sort: i => height[i] * temperature[i], shield: "urukHai"}, // Orc
{name: "Ugluk (Orkish)", base: 37, odd: 1.5, sort: i => (height[i] * temperature[i]) / biomeGoals(i, [1, 2, 10, 11]), shield: "moriaOrc"}, // Orc {name: "Ugluk (Orc)", base: 37, odd: 1.5, sort: i => (height[i] * temperature[i]) / biomeGoals(i, [1, 2, 10, 11]), shield: "moriaOrc"}, // Orc
{name: "Yotunn (Giants)", base: 38, odd: 0.7, sort: i => tempDiff(i, -10), shield: "pavise"}, // Giant {name: "Yotunn (Giants)", base: 38, odd: 0.7, sort: i => tempDiff(i, -10), shield: "pavise"}, // Giant
{name: "Rake (Drakonic)", base: 39, odd: 0.7, sort: i => -s[i], shield: "fantasy2"}, // Draconic {name: "Rake (Drakonic)", base: 39, odd: 0.7, sort: i => -s[i], shield: "fantasy2"}, // Draconic
{name: "Arago (Arachnid)", base: 40, odd: 0.7, sort: i => temperature[i] - s[i], shield: "horsehead2"}, // Arachnid {name: "Arago (Arachnid)", base: 40, odd: 0.7, sort: i => temperature[i] - s[i], shield: "horsehead2"}, // Arachnid

View file

@ -9,27 +9,30 @@ window.Markers = (function () {
const isFantasy = culturesSet.includes("Fantasy"); const isFantasy = culturesSet.includes("Fantasy");
return [ return [
{type: "volcanoes", icon: "🌋", multiplier: 1, fn: addVolcanoes}, {type: "volcanoes", icon: "🌋", multiplier: 10, fn: addVolcanoes},
{type: "hot-springs", icon: "♨️", multiplier: 1, fn: addHotSprings}, {type: "hot-springs", icon: "♨️", multiplier: 20, fn: addHotSprings},
{type: "mines", icon: "⛏️", multiplier: 1, fn: addMines}, {type: "mines", icon: "⛏️", multiplier: 5, fn: addMines},
{type: "bridges", icon: "🌉", multiplier: 1, fn: addBridges}, {type: "bridges", icon: "🌉", multiplier: 10, fn: addBridges},
{type: "inns", icon: "🍻", multiplier: 1, fn: addInns}, {type: "inns", icon: "🍻", multiplier: 20, fn: addInns},
{type: "lighthouses", icon: "🚨", multiplier: 1, fn: addLighthouses}, {type: "lighthouses", icon: "🚨", multiplier: 1, fn: addLighthouses},
{type: "waterfalls", icon: "⟱", multiplier: 1, fn: addWaterfalls}, {type: "waterfalls", icon: "⟱", multiplier: 3, fn: addWaterfalls},
{type: "battlefields", icon: "⚔️", multiplier: 1, fn: addBattlefields}, {type: "battlefields", icon: "⚔️", multiplier: 1, fn: addBattlefields},
{type: "dungeons", icon: "🗝️", multiplier: 1, fn: addDungeons}, {type: "dungeons", icon: "🗝️", multiplier: 20, fn: addDungeons},
{type: "lake-monsters", icon: "🐉", multiplier: 1, fn: addLakeMonsters}, {type: "lake-monsters", icon: "🐉", multiplier: 2, fn: addLakeMonsters},
{type: "sea-monsters", icon: "🦑", multiplier: 1, fn: addSeaMonsters}, {type: "sea-monsters", icon: "🦑", multiplier: 5, fn: addSeaMonsters},
{type: "hill-monsters", icon: "👹", multiplier: 1, fn: addHillMonsters}, {type: "hill-monsters", icon: "👹", multiplier: 20, fn: addHillMonsters},
{type: "sacred-mountains", icon: "🗻", multiplier: 1, fn: addSacredMountains}, {type: "sacred-mountains", icon: "🗻", multiplier: 5, fn: addSacredMountains},
{type: "sacred-forests", icon: "🌳", multiplier: 1, fn: addSacredForests}, {type: "sacred-forests", icon: "🌳", multiplier: 10, fn: addSacredForests},
{type: "sacred-pineries", icon: "🌲", multiplier: 1, fn: addSacredPineries}, {type: "sacred-pineries", icon: "🌲", multiplier: 20, fn: addSacredPineries},
{type: "sacred-palm-groves", icon: "🌴", multiplier: 1, fn: addSacredPalmGroves}, {type: "sacred-palm-groves", icon: "🌴", multiplier: 1, fn: addSacredPalmGroves},
{type: "brigands", icon: "💰", multiplier: 1, fn: addBrigands}, {type: "brigands", icon: "💰", multiplier: 5, fn: addBrigands},
{type: "pirates", icon: "🏴‍☠️", multiplier: 1, fn: addPirates}, {type: "pirates", icon: "🏴‍☠️", multiplier: 3, fn: addPirates},
{type: "statues", icon: "🗿", multiplier: 1, fn: addStatues}, {type: "statues", icon: "🗿", multiplier: 5, fn: addStatues},
{type: "ruines", icon: "🏺", multiplier: 1, fn: addRuines}, {type: "ruines", icon: "🏺", multiplier: 10, fn: addRuines},
{type: "portals", icon: "🌀", multiplier: +isFantasy, fn: addPortals} {type: "spiders", icon: "🕷️", multiplier: 25, fn: addHillMonsters},
{type: "giant goat heard", icon: "🐐", multiplier: 25, fn: addHillMonsters},
{type: "citadel", icon: "🏯", multiplier: 25, fn: addSacredPineries},
{type: "portals", icon: "🌀", multiplier: 4, fn: addPortals}
]; ];
} }
@ -434,7 +437,7 @@ window.Markers = (function () {
const [cell] = extractAnyElement(taverns); const [cell] = extractAnyElement(taverns);
const id = addMarker({cell, icon, type, px: 14}); const id = addMarker({cell, icon, type, px: 14});
const typeName = P(0.3) ? "inn" : "tavern"; const typeName = P(0.3) ? "inn" : "tavern";
const isAnimalThemed = P(0.7); const isAnimalThemed = P(0.85);
const animal = ra(animals); const animal = ra(animals);
const name = isAnimalThemed ? (P(0.6) ? ra(colors) + " " + animal : ra(adjectives) + " " + animal) : ra(adjectives) + " " + capitalize(type); const name = isAnimalThemed ? (P(0.6) ? ra(colors) + " " + animal : ra(adjectives) + " " + animal) : ra(adjectives) + " " + capitalize(type);
const meal = isAnimalThemed && P(0.3) ? animal : ra(courses); const meal = isAnimalThemed && P(0.3) ? animal : ra(courses);
@ -857,6 +860,7 @@ window.Markers = (function () {
} }
} }
function addPortals(type, icon, multiplier) { function addPortals(type, icon, multiplier) {
const {burgs} = pack; const {burgs} = pack;
let portals = burgs let portals = burgs

View file

@ -117,6 +117,7 @@ function getMapData() {
markers markers
].join("\r\n"); ].join("\r\n");
TIME && console.timeEnd("createMapData"); TIME && console.timeEnd("createMapData");
debugger
return mapData; return mapData;
} }
@ -126,6 +127,7 @@ function dowloadMap() {
closeDialogs("#alert"); closeDialogs("#alert");
const mapData = getMapData(); const mapData = getMapData();
debugger
const blob = new Blob([mapData], {type: "text/plain"}); const blob = new Blob([mapData], {type: "text/plain"});
const URL = window.URL.createObjectURL(blob); const URL = window.URL.createObjectURL(blob);
const link = document.createElement("a"); const link = document.createElement("a");

View file

@ -405,19 +405,64 @@ function editBurg(id) {
function updateMFCGFrame(burg) { function updateMFCGFrame(burg) {
const mfcgURL = getMFCGlink(burg); const mfcgURL = getMFCGlink(burg);
const burgGeneratorURL = getBurgLink(burg);
document.getElementById("mfcgPreview").setAttribute("src", mfcgURL); document.getElementById("mfcgPreview").setAttribute("src", mfcgURL);
document.getElementById("mfcgLink").setAttribute("href", mfcgURL); document.getElementById("mfcgLink").setAttribute("href", mfcgURL);
document.getElementById("burgGenerator").setAttribute("href", burgGeneratorURL);
debug
} }
function getBurgSeed(burg) { function getBurgSeed(burg) {
return burg.MFCG || Number(`${seed}${String(burg.i).padStart(4, 0)}`); return burg.MFCG || Number(`${seed}${String(burg.i).padStart(4, 0)}`);
} }
function getBurgLink(burg) {
const {cells} = pack;
let burgCulture = pack.cultures[burg.culture].name.split('(')[1].split(')')[0];
const {name, population, cell} = burg;
const burgSeed = getBurgSeed(burg);
const sizeRaw = 0.43 * Math.pow((population * populationRate) / urbanDensity, 0.385);
const size = minmax(Math.ceil(sizeRaw), 2, 40);
const people = rn(population * populationRate * urbanization);
const hub = +cells.road[cell] > 50;
const river = cells.r[cell] ? 1 : 0;
const coast = +burg.port;
const citadel = +burg.citadel;
const walls = +burg.walls;
const plaza = +burg.plaza;
const temple = +burg.temple;
const shanty = +burg.shanty;
const sea = coast && cells.haven[cell] ? getSeaDirections(cell) : "";
function getSeaDirections(i) {
const p1 = cells.p[i];
const p2 = cells.p[cells.haven[i]];
let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
if (deg < 0) deg += 360;
const norm = rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
return "&sea=" + norm;
}
let townSize = "tiny";
if (people > 200) townSize = 'small';
if (people > 900) townSize = 'medium';
if (people > 2_000) townSize = 'average';
if (people > 5_500) townSize = 'big';
const baseURL = "http://localhost:9090/town/";
const url = `${baseURL}${name}-${burgCulture}/${townSize}`;
return url;
}
function getMFCGlink(burg) { function getMFCGlink(burg) {
const {cells} = pack; const {cells} = pack;
const {name, population, cell} = burg; const {name, population, cell} = burg;
const burgSeed = getBurgSeed(burg); const burgSeed = getBurgSeed(burg);
const sizeRaw = 0.43 * Math.pow((population * populationRate) / urbanDensity, 0.385); const sizeRaw = 0.43 * Math.pow((population * populationRate) / urbanDensity, 0.385);
const size = minmax(Math.ceil(sizeRaw), 2, 40); const size = minmax(Math.ceil(sizeRaw), 2, 40)*3;
const people = rn(population * populationRate * urbanization); const people = rn(population * populationRate * urbanization);
const hub = +cells.road[cell] > 50; const hub = +cells.road[cell] > 50;