refactor: generate relations

This commit is contained in:
Azgaar 2022-09-04 22:28:50 +03:00
parent 73431ec743
commit 136e71eae9
7 changed files with 85 additions and 66 deletions

View file

@ -7,7 +7,7 @@ import {initLayers, renderLayer, restoreLayers} from "layers";
// @ts-expect-error js module // @ts-expect-error js module
import {drawScaleBar, Rulers} from "modules/measurers"; import {drawScaleBar, Rulers} from "modules/measurers";
// @ts-expect-error js module // @ts-expect-error js module
import {unfog, downloadFile} from "modules/ui/editors"; import {unfog} from "modules/ui/editors";
// @ts-expect-error js module // @ts-expect-error js module
import {applyMapSize, randomizeOptions} from "modules/ui/options"; import {applyMapSize, randomizeOptions} from "modules/ui/options";
// @ts-expect-error js module // @ts-expect-error js module
@ -76,8 +76,6 @@ async function generate(options?: IGenerationOptions) {
// if (route === 2) drawPoint(pack.cells.p[index], {color: "black"}); // if (route === 2) drawPoint(pack.cells.p[index], {color: "black"});
// }); // });
downloadDiplomacyData();
WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`); WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`);
// showStatistics(); // showStatistics();
INFO && console.groupEnd(); INFO && console.groupEnd();
@ -86,19 +84,6 @@ async function generate(options?: IGenerationOptions) {
} }
} }
function downloadDiplomacyData() {
const states = pack.states.filter(s => s.i && !s.removed);
const valid = states.map(s => s.i);
let data = "," + states.map(s => s.name).join(",") + "\n"; // headers
states.forEach(s => {
const rels = s.diplomacy.filter((v, i) => valid.includes(i));
data += s.name + "," + rels.join(",") + "\n";
});
// downloadFile(data, "relations.csv");
}
function showGenerationError(error: Error) { function showGenerationError(error: Error) {
clearMainTip(); clearMainTip();
ERROR && console.error(error); ERROR && console.error(error);

View file

@ -23,9 +23,16 @@ export function createAreaTiers(statistics: TStateStatistics) {
}; };
} }
export function defineStateForm(type: TCultureType, areaTier: AreaTiers, nameBase: number, burgsNumber: number) { export function defineStateForm(
type: TCultureType,
areaTier: AreaTiers,
nameBase: number,
burgsNumber: number,
relations: TRelation[],
neighbors: number[]
) {
const form = defineForm(type, areaTier); const form = defineForm(type, areaTier);
const formName = defineFormName(form, nameBase, areaTier, burgsNumber); const formName = defineFormName(form, nameBase, areaTier, burgsNumber, relations, neighbors);
return {form, formName}; return {form, formName};
} }
@ -51,9 +58,11 @@ function defineFormName(
form: ReturnType<typeof defineForm>, form: ReturnType<typeof defineForm>,
nameBase: number, nameBase: number,
areaTier: AreaTiers, areaTier: AreaTiers,
burgsNumber: number burgsNumber: number,
relations: TRelation[],
neighbors: number[]
) { ) {
if (form === "Monarchy") return defineMonarchyForm(nameBase, areaTier); if (form === "Monarchy") return defineMonarchyForm(nameBase, areaTier, relations, neighbors);
if (form === "Republic") return defineRepublicForm(areaTier, burgsNumber); if (form === "Republic") return defineRepublicForm(areaTier, burgsNumber);
if (form === "Union") return rw(StateForms.union); if (form === "Union") return rw(StateForms.union);
if (form === "Theocracy") return defineTheocracyForm(nameBase, areaTier); if (form === "Theocracy") return defineTheocracyForm(nameBase, areaTier);
@ -63,11 +72,10 @@ function defineFormName(
} }
// Default name depends on area tier, some name bases have special names for tiers // Default name depends on area tier, some name bases have special names for tiers
function defineMonarchyForm(nameBase: number, areaTier: AreaTiers, diplomacy = [""], neighbors = []) { function defineMonarchyForm(nameBase: number, areaTier: AreaTiers, relations: TRelation[], neighbors: number[]) {
const form = StateForms.monarchy[areaTier]; const form = StateForms.monarchy[areaTier];
// TODO: specific names for vassals const isVassal = relations.includes("Vassal");
const isVassal = diplomacy.includes("Vassal");
if (isVassal) { if (isVassal) {
if (areaTier === AreaTiers.DUCHY && neighbors.length > 1 && rand(6) < neighbors.length) return "Marches"; if (areaTier === AreaTiers.DUCHY && neighbors.length > 1 && rand(6) < neighbors.length) return "Marches";
if (nameBase === NAMEBASE.English && P(0.3)) return "Dominion"; if (nameBase === NAMEBASE.English && P(0.3)) return "Dominion";

View file

@ -7,31 +7,12 @@ import {createCapitals} from "./createCapitals";
import {createStateData} from "./createStateData"; import {createStateData} from "./createStateData";
import {createTowns} from "./createTowns"; import {createTowns} from "./createTowns";
import {expandStates} from "./expandStates"; import {expandStates} from "./expandStates";
import {generateDiplomacy} from "./generateDiplomacy"; import {generateRelations} from "./generateRelations";
import {specifyBurgs} from "./specifyBurgs"; import {specifyBurgs} from "./specifyBurgs";
import {specifyStates} from "./specifyStates"; import {specifyStates} from "./specifyStates";
type TCellsData = Pick< // prettier-ignore
IPack["cells"], type TCellsData = Pick<IPack["cells"], | "v" | "c" | "p" | "b" | "i" | "g" | "area" | "h" | "f" | "t" | "haven" | "harbor" | "r" | "fl" | "biome" | "s" | "pop" | "culture">;
| "v"
| "c"
| "p"
| "b"
| "i"
| "g"
| "area"
| "h"
| "f"
| "t"
| "haven"
| "harbor"
| "r"
| "fl"
| "biome"
| "s"
| "pop"
| "culture"
>;
export function generateBurgsAndStates( export function generateBurgsAndStates(
cultures: TCultures, cultures: TCultures,
@ -87,7 +68,7 @@ export function generateBurgsAndStates(
const burgIds = assignBurgIds(burgs); const burgIds = assignBurgIds(burgs);
const statistics = collectStatistics({...cells, state: stateIds, burg: burgIds}, burgs); const statistics = collectStatistics({...cells, state: stateIds, burg: burgIds}, burgs);
const {chronicle, diplomacy} = generateDiplomacy(statesData, statistics, pick(cells, "f")); const diplomacy = generateRelations(statesData, statistics, pick(cells, "f"));
const states = specifyStates(statesData, statistics, diplomacy, cultures, burgs); const states = specifyStates(statesData, statistics, diplomacy, cultures, burgs);
return {burgIds, stateIds, burgs, states}; return {burgIds, stateIds, burgs, states};

View file

@ -1,34 +1,32 @@
import * as d3 from "d3"; import * as d3 from "d3";
import {TIME} from "config/logging"; import {TIME} from "config/logging";
import {TStateStatistics} from "./collectStatistics";
import {TStateData} from "./createStateData";
import {P, rw} from "utils/probabilityUtils"; import {P, rw} from "utils/probabilityUtils";
import {relations} from "./config"; import {relations} from "./config";
export type TDiplomacy = {[key: number]: TRelations[]}; import type {TStateStatistics} from "./collectStatistics";
import type {TStateData} from "./createStateData";
export type TDiplomacy = {[key: number]: TRelation[]};
interface IDiplomacyData { interface IDiplomacyData {
i: number; i: number;
type: TCultureType; type: TCultureType;
center: number; center: number;
expansionism: number;
area: number; area: number;
neighbors: number[]; neighbors: number[];
} }
export function generateDiplomacy( export function generateRelations(
statesData: TStateData[], statesData: TStateData[],
statistics: TStateStatistics, statistics: TStateStatistics,
cells: Pick<IPack["cells"], "f"> cells: Pick<IPack["cells"], "f">
) { ) {
TIME && console.time("generateDiplomacy"); TIME && console.time("generateRelations");
const chronicle: string[] = [];
const diplomacy = getBlankDiplomacyMatrix(statesData); const diplomacy = getBlankDiplomacyMatrix(statesData);
if (statesData.length < 2) return diplomacy;
if (statesData.length < 2) {
return {chronicle, diplomacy};
}
const stateAreas = Object.values(statistics).map(({area}) => area); const stateAreas = Object.values(statistics).map(({area}) => area);
const averageStateArea = d3.mean(stateAreas)!; const averageStateArea = d3.mean(stateAreas)!;
@ -81,8 +79,8 @@ export function generateDiplomacy(
} }
} }
TIME && console.timeEnd("generateDiplomacy"); TIME && console.timeEnd("generateRelations");
return {chronicle, diplomacy}; return diplomacy;
} }
function getBlankDiplomacyMatrix(statesData: TStateData[]) { function getBlankDiplomacyMatrix(statesData: TStateData[]) {
@ -95,9 +93,9 @@ function getBlankDiplomacyMatrix(statesData: TStateData[]) {
} }
function getDiplomacyData(stateData: TStateData, statistics: TStateStatistics): IDiplomacyData { function getDiplomacyData(stateData: TStateData, statistics: TStateStatistics): IDiplomacyData {
const {i, type, center} = stateData; const {i, type, center, expansionism} = stateData;
const {neighbors, area} = statistics[i]; const {neighbors, area} = statistics[i];
return {i, type, center, neighbors, area}; return {i, type, center, expansionism, neighbors, area};
} }
function detectVassalState(from: IDiplomacyData, to: IDiplomacyData, averageStateArea: number) { function detectVassalState(from: IDiplomacyData, to: IDiplomacyData, averageStateArea: number) {

View file

@ -0,0 +1,35 @@
import {TIME} from "config/logging";
import type {TStateData} from "./createStateData";
import type {TDiplomacy} from "./generateRelations";
export function simulateWars(statesData: TStateData[], diplomacy: TDiplomacy) {
TIME && console.time("simulateWars");
// declare wars
for (const {i} of statesData) {
const relations = diplomacy[i];
if (!relations.includes("Rival")) continue; // no rivals to attack
if (relations.includes("Vassal")) continue; // not independent
if (relations.includes("Enemy")) continue; // already at war
// select candidates to attack: rival independent states
const candidates = relations
.map((relation, index) => (relation === "Rival" && !diplomacy[index].includes("Vassal") ? index : 0))
.filter(index => index);
if (!candidates.length) continue;
const attacker = getDiplomacyData(statesData[i], statistics);
const defender = getDiplomacyData(statesData[ra(candidates)], statistics);
const attackerPower = attacker.area * attacker.expansionism;
const defenderPower = defender.area * defender.expansionism;
if (attackerPower < defenderPower * gauss(1.6, 0.8, 0, 10, 2)) continue; // defender is too strong
const attackers = [attacker];
const defenders = [defender];
}
TIME && console.timeEnd("simulateWars");
return null;
}

View file

@ -7,7 +7,7 @@ import {isBurg} from "utils/typeUtils";
import type {TStateStatistics} from "./collectStatistics"; import type {TStateStatistics} from "./collectStatistics";
import type {TStateData} from "./createStateData"; import type {TStateData} from "./createStateData";
import type {TDiplomacy} from "./generateDiplomacy"; import type {TDiplomacy} from "./generateRelations";
export function specifyStates( export function specifyStates(
statesData: TStateData[], statesData: TStateData[],
@ -24,16 +24,17 @@ export function specifyStates(
const states: IState[] = statesData.map(stateData => { const states: IState[] = statesData.map(stateData => {
const {i, center, type, culture, capital} = stateData; const {i, center, type, culture, capital} = stateData;
const {area, burgs: burgsNumber, ...stats} = statistics[i]; const {area, burgs: burgsNumber, neighbors, ...stats} = statistics[i];
const color = colors[i]; const color = colors[i];
const capitalBurg = burgs[capital]; const capitalBurg = burgs[capital];
const capitalName = isBurg(capitalBurg) ? capitalBurg.name : null; const capitalName = isBurg(capitalBurg) ? capitalBurg.name : null;
if (!capitalName) throw new Error("State capital is not a burg"); if (!capitalName) throw new Error("State capital is not a burg");
const relations = diplomacy[i];
const nameBase = getNameBase(culture); const nameBase = getNameBase(culture);
const areaTier = getAreaTier(area); const areaTier = getAreaTier(area);
const {form, formName} = defineStateForm(type, areaTier, nameBase, burgsNumber); const {form, formName} = defineStateForm(type, areaTier, nameBase, burgsNumber, relations, neighbors);
const name = defineStateName(center, capitalName, nameBase, formName); const name = defineStateName(center, capitalName, nameBase, formName);
const fullName = defineFullStateName(name, formName); const fullName = defineFullStateName(name, formName);
@ -47,7 +48,8 @@ export function specifyStates(
area, area,
burgs: burgsNumber, burgs: burgsNumber,
...stats, ...stats,
diplomacy: diplomacy[i] neighbors,
relations
}; };
return state; return state;
}); });

View file

@ -18,7 +18,7 @@ interface IState {
rural: number; rural: number;
urban: number; urban: number;
neighbors: number[]; neighbors: number[];
diplomacy: TRelations[]; relations: TRelation[];
removed?: boolean; removed?: boolean;
} }
@ -38,4 +38,14 @@ interface ICoa {
t1: string; t1: string;
} }
type TRelations = "Ally" | "Friendly" | "Neutral" | "Suspicion" | "Rival" | "Unknown" | "Suzerain" | "Vassal" | "x"; type TRelation =
| "Ally"
| "Friendly"
| "Neutral"
| "Suspicion"
| "Rival"
| "Unknown"
| "Suzerain"
| "Vassal"
| "Enemy"
| "x";