diff --git a/index.html b/index.html
index d6c1ecfa..59b2f557 100644
--- a/index.html
+++ b/index.html
@@ -7659,7 +7659,6 @@
-
diff --git a/package.json b/package.json
index 7ea67ccc..b8ea17d3 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"vite": "^3.0.0-beta.3"
},
"dependencies": {
+ "flatqueue": "^2.0.3",
"lineclip": "^1.1.5",
"polylabel": "^1.1.0"
}
diff --git a/src/libs/priority-queue.min.js b/src/libs/priority-queue.min.js
deleted file mode 100644
index acf2506b..00000000
--- a/src/libs/priority-queue.min.js
+++ /dev/null
@@ -1 +0,0 @@
-!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).PriorityQueue=t()}}(function(){return function t(e,i,r){function o(n,s){if(!i[n]){if(!e[n]){var h="function"==typeof require&&require;if(!s&&h)return h(n,!0);if(a)return a(n,!0);var u=new Error("Cannot find module '"+n+"'");throw u.code="MODULE_NOT_FOUND",u}var p=i[n]={exports:{}};e[n][0].call(p.exports,function(t){var i=e[n][1][t];return o(i||t)},p,p.exports,t,e,i,r)}return i[n].exports}for(var a="function"==typeof require&&require,n=0;n>>1],e)>=0?o=a+1:r=a;return o},e.exports=function(){function t(t){var e;this.options=t,this.comparator=this.options.comparator,this.data=(null!=(e=this.options.initialValues)?e.slice(0):void 0)||[],this.data.sort(this.comparator).reverse()}return t.prototype.queue=function(t){var e;e=r(this.data,t,this.comparator),this.data.splice(e,0,t)},t.prototype.dequeue=function(){return this.data.pop()},t.prototype.peek=function(){return this.data[this.data.length-1]},t.prototype.clear=function(){this.data.length=0},t}()},{}],4:[function(t,e,i){e.exports=function(){function t(t){var e,i,r,o,a,n,s,h;for(this.comparator=(null!=t?t.comparator:void 0)||function(t,e){return t-e},this.pageSize=(null!=t?t.pageSize:void 0)||512,this.length=0,s=0;1<a;0<=a?++i:--i)e.push(null);if(this._memory=[],this._mask=this.pageSize-1,t.initialValues)for(r=0,o=(n=t.initialValues).length;r0&&(this._write(1,e),this._bubbleDown(1,e)),t},t.prototype.peek=function(){return this._read(1)},t.prototype.clear=function(){this.length=0,this._memory.length=0},t.prototype._write=function(t,e){var i;for(i=t>>this._shift;i>=this._memory.length;)this._memory.push(this._emptyMemoryPageTemplate.slice(0));return this._memory[i][t&this._mask]=e},t.prototype._read=function(t){return this._memory[t>>this._shift][t&this._mask]},t.prototype._bubbleUp=function(t,e){var i,r,o,a;for(i=this.comparator;t>1&&(r=t&this._mask,t3?o=t&~this._mask|r>>1:r<2?(o=t-this.pageSize>>this._shift,o+=o&~(this._mask>>1),o|=this.pageSize>>1):o=t-2,!(i(a=this._read(o),e)<0));)this._write(o,e),this._write(t,a),t=o},t.prototype._bubbleDown=function(t,e){var i,r,o,a,n;for(n=this.comparator;tthis._mask&&!(t&this._mask-1)?i=r=t+2:t&this.pageSize>>1?(i=(t&~this._mask)>>1,r=(i=(i|=t&this._mask>>1)+1<0)for(t=e=1,i=this.data.length;1<=i?ei;t=1<=i?++e:--e)this._bubbleUp(t)},t.prototype.queue=function(t){this.data.push(t),this._bubbleUp(this.data.length-1)},t.prototype.dequeue=function(){var t,e;return e=this.data[0],t=this.data.pop(),this.data.length>0&&(this.data[0]=t,this._bubbleDown(0)),e},t.prototype.peek=function(){return this.data[0]},t.prototype.clear=function(){this.length=0,this.data.length=0},t.prototype._bubbleUp=function(t){for(var e,i;t>0&&(e=t-1>>>1,this.comparator(this.data[t],this.data[e])<0);)i=this.data[e],this.data[e]=this.data[t],this.data[t]=i,t=e},t.prototype._bubbleDown=function(t){var e,i,r,o,a;for(e=this.data.length-1;o=(i=1+(t<<1))+1,r=t,i<=e&&this.comparator(this.data[i],this.data[r])<0&&(r=i),o<=e&&this.comparator(this.data[o],this.data[r])<0&&(r=o),r!==t;)a=this.data[r],this.data[r]=this.data[t],this.data[t]=a,t=r},t}()},{}]},{},[1])(1)});
\ No newline at end of file
diff --git a/src/main.js b/src/main.js
index 6779428b..5ade82e0 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,6 +1,8 @@
// Azgaar (azgaar.fmg@yandex.com). Minsk, 2017-2022. MIT License
// https://github.com/Azgaar/Fantasy-Map-Generator
+import FlatQueue from "flatqueue";
+
import "./components";
import {ERROR, INFO, TIME, WARN} from "./config/logging";
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
if (!burg) return;
- const cellsArray = [],
- cost = [],
- power = rand(20, 37);
- const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
- queue.queue({e: burg.cell, p: 0});
+ const cellsArray = [];
+ const costs = [];
+ const power = rand(20, 37);
+
+ const queue = new FlatQueue();
+ queue.push(burg.cell, 0);
while (queue.length) {
- const next = queue.dequeue();
- if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e);
- used[next.e] = 1;
+ const priority = queue.peekValue();
+ const next = queue.pop();
- cells.c[next.e].forEach(function (e) {
- const r = cells.road[next.e];
- const c = r ? Math.max(10 - r, 1) : 100;
- const p = next.p + c;
- if (p > power) return;
+ if (cells.burg[next] || cells.pop[next]) cellsArray.push(next);
+ used[next] = 1;
- if (!cost[e] || p < cost[e]) {
- cost[e] = p;
- queue.queue({e, p});
+ cells.c[next].forEach(neibCellId => {
+ const roadValue = cells.road[next];
+ 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
if (!burg) return;
- const cellsArray = [],
- cost = [],
- power = rand(5, 25);
- const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
- queue.queue({e: burg.cell, p: 0});
+ const cellsArray = [];
+ const costs = [];
+ const power = rand(5, 25);
+
+ const queue = new FlatQueue();
+ queue.push(burg.cell, 0);
while (queue.length) {
- const next = queue.dequeue();
- if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e);
- used[next.e] = 1;
+ const priority = queue.peekValue();
+ const next = queue.pop();
- cells.c[next.e].forEach(function (e) {
- const c = rand(1, 10);
- const p = next.p + c;
- if (p > power) return;
+ if (cells.burg[next] || cells.pop[next]) cellsArray.push(next);
+ used[next] = 1;
- if (!cost[e] || p < cost[e]) {
- cost[e] = p;
- queue.queue({e, p});
+ cells.c[next].forEach(neibCellId => {
+ const cost = rand(1, 10);
+ 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");
const name = note[0] ? note[0].name.replace(" Volcano", "") + " Eruption" : "Volcano Eruption";
- const cellsArray = [],
- queue = [cell],
- power = rand(10, 30);
+ const cellsArray = [];
+ const queue = [cell];
+ const power = rand(10, 30);
while (queue.length) {
const q = P(0.5) ? queue.shift() : queue.pop();
@@ -1545,9 +1553,9 @@ function addZones(number = 1) {
if (!roads.length) return;
const cell = +ra(roads);
- const cellsArray = [],
- queue = [cell],
- power = rand(3, 15);
+ const cellsArray = [];
+ const queue = [cell];
+ const power = rand(3, 15);
while (queue.length) {
const q = P(0.3) ? queue.shift() : queue.pop();
@@ -1570,9 +1578,9 @@ function addZones(number = 1) {
if (!elevated.length) return;
const cell = ra(elevated);
- const cellsArray = [],
- queue = [cell],
- power = rand(3, 15);
+ const cellsArray = [];
+ const queue = [cell];
+ const power = rand(3, 15);
while (queue.length) {
const q = queue.pop();
@@ -1602,9 +1610,9 @@ function addZones(number = 1) {
const cell = +ra(rivers),
river = cells.r[cell];
- const cellsArray = [],
- queue = [cell],
- power = rand(5, 30);
+ const cellsArray = [];
+ const queue = [cell];
+ const power = rand(5, 30);
while (queue.length) {
const q = queue.pop();
@@ -1627,9 +1635,9 @@ function addZones(number = 1) {
if (!coastal.length) return;
const cell = +ra(coastal);
- const cellsArray = [],
- queue = [cell],
- power = rand(10, 30);
+ const cellsArray = [];
+ const queue = [cell];
+ const power = rand(10, 30);
while (queue.length) {
const q = queue.shift();
diff --git a/src/modules/burgs-and-states.js b/src/modules/burgs-and-states.js
index 6fe4a684..9d3c5eb2 100644
--- a/src/modules/burgs-and-states.js
+++ b/src/modules/burgs-and-states.js
@@ -1,3 +1,5 @@
+import FlatQueue from "flatqueue";
+
import {TIME} from "config/logging";
import {layerIsOn} from "layers";
import {Voronoi} from "/src/modules/voronoi";
@@ -375,44 +377,46 @@ window.BurgsAndStates = (function () {
const {cells, states, cultures, burgs} = pack;
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 neutral = (cells.i.length / 5000) * 2500 * neutralInput.value * statesNeutral; // limit cost for state growth
states
.filter(s => s.i && !s.removed)
- .forEach(s => {
- const capitalCell = burgs[s.capital].cell;
- cells.state[capitalCell] = s.i;
- const cultureCenter = cultures[s.culture].center;
- const b = cells.biome[cultureCenter]; // state native biome
- queue.queue({e: s.center, p: 0, s: s.i, b});
- cost[s.center] = 1;
+ .forEach(state => {
+ const capitalCell = burgs[state.capital].cell;
+ cells.state[capitalCell] = state.i;
+ const cultureCenter = cultures[state.culture].center;
+ const biome = cells.biome[cultureCenter]; // state native biome
+ queue.push({cellId: state.center, stateId: state.i, b: biome}, 0);
+ cost[state.center] = 1;
});
while (queue.length) {
- const next = queue.dequeue();
- const {e, p, s, b} = next;
- const {type, culture} = states[s];
+ const priority = queue.peekValue();
+ const {cellId, stateId, biome} = queue.pop();
+ const {type, culture} = states[stateId];
- cells.c[e].forEach(e => {
- if (cells.state[e] && e === states[cells.state[e]].center) return; // do not overwrite capital cells
+ cells.c[cellId].forEach(neibCellId => {
+ 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 populationCost = cells.h[e] < 20 ? 0 : cells.s[e] ? Math.max(20 - cells.s[e], 0) : 5000;
- const biomeCost = getBiomeCost(b, cells.biome[e], type);
- const heightCost = getHeightCost(pack.features[cells.f[e]], cells.h[e], type);
- const riverCost = getRiverCost(cells.r[e], e, type);
- const typeCost = getTypeCost(cells.t[e], type);
+ const cultureCost = culture === cells.culture[neibCellId] ? -9 : 100;
+ const populationCost =
+ cells.h[neibCellId] < 20 ? 0 : cells.s[neibCellId] ? Math.max(20 - cells.s[neibCellId], 0) : 5000;
+ const biomeCost = getBiomeCost(biome, cells.biome[neibCellId], type);
+ const heightCost = getHeightCost(pack.features[cells.f[neibCellId]], cells.h[neibCellId], 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 totalCost = p + 10 + cellCost / states[s].expansionism;
+ const totalCost = priority + 10 + cellCost / states[stateId].expansionism;
if (totalCost > neutral) return;
- if (!cost[e] || totalCost < cost[e]) {
- if (cells.h[e] >= 20) cells.state[e] = s; // assign state to cell
- cost[e] = totalCost;
- queue.queue({e, p: totalCost, s, b});
+ if (!cost[neibCellId] || totalCost < cost[neibCellId]) {
+ if (cells.h[neibCellId] >= 20) cells.state[neibCellId] = stateId; // assign state to cell
+ cost[neibCellId] = totalCost;
+
+ queue.push({cellId: neibCellId, stateId, biome}, totalCost);
}
});
}
@@ -571,24 +575,24 @@ window.BurgsAndStates = (function () {
]; // right point
// connect leftmost and rightmost points with shortest path
- const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
- const cost = [],
- from = [];
- queue.queue({e: start, p: 0});
+ const queue = new FlatQueue();
+ const cost = [];
+ const from = [];
+ queue.push(start, 0);
while (queue.length) {
- const next = queue.dequeue(),
- n = next.e,
- p = next.p;
- if (n === end) break;
+ const priority = queue.peekValue();
+ const next = queue.pop();
- for (const v of c.v[n]) {
- if (v === -1) continue;
- const totalCost = p + (inside[v] ? 1 : 100);
- if (from[v] || totalCost >= cost[v]) continue;
- cost[v] = totalCost;
- from[v] = n;
- queue.queue({e: v, p: totalCost});
+ if (next === end) break;
+
+ for (const vertex of c.v[next]) {
+ if (vertex === -1) continue;
+ const totalCost = priority + (inside[vertex] ? 1 : 100);
+ if (from[vertex] || totalCost >= cost[vertex]) continue;
+ cost[vertex] = totalCost;
+ from[vertex] = next;
+ queue.push(vertex, totalCost);
}
}
@@ -1212,33 +1216,34 @@ window.BurgsAndStates = (function () {
});
// expand generated provinces
- const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
+ const queue = new FlatQueue();
const cost = [];
- provinces.forEach(function (p) {
- if (!p.i || p.removed) return;
- cells.province[p.center] = p.i;
- queue.queue({e: p.center, p: 0, province: p.i, state: p.state});
- cost[p.center] = 1;
+ provinces.forEach(province => {
+ const {i, center, state, removed} = province;
+ if (!i || removed) return;
+
+ cells.province[center] = i;
+ queue.push({cellId: center, provinceId: i, stateId: state}, 0);
+ cost[province.center] = 1;
});
while (queue.length) {
- const next = queue.dequeue(),
- n = next.e,
- p = next.p,
- province = next.province,
- state = next.state;
- cells.c[n].forEach(function (e) {
- const land = cells.h[e] >= 20;
- if (!land && !cells.t[e]) return; // cannot pass deep ocean
- if (land && cells.state[e] !== state) return;
- const evevation = cells.h[e] >= 70 ? 100 : cells.h[e] >= 50 ? 30 : cells.h[e] >= 20 ? 10 : 100;
- const totalCost = p + evevation;
+ const priority = queue.peekValue();
+ const {cellId, provinceId, stateId} = queue.pop();
+
+ cells.c[cellId].forEach(neibCellId => {
+ const land = cells.h[neibCellId] >= 20;
+ if (!land && !cells.t[neibCellId]) return; // cannot pass deep ocean
+ if (land && cells.state[neibCellId] !== stateId) return;
+ const evevation =
+ cells.h[neibCellId] >= 70 ? 100 : cells.h[neibCellId] >= 50 ? 30 : cells.h[neibCellId] >= 20 ? 10 : 100;
+ const totalCost = priority + evevation;
if (totalCost > max) return;
- if (!cost[e] || totalCost < cost[e]) {
- if (land) cells.province[e] = province; // assign province to a cell
- cost[e] = totalCost;
- queue.queue({e, p: totalCost, province, state});
+ if (!cost[neibCellId] || totalCost < cost[neibCellId]) {
+ if (land) cells.province[neibCellId] = provinceId;
+ cost[neibCellId] = totalCost;
+ queue.push({cellId: neibCellId, provinceId, stateId}, totalCost);
}
});
}
@@ -1282,26 +1287,26 @@ window.BurgsAndStates = (function () {
cells.province[center] = province;
// expand province
- const cost = [];
- cost[center] = 1;
- queue.queue({e: center, p: 0});
- while (queue.length) {
- const next = queue.dequeue(),
- n = next.e,
- p = next.p;
+ const costs = [];
+ costs[center] = 1;
+ queue.push(center, 0);
- cells.c[n].forEach(function (e) {
- if (cells.province[e]) return;
- const land = cells.h[e] >= 20;
- 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;
- const totalCost = p + ter;
+ while (queue.length) {
+ const priority = queue.peekValue();
+ const next = queue.pop();
+
+ cells.c[next].forEach(neibCellId => {
+ 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 (!cost[e] || totalCost < cost[e]) {
- if (land && cells.state[e] === s.i) cells.province[e] = province; // assign province to a cell
- cost[e] = totalCost;
- queue.queue({e, p: totalCost});
+ if (!costs[neibCellId] || totalCost < costs[neibCellId]) {
+ if (land && cells.state[neibCellId] === s.i) cells.province[neibCellId] = province; // assign province to a cell
+ costs[neibCellId] = 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
function isPassable(from, to) {
if (cells.f[from] !== cells.f[to]) return false; // on different islands
- const queue = [from],
- used = new Uint8Array(cells.i.length),
- state = cells.state[from];
+ const queue = [from];
+
+ const used = new Uint8Array(cells.i.length);
+ const state = cells.state[from];
+
while (queue.length) {
const current = queue.pop();
if (current === to) return true; // way is found
diff --git a/src/modules/cultures-generator.js b/src/modules/cultures-generator.js
index 5bd38a75..18f21974 100644
--- a/src/modules/cultures-generator.js
+++ b/src/modules/cultures-generator.js
@@ -1,3 +1,5 @@
+import FlatQueue from "flatqueue";
+
import {TIME} from "config/logging";
import {getColors} from "utils/colorUtils";
import {rn, minmax} from "utils/numberUtils";
@@ -481,36 +483,37 @@ window.Cultures = (function () {
TIME && console.time("expandCultures");
cells = pack.cells;
- const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
- pack.cultures.forEach(function (c) {
- if (!c.i || c.removed) return;
- queue.queue({e: c.center, p: 0, c: c.i});
+ const queue = new FlatQueue();
+ pack.cultures.forEach(culture => {
+ if (!culture.i || culture.removed) return;
+ queue.push({cellId: culture.center, cultureId: culture.i}, 0);
});
const neutral = (cells.i.length / 5000) * 3000 * neutralInput.value; // limit cost for culture growth
const cost = [];
+
while (queue.length) {
- const next = queue.dequeue(),
- n = next.e,
- p = next.p,
- c = next.c;
- const type = pack.cultures[c].type;
- cells.c[n].forEach(function (e) {
- const biome = cells.biome[e];
- const biomeCost = getBiomeCost(c, biome, type);
- const biomeChangeCost = biome === cells.biome[n] ? 0 : 20; // penalty on biome change
- const heightCost = getHeightCost(e, cells.h[e], type);
- const riverCost = getRiverCost(cells.r[e], e, type);
- const typeCost = getTypeCost(cells.t[e], type);
+ const priority = queue.peekValue();
+ const {cellId, cultureId} = queue.pop();
+
+ const type = pack.cultures[cultureId].type;
+ cells.c[cellId].forEach(neibCellId => {
+ const biome = cells.biome[neibCellId];
+ const biomeCost = getBiomeCost(cultureId, biome, type);
+ const biomeChangeCost = biome === cells.biome[cellId] ? 0 : 20; // penalty on biome change
+ const heightCost = getHeightCost(neibCellId, cells.h[neibCellId], type);
+ const riverCost = getRiverCost(cells.r[neibCellId], neibCellId, type);
+ const typeCost = getTypeCost(cells.t[neibCellId], type);
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 (!cost[e] || totalCost < cost[e]) {
- if (cells.s[e] > 0) cells.culture[e] = c; // assign culture to populated cell
- cost[e] = totalCost;
- queue.queue({e, p: totalCost, c});
+ if (!cost[neibCellId] || totalCost < cost[neibCellId]) {
+ if (cells.s[neibCellId] > 0) cells.culture[neibCellId] = cultureId; // assign culture to populated cell
+ cost[neibCellId] = totalCost;
+ queue.push({cellId: neibCellId, cultureId}, totalCost);
}
});
}
diff --git a/src/modules/religions-generator.js b/src/modules/religions-generator.js
index 04091340..bec0f82f 100644
--- a/src/modules/religions-generator.js
+++ b/src/modules/religions-generator.js
@@ -1,3 +1,5 @@
+import FlatQueue from "flatqueue";
+
import {TIME} from "config/logging";
import {findAll} from "utils/graphUtils";
import {unique} from "utils/arrayUtils";
@@ -574,50 +576,48 @@ window.Religions = (function () {
// growth algorithm to assign cells to religions
const expandReligions = function () {
- const cells = pack.cells,
- religions = pack.religions;
- const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
+ const {cells, religions} = pack;
+ const queue = new FlatQueue();
const cost = [];
religions
.filter(r => r.type === "Organized" || r.type === "Cult")
- .forEach(r => {
- cells.religion[r.center] = r.i;
- queue.queue({e: r.center, p: 0, r: r.i, s: cells.state[r.center], c: r.culture});
- cost[r.center] = 1;
+ .forEach(({i, center, culture}) => {
+ cells.religion[center] = i;
+ const stateId = cells.state[center];
+ 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 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) {
- const next = queue.dequeue(),
- n = next.e,
- p = next.p,
- r = next.r,
- c = next.c,
- s = next.s;
- const expansion = religions[r].expansion;
+ const priority = queue.peekValue();
+ const {cellId, religionId, stateId, cultureId, biome} = queue.pop();
- cells.c[n].forEach(function (e) {
- if (expansion === "culture" && c !== cells.culture[e]) return;
- if (expansion === "state" && s !== cells.state[e]) return;
+ const expansion = religions[religionId].expansion;
- const cultureCost = c !== cells.culture[e] ? 10 : 0;
- const stateCost = s !== cells.state[e] ? 10 : 0;
- const biomeCost = cells.road[e] ? 1 : biomesData.cost[cells.biome[e]];
- const populationCost = Math.max(rn(popCost - cells.pop[e]), 0);
- const heightCost = Math.max(cells.h[e], 20) - 20;
- const waterCost = cells.h[e] < 20 ? (cells.road[e] ? 50 : 1000) : 0;
+ cells.c[cellId].forEach(neibCellId => {
+ if (expansion === "culture" && cultureId !== cells.culture[neibCellId]) return;
+ if (expansion === "state" && stateId !== cells.state[neibCellId]) return;
+
+ const cultureCost = cultureId !== cells.culture[neibCellId] ? 10 : 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 =
- p +
- (cultureCost + stateCost + biomeCost + populationCost + heightCost + waterCost) / religions[r].expansionism;
+ priority +
+ (cultureCost + stateCost + biomeCost + populationCost + heightCost + waterCost) /
+ religions[religionId].expansionism;
if (totalCost > neutral) return;
- if (!cost[e] || totalCost < cost[e]) {
- if (cells.h[e] >= 20 && cells.culture[e]) cells.religion[e] = r; // assign religion to cell
- cost[e] = totalCost;
- queue.queue({e, p: totalCost, r, c, s});
+ if (!cost[neibCellId] || totalCost < cost[neibCellId]) {
+ if (cells.h[neibCellId] >= 20 && cells.culture[neibCellId]) cells.religion[neibCellId] = religionId; // assign religion to cell
+ cost[neibCellId] = totalCost;
+ queue.push({cellId: neibCellId, religionId, cultureId, stateId}, totalCost);
}
});
}
@@ -625,43 +625,40 @@ window.Religions = (function () {
// growth algorithm to assign cells to heresies
const expandHeresies = function () {
- const cells = pack.cells,
- religions = pack.religions;
- const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
+ const {cells, religions} = pack;
+ const queue = new FlatQueue();
const cost = [];
religions
.filter(r => r.type === "Heresy")
- .forEach(r => {
- const b = cells.religion[r.center]; // "base" religion id
- cells.religion[r.center] = r.i; // heresy id
- queue.queue({e: r.center, p: 0, r: r.i, b});
- cost[r.center] = 1;
+ .forEach(({i, center}) => {
+ const baseReligionId = cells.religion[center];
+ cells.religion[center] = i; // heresy id
+ queue.push({cellId: center, religionId: i, baseReligionId}, 0);
+ cost[center] = 1;
});
const neutral = (cells.i.length / 5000) * 500 * neutralInput.value; // limit cost for heresies growth
while (queue.length) {
- const next = queue.dequeue(),
- n = next.e,
- p = next.p,
- r = next.r,
- b = next.b;
+ const priority = queue.peekValue();
+ const {cellId, religionId, baseReligionId} = queue.pop();
- cells.c[n].forEach(function (e) {
- const religionCost = cells.religion[e] === b ? 0 : 2000;
- const biomeCost = cells.road[e] ? 0 : biomesData.cost[cells.biome[e]];
- const heightCost = Math.max(cells.h[e], 20) - 20;
- const waterCost = cells.h[e] < 20 ? (cells.road[e] ? 50 : 1000) : 0;
+ cells.c[cellId].forEach(neibCellId => {
+ const religionCost = cells.religion[neibCellId] === baseReligionId ? 0 : 2000;
+ const biomeCost = cells.road[neibCellId] ? 0 : biomesData.cost[cells.biome[neibCellId]];
+ const heightCost = Math.max(cells.h[neibCellId], 20) - 20;
+ const waterCost = cells.h[neibCellId] < 20 ? (cells.road[neibCellId] ? 50 : 1000) : 0;
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 (!cost[e] || totalCost < cost[e]) {
- if (cells.h[e] >= 20 && cells.culture[e]) cells.religion[e] = r; // assign religion to cell
- cost[e] = totalCost;
- queue.queue({e, p: totalCost, r});
+ if (!cost[neibCellId] || totalCost < cost[neibCellId]) {
+ if (cells.h[neibCellId] >= 20 && cells.culture[neibCellId]) cells.religion[neibCellId] = religionId; // assign religion to cell
+ cost[neibCellId] = totalCost;
+ queue.push({cellId: neibCellId, religionId, baseReligionId}, totalCost);
}
});
}
diff --git a/src/modules/routes-generator.js b/src/modules/routes-generator.js
index d4fc6aa9..878b81ca 100644
--- a/src/modules/routes-generator.js
+++ b/src/modules/routes-generator.js
@@ -1,3 +1,5 @@
+import FlatQueue from "flatqueue";
+
import {TIME} from "config/logging";
import {findCell} from "utils/graphUtils";
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)
function findLandPath(start, exit = null, toRoad = null) {
const cells = pack.cells;
- const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
- const cost = [],
- from = [];
- queue.queue({e: start, p: 0});
+ const queue = new FlatQueue();
+ const cost = [];
+ const from = [];
+ queue.push(start, 0);
while (queue.length) {
- const next = queue.dequeue(),
- n = next.e,
- p = next.p;
- if (toRoad && cells.road[n]) return [from, n];
+ const priority = queue.peekValue();
+ const next = queue.pop();
- for (const c of cells.c[n]) {
- 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
- const habitability = biomesData.habitability[cells.biome[c]];
+ if (toRoad && cells.road[next]) return [from, next];
+
+ for (const neibCellId of cells.c[next]) {
+ 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)
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 heightCost = cells.h[c] > 80 ? cells.h[c] : 0; // routes tend to avoid mountainous areas
+ const heightChangeCost = Math.abs(cells.h[neibCellId] - cells.h[next]) * 10; // routes tend to avoid elevation changes
+ const heightCost = cells.h[neibCellId] > 80 ? cells.h[neibCellId] : 0; // routes tend to avoid mountainous areas
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;
- from[c] = n;
- if (c === exit) return [from, exit];
- cost[c] = totalCost;
- queue.queue({e: c, p: totalCost});
+ if (from[neibCellId] || totalCost >= cost[neibCellId]) continue;
+ from[neibCellId] = next;
+ if (neibCellId === exit) return [from, exit];
+ cost[neibCellId] = totalCost;
+ queue.push(neibCellId, totalCost);
}
}
return [from, exit];
@@ -247,32 +249,36 @@ window.Routes = (function () {
// find water paths
function findOceanPath(start, exit = null, toRoute = null) {
- const cells = pack.cells,
- temp = grid.cells.temp;
- const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
- const cost = [],
- from = [];
- queue.queue({e: start, p: 0});
+ const cells = pack.cells;
+ const temp = grid.cells.temp;
+
+ const queue = new FlatQueue();
+ const cost = [];
+ const from = [];
+ queue.push(start, 0);
while (queue.length) {
- const next = queue.dequeue(),
- n = next.e,
- p = next.p;
- if (toRoute && n !== start && cells.road[n]) return [from, n, true];
+ const priority = queue.peekValue();
+ const next = queue.pop();
- for (const c of cells.c[n]) {
- if (c === exit) {
- from[c] = n;
+ if (toRoute && next !== start && cells.road[next]) return [from, next, true];
+
+ for (const neibCellId of cells.c[next]) {
+ if (neibCellId === exit) {
+ from[neibCellId] = next;
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;
- (from[c] = n), (cost[c] = totalCost);
- queue.queue({e: c, p: totalCost});
+ if (cells.h[neibCellId] >= 20) continue; // ignore land cells
+ if (temp[cells.g[neibCellId]] <= -5) continue; // ignore cells with term <= -5
+
+ 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];
diff --git a/yarn.lock b/yarn.lock
index 8e631f01..0d7fc8f3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -128,6 +128,11 @@ esbuild@^0.14.43:
esbuild-windows-64 "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:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"