diff --git a/modules/burgs-and-states.js b/modules/burgs-and-states.js index cff34d46..758e3c50 100644 --- a/modules/burgs-and-states.js +++ b/modules/burgs-and-states.js @@ -163,6 +163,143 @@ window.BurgsAndStates = (function () { } }; + const regenerateStates = function() { + const localSeed = generateSeed(); + Math.random = aleaPRNG(localSeed); + + const statesCount = +regionsOutput.value; + const burgs = pack.burgs.filter(b => b.i && !b.removed); + if (!burgs.length) return tip("There are no any burgs to generate states. Please create burgs first", false, "error"); + if (burgs.length < statesCount) + tip(`Not enough burgs to generate ${statesCount} states. Will generate only ${burgs.length} states`, false, "warn"); + + // turn all old capitals into towns, except for the capitals of locked states + burgs + .filter(b => b.capital && pack.states.find(s => s.lock && s.capital === b.i) === undefined) + .forEach(b => { + moveBurgToGroup(b.i, "towns"); + b.capital = 0; + }); + + // remove emblems + document.querySelectorAll("[id^=stateCOA]").forEach(el => el.remove()); + document.querySelectorAll("[id^=provinceCOA]").forEach(el => el.remove()); + emblems.selectAll("use").remove(); + + unfog(); + + if (!statesCount) { + tip(`Cannot generate zero states. Please check the States Number option`, false, "warn"); + pack.states = pack.states.slice(0, 1); // remove all except of neutrals + pack.states[0].diplomacy = []; // clear diplomacy + pack.provinces = [0]; // remove all provinces + pack.cells.state = new Uint16Array(pack.cells.i.length); // reset cells data + borders.selectAll("path").remove(); // remove borders + regions.selectAll("path").remove(); // remove states fill + labels.select("#states").selectAll("text"); // remove state labels + defs.select("#textPaths").selectAll("path[id*='stateLabel']").remove(); // remove state labels paths + + if (document.getElementById("burgsOverviewRefresh").offsetParent) burgsOverviewRefresh.click(); + if (document.getElementById("statesEditorRefresh").offsetParent) statesEditorRefresh.click(); + return; + } + + // burg local ids sorted by a bit randomized population. Also ignore burgs of a locked state. + const sortedBurgs = burgs + .filter(b => !pack.states[b.state] || !pack.states[b.state].lock) + .map((b, i) => [b, b.population * Math.random()]) + .sort((a, b) => b[1] - a[1]) + .map(b => b[0]); + const capitalsTree = d3.quadtree(); + + const neutral = pack.states[0].name; // neutrals name + const count = Math.min(statesCount, burgs.length) + 1; // +1 for neutral + let spacing = (graphWidth + graphHeight) / 2 / count; // min distance between capitals + + const states = []; + // Get all the states to restore + let statesToRestore = []; + if (pack.states) { + pack.states.forEach(state => { + if (!state.lock) return; + + statesToRestore.push(state) + }); + } + + d3.range(count).forEach(i => { + if (!i) { + states.push({i, name: neutral}); + return; + } + + // If we still have states to restore from the locks, restore those first and assign them the right ids. + if (statesToRestore.length) { + const [toRestore, ...rest] = statesToRestore; + + toRestore.old_i = toRestore.i; + toRestore.i = i; + states.push(toRestore); + + // Also reassign the state id of all provinces of this state for locked provinces + toRestore.provinces.forEach(id => { + if (!pack.provinces[id]) return; + + pack.provinces[id].state = toRestore.i; + }); + + statesToRestore = rest; + + const {x, y} = burgs[toRestore.capital]; + capitalsTree.add([x, y]); + return; + } + + let capital = null; + for (const burg of sortedBurgs) { + const {x, y} = burg; + if (capitalsTree.find(x, y, spacing) === undefined) { + burg.capital = 1; + capital = burg; + capitalsTree.add([x, y]); + moveBurgToGroup(burg.i, "cities"); + break; + } + + spacing = Math.max(spacing - 1, 1); + } + + const culture = capital.culture; + const basename = + capital.name.length < 9 && capital.cell % 5 === 0 ? capital.name : Names.getCulture(culture, 3, 6, "", 0); + const name = Names.getState(basename, culture); + const nomadic = [1, 2, 3, 4].includes(pack.cells.biome[capital.cell]); + const type = nomadic + ? "Nomadic" + : pack.cultures[culture].type === "Nomadic" + ? "Generic" + : pack.cultures[culture].type; + const expansionism = rn(Math.random() * powerInput.value + 1, 1); + + const cultureType = pack.cultures[culture].type; + const coa = COA.generate(capital.coa, 0.3, null, cultureType); + coa.shield = capital.coa.shield; + + states.push({ + i, + name, + type, + capital: capital.i, + center: capital.cell, + culture, + expansionism, + coa + }); + }); + + pack.states = states; + } + // define burg coordinates, coa, port status and define details const specifyBurgs = function () { TIME && console.time("specifyBurgs"); @@ -364,6 +501,12 @@ window.BurgsAndStates = (function () { TIME && console.time("expandStates"); const {cells, states, cultures, burgs} = pack; + const prevStates = {}; + if (cells.state) { + cells.state.forEach(function (i, index) { + prevStates[index] = i; + }) + } cells.state = new Uint16Array(cells.i.length); const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p}); const cost = []; @@ -372,6 +515,18 @@ window.BurgsAndStates = (function () { states .filter(s => s.i && !s.removed) .forEach(s => { + if (s.lock) { + Object.entries(prevStates).forEach(function ([index, stateId]) { + if (stateId === s.old_i) { + cells.state[index] = s.i; + cost[index] = neutral; + } + }); + + delete s.old_i; + return; + } + const capitalCell = burgs[s.capital].cell; cells.state[capitalCell] = s.i; const cultureCenter = cultures[s.culture].center; @@ -387,6 +542,8 @@ window.BurgsAndStates = (function () { cells.c[e].forEach(e => { if (cells.state[e] && e === states[cells.state[e]].center) return; // do not overwrite capital cells + // Do not overwrite cells from a locked state. + if (states[cells.state[e]].lock) return; 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; @@ -1148,7 +1305,17 @@ window.BurgsAndStates = (function () { Math.random = aleaPRNG(localSeed); const {cells, states, burgs} = pack; - const provinces = (pack.provinces = [0]); + const provincesToRestore = pack.provinces ? + pack.provinces.filter(p => p.lock || (states[p.state] && states[p.state].lock)) + : []; + const provinces = (pack.provinces = [0].concat(...provincesToRestore)); + + const prevProvinces = {}; + if (cells.province) { + cells.province.forEach((i, index) => { + prevProvinces[index] = i; + }) + } cells.province = new Uint16Array(cells.i.length); // cell state const percentage = +provincesInput.value; @@ -1170,10 +1337,16 @@ window.BurgsAndStates = (function () { // generate provinces for a selected burgs states.forEach(s => { - s.provinces = []; + s.provinces = s.provinces ? s.provinces.filter(p => p.lock) : []; + // Don't regenerate provinces of a locked state + if (s.lock) return; + if (!s.i || s.removed) return; const stateBurgs = burgs - .filter(b => b.state === s.i && !b.removed) + // Filter for burgs of this state that haven't been removed and that are not in a locked province. + .filter(b => + b.state === s.i && !b.removed && provincesToRestore.find(p => prevProvinces[b.cell] === p.i) === undefined + ) .sort((a, b) => b.population * gauss(1, 0.2, 0.5, 1.5, 3) - a.population) .sort((a, b) => b.capital - a.capital); if (stateBurgs.length < 2) return; // at least 2 provinces are required @@ -1200,11 +1373,31 @@ window.BurgsAndStates = (function () { } }); + // Restore the indexes of locked and kept provinces + provincesToRestore.forEach((province, index) => { + province.old_i = province.i; + province.i = index + 1; + states[province.state].provinces.push(province.i); + }); + // expand generated provinces const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p}); const cost = []; provinces.forEach(function (p) { if (!p.i || p.removed) return; + + // Then restore the cells of locked or kept provinces + if (p.old_i) { + Object.entries(prevProvinces).forEach(function ([index, provId]) { + if (provId === p.old_i) { + cells.province[index] = p.i; + } + }); + + delete p.old_i; + return; + } + cells.province[p.center] = p.i; queue.queue({e: p.center, p: 0, province: p.i, state: p.state}); cost[p.center] = 1; @@ -1217,6 +1410,16 @@ window.BurgsAndStates = (function () { province = next.province, state = next.state; cells.c[n].forEach(function (e) { + // Do not overwrite cells from a locked state or province. + if ( + (provinces[cells.province[e]] && provinces[cells.province[e]].lock) || + ( + provinces[cells.province[e]] && + states[provinces[cells.province[e]].state] && + states[provinces[cells.province[e]].state].lock + ) + ) return; + const land = cells.h[e] >= 20; if (!land && !cells.t[e]) return; // cannot pass deep ocean if (land && cells.state[e] !== state) return; @@ -1358,6 +1561,7 @@ window.BurgsAndStates = (function () { return { generate, + regenerateStates, expandStates, normalizeStates, assignColors, diff --git a/modules/ui/tools.js b/modules/ui/tools.js index f29749fa..f7a1b7a6 100644 --- a/modules/ui/tools.js +++ b/modules/ui/tools.js @@ -138,93 +138,7 @@ function recalculatePopulation() { } function regenerateStates() { - const localSeed = generateSeed(); - Math.random = aleaPRNG(localSeed); - - const statesCount = +regionsOutput.value; - const burgs = pack.burgs.filter(b => b.i && !b.removed); - if (!burgs.length) return tip("There are no any burgs to generate states. Please create burgs first", false, "error"); - if (burgs.length < statesCount) - tip(`Not enough burgs to generate ${statesCount} states. Will generate only ${burgs.length} states`, false, "warn"); - - // turn all old capitals into towns - burgs - .filter(b => b.capital) - .forEach(b => { - moveBurgToGroup(b.i, "towns"); - b.capital = 0; - }); - - // remove emblems - document.querySelectorAll("[id^=stateCOA]").forEach(el => el.remove()); - document.querySelectorAll("[id^=provinceCOA]").forEach(el => el.remove()); - emblems.selectAll("use").remove(); - - unfog(); - - if (!statesCount) { - tip(`Cannot generate zero states. Please check the States Number option`, false, "warn"); - pack.states = pack.states.slice(0, 1); // remove all except of neutrals - pack.states[0].diplomacy = []; // clear diplomacy - pack.provinces = [0]; // remove all provinces - pack.cells.state = new Uint16Array(pack.cells.i.length); // reset cells data - borders.selectAll("path").remove(); // remove borders - regions.selectAll("path").remove(); // remove states fill - labels.select("#states").selectAll("text"); // remove state labels - defs.select("#textPaths").selectAll("path[id*='stateLabel']").remove(); // remove state labels paths - - if (document.getElementById("burgsOverviewRefresh").offsetParent) burgsOverviewRefresh.click(); - if (document.getElementById("statesEditorRefresh").offsetParent) statesEditorRefresh.click(); - return; - } - - // burg local ids sorted by a bit randomized population: - const sortedBurgs = burgs - .map((b, i) => [b, b.population * Math.random()]) - .sort((a, b) => b[1] - a[1]) - .map(b => b[0]); - const capitalsTree = d3.quadtree(); - - const neutral = pack.states[0].name; // neutrals name - const count = Math.min(statesCount, burgs.length) + 1; // +1 for neutral - let spacing = (graphWidth + graphHeight) / 2 / count; // min distance between capitals - - pack.states = d3.range(count).map(i => { - if (!i) return {i, name: neutral}; - - let capital = null; - for (const burg of sortedBurgs) { - const {x, y} = burg; - if (capitalsTree.find(x, y, spacing) === undefined) { - burg.capital = 1; - capital = burg; - capitalsTree.add([x, y]); - moveBurgToGroup(burg.i, "cities"); - break; - } - - spacing = Math.max(spacing - 1, 1); - } - - const culture = capital.culture; - const basename = - capital.name.length < 9 && capital.cell % 5 === 0 ? capital.name : Names.getCulture(culture, 3, 6, "", 0); - const name = Names.getState(basename, culture); - const nomadic = [1, 2, 3, 4].includes(pack.cells.biome[capital.cell]); - const type = nomadic - ? "Nomadic" - : pack.cultures[culture].type === "Nomadic" - ? "Generic" - : pack.cultures[culture].type; - const expansionism = rn(Math.random() * powerInput.value + 1, 1); - - const cultureType = pack.cultures[culture].type; - const coa = COA.generate(capital.coa, 0.3, null, cultureType); - coa.shield = capital.coa.shield; - - return {i, name, type, capital: capital.i, center: capital.cell, culture, expansionism, coa}; - }); - + BurgsAndStates.regenerateStates(); BurgsAndStates.expandStates(); BurgsAndStates.normalizeStates(); BurgsAndStates.collectStatistics();