refactor - stateForms start

This commit is contained in:
Azgaar 2022-09-02 01:28:17 +03:00
parent 60e69348a9
commit 2be3c68290
15 changed files with 277 additions and 89 deletions

View file

@ -16,6 +16,7 @@ import {
} from "utils/unitUtils";
import {showMainTip, tip} from "scripts/tooltips";
import {defineEmblemData} from "./utils";
import {isState} from "utils/typeUtils";
export const onMouseMove = debounce(handleMouseMove, 100);
@ -75,8 +76,9 @@ const getHoveredElement = (tagName: string, group: string, subgroup: string, isL
if (layerIsOn("togglePopulation")) return "populationLayer";
if (layerIsOn("toggleTemp")) return "temperatureLayer";
if (layerIsOn("toggleBiomes") && biome[cellId]) return "biomesLayer";
if (religion[cellId]) return "religionsLayer"; // layerIsOn("toggleReligions") &&
if (layerIsOn("toggleProvinces") || (layerIsOn("toggleStates") && state[cellId])) return "statesLayer";
if (layerIsOn("toggleReligions") && religion[cellId]) return "religionsLayer";
// if (layerIsOn("toggleProvinces") || (layerIsOn("toggleStates") && state[cellId])) return "statesLayer";
if (state[cellId]) return "statesLayer";
if (layerIsOn("toggleCultures") && culture[cellId]) return "culturesLayer";
if (layerIsOn("toggleHeight")) return "heightLayer";
@ -193,16 +195,18 @@ const onHoverEventsMap: OnHoverEventMap = {
},
statesLayer: ({packCellId}) => {
const state = pack.cells.state[packCellId];
const stateName = pack.states[state].fullName;
const province = pack.cells.province[packCellId];
const prov = province ? `${pack.provinces[province].fullName}, ` : "";
tip(prov + stateName);
const stateId = pack.cells.state[packCellId];
const state = pack.states[stateId];
const stateName = isState(state) ? state.fullName || state.name : state.name;
highlightDialogLine("statesEditor", state);
highlightDialogLine("diplomacyEditor", state);
highlightDialogLine("militaryEditor", state);
highlightDialogLine("provincesEditor", province);
const provinceId = pack.cells.province[packCellId];
const provinceName = provinceId ? `${pack.provinces[provinceId].fullName}, ` : "";
tip(provinceName + stateName);
highlightDialogLine("statesEditor", stateId);
highlightDialogLine("diplomacyEditor", stateId);
highlightDialogLine("militaryEditor", stateId);
highlightDialogLine("provincesEditor", provinceId);
},
culturesLayer: ({packCellId}) => {

View file

@ -70,8 +70,8 @@ async function generate(options?: IGenerationOptions) {
// renderLayer("biomes");
renderLayer("burgs");
renderLayer("routes");
// renderLayer("states");
renderLayer("religions");
renderLayer("states");
// renderLayer("religions");
// pack.cells.route.forEach((route, index) => {
// if (route === 2) drawPoint(pack.cells.p[index], {color: "black"});

View file

@ -0,0 +1,46 @@
import {MIN_LAND_HEIGHT} from "config/generation";
import {TIME} from "config/logging";
export type TStateStatistics = Record<number, typeof initialData>;
const initialData = {cells: 0, area: 0, burgs: 0, rural: 0, urban: 0, neighbors: [] as number[]};
// calculate states data like area, population, etc.
export function collectStatistics(
cells: Pick<IPack["cells"], "i" | "c" | "h" | "area" | "pop" | "state" | "burg">,
burgs: TBurgs
) {
TIME && console.time("collectStatistics");
const statesData: TStateStatistics = {};
const initiate = (stateId: number) => {
statesData[stateId] = structuredClone(initialData);
};
// check for neighboring states
const checkNeib = (neibCellId: number, stateId: number) => {
const neibStateId = cells.state[neibCellId];
if (!neibStateId || neibStateId === stateId) return;
if (!statesData[stateId].neighbors.includes(neibStateId)) statesData[stateId].neighbors.push(neibStateId);
};
for (const cellId of cells.i) {
if (cells.h[cellId] < MIN_LAND_HEIGHT) continue;
const stateId = cells.state[cellId];
if (!statesData[stateId]) initiate(stateId);
cells.c[cellId].forEach(neibCellId => checkNeib(neibCellId, stateId));
statesData[stateId].cells += 1;
statesData[stateId].area += cells.area[cellId];
statesData[stateId].rural += cells.pop[cellId];
const burgId = cells.burg[cellId];
if (burgId) {
statesData[stateId].burgs += 1;
statesData[stateId].urban += (burgs[burgId] as IBurg)?.population || 0;
}
}
TIME && console.timeEnd("collectStatistics");
return statesData;
}

View file

@ -0,0 +1,34 @@
import {TIME} from "config/logging";
import {getInputNumber} from "utils/nodeUtils";
import {rn} from "utils/numberUtils";
import type {createCapitals} from "./createCapitals";
import {defineStateName} from "./defineStateName";
import {generateStateEmblem} from "./generateStateEmblem";
type TCapitals = ReturnType<typeof createCapitals>;
export type TStateData = Pick<
IState,
"i" | "name" | "type" | "culture" | "center" | "expansionism" | "capital" | "coa"
>;
export function createStateData(capitals: TCapitals, cultures: TCultures) {
TIME && console.time("createStates");
const powerInput = getInputNumber("powerInput");
const statesData: TStateData[] = capitals.map((capital, index) => {
const {cell: cellId, culture: cultureId, name: capitalName} = capital;
const id = index + 1;
const name = defineStateName(cellId, capitalName, cultureId, cultures);
const {type, shield} = cultures[cultureId] as ICulture;
const expansionism = rn(Math.random() * powerInput + 1, 1);
const coa = generateStateEmblem(type, shield);
return {i: id, name, type, center: cellId, expansionism, capital: id, culture: cultureId, coa};
});
TIME && console.timeEnd("createStates");
return statesData;
}

View file

@ -1,44 +0,0 @@
import {TIME} from "config/logging";
import {getColors} from "utils/colorUtils";
import {getInputNumber} from "utils/nodeUtils";
import {rn} from "utils/numberUtils";
import {each} from "utils/probabilityUtils";
import {NEUTRALS} from "./config";
import type {createCapitals} from "./createCapitals";
const {Names, COA} = window;
type TCapitals = ReturnType<typeof createCapitals>;
export function createStates(capitals: TCapitals, cultures: TCultures): TStates {
TIME && console.time("createStates");
const colors = getColors(capitals.length);
const powerInput = getInputNumber("powerInput");
const states = capitals.map((capital, index) => {
const {cell: cellId, culture: cultureId, name: capitalName} = capital;
const id = index + 1;
const name = getStateName(cellId, capitalName, cultureId, cultures);
const color = colors[index];
const {type, shield: cultureShield} = cultures[cultureId] as ICulture;
const expansionism = rn(Math.random() * powerInput + 1, 1);
const shield = COA.getShield(cultureShield, null);
const coa: ICoa = {...COA.generate(null, null, null, type), shield};
return {i: id, name, type, center: cellId, color, expansionism, capital: id, culture: cultureId, coa} as IState;
});
TIME && console.timeEnd("createStates");
return [NEUTRALS, ...states];
}
function getStateName(cellId: number, capitalName: string, cultureId: number, cultures: TCultures): string {
const useCapitalName = capitalName.length < 9 && each(5)(cellId);
const nameBase = cultures[cultureId].base;
const basename: string = useCapitalName ? capitalName : Names.getBaseShort(nameBase);
return Names.getState(basename, basename);
}

View file

@ -0,0 +1,64 @@
import * as d3 from "d3";
import type {TStateStatistics} from "./collectStatistics";
const generic = {Monarchy: 25, Republic: 2, Union: 1};
const naval = {Monarchy: 25, Republic: 8, Union: 3};
const republic = {
Republic: 75,
Federation: 4,
"Trade Company": 4,
"Most Serene Republic": 2,
Oligarchy: 2,
Tetrarchy: 1,
Triumvirate: 1,
Diarchy: 1,
Junta: 1
};
const union = {
Union: 3,
League: 4,
Confederation: 1,
"United Kingdom": 1,
"United Republic": 1,
"United Provinces": 2,
Commonwealth: 1,
Heptarchy: 1
};
const theocracy = {Theocracy: 20, Brotherhood: 1, Thearchy: 2, See: 1, "Holy State": 1};
const anarchy = {"Free Territory": 2, Council: 3, Commune: 1, Community: 1};
const monarchy = ["Duchy", "Grand Duchy", "Principality", "Kingdom", "Empire"]; // per area tier
enum AreaTiers {
DUCHY = 0,
GRAND_DUCHY = 1,
PRINCIPALITY = 2,
KINGDOM = 3,
EMPIRE = 4
}
// create 5 area tiers, where 4 are the biggest, 0 the smallest
export function createAreaTiers(statistics: TStateStatistics) {
const stateAreas = Object.entries(statistics)
.filter(([id]) => Number(id))
.map(([, {area}]) => area);
const medianArea = d3.median(stateAreas)!;
const topTierIndex = Math.max(Math.ceil(stateAreas.length ** 0.4) - 2, 0);
const minTopTierArea = stateAreas.sort((a, b) => b - a)[topTierIndex];
return (area: number) => {
const tier = Math.min(Math.floor((area / medianArea) * 2.6), 4) as AreaTiers;
if (tier === AreaTiers.EMPIRE && area < minTopTierArea) return AreaTiers.KINGDOM;
return tier;
};
}
export function defineStateForm(type: TCultureType, areaTier: AreaTiers) {
return {form: "testForm", formName: "testFormName"};
}

View file

@ -0,0 +1,15 @@
import {each} from "utils/probabilityUtils";
const {Names} = window;
export function defineStateName(cellId: number, capitalName: string, cultureId: number, cultures: TCultures): string {
const useCapitalName = capitalName.length < 9 && each(5)(cellId);
const nameBase = cultures[cultureId].base;
const basename: string = useCapitalName ? capitalName : Names.getBaseShort(nameBase);
return Names.getState(basename, basename);
}
export function defineFullStateName(name: string, form: string) {
return `${name} ${form}`;
}

View file

@ -4,12 +4,12 @@ import {TIME} from "config/logging";
import {getInputNumber} from "utils/nodeUtils";
import {minmax} from "utils/numberUtils";
import {ELEVATION, FOREST_BIOMES, MIN_LAND_HEIGHT, DISTANCE_FIELD} from "config/generation";
import {isNeutals} from "utils/typeUtils";
import type {TStateData} from "./createStateData";
// growth algorithm to assign cells to states
export function expandStates(
capitalCells: Map<number, boolean>,
states: TStates,
statesData: TStateData[],
features: TPackFeatures,
cells: Pick<IPack["cells"], "c" | "h" | "f" | "t" | "r" | "fl" | "s" | "biome" | "culture">
) {
@ -24,10 +24,7 @@ export function expandStates(
const neutralInput = getInputNumber("neutralInput");
const maxExpansionCost = (cellsNumber / 2) * neutralInput * statesNeutral;
for (const state of states) {
if (state.i === 0) continue;
const {i: stateId, center: cellId} = state as IState;
for (const {i: stateId, center: cellId} of statesData) {
stateIds[cellId] = stateId;
cost[cellId] = 1;
queue.push({cellId, stateId}, 0);
@ -66,11 +63,13 @@ export function expandStates(
const GENERIC_LANDLOCKED_FEE = 0;
const NAVAL_LANDLOCKED_FEE = 30;
const statesMap = new Map<number, TStateData>(statesData.map(stateData => [stateData.i, stateData]));
while (queue.length) {
const priority = queue.peekValue()!;
const {cellId, stateId} = queue.pop()!;
const {type, culture, center, expansionism} = getState(stateId);
const {type, culture, center, expansionism} = statesMap.get(stateId)!;
const capitalBiome = cells.biome[center];
cells.c[cellId].forEach(neibCellId => {
@ -100,12 +99,6 @@ export function expandStates(
return normalizeStates(stateIds, capitalCells, cells.c, cells.h);
function getState(stateId: number) {
const state = states[stateId];
if (isNeutals(state)) throw new Error("Neutrals cannot expand");
return state;
}
function getCultureCost(cellId: number, stateCulture: number) {
return cells.culture[cellId] === stateCulture ? SAME_CULTURE_BONUS : DIFFERENT_CULTURES_FEE;
}

View file

@ -1,12 +1,14 @@
import {WARN} from "config/logging";
import {pick} from "utils/functionUtils";
import {getInputNumber} from "utils/nodeUtils";
import {collectStatistics} from "./collectStatistics";
import {NEUTRALS, NO_BURG} from "./config";
import {createCapitals} from "./createCapitals";
import {createStates} from "./createStates";
import {createStateData} from "./createStateData";
import {createTowns} from "./createTowns";
import {expandStates} from "./expandStates";
import {specifyBurgs} from "./specifyBurgs";
import {specifyStates} from "./specifyStates";
export function generateBurgsAndStates(
cultures: TCultures,
@ -16,7 +18,24 @@ export function generateBurgsAndStates(
vertices: IGraphVertices,
cells: Pick<
IPack["cells"],
"v" | "c" | "p" | "b" | "i" | "g" | "h" | "f" | "t" | "haven" | "harbor" | "r" | "fl" | "biome" | "s" | "culture"
| "v"
| "c"
| "p"
| "b"
| "i"
| "g"
| "area"
| "h"
| "f"
| "t"
| "haven"
| "harbor"
| "r"
| "fl"
| "biome"
| "s"
| "pop"
| "culture"
>
): {burgIds: Uint16Array; stateIds: Uint16Array; burgs: TBurgs; states: TStates} {
const cellsNumber = cells.i.length;
@ -34,7 +53,7 @@ export function generateBurgsAndStates(
const capitals = createCapitals(statesNumber, scoredCellIds, cultures, pick(cells, "p", "f", "culture"));
const capitalCells = new Map(capitals.map(({cell}) => [cell, true]));
const states = createStates(capitals, cultures);
const statesData = createStateData(capitals, cultures);
const towns = createTowns(
cultures,
@ -44,7 +63,7 @@ export function generateBurgsAndStates(
const stateIds = expandStates(
capitalCells,
states,
statesData,
features,
pick(cells, "c", "h", "f", "t", "r", "fl", "s", "biome", "culture")
);
@ -57,13 +76,16 @@ export function generateBurgsAndStates(
temp,
vertices,
cultures,
states,
statesData,
rivers,
pick(cells, "v", "p", "g", "h", "f", "haven", "harbor", "s", "biome", "fl", "r")
);
const burgIds = assignBurgIds(burgs);
const statistics = collectStatistics({...cells, state: stateIds, burg: burgIds}, burgs);
const states = specifyStates(statesData, statistics, stateIds, burgIds);
return {burgIds, stateIds, burgs, states};
function getScoredCellIds() {

View file

@ -0,0 +1,8 @@
const {COA} = window;
export function generateStateEmblem(type: string, cultureShield: string) {
const shield = COA.getShield(cultureShield, null);
const coa: ICoa = {...COA.generate(null, null, null, type), shield};
return coa;
}

View file

@ -6,14 +6,13 @@ import {gauss, P} from "utils/probabilityUtils";
import {NO_BURG} from "./config";
import type {createCapitals} from "./createCapitals";
import type {createStates} from "./createStates";
import type {TStateData} from "./createStateData";
import type {createTowns} from "./createTowns";
const {COA} = window;
type TCapitals = ReturnType<typeof createCapitals>;
type TTowns = ReturnType<typeof createTowns>;
type TStatesReturn = ReturnType<typeof createStates>;
export function specifyBurgs(
capitals: TCapitals,
@ -23,12 +22,14 @@ export function specifyBurgs(
temp: Int8Array,
vertices: IGraphVertices,
cultures: TCultures,
states: TStatesReturn,
statesData: TStateData[],
rivers: Omit<IRiver, "name" | "basin" | "type">[],
cells: Pick<IPack["cells"], "v" | "p" | "g" | "h" | "f" | "haven" | "harbor" | "s" | "biome" | "fl" | "r">
): TBurgs {
TIME && console.time("specifyBurgs");
const stateDataMap = new Map(statesData.map(data => [data.i, data]));
const burgs = [...capitals, ...towns].map((burgData, index) => {
const {cell, culture, capital} = burgData;
const state = stateIds[cell];
@ -38,7 +39,8 @@ export function specifyBurgs(
const [x, y] = defineLocation(cell, port);
const type = defineType(cell, port, population);
const coa: ICoa = defineEmblem(state, culture, port, capital, type, cultures, states);
const stateData = stateDataMap.get(state)!;
const coa: ICoa = defineEmblem(culture, port, capital, type, cultures, stateData);
const burg: IBurg = {i: index + 1, ...burgData, state, port, population, x, y, type, coa};
return burg;
@ -119,28 +121,27 @@ export function specifyBurgs(
}
function defineEmblem(
stateId: number,
cultureId: number,
port: number,
capital: Logical,
type: TCultureType,
cultures: TCultures,
states: TStatesReturn
stateData: TStateData
) {
const coaType = capital && P(0.2) ? "Capital" : type === "Generic" ? "City" : type;
const cultureShield = cultures[cultureId].shield;
const stateShield = ((states[stateId] as IState)?.coa as ICoa)?.shield;
if (stateId === 0) {
if (!stateData) {
const baseCoa = COA.generate(null, 0, null, coaType);
const shield = COA.getShield(cultureShield, stateShield);
const shield = COA.getShield(cultureShield);
return {...baseCoa, shield};
}
const {culture: stateCultureId, coa: stateCOA} = states[stateId] as IState;
const {culture: stateCultureId, coa: stateCOA} = stateData;
const kinship = defineKinshipToStateEmblem();
const baseCoa = COA.generate(stateCOA, kinship, null, coaType);
const stateShield = (stateData.coa as ICoa)?.shield;
const shield = COA.getShield(cultureShield, stateShield);
return {...baseCoa, shield};

View file

@ -0,0 +1,37 @@
import {TIME} from "config/logging";
import {getColors} from "utils/colorUtils";
import {NEUTRALS} from "./config";
import {createAreaTiers, defineStateForm} from "./defineStateForm";
import {defineFullStateName} from "./defineStateName";
import type {TStateStatistics} from "./collectStatistics";
import type {TStateData} from "./createStateData";
export function specifyStates(
statesData: TStateData[],
statistics: TStateStatistics,
stateIds: Uint16Array,
burgIds: Uint16Array
): TStates {
TIME && console.time("specifyState");
const colors = getColors(statesData.length);
const getAreaTier = createAreaTiers(statistics);
const states: IState[] = statesData.map((stateData, index) => {
const {i, type, name} = stateData;
const {area, ...stats} = statistics[i];
const areaTier = getAreaTier(area);
const {form, formName} = defineStateForm(type, areaTier);
const fullName = defineFullStateName(name, form);
const color = colors[index];
const state: IState = {...stateData, form, formName, fullName, color, area, ...stats};
return state;
});
TIME && console.timeEnd("specifyState");
return [NEUTRALS, ...states];
}

View file

@ -104,7 +104,7 @@ export function createPack(grid: IGrid): IPack {
rawRivers,
vertices,
{
...pick(cells, "v", "c", "p", "b", "i", "g"),
...pick(cells, "v", "c", "p", "b", "i", "g", "area"),
h: heights,
f: featureIds,
t: distanceField,
@ -114,6 +114,7 @@ export function createPack(grid: IGrid): IPack {
fl: flux,
biome,
s: suitability,
pop: population,
culture: cultureIds
}
);
@ -148,7 +149,6 @@ export function createPack(grid: IGrid): IPack {
}
});
// Religions.generate();
// BurgsAndStates.defineStateForms();
// BurgsAndStates.generateProvinces();
// BurgsAndStates.defineBurgFeatures();