refactor(religions): expand religions

This commit is contained in:
Azgaar 2022-08-27 00:51:29 +03:00
parent 0a77845ebc
commit 707cdd77ac
11 changed files with 136 additions and 42 deletions

View file

@ -26,6 +26,7 @@ import {createGrid} from "./grid/grid";
import {createPack} from "./pack/pack";
import {getInputValue, setInputValue} from "utils/nodeUtils";
import {calculateMapCoordinates} from "modules/coordinates";
import {drawPolygons} from "utils/debugUtils";
const {Zoom, ThreeD} = window;
@ -70,6 +71,8 @@ async function generate(options?: IGenerationOptions) {
renderLayer("burgs");
renderLayer("routes");
drawPolygons(pack.cells.religion, pack.cells.v, pack.vertices.p, {fillOpacity: 0.8, excludeZeroes: true});
WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`);
// showStatistics();
INFO && console.groupEnd();

View file

@ -128,7 +128,7 @@ export function createPack(grid: IGrid): IPack {
burg: burgIds
});
const {religionIds} = generateReligions({
const {religionIds, religions} = generateReligions({
states,
cultures,
burgs,
@ -143,7 +143,8 @@ export function createPack(grid: IGrid): IPack {
pop: population,
culture: cultureIds,
burg: burgIds,
state: stateIds
state: stateIds,
route: cellRoutes
}
});
@ -191,11 +192,12 @@ export function createPack(grid: IGrid): IPack {
// province
},
features: mergedFeatures,
rivers: rawRivers, // "name" | "basin" | "type"
// rivers: rawRivers, // "name" | "basin" | "type"
cultures,
states,
burgs,
routes
routes,
religions
};
return pack;

View file

@ -1,10 +1,12 @@
import FlatQueue from "flatqueue";
import {ROUTES} from "config/generation";
import {getInputNumber} from "utils/nodeUtils";
import {gauss} from "utils/probabilityUtils";
import {isReligion} from "utils/typeUtils";
type TReligionData = Pick<IReligion, "i" | "type" | "center" | "culture" | "expansion" | "expansionism">;
type TCellsData = Pick<IPack["cells"], "i" | "c" | "culture">;
type TCellsData = Pick<IPack["cells"], "i" | "c" | "biome" | "culture" | "state" | "route">;
export function expandReligions(religions: TReligionData[], cells: TCellsData) {
const religionIds = spreadFolkReligions(religions, cells);
@ -15,6 +17,10 @@ export function expandReligions(religions: TReligionData[], cells: TCellsData) {
const neutralInput = getInputNumber("neutralInput");
const maxExpansionCost = (cells.i.length / 25) * gauss(1, 0.3, 0.2, 2, 2) * neutralInput;
const biomePassageCost = (cellId: number) => biomesData.cost[cells.biome[cellId]];
const isMainRoad = (cellId: number) => cells.route[cellId] === ROUTES.MAIN_ROAD;
const isSeaRoute = (cellId: number) => cells.route[cellId] === ROUTES.SEA_ROUTE;
for (const religion of religions) {
if (!isReligion(religion as IReligion) || (religion as IReligion).type === "Folk") continue;
@ -24,6 +30,37 @@ export function expandReligions(religions: TReligionData[], cells: TCellsData) {
queue.push({cellId, religionId}, 0);
}
const religionsMap = new Map<number, TReligionData>(religions.map(religion => [religion.i, religion]));
while (queue.length) {
const priority = queue.peekValue()!;
const {cellId, religionId} = queue.pop()!;
const {culture, center, expansion, expansionism} = religionsMap.get(religionId)!;
cells.c[cellId].forEach(neibCellId => {
// if (neibCellId === center && religionIds[neibCellId]) return; // do not overwrite center cells
if (expansion === "culture" && culture !== cells.culture[neibCellId]) return;
if (expansion === "state" && cells.state[center] !== cells.state[neibCellId]) return;
const cultureCost = culture !== cells.culture[neibCellId] ? 50 : 0;
const stateCost = cells.state[center] !== cells.state[neibCellId] ? 50 : 0;
const passageCost = isMainRoad(neibCellId) ? 1 : biomePassageCost(neibCellId); // [1, 5000]
const waterCost = isSeaRoute(neibCellId) ? 50 : 1000;
const cellCost = Math.max(cultureCost + stateCost + passageCost + waterCost, 0);
const totalCost = priority + 10 + cellCost / expansionism;
if (totalCost > maxExpansionCost) return;
if (!cost[neibCellId] || totalCost < cost[neibCellId]) {
if (cells.culture[neibCellId]) religionIds[neibCellId] = religionId; // assign religion to cell
cost[neibCellId] = totalCost;
queue.push({cellId: neibCellId, religionId}, totalCost);
}
});
}
return religionIds;
}

View file

@ -6,7 +6,7 @@ import {specifyReligions} from "./specifyReligions";
type TCellsData = Pick<
IPack["cells"],
"i" | "c" | "p" | "g" | "h" | "t" | "biome" | "pop" | "culture" | "burg" | "state"
"i" | "c" | "p" | "g" | "h" | "t" | "biome" | "pop" | "culture" | "burg" | "state" | "route"
>;
export function generateReligions({
@ -29,7 +29,7 @@ export function generateReligions({
cultures,
states,
burgs,
pick(cells, "i", "c", "culture", "burg", "state")
pick(cells, "i", "c", "biome", "culture", "burg", "state", "route")
);
console.log(religions);

View file

@ -17,7 +17,7 @@ export function specifyReligions(
cultures: TCultures,
states: TStates,
burgs: TBurgs,
cells: Pick<IPack["cells"], "i" | "c" | "culture" | "burg" | "state">
cells: Pick<IPack["cells"], "i" | "c" | "biome" | "culture" | "burg" | "state" | "route">
): {religions: TReligions; religionIds: Uint16Array} {
const rawReligions = religionsData.map(({type, form, culture: cultureId, center}, index) => {
const supreme = getDeityName(cultures, cultureId);

View file

@ -2,20 +2,22 @@ type Logical = number & (1 | 0); // data type for logical numbers
type UnknownObject = {[key: string]: unknown};
// extract element from array
type Entry<T> = T[number];
type noop = () => void;
interface Dict<T> {
[key: string]: T;
}
// extract element from array
type Entry<T> = T[number];
// element of Object.entries
type ObjectEntry<T> = [string, T];
type UintArray = Uint8Array | Uint16Array | Uint32Array;
type IntArray = Int8Array | Int16Array | Int32Array;
type FloatArray = Float32Array | Float64Array;
type TypedArray = UintArray | IntArray | FloatArray;
type RGB = `rgb(${number}, ${number}, ${number})`;
type Hex = `#${string}`;

View file

@ -3,9 +3,9 @@ interface IPack extends IGraph {
features: TPackFeatures;
states: TStates;
cultures: TCultures;
provinces: IProvince[];
provinces: TProvinces;
burgs: TBurgs;
rivers: IRiver[];
rivers: TRivers;
religions: TReligions;
routes: TRoutes;
}
@ -38,27 +38,3 @@ interface IPackBase extends IGraph {
cells: IGraphCells & Partial<IPackCells>;
features?: TPackFeatures;
}
interface IProvince {
i: number;
name: string;
fullName: string;
removed?: boolean;
}
interface IRiver {
i: number;
name: string;
basin: number;
parent: number;
type: string;
source: number;
mouth: number;
sourceWidth: number;
width: number;
widthFactor: number;
length: number;
discharge: number;
cells: number[];
points?: number[];
}

8
src/types/pack/provinces.d.ts vendored Normal file
View file

@ -0,0 +1,8 @@
interface IProvince {
i: number;
name: string;
fullName: string;
removed?: boolean;
}
type TProvinces = IProvince[];

18
src/types/pack/rivers.d.ts vendored Normal file
View file

@ -0,0 +1,18 @@
interface IRiver {
i: number;
name: string;
basin: number;
parent: number;
type: string;
source: number;
mouth: number;
sourceWidth: number;
width: number;
widthFactor: number;
length: number;
discharge: number;
cells: number[];
points?: number[];
}
type TRivers = IRiver[];

View file

@ -15,8 +15,9 @@ const cardinal12: Hex[] = [
"#eb8de7"
];
type ColorScheme = d3.ScaleSequential<string>;
const colorSchemeMap: Dict<ColorScheme> = {
export type TColorScheme = "default" | "bright" | "light" | "green" | "rainbow" | "monochrome";
const colorSchemeMap: {[key in TColorScheme]: d3.ScaleSequential<string>} = {
default: d3.scaleSequential(d3.interpolateSpectral),
bright: d3.scaleSequential(d3.interpolateSpectral),
light: d3.scaleSequential(d3.interpolateRdYlGn),
green: d3.scaleSequential(d3.interpolateGreens),
@ -27,7 +28,7 @@ const colorSchemeMap: Dict<ColorScheme> = {
export function getColors(number: number) {
if (number <= cardinal12.length) return d3.shuffle(cardinal12.slice(0, number));
const scheme = colorSchemeMap.bright;
const scheme = colorSchemeMap.default;
const colors = d3.range(number).map(index => {
if (index < 12) return cardinal12[index];
@ -39,7 +40,7 @@ export function getColors(number: number) {
}
export function getRandomColor(): Hex {
const scheme = colorSchemeMap.bright;
const scheme = colorSchemeMap.default;
const rgb = scheme(Math.random())!;
return d3.color(rgb)?.formatHex() as Hex;
}
@ -54,7 +55,7 @@ export function getMixedColor(hexColor: string, mixation = 0.2, bright = 0.3) {
return d3.color(mixedColor)!.brighter(bright).hex();
}
export function getColorScheme(schemeName: string) {
export function getColorScheme(schemeName: TColorScheme) {
return colorSchemeMap[schemeName] || colorSchemeMap.bright;
}

View file

@ -1,5 +1,6 @@
// utils to be used for debugging (not in PROD)
import {getColorScheme, TColorScheme} from "./colorUtils";
import {getNormal} from "./lineUtils";
export function drawPoint([x, y]: TPoint, {radius = 1, color = "red"} = {}) {
@ -55,3 +56,49 @@ export function drawText(text: string | number, [x, y]: TPoint, {size = 6, color
.attr("stroke", "none")
.text(text);
}
export function drawPolygons(
values: TypedArray,
cellVertices: number[][],
vertexPoints: TPoints,
{
fillOpacity = 0.3,
stroke = "#222",
strokeWidth = 0.2,
colorScheme = "default",
excludeZeroes = false
}: {
fillOpacity?: number;
stroke?: string;
strokeWidth?: number;
colorScheme?: TColorScheme;
excludeZeroes?: boolean;
} = {}
) {
const cellIds = [...Array(values.length).keys()];
const data = excludeZeroes ? cellIds.filter(id => values[id] !== 0) : cellIds;
const getPolygon = (id: number) => {
const vertices = cellVertices[id];
const points = vertices.map(id => vertexPoints[id]);
return `${points.join(" ")} ${points[0].join(",")}`;
};
// get fill from normalizing and interpolating values to color scheme
const min = Math.min(...values);
const max = Math.max(...values);
const normalized = Array.from(values).map(value => (value - min) / (max - min));
const scheme = getColorScheme(colorScheme);
const getFill = (id: number) => scheme(normalized[id])!;
debug
.selectAll("polyline")
.data(data)
.enter()
.append("polyline")
.attr("points", getPolygon)
.attr("fill", getFill)
.attr("fill-opacity", fillOpacity)
.attr("stroke", stroke)
.attr("stroke-width", strokeWidth);
}