refactor(generation): cultures continue

This commit is contained in:
max 2022-07-28 21:08:49 +03:00
parent fa2b873bf8
commit 6d70458aaf
3 changed files with 50 additions and 20 deletions

View file

@ -40,6 +40,7 @@ export const generateCultures = function (
const cultureIds = new Uint16Array(cells.i.length); // cell cultures const cultureIds = new Uint16Array(cells.i.length); // cell cultures
const populatedCellIds = cells.i.filter(cellId => cells.pop[cellId] > 0); const populatedCellIds = cells.i.filter(cellId => cells.pop[cellId] > 0);
const maxSuitability = d3.max(cells.s)!;
const culturesNumber = getCulturesNumber(populatedCellIds.length); const culturesNumber = getCulturesNumber(populatedCellIds.length);
if (!culturesNumber) return {cultureIds, cultures: [wildlands]}; if (!culturesNumber) return {cultureIds, cultures: [wildlands]};
@ -117,12 +118,12 @@ export const generateCultures = function (
function selectCulturesData(culturesNumber: number) { function selectCulturesData(culturesNumber: number) {
let defaultCultures = getDefault(culturesNumber); let defaultCultures = getDefault(culturesNumber);
if (defaultCultures.length >= culturesNumber) return defaultCultures; if (defaultCultures.length <= culturesNumber) return defaultCultures;
const culturesAvailable = Math.min(culturesNumber, defaultCultures.length);
const cultures = []; const cultures = [];
const MAX_ITERATIONS = 200;
for (let culture, rnd, i = 0; cultures.length < culturesAvailable && i < 200; i++) { for (let culture, rnd, i = 0; cultures.length < culturesNumber && i < MAX_ITERATIONS; i++) {
do { do {
rnd = rand(defaultCultures.length - 1); rnd = rand(defaultCultures.length - 1);
culture = defaultCultures[rnd]; culture = defaultCultures[rnd];
@ -135,34 +136,59 @@ export const generateCultures = function (
} }
function placeCenter(sortingString: string) { function placeCenter(sortingString: string) {
let spacing = (graphWidth + graphHeight) / 2 / culturesNumber;
const sorted = sortPopulatedCellByCultureSuitability(sortingString);
const MAX = Math.floor(sorted.length / 2);
const BIAS_EXPONENT = 6;
return (function getCellId(): number {
const cellId = sorted[biased(0, MAX, BIAS_EXPONENT)];
if (centers.find(...cells.p[cellId], spacing) !== undefined) {
// to close to another center, try again with reduced spacing
spacing *= 0.9;
return getCellId(); // call recursively
}
return cellId;
})();
}
function sortPopulatedCellByCultureSuitability(sortingString: string) {
let cellId: number; let cellId: number;
const sMax = d3.max(cells.s)!;
const sortingMethods = { const sortingMethods = {
n: () => Math.ceil((cells.s[cellId] / sMax) * 3), // normalized cell score n: () => Math.ceil((cells.s[cellId] / maxSuitability) * 3), // normalized cell score
td: (goalTemp: number) => { td: (goalTemp: number) => {
const tempDelta = Math.abs(temp[cells.g[cellId]] - goalTemp); const tempDelta = Math.abs(temp[cells.g[cellId]] - goalTemp);
return tempDelta ? tempDelta + 1 : 1; return tempDelta ? tempDelta + 1 : 1;
}, },
bd: (biomes: number[], fee = 4) => { bd: (biomes: number[], fee = 4) => {
return biomes.includes(cells.biome[cellId]) ? 1 : fee; return biomes.includes(cells.biome[cellId]) ? 1 : fee;
}, },
sf: (fee = 4) => { sf: (fee = 4) => {
const haven = cells.haven[cellId]; const haven = cells.haven[cellId];
const havenHeature = features[haven]; const havenHeature = features[haven];
return haven && havenHeature && havenHeature.type !== "lake" ? 1 : fee; return haven && havenHeature && havenHeature.type !== "lake" ? 1 : fee;
}, },
t: () => cells.t[cellId], t: () => cells.t[cellId],
h: () => cells.h[cellId], h: () => cells.h[cellId],
s: () => cells.s[cellId] s: () => cells.s[cellId]
}; };
const allSortingMethods = `{${Object.keys(sortingMethods).join(", ")}}`; const allSortingMethods = `{${Object.keys(sortingMethods).join(", ")}}`;
const sortFn = new Function(allSortingMethods, "return " + sortingString); const sortFn = new Function(allSortingMethods, "return " + sortingString);
const comparator = (a: number, b: number) => { const comparator = (a: number, b: number) => {
cellId = a; cellId = a;
const cellA = sortFn({...sortingMethods}); const cellA = sortFn(sortingMethods);
cellId = b; cellId = b;
const cellB = sortFn(sortingMethods); const cellB = sortFn(sortingMethods);
@ -170,15 +196,8 @@ export const generateCultures = function (
return cellB - cellA; return cellB - cellA;
}; };
let spacing = (graphWidth + graphHeight) / 2 / culturesNumber;
const sorted = Array.from(populatedCellIds).sort(comparator); const sorted = Array.from(populatedCellIds).sort(comparator);
const max = Math.floor(sorted.length / 2); return sorted;
do {
cellId = sorted[biased(0, max, 5)];
spacing *= 0.9;
} while (centers.find(...cells.p[cellId], spacing) !== undefined);
return cellId;
} }
// set culture type based on culture center position // set culture type based on culture center position
@ -230,7 +249,8 @@ export const generateCultures = function (
} }
// check if base is in nameBases // check if base is in nameBases
if (base > nameBases.length) return base; if (base < nameBases.length) return base;
ERROR && console.error(`Name base ${base} is not available, applying a fallback one`); ERROR && console.error(`Name base ${base} is not available, applying a fallback one`);
return base % nameBases.length; return base % nameBases.length;
} }

View file

@ -25,14 +25,16 @@ const colorSchemeMap: Dict<ColorScheme> = {
}; };
export function getColors(number: number) { export function getColors(number: number) {
const scheme = colorSchemeMap.bright; if (number <= cardinal12.length) return d3.shuffle(cardinal12.slice(0, number));
const scheme = colorSchemeMap.bright;
const colors = d3.range(number).map(index => { const colors = d3.range(number).map(index => {
if (index < 12) return cardinal12[index]; if (index < 12) return cardinal12[index];
const rgb = scheme((index - 12) / (number - 12))!; const rgb = scheme((index - 12) / (number - 12))!;
return d3.color(rgb)!.formatHex() as Hex; return d3.color(rgb)!.formatHex() as Hex;
}); });
return d3.shuffle(colors); return d3.shuffle(colors);
} }

View file

@ -49,9 +49,17 @@ export function rw(object: {[key: string]: number}) {
return ra(weightedArray); return ra(weightedArray);
} }
// return a random integer from min to max biased towards one end based on exponent distribution (the bigger ex the higher bias towards min) // return a random integer from min to max biased towards one end based on exponent distribution
export function biased(min: number, max: number, ex: number) { // the bigger exponent the higher bias towards min
return Math.round(min + (max - min) * Math.pow(Math.random(), ex)); // biased(0, 10, 10): {0: 74%, 1: 9%, 2: 4%, 3: 3%, 4: 2%, 5: 2%, 6: 2%, 7: 1%, 8: 1%, 9: 1%, 10: 0%}
// biased(0, 10, 5): {0: 55%, 1: 14%, 2: 7%, 3: 5%, 4: 4%, 5: 4%, 6: 3%, 7%: 3%, 8: 2%, 9: 2%, 10: 1%}
// biased(0, 10, 4): {0: 46%, 1: 15%, 2: 8%, 3: 6%, 4: 5%, 5: 4%, 6: 4%, 7%: 3, 8: 3%, 9: 3%, 10: 1%}
// biased(0, 10, 3): {0: 36%, 1: 16%, 2: 10%, 3: 8%, 4: 6%, 5: 5%, 6: 5%, 7%: 4, 8: 4%, 9: 4%, 10: 2%}
// biased(0, 10, 2): {0: 22%, 1: 17%, 2: 11%, 3: 9%, 4: 8%, 5: 7%, 6: 6%, 7%: 6, 8: 6%, 9: 5%, 10: 2%}
// biased{0, 10, 1): {0: 5%, 1: 10%, 2: 10%, 3: 10%, 4: 10%, 5: 10%, 6: 10%, 7%: 10, 8%: 10, 9: 10%, 10: 5%}
export function biased(min: number, max: number, exponent: number) {
if (exponent <= 1) throw new Error("Exponent must be greater than 1");
return Math.round(min + (max - min) * Math.pow(Math.random(), exponent));
} }
// get number from string in format "1-3" or "2" or "0.5" // get number from string in format "1-3" or "2" or "0.5"