refactor(es modules): replace PrioriryQueue with faster FlatQueue

This commit is contained in:
Azgaar 2022-07-01 16:10:36 +03:00
parent 6a7ec6513a
commit 6ea6e52b75
9 changed files with 267 additions and 242 deletions

View file

@ -7659,7 +7659,6 @@
<script src="/src/libs/jquery-ui.min.js"></script> <script src="/src/libs/jquery-ui.min.js"></script>
<script src="/src/versioning.js"></script> <script src="/src/versioning.js"></script>
<script src="/src/libs/d3.min.js"></script> <script src="/src/libs/d3.min.js"></script>
<script src="/src/libs/priority-queue.min.js"></script>
<script src="/src/libs/delaunator.min.js"></script> <script src="/src/libs/delaunator.min.js"></script>
<script type="module" src="/src/modules/heightmap-generator.js"></script> <script type="module" src="/src/modules/heightmap-generator.js"></script>

View file

@ -12,6 +12,7 @@
"vite": "^3.0.0-beta.3" "vite": "^3.0.0-beta.3"
}, },
"dependencies": { "dependencies": {
"flatqueue": "^2.0.3",
"lineclip": "^1.1.5", "lineclip": "^1.1.5",
"polylabel": "^1.1.0" "polylabel": "^1.1.0"
} }

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,8 @@
// Azgaar (azgaar.fmg@yandex.com). Minsk, 2017-2022. MIT License // Azgaar (azgaar.fmg@yandex.com). Minsk, 2017-2022. MIT License
// https://github.com/Azgaar/Fantasy-Map-Generator // https://github.com/Azgaar/Fantasy-Map-Generator
import FlatQueue from "flatqueue";
import "./components"; import "./components";
import {ERROR, INFO, TIME, WARN} from "./config/logging"; import {ERROR, INFO, TIME, WARN} from "./config/logging";
import {UINT16_MAX} from "./constants"; import {UINT16_MAX} from "./constants";
@ -1401,26 +1403,29 @@ function addZones(number = 1) {
const burg = ra(burgs.filter(b => !used[b.cell] && b.i && !b.removed)); // random burg const burg = ra(burgs.filter(b => !used[b.cell] && b.i && !b.removed)); // random burg
if (!burg) return; if (!burg) return;
const cellsArray = [], const cellsArray = [];
cost = [], const costs = [];
power = rand(20, 37); const power = rand(20, 37);
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
queue.queue({e: burg.cell, p: 0}); const queue = new FlatQueue();
queue.push(burg.cell, 0);
while (queue.length) { while (queue.length) {
const next = queue.dequeue(); const priority = queue.peekValue();
if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e); const next = queue.pop();
used[next.e] = 1;
cells.c[next.e].forEach(function (e) { if (cells.burg[next] || cells.pop[next]) cellsArray.push(next);
const r = cells.road[next.e]; used[next] = 1;
const c = r ? Math.max(10 - r, 1) : 100;
const p = next.p + c;
if (p > power) return;
if (!cost[e] || p < cost[e]) { cells.c[next].forEach(neibCellId => {
cost[e] = p; const roadValue = cells.road[next];
queue.queue({e, p}); const cost = roadValue ? Math.max(10 - roadValue, 1) : 100;
const totalPriority = priority + cost;
if (totalPriority > power) return;
if (!costs[neibCellId] || totalPriority < costs[neibCellId]) {
costs[neibCellId] = totalPriority;
queue.push(neibCellId, totalPriority);
} }
}); });
} }
@ -1481,25 +1486,28 @@ function addZones(number = 1) {
const burg = ra(burgs.filter(b => !used[b.cell] && b.i && !b.removed)); // random burg const burg = ra(burgs.filter(b => !used[b.cell] && b.i && !b.removed)); // random burg
if (!burg) return; if (!burg) return;
const cellsArray = [], const cellsArray = [];
cost = [], const costs = [];
power = rand(5, 25); const power = rand(5, 25);
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
queue.queue({e: burg.cell, p: 0}); const queue = new FlatQueue();
queue.push(burg.cell, 0);
while (queue.length) { while (queue.length) {
const next = queue.dequeue(); const priority = queue.peekValue();
if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e); const next = queue.pop();
used[next.e] = 1;
cells.c[next.e].forEach(function (e) { if (cells.burg[next] || cells.pop[next]) cellsArray.push(next);
const c = rand(1, 10); used[next] = 1;
const p = next.p + c;
if (p > power) return;
if (!cost[e] || p < cost[e]) { cells.c[next].forEach(neibCellId => {
cost[e] = p; const cost = rand(1, 10);
queue.queue({e, p}); const totalPriority = priority + cost;
if (totalPriority > power) return;
if (!costs[neibCellId] || totalPriority < costs[neibCellId]) {
costs[neibCellId] = totalPriority;
queue.push(neibCellId, totalPriority);
} }
}); });
} }
@ -1522,9 +1530,9 @@ function addZones(number = 1) {
if (note[0]) note[0].legend = note[0].legend.replace("Active volcano", "Erupting volcano"); if (note[0]) note[0].legend = note[0].legend.replace("Active volcano", "Erupting volcano");
const name = note[0] ? note[0].name.replace(" Volcano", "") + " Eruption" : "Volcano Eruption"; const name = note[0] ? note[0].name.replace(" Volcano", "") + " Eruption" : "Volcano Eruption";
const cellsArray = [], const cellsArray = [];
queue = [cell], const queue = [cell];
power = rand(10, 30); const power = rand(10, 30);
while (queue.length) { while (queue.length) {
const q = P(0.5) ? queue.shift() : queue.pop(); const q = P(0.5) ? queue.shift() : queue.pop();
@ -1545,9 +1553,9 @@ function addZones(number = 1) {
if (!roads.length) return; if (!roads.length) return;
const cell = +ra(roads); const cell = +ra(roads);
const cellsArray = [], const cellsArray = [];
queue = [cell], const queue = [cell];
power = rand(3, 15); const power = rand(3, 15);
while (queue.length) { while (queue.length) {
const q = P(0.3) ? queue.shift() : queue.pop(); const q = P(0.3) ? queue.shift() : queue.pop();
@ -1570,9 +1578,9 @@ function addZones(number = 1) {
if (!elevated.length) return; if (!elevated.length) return;
const cell = ra(elevated); const cell = ra(elevated);
const cellsArray = [], const cellsArray = [];
queue = [cell], const queue = [cell];
power = rand(3, 15); const power = rand(3, 15);
while (queue.length) { while (queue.length) {
const q = queue.pop(); const q = queue.pop();
@ -1602,9 +1610,9 @@ function addZones(number = 1) {
const cell = +ra(rivers), const cell = +ra(rivers),
river = cells.r[cell]; river = cells.r[cell];
const cellsArray = [], const cellsArray = [];
queue = [cell], const queue = [cell];
power = rand(5, 30); const power = rand(5, 30);
while (queue.length) { while (queue.length) {
const q = queue.pop(); const q = queue.pop();
@ -1627,9 +1635,9 @@ function addZones(number = 1) {
if (!coastal.length) return; if (!coastal.length) return;
const cell = +ra(coastal); const cell = +ra(coastal);
const cellsArray = [], const cellsArray = [];
queue = [cell], const queue = [cell];
power = rand(10, 30); const power = rand(10, 30);
while (queue.length) { while (queue.length) {
const q = queue.shift(); const q = queue.shift();

View file

@ -1,3 +1,5 @@
import FlatQueue from "flatqueue";
import {TIME} from "config/logging"; import {TIME} from "config/logging";
import {layerIsOn} from "layers"; import {layerIsOn} from "layers";
import {Voronoi} from "/src/modules/voronoi"; import {Voronoi} from "/src/modules/voronoi";
@ -375,44 +377,46 @@ window.BurgsAndStates = (function () {
const {cells, states, cultures, burgs} = pack; const {cells, states, cultures, burgs} = pack;
cells.state = new Uint16Array(cells.i.length); cells.state = new Uint16Array(cells.i.length);
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p}); const queue = new FlatQueue();
const cost = []; const cost = [];
const neutral = (cells.i.length / 5000) * 2500 * neutralInput.value * statesNeutral; // limit cost for state growth const neutral = (cells.i.length / 5000) * 2500 * neutralInput.value * statesNeutral; // limit cost for state growth
states states
.filter(s => s.i && !s.removed) .filter(s => s.i && !s.removed)
.forEach(s => { .forEach(state => {
const capitalCell = burgs[s.capital].cell; const capitalCell = burgs[state.capital].cell;
cells.state[capitalCell] = s.i; cells.state[capitalCell] = state.i;
const cultureCenter = cultures[s.culture].center; const cultureCenter = cultures[state.culture].center;
const b = cells.biome[cultureCenter]; // state native biome const biome = cells.biome[cultureCenter]; // state native biome
queue.queue({e: s.center, p: 0, s: s.i, b}); queue.push({cellId: state.center, stateId: state.i, b: biome}, 0);
cost[s.center] = 1; cost[state.center] = 1;
}); });
while (queue.length) { while (queue.length) {
const next = queue.dequeue(); const priority = queue.peekValue();
const {e, p, s, b} = next; const {cellId, stateId, biome} = queue.pop();
const {type, culture} = states[s]; const {type, culture} = states[stateId];
cells.c[e].forEach(e => { cells.c[cellId].forEach(neibCellId => {
if (cells.state[e] && e === states[cells.state[e]].center) return; // do not overwrite capital cells if (cells.state[neibCellId] && neibCellId === states[cells.state[neibCellId]].center) return; // do not overwrite capital cells
const cultureCost = culture === cells.culture[e] ? -9 : 100; const cultureCost = culture === cells.culture[neibCellId] ? -9 : 100;
const populationCost = cells.h[e] < 20 ? 0 : cells.s[e] ? Math.max(20 - cells.s[e], 0) : 5000; const populationCost =
const biomeCost = getBiomeCost(b, cells.biome[e], type); cells.h[neibCellId] < 20 ? 0 : cells.s[neibCellId] ? Math.max(20 - cells.s[neibCellId], 0) : 5000;
const heightCost = getHeightCost(pack.features[cells.f[e]], cells.h[e], type); const biomeCost = getBiomeCost(biome, cells.biome[neibCellId], type);
const riverCost = getRiverCost(cells.r[e], e, type); const heightCost = getHeightCost(pack.features[cells.f[neibCellId]], cells.h[neibCellId], type);
const typeCost = getTypeCost(cells.t[e], type); const riverCost = getRiverCost(cells.r[neibCellId], neibCellId, type);
const typeCost = getTypeCost(cells.t[neibCellId], type);
const cellCost = Math.max(cultureCost + populationCost + biomeCost + heightCost + riverCost + typeCost, 0); const cellCost = Math.max(cultureCost + populationCost + biomeCost + heightCost + riverCost + typeCost, 0);
const totalCost = p + 10 + cellCost / states[s].expansionism; const totalCost = priority + 10 + cellCost / states[stateId].expansionism;
if (totalCost > neutral) return; if (totalCost > neutral) return;
if (!cost[e] || totalCost < cost[e]) { if (!cost[neibCellId] || totalCost < cost[neibCellId]) {
if (cells.h[e] >= 20) cells.state[e] = s; // assign state to cell if (cells.h[neibCellId] >= 20) cells.state[neibCellId] = stateId; // assign state to cell
cost[e] = totalCost; cost[neibCellId] = totalCost;
queue.queue({e, p: totalCost, s, b});
queue.push({cellId: neibCellId, stateId, biome}, totalCost);
} }
}); });
} }
@ -571,24 +575,24 @@ window.BurgsAndStates = (function () {
]; // right point ]; // right point
// connect leftmost and rightmost points with shortest path // connect leftmost and rightmost points with shortest path
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p}); const queue = new FlatQueue();
const cost = [], const cost = [];
from = []; const from = [];
queue.queue({e: start, p: 0}); queue.push(start, 0);
while (queue.length) { while (queue.length) {
const next = queue.dequeue(), const priority = queue.peekValue();
n = next.e, const next = queue.pop();
p = next.p;
if (n === end) break;
for (const v of c.v[n]) { if (next === end) break;
if (v === -1) continue;
const totalCost = p + (inside[v] ? 1 : 100); for (const vertex of c.v[next]) {
if (from[v] || totalCost >= cost[v]) continue; if (vertex === -1) continue;
cost[v] = totalCost; const totalCost = priority + (inside[vertex] ? 1 : 100);
from[v] = n; if (from[vertex] || totalCost >= cost[vertex]) continue;
queue.queue({e: v, p: totalCost}); cost[vertex] = totalCost;
from[vertex] = next;
queue.push(vertex, totalCost);
} }
} }
@ -1212,33 +1216,34 @@ window.BurgsAndStates = (function () {
}); });
// expand generated provinces // expand generated provinces
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p}); const queue = new FlatQueue();
const cost = []; const cost = [];
provinces.forEach(function (p) { provinces.forEach(province => {
if (!p.i || p.removed) return; const {i, center, state, removed} = province;
cells.province[p.center] = p.i; if (!i || removed) return;
queue.queue({e: p.center, p: 0, province: p.i, state: p.state});
cost[p.center] = 1; cells.province[center] = i;
queue.push({cellId: center, provinceId: i, stateId: state}, 0);
cost[province.center] = 1;
}); });
while (queue.length) { while (queue.length) {
const next = queue.dequeue(), const priority = queue.peekValue();
n = next.e, const {cellId, provinceId, stateId} = queue.pop();
p = next.p,
province = next.province, cells.c[cellId].forEach(neibCellId => {
state = next.state; const land = cells.h[neibCellId] >= 20;
cells.c[n].forEach(function (e) { if (!land && !cells.t[neibCellId]) return; // cannot pass deep ocean
const land = cells.h[e] >= 20; if (land && cells.state[neibCellId] !== stateId) return;
if (!land && !cells.t[e]) return; // cannot pass deep ocean const evevation =
if (land && cells.state[e] !== state) return; cells.h[neibCellId] >= 70 ? 100 : cells.h[neibCellId] >= 50 ? 30 : cells.h[neibCellId] >= 20 ? 10 : 100;
const evevation = cells.h[e] >= 70 ? 100 : cells.h[e] >= 50 ? 30 : cells.h[e] >= 20 ? 10 : 100; const totalCost = priority + evevation;
const totalCost = p + evevation;
if (totalCost > max) return; if (totalCost > max) return;
if (!cost[e] || totalCost < cost[e]) { if (!cost[neibCellId] || totalCost < cost[neibCellId]) {
if (land) cells.province[e] = province; // assign province to a cell if (land) cells.province[neibCellId] = provinceId;
cost[e] = totalCost; cost[neibCellId] = totalCost;
queue.queue({e, p: totalCost, province, state}); queue.push({cellId: neibCellId, provinceId, stateId}, totalCost);
} }
}); });
} }
@ -1282,26 +1287,26 @@ window.BurgsAndStates = (function () {
cells.province[center] = province; cells.province[center] = province;
// expand province // expand province
const cost = []; const costs = [];
cost[center] = 1; costs[center] = 1;
queue.queue({e: center, p: 0}); queue.push(center, 0);
while (queue.length) {
const next = queue.dequeue(),
n = next.e,
p = next.p;
cells.c[n].forEach(function (e) { while (queue.length) {
if (cells.province[e]) return; const priority = queue.peekValue();
const land = cells.h[e] >= 20; const next = queue.pop();
if (cells.state[e] && cells.state[e] !== s.i) return;
const ter = land ? (cells.state[e] === s.i ? 3 : 20) : cells.t[e] ? 10 : 30; cells.c[next].forEach(neibCellId => {
const totalCost = p + ter; if (cells.province[neibCellId]) return;
const land = cells.h[neibCellId] >= 20;
if (cells.state[neibCellId] && cells.state[neibCellId] !== s.i) return;
const cost = land ? (cells.state[neibCellId] === s.i ? 3 : 20) : cells.t[neibCellId] ? 10 : 30;
const totalCost = priority + cost;
if (totalCost > max) return; if (totalCost > max) return;
if (!cost[e] || totalCost < cost[e]) { if (!costs[neibCellId] || totalCost < costs[neibCellId]) {
if (land && cells.state[e] === s.i) cells.province[e] = province; // assign province to a cell if (land && cells.state[neibCellId] === s.i) cells.province[neibCellId] = province; // assign province to a cell
cost[e] = totalCost; costs[neibCellId] = totalCost;
queue.queue({e, p: totalCost}); queue.push(neibCellId, totalCost);
} }
}); });
} }
@ -1344,9 +1349,11 @@ window.BurgsAndStates = (function () {
// check if there is a land way within the same state between two cells // check if there is a land way within the same state between two cells
function isPassable(from, to) { function isPassable(from, to) {
if (cells.f[from] !== cells.f[to]) return false; // on different islands if (cells.f[from] !== cells.f[to]) return false; // on different islands
const queue = [from], const queue = [from];
used = new Uint8Array(cells.i.length),
state = cells.state[from]; const used = new Uint8Array(cells.i.length);
const state = cells.state[from];
while (queue.length) { while (queue.length) {
const current = queue.pop(); const current = queue.pop();
if (current === to) return true; // way is found if (current === to) return true; // way is found

View file

@ -1,3 +1,5 @@
import FlatQueue from "flatqueue";
import {TIME} from "config/logging"; import {TIME} from "config/logging";
import {getColors} from "utils/colorUtils"; import {getColors} from "utils/colorUtils";
import {rn, minmax} from "utils/numberUtils"; import {rn, minmax} from "utils/numberUtils";
@ -481,36 +483,37 @@ window.Cultures = (function () {
TIME && console.time("expandCultures"); TIME && console.time("expandCultures");
cells = pack.cells; cells = pack.cells;
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p}); const queue = new FlatQueue();
pack.cultures.forEach(function (c) { pack.cultures.forEach(culture => {
if (!c.i || c.removed) return; if (!culture.i || culture.removed) return;
queue.queue({e: c.center, p: 0, c: c.i}); queue.push({cellId: culture.center, cultureId: culture.i}, 0);
}); });
const neutral = (cells.i.length / 5000) * 3000 * neutralInput.value; // limit cost for culture growth const neutral = (cells.i.length / 5000) * 3000 * neutralInput.value; // limit cost for culture growth
const cost = []; const cost = [];
while (queue.length) { while (queue.length) {
const next = queue.dequeue(), const priority = queue.peekValue();
n = next.e, const {cellId, cultureId} = queue.pop();
p = next.p,
c = next.c; const type = pack.cultures[cultureId].type;
const type = pack.cultures[c].type; cells.c[cellId].forEach(neibCellId => {
cells.c[n].forEach(function (e) { const biome = cells.biome[neibCellId];
const biome = cells.biome[e]; const biomeCost = getBiomeCost(cultureId, biome, type);
const biomeCost = getBiomeCost(c, biome, type); const biomeChangeCost = biome === cells.biome[cellId] ? 0 : 20; // penalty on biome change
const biomeChangeCost = biome === cells.biome[n] ? 0 : 20; // penalty on biome change const heightCost = getHeightCost(neibCellId, cells.h[neibCellId], type);
const heightCost = getHeightCost(e, cells.h[e], type); const riverCost = getRiverCost(cells.r[neibCellId], neibCellId, type);
const riverCost = getRiverCost(cells.r[e], e, type); const typeCost = getTypeCost(cells.t[neibCellId], type);
const typeCost = getTypeCost(cells.t[e], type);
const totalCost = const totalCost =
p + (biomeCost + biomeChangeCost + heightCost + riverCost + typeCost) / pack.cultures[c].expansionism; priority +
(biomeCost + biomeChangeCost + heightCost + riverCost + typeCost) / pack.cultures[cultureId].expansionism;
if (totalCost > neutral) return; if (totalCost > neutral) return;
if (!cost[e] || totalCost < cost[e]) { if (!cost[neibCellId] || totalCost < cost[neibCellId]) {
if (cells.s[e] > 0) cells.culture[e] = c; // assign culture to populated cell if (cells.s[neibCellId] > 0) cells.culture[neibCellId] = cultureId; // assign culture to populated cell
cost[e] = totalCost; cost[neibCellId] = totalCost;
queue.queue({e, p: totalCost, c}); queue.push({cellId: neibCellId, cultureId}, totalCost);
} }
}); });
} }

View file

@ -1,3 +1,5 @@
import FlatQueue from "flatqueue";
import {TIME} from "config/logging"; import {TIME} from "config/logging";
import {findAll} from "utils/graphUtils"; import {findAll} from "utils/graphUtils";
import {unique} from "utils/arrayUtils"; import {unique} from "utils/arrayUtils";
@ -574,50 +576,48 @@ window.Religions = (function () {
// growth algorithm to assign cells to religions // growth algorithm to assign cells to religions
const expandReligions = function () { const expandReligions = function () {
const cells = pack.cells, const {cells, religions} = pack;
religions = pack.religions; const queue = new FlatQueue();
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
const cost = []; const cost = [];
religions religions
.filter(r => r.type === "Organized" || r.type === "Cult") .filter(r => r.type === "Organized" || r.type === "Cult")
.forEach(r => { .forEach(({i, center, culture}) => {
cells.religion[r.center] = r.i; cells.religion[center] = i;
queue.queue({e: r.center, p: 0, r: r.i, s: cells.state[r.center], c: r.culture}); const stateId = cells.state[center];
cost[r.center] = 1; queue.push({cellId: center, religionId: i, stateId, cultureId: culture}, 0);
cost[center] = 1;
}); });
const neutral = (cells.i.length / 5000) * 200 * gauss(1, 0.3, 0.2, 2, 2) * neutralInput.value; // limit cost for organized religions growth const neutral = (cells.i.length / 5000) * 200 * gauss(1, 0.3, 0.2, 2, 2) * neutralInput.value; // limit cost for organized religions growth
const popCost = d3.max(cells.pop) / 3; // enougth population to spered religion without penalty const popCost = d3.max(cells.pop) / 3; // enough population to spered religion without penalty
while (queue.length) { while (queue.length) {
const next = queue.dequeue(), const priority = queue.peekValue();
n = next.e, const {cellId, religionId, stateId, cultureId, biome} = queue.pop();
p = next.p,
r = next.r,
c = next.c,
s = next.s;
const expansion = religions[r].expansion;
cells.c[n].forEach(function (e) { const expansion = religions[religionId].expansion;
if (expansion === "culture" && c !== cells.culture[e]) return;
if (expansion === "state" && s !== cells.state[e]) return;
const cultureCost = c !== cells.culture[e] ? 10 : 0; cells.c[cellId].forEach(neibCellId => {
const stateCost = s !== cells.state[e] ? 10 : 0; if (expansion === "culture" && cultureId !== cells.culture[neibCellId]) return;
const biomeCost = cells.road[e] ? 1 : biomesData.cost[cells.biome[e]]; if (expansion === "state" && stateId !== cells.state[neibCellId]) return;
const populationCost = Math.max(rn(popCost - cells.pop[e]), 0);
const heightCost = Math.max(cells.h[e], 20) - 20; const cultureCost = cultureId !== cells.culture[neibCellId] ? 10 : 0;
const waterCost = cells.h[e] < 20 ? (cells.road[e] ? 50 : 1000) : 0; const stateCost = stateId !== cells.state[neibCellId] ? 10 : 0;
const biomeCost = cells.road[neibCellId] ? 1 : biomesData.cost[cells.biome[neibCellId]];
const populationCost = Math.max(rn(popCost - cells.pop[neibCellId]), 0);
const heightCost = Math.max(cells.h[neibCellId], 20) - 20;
const waterCost = cells.h[neibCellId] < 20 ? (cells.road[neibCellId] ? 50 : 1000) : 0;
const totalCost = const totalCost =
p + priority +
(cultureCost + stateCost + biomeCost + populationCost + heightCost + waterCost) / religions[r].expansionism; (cultureCost + stateCost + biomeCost + populationCost + heightCost + waterCost) /
religions[religionId].expansionism;
if (totalCost > neutral) return; if (totalCost > neutral) return;
if (!cost[e] || totalCost < cost[e]) { if (!cost[neibCellId] || totalCost < cost[neibCellId]) {
if (cells.h[e] >= 20 && cells.culture[e]) cells.religion[e] = r; // assign religion to cell if (cells.h[neibCellId] >= 20 && cells.culture[neibCellId]) cells.religion[neibCellId] = religionId; // assign religion to cell
cost[e] = totalCost; cost[neibCellId] = totalCost;
queue.queue({e, p: totalCost, r, c, s}); queue.push({cellId: neibCellId, religionId, cultureId, stateId}, totalCost);
} }
}); });
} }
@ -625,43 +625,40 @@ window.Religions = (function () {
// growth algorithm to assign cells to heresies // growth algorithm to assign cells to heresies
const expandHeresies = function () { const expandHeresies = function () {
const cells = pack.cells, const {cells, religions} = pack;
religions = pack.religions; const queue = new FlatQueue();
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
const cost = []; const cost = [];
religions religions
.filter(r => r.type === "Heresy") .filter(r => r.type === "Heresy")
.forEach(r => { .forEach(({i, center}) => {
const b = cells.religion[r.center]; // "base" religion id const baseReligionId = cells.religion[center];
cells.religion[r.center] = r.i; // heresy id cells.religion[center] = i; // heresy id
queue.queue({e: r.center, p: 0, r: r.i, b}); queue.push({cellId: center, religionId: i, baseReligionId}, 0);
cost[r.center] = 1; cost[center] = 1;
}); });
const neutral = (cells.i.length / 5000) * 500 * neutralInput.value; // limit cost for heresies growth const neutral = (cells.i.length / 5000) * 500 * neutralInput.value; // limit cost for heresies growth
while (queue.length) { while (queue.length) {
const next = queue.dequeue(), const priority = queue.peekValue();
n = next.e, const {cellId, religionId, baseReligionId} = queue.pop();
p = next.p,
r = next.r,
b = next.b;
cells.c[n].forEach(function (e) { cells.c[cellId].forEach(neibCellId => {
const religionCost = cells.religion[e] === b ? 0 : 2000; const religionCost = cells.religion[neibCellId] === baseReligionId ? 0 : 2000;
const biomeCost = cells.road[e] ? 0 : biomesData.cost[cells.biome[e]]; const biomeCost = cells.road[neibCellId] ? 0 : biomesData.cost[cells.biome[neibCellId]];
const heightCost = Math.max(cells.h[e], 20) - 20; const heightCost = Math.max(cells.h[neibCellId], 20) - 20;
const waterCost = cells.h[e] < 20 ? (cells.road[e] ? 50 : 1000) : 0; const waterCost = cells.h[neibCellId] < 20 ? (cells.road[neibCellId] ? 50 : 1000) : 0;
const totalCost = const totalCost =
p + (religionCost + biomeCost + heightCost + waterCost) / Math.max(religions[r].expansionism, 0.1); priority +
(religionCost + biomeCost + heightCost + waterCost) / Math.max(religions[religionId].expansionism, 0.1);
if (totalCost > neutral) return; if (totalCost > neutral) return;
if (!cost[e] || totalCost < cost[e]) { if (!cost[neibCellId] || totalCost < cost[neibCellId]) {
if (cells.h[e] >= 20 && cells.culture[e]) cells.religion[e] = r; // assign religion to cell if (cells.h[neibCellId] >= 20 && cells.culture[neibCellId]) cells.religion[neibCellId] = religionId; // assign religion to cell
cost[e] = totalCost; cost[neibCellId] = totalCost;
queue.queue({e, p: totalCost, r}); queue.push({cellId: neibCellId, religionId, baseReligionId}, totalCost);
} }
}); });
} }

View file

@ -1,3 +1,5 @@
import FlatQueue from "flatqueue";
import {TIME} from "config/logging"; import {TIME} from "config/logging";
import {findCell} from "utils/graphUtils"; import {findCell} from "utils/graphUtils";
import {last} from "utils/arrayUtils"; import {last} from "utils/arrayUtils";
@ -169,33 +171,33 @@ window.Routes = (function () {
// Find a land path to a specific cell (exit), to a closest road (toRoad), or to all reachable cells (null, null) // Find a land path to a specific cell (exit), to a closest road (toRoad), or to all reachable cells (null, null)
function findLandPath(start, exit = null, toRoad = null) { function findLandPath(start, exit = null, toRoad = null) {
const cells = pack.cells; const cells = pack.cells;
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p}); const queue = new FlatQueue();
const cost = [], const cost = [];
from = []; const from = [];
queue.queue({e: start, p: 0}); queue.push(start, 0);
while (queue.length) { while (queue.length) {
const next = queue.dequeue(), const priority = queue.peekValue();
n = next.e, const next = queue.pop();
p = next.p;
if (toRoad && cells.road[n]) return [from, n];
for (const c of cells.c[n]) { if (toRoad && cells.road[next]) return [from, next];
if (cells.h[c] < 20) continue; // ignore water cells
const stateChangeCost = cells.state && cells.state[c] !== cells.state[n] ? 400 : 0; // trails tend to lay within the same state for (const neibCellId of cells.c[next]) {
const habitability = biomesData.habitability[cells.biome[c]]; if (cells.h[neibCellId] < 20) continue; // ignore water cells
const stateChangeCost = cells.state && cells.state[neibCellId] !== cells.state[next] ? 400 : 0; // trails tend to lay within the same state
const habitability = biomesData.habitability[cells.biome[neibCellId]];
if (!habitability) continue; // avoid inhabitable cells (eg. lava, glacier) if (!habitability) continue; // avoid inhabitable cells (eg. lava, glacier)
const habitedCost = habitability ? Math.max(100 - habitability, 0) : 400; // routes tend to lay within populated areas const habitedCost = habitability ? Math.max(100 - habitability, 0) : 400; // routes tend to lay within populated areas
const heightChangeCost = Math.abs(cells.h[c] - cells.h[n]) * 10; // routes tend to avoid elevation changes const heightChangeCost = Math.abs(cells.h[neibCellId] - cells.h[next]) * 10; // routes tend to avoid elevation changes
const heightCost = cells.h[c] > 80 ? cells.h[c] : 0; // routes tend to avoid mountainous areas const heightCost = cells.h[neibCellId] > 80 ? cells.h[neibCellId] : 0; // routes tend to avoid mountainous areas
const cellCoast = 10 + stateChangeCost + habitedCost + heightChangeCost + heightCost; const cellCoast = 10 + stateChangeCost + habitedCost + heightChangeCost + heightCost;
const totalCost = p + (cells.road[c] || cells.burg[c] ? cellCoast / 3 : cellCoast); const totalCost = priority + (cells.road[neibCellId] || cells.burg[neibCellId] ? cellCoast / 3 : cellCoast);
if (from[c] || totalCost >= cost[c]) continue; if (from[neibCellId] || totalCost >= cost[neibCellId]) continue;
from[c] = n; from[neibCellId] = next;
if (c === exit) return [from, exit]; if (neibCellId === exit) return [from, exit];
cost[c] = totalCost; cost[neibCellId] = totalCost;
queue.queue({e: c, p: totalCost}); queue.push(neibCellId, totalCost);
} }
} }
return [from, exit]; return [from, exit];
@ -247,32 +249,36 @@ window.Routes = (function () {
// find water paths // find water paths
function findOceanPath(start, exit = null, toRoute = null) { function findOceanPath(start, exit = null, toRoute = null) {
const cells = pack.cells, const cells = pack.cells;
temp = grid.cells.temp; const temp = grid.cells.temp;
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
const cost = [], const queue = new FlatQueue();
from = []; const cost = [];
queue.queue({e: start, p: 0}); const from = [];
queue.push(start, 0);
while (queue.length) { while (queue.length) {
const next = queue.dequeue(), const priority = queue.peekValue();
n = next.e, const next = queue.pop();
p = next.p;
if (toRoute && n !== start && cells.road[n]) return [from, n, true];
for (const c of cells.c[n]) { if (toRoute && next !== start && cells.road[next]) return [from, next, true];
if (c === exit) {
from[c] = n; for (const neibCellId of cells.c[next]) {
if (neibCellId === exit) {
from[neibCellId] = next;
return [from, exit, true]; return [from, exit, true];
} }
if (cells.h[c] >= 20) continue; // ignore land cells
if (temp[cells.g[c]] <= -5) continue; // ignore cells with term <= -5
const dist2 = (cells.p[c][1] - cells.p[n][1]) ** 2 + (cells.p[c][0] - cells.p[n][0]) ** 2;
const totalCost = p + (cells.road[c] ? 1 + dist2 / 2 : dist2 + (cells.t[c] ? 1 : 100));
if (from[c] || totalCost >= cost[c]) continue; if (cells.h[neibCellId] >= 20) continue; // ignore land cells
(from[c] = n), (cost[c] = totalCost); if (temp[cells.g[neibCellId]] <= -5) continue; // ignore cells with term <= -5
queue.queue({e: c, p: totalCost});
const dist2 =
(cells.p[neibCellId][1] - cells.p[next][1]) ** 2 + (cells.p[neibCellId][0] - cells.p[next][0]) ** 2;
const totalCost = priority + (cells.road[neibCellId] ? 1 + dist2 / 2 : dist2 + (cells.t[neibCellId] ? 1 : 100));
if (from[neibCellId] || totalCost >= cost[neibCellId]) continue;
(from[neibCellId] = next), (cost[neibCellId] = totalCost);
queue.push(neibCellId, totalCost);
} }
} }
return [from, exit, false]; return [from, exit, false];

View file

@ -128,6 +128,11 @@ esbuild@^0.14.43:
esbuild-windows-64 "0.14.47" esbuild-windows-64 "0.14.47"
esbuild-windows-arm64 "0.14.47" esbuild-windows-arm64 "0.14.47"
flatqueue@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/flatqueue/-/flatqueue-2.0.3.tgz#dd23673cb478e25d5a9acb9dcbb85c89e9e8e64e"
integrity sha512-RZCWZNkmxzUOh8jqEcEGZCycb3B8KAfpPwg3H//cURasunYxsg1eIvE+QDSjX+ZPHTIVfINfK1aLTrVKKO0i4g==
fsevents@~2.3.2: fsevents@~2.3.2:
version "2.3.2" version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"