mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 01:41:22 +01:00
refactor(es modules): replace PrioriryQueue with faster FlatQueue
This commit is contained in:
parent
6a7ec6513a
commit
6ea6e52b75
9 changed files with 267 additions and 242 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
src/libs/priority-queue.min.js
vendored
1
src/libs/priority-queue.min.js
vendored
File diff suppressed because one or more lines are too long
100
src/main.js
100
src/main.js
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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];
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue