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"