Add files via upload

it can generate more buildings with calculated sizes
This commit is contained in:
Tsyxy 2021-07-07 16:33:44 +02:00 committed by GitHub
parent 44b3911e65
commit f0707c51a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,10 +1,11 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.BurgsAndStates = factory());
}(this, (function () { 'use strict';
(global.BurgsAndStates = factory());
}(this, (function () {
'use strict';
const generate = function() {
const generate = function () {
const cells = pack.cells, cultures = pack.cultures, n = cells.i.length;
cells.burg = new Uint16Array(n); // cell burg
@ -41,18 +42,30 @@
if (sorted.length < count * 10) {
count = Math.floor(sorted.length / 10);
if (!count) {WARN && console.warn(`There is no populated cells. Cannot generate states`); return burgs;}
else {WARN && console.warn(`Not enough populated cells (${sorted.length}). Will generate only ${count} states`);}
if (!count) { WARN && console.warn(`There is no populated cells. Cannot generate states`); return burgs; }
else { WARN && console.warn(`Not enough populated cells (${sorted.length}). Will generate only ${count} states`); }
}
let burgsTree = d3.quadtree();
let spacing = (graphWidth + graphHeight) / 2 / count; // min distance between capitals
for (let i=0; burgs.length <= count; i++) {
for (let i = 0; burgs.length <= count; i++) {
const cell = sorted[i], x = cells.p[cell][0], y = cells.p[cell][1];
if (burgsTree.find(x, y, spacing) === undefined) {
burgs.push({cell, x, y});
let asd = { cell, x, y/*, _port: null */ };//we did some testing on when did the port value change
/*Object.defineProperty(asd, 'port', {
get: function () {
return this._port;
},
set: function (value) {
console.warn('changed options from:', this._port, 'to', value);
this._port = value;
}
});
*/
burgs.push(asd);
burgsTree.add([x, y]);
}
@ -71,10 +84,10 @@
// For each capital create a state
function createStates() {
TIME && console.time('createStates');
const states = [{i:0, name: "Neutrals"}];
const colors = getColors(burgs.length-1);
const states = [{ i: 0, name: "Neutrals" }];
const colors = getColors(burgs.length - 1);
burgs.forEach(function(b, i) {
burgs.forEach(function (b, i) {
if (!i) return; // skip first element
// burgs data
@ -86,13 +99,13 @@
// states data
const expansionism = rn(Math.random() * powerInput.value + 1, 1);
const basename = b.name.length < 9 && b.cell%5 === 0 ? b.name : Names.getCultureShort(b.culture);
const basename = b.name.length < 9 && b.cell % 5 === 0 ? b.name : Names.getCultureShort(b.culture);
const name = Names.getState(basename, b.culture);
const type = cultures[b.culture].type;
const coa = COA.generate(null, null, null, type);
coa.shield = COA.getShield(b.culture, null);
states.push({i, color: colors[i-1], name, expansionism, capital: i, type, center: b.cell, culture: b.culture, coa});
states.push({ i, color: colors[i - 1], name, expansionism, capital: i, type, center: b.cell, culture: b.culture, coa });
cells.burg[b.cell] = i;
});
@ -103,7 +116,7 @@
// place secondary settlements based on geo and economical evaluation
function placeTowns() {
TIME && console.time('placeTowns');
const score = new Int16Array(cells.s.map(s => s * gauss(1,3,0,20,3))); // a bit randomized cell score for towns placement
const score = new Int16Array(cells.s.map(s => s * gauss(1, 3, 0, 20, 3))); // a bit randomized cell score for towns placement
const sorted = cells.i.filter(i => !cells.burg[i] && score[i] > 0 && cells.culture[i]).sort((a, b) => score[b] - score[a]); // filtered and sorted array of indexes
const desiredNumber = manorsInput.value == 1000 ? rn(sorted.length / 5 / (grid.points.length / 10000) ** .8) : manorsInput.valueAsNumber;
@ -114,7 +127,7 @@
let spacing = (graphWidth + graphHeight) / 150 / (burgsNumber ** .7 / 66); // min distance between towns
while (burgsAdded < burgsNumber && spacing > 1) {
for (let i=0; burgsAdded < burgsNumber && i < sorted.length; i++) {
for (let i = 0; burgsAdded < burgsNumber && i < sorted.length; i++) {
if (cells.burg[sorted[i]]) continue;
const cell = sorted[i], x = cells.p[cell][0], y = cells.p[cell][1];
const s = spacing * gauss(1, .3, .2, 2, 2); // randomize to make placement not uniform
@ -122,7 +135,7 @@
const burg = burgs.length;
const culture = cells.culture[cell];
const name = Names.getCulture(culture);
burgs.push({cell, x, y, state: 0, i: burg, culture, name, capital: 0, feature:cells.f[cell]});
burgs.push({ cell, x, y, state: 0, i: burg, culture, name, capital: 0, feature: cells.f[cell] });
burgsTree.add([x, y]);
cells.burg[cell] = burg;
burgsAdded++;
@ -134,18 +147,18 @@
ERROR && console.error(`Cannot place all burgs. Requested ${desiredNumber}, placed ${burgsAdded}`);
}
burgs[0] = {name:undefined}; // do not store burgsTree anymore
burgs[0] = { name: undefined }; // do not store burgsTree anymore
TIME && console.timeEnd('placeTowns');
}
}
// define burg coordinates, coa, port status and define details
const specifyBurgs = function() {
const specifyBurgs = function () {
TIME && console.time("specifyBurgs");
const cells = pack.cells, vertices = pack.vertices, features = pack.features, temp = grid.cells.temp;
for (const b of pack.burgs) {
if (!b.i || b.lock) continue;
if (!b.i) continue;
const i = b.cell;
// asign port status to some coastline burgs with temp > 0 °C
@ -169,13 +182,13 @@
}
// add random factor
b.population = rn(b.population * gauss(2,3,.6,20,3), 3);
b.population = rn(b.population * gauss(2, 3, .6, 20, 3), 3);
// shift burgs on rivers semi-randomly and just a bit
if (!b.port && cells.r[i]) {
const shift = Math.min(cells.fl[i]/150, 1);
if (i%2) b.x = rn(b.x + shift, 2); else b.x = rn(b.x - shift, 2);
if (cells.r[i]%2) b.y = rn(b.y + shift, 2); else b.y = rn(b.y - shift, 2);
const shift = Math.min(cells.fl[i] / 150, 1);
if (i % 2) b.x = rn(b.x + shift, 2); else b.x = rn(b.x - shift, 2);
if (cells.r[i] % 2) b.y = rn(b.y + shift, 2); else b.y = rn(b.y - shift, 2);
}
// define emblem
@ -202,7 +215,7 @@
TIME && console.timeEnd("specifyBurgs");
}
const getType = function(i, port) {
const getType = function (i, port) {
const cells = pack.cells;
if (port) return "Naval";
if (cells.haven[i] && pack.features[cells.f[cells.haven[i]]].type === "lake") return "Lake";
@ -216,22 +229,184 @@
return "Generic";
}
const defineBurgFeatures = function(newburg) {
//i made some changes here
const defineBurgFeatures = function (newburg) {
const cells = pack.cells;
pack.burgs.filter(b => newburg ? b.i == newburg.i : (b.i && !b.removed)).forEach(b => {
const pop = b.population;
b.citadel = b.capital || pop > 50 && P(.75) || P(.5) ? 1 : 0;
b.plaza = pop > 50 || pop > 30 && P(.75) || pop > 10 && P(.5) || P(.25) ? 1 : 0;
b.walls = b.capital || pop > 30 || pop > 20 && P(.75) || pop > 10 && P(.5) || P(.2) ? 1 : 0;
b.citadel = citadelGeneration(b);
//b.plaza = pop > 50 || pop > 30 && P(.75) || pop > 10 && P(.5) || P(.25) ? 1 : 0;
b.plaza = Math.sqrt(cells.road[b.cell]);
b.plaza -= b.plaza % 1;
//b.walls = b.capital || pop > 30 || pop > 20 && P(.75) || pop > 10 && P(.5) || P(.2) ? 2 : 1;
b.walls = wallGenerator(b);
b.shanty = pop > 30 || pop > 20 && P(.75) || b.walls && P(.75) ? 1 : 0;
const religion = cells.religion[b.cell];
const theocracy = pack.states[b.state].form === "Theocracy";
b.temple = religion && theocracy || pop > 50 || pop > 35 && P(.75) || pop > 20 && P(.5) ? 1 : 0;
//b.temple = religion && theocracy || pop > 50 || pop > 35 && P(.75) || pop > 20 && P(.5) ? 1 : 0;
b.port = generatePortSize(b);
b.library = libraryGenerator(b);
b.temple = templeGenerator(b, religion, theocracy);
b.hospital = hospitalGenerator(b);
b.manufactory = manufactoryGenerator(b);
b.labour = labourGenerator(b);
b.pasture = pastureGenerator(b);
b.industrial = b.manufactory * b.manufactory * 15;
b.food = b.pasture * b.pasture * 5 + b.port * 100;
b.landtrade = b.plaza * 20;
b.watertrade = 10 * b.port * b.port;
});
}
function citadelGeneration(b) {
var citadel = 0;
/* for (let pop = b.population; pop >= 5;) {
citadel++;
pop = pop - 5;
} maybe i will use it for walls instead, that depends more on population*/
if (b.capital) {
citadel += 2;
}
if (b.port) {
citadel += 1;
}
citadel += pack.states[b.state].expansionism * Math.random();
if (citadel < 0) { citadel = 0; } else {
citadel = citadel - citadel % 1;
}
return citadel
}
const drawBurgs = function() {
function wallGenerator(b) {
let wall = 0;
if (b.type === "Nomadic") {
return 0;
} else if (b.type === "Highland" || b.type === "Hunting") {
for (let pop = b.population; pop >= 10;) {
wall++;
pop = pop - 5;
}
} else if (b.type === "Lake" || b.type === "River" || b.type === "Naval" || b.type === "Generic") {
for (let pop = b.population; pop >= 5;) {
wall++;
pop = pop - 3;
}
}
return wall;
}
function libraryGenerator(b) {
let lib = 0;
let multip = 1;
let culture = pack.cultures[b.culture];
let cul = pack.cultures[b.culture].type;
if (cul === "Nomadic" || cul === "Hunting") { return 0 }
if (pack.states[b.state].culture === culture) {
multip = multip * 1, 5;
}
if (cul === "Naval" || cul === "Lake") {
multip = multip * 1, 5;
}
for (let mark = b.plaza; mark >= 4; mark = mark - 4) {
multip = multip * 1, 5;
}
multip = multip / Math.sqrt(pack.states[b.state].expansionism);
for (let pop = b.population; pop >= 10;) {
lib += multip;
pop = pop - 5;
}
return lib - lib % 1;
}
function templeGenerator(b, religion, theocracy) {
let faith = pack.religions[pack.cells.religion[b.cell]];
const statereligion = religion === pack.states[b.state].religion;
let multip = 1;
let temple = 0;
if (statereligion) { multip += 0, 5 }
if (theocracy) { multip = multip * 1, 5 }
if (theocracy && !statereligion) { multip = multip / 3 }
if (pack.cultures[b.culture].center == pack.religions[pack.cells.religion[b.cell]].center) {
multip = multip * 1, 2;
}
if (faith.type === "Organized" || faith.type === "Heresy") { multip = multip * 1, 2 }
if (faith.type === "Cult") { multip = multip / 1, 5 }
if (faith.type === "Folk") {
multip = multip / 1, 5;
temple++;
}
for (let pop = b.population; pop >= 3;) {
temple += multip;
pop = pop - 6;
}
return temple - temple % 1;
}
function generatePortSize(b) {
let multip = 1;
let portSize = 0;
let cul = pack.cultures[b.culture].type;
if (b.port == 0) { return 0 }
else {
if (cul === "Naval") {
multip++;
}
let tradep = b.population * b.plaza;
for (; tradep >= 4;) {
portSize += multip;
tradep = tradep - 3;
}
}
return portSize - portSize % 1;
}
function hospitalGenerator(b) {
let multip = 1;
let hospital = 0;
let faith = pack.religions[pack.cells.religion[b.cell]];
if (faith.type === "Organized") {
multip = 1, 5
}
for (let health = Math.sqrt(b.population * (b.library + 1)); health > 4; health -= 5) {
hospital += multip;
}
return hospital - hospital % 1;
}
function manufactoryGenerator(b) {
let manufactory = 0;
let height = pack.cells.h[b.cell];
let cul = pack.cultures[b.culture].type;
if (height >= 60) {
manufactory++;
}
if (cul === "Nomadic") { manufactory -= 4 }
for (let pop = b.population; pop >= 3;) {
manufactory++;
pop = pop - 6;
}
if (manufactory <= 0) { return 0; }
else { return manufactory; }
}
function labourGenerator(b) {
let science = ((b.library + b.manufactory) / 2) / 3;
return science - science % 1;
}
function pastureGenerator(b) {
let herd = 0;
let multip = 1;
let culture = pack.cultures[b.culture];
let cul = pack.cultures[b.culture].type;
if (cul === "Nomadic") { multip++; }
if (culture === "Nomadic") { multip++; }
if (cul === "Naval") { multip -= 0, 5; }
for (let pop = b.population; pop >= 3;) {
herd += multip;
pop = pop - 4;
}
return herd - herd % 1;
}
const drawBurgs = function () {
TIME && console.time("drawBurgs");
// remove old data
@ -248,11 +423,11 @@
const caSize = capitalAnchors.attr("size") || 2;
capitalIcons.selectAll("circle").data(capitals).enter()
.append("circle").attr("id", d => "burg"+d.i).attr("data-id", d => d.i)
.append("circle").attr("id", d => "burg" + d.i).attr("data-id", d => d.i)
.attr("cx", d => d.x).attr("cy", d => d.y).attr("r", capitalSize);
capitalLabels.selectAll("text").data(capitals).enter()
.append("text").attr("id", d => "burgLabel"+d.i).attr("data-id", d => d.i)
.append("text").attr("id", d => "burgLabel" + d.i).attr("data-id", d => d.i)
.attr("x", d => d.x).attr("y", d => d.y).attr("dy", `${capitalSize * -1.5}px`).text(d => d.name);
capitalAnchors.selectAll("use").data(capitals.filter(c => c.port)).enter()
@ -269,14 +444,14 @@
const taSize = townsAnchors.attr("size") || 1;
townIcons.selectAll("circle").data(towns).enter()
.append("circle").attr("id", d => "burg"+d.i).attr("data-id", d => d.i)
.append("circle").attr("id", d => "burg" + d.i).attr("data-id", d => d.i)
.attr("cx", d => d.x).attr("cy", d => d.y).attr("r", townSize);
townLabels.selectAll("text").data(towns).enter()
.append("text").attr("id", d => "burgLabel"+d.i).attr("data-id", d => d.i)
.append("text").attr("id", d => "burgLabel" + d.i).attr("data-id", d => d.i)
.attr("x", d => d.x).attr("y", d => d.y).attr("dy", `${townSize * -1.5}px`).text(d => d.name);
townsAnchors.selectAll("use").data(towns.filter(c => c.port)).enter()
townsAnchors.selectAll("use").data(towns.filter(c => c.port)).enter()
.append("use").attr("xlink:href", "#icon-anchor").attr("data-id", d => d.i)
.attr("x", d => rn(d.x - taSize * .47, 2)).attr("y", d => rn(d.y - taSize * .47, 2))
.attr("width", taSize).attr("height", taSize);
@ -285,30 +460,27 @@
}
// growth algorithm to assign cells to states like we did for cultures
const expandStates = function() {
const expandStates = function () {
TIME && console.time("expandStates");
const {cells, states, cultures, burgs} = pack;
const cells = pack.cells, states = pack.states, cultures = pack.cultures, burgs = pack.burgs;
cells.state = new Uint16Array(cells.i.length);
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
cells.state = new Uint16Array(cells.i.length); // cell state
const queue = new PriorityQueue({ comparator: (a, b) => a.p - b.p });
const cost = [];
const neutral = cells.i.length / 5000 * 2500 * neutralInput.value * statesNeutral.value; // 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});
states.filter(s => s.i && !s.removed).forEach(function (s) {
cells.state[burgs[s.capital].cell] = s.i;
const b = cells.biome[cultures[s.culture].center]; // native biome
queue.queue({ e: s.center, p: 0, s: s.i, b });
cost[s.center] = 1;
});
const neutral = cells.i.length / 5000 * 2500 * neutralInput.value * statesNeutral.value; // limit cost for state growth
while (queue.length) {
const next = queue.dequeue();
const {e, p, s, b} = next;
const {type, culture} = states[s];
const next = queue.dequeue(), n = next.e, p = next.p, s = next.s, b = next.b;
const type = states[s].type;
const culture = states[s].culture;
cells.c[e].forEach(e => {
cells.c[n].forEach(function (e) {
if (cells.state[e] && e === states[cells.state[e]].center) return; // do not overwrite capital cells
const cultureCost = culture === cells.culture[e] ? -9 : 100;
@ -325,7 +497,7 @@
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});
queue.queue({ e, p: totalCost, s, b });
}
});
}
@ -367,7 +539,7 @@
TIME && console.timeEnd("expandStates");
}
const normalizeStates = function() {
const normalizeStates = function () {
TIME && console.time("normalizeStates");
const cells = pack.cells, burgs = pack.burgs;
@ -391,35 +563,35 @@
TIME && console.time('updateCulturesForBurgsAndStates');
// Assign the culture associated with the burgs cell.
pack.burgs = pack.burgs.map( (burg, index) => {
pack.burgs = pack.burgs.map((burg, index) => {
// Ignore metadata burg
if(index === 0) {
if (index === 0) {
return burg;
}
return {...burg, culture: pack.cells.culture[burg.cell]};
return { ...burg, culture: pack.cells.culture[burg.cell] };
});
// Assign the culture associated with the states' center cell.
pack.states = pack.states.map( (state, index) => {
pack.states = pack.states.map((state, index) => {
// Ignore neutrals state
if(index === 0) {
if (index === 0) {
return state;
}
return {...state, culture: pack.cells.culture[state.center]};
return { ...state, culture: pack.cells.culture[state.center] };
});
TIME && console.timeEnd('updateCulturesForBurgsAndStates');
}
// calculate and draw curved state labels for a list of states
const drawStateLabels = function(list) {
const drawStateLabels = function (list) {
TIME && console.time("drawStateLabels");
const cells = pack.cells, features = pack.features, states = pack.states;
const paths = []; // text paths
lineGen.curve(d3.curveBundle.beta(1));
for (const s of states) {
if (!s.i || s.removed || !s.cells || (list && !list.includes(s.i))) continue;
if (!s.i || s.removed || (list && !list.includes(s.i))) continue;
const used = [];
const visualCenter = findCell(s.pole[0], s.pole[1]);
const start = cells.state[visualCenter] === s.i ? visualCenter : s.center;
@ -428,7 +600,7 @@
const delaunay = Delaunator.from(points);
const voronoi = new Voronoi(delaunay, points, points.length);
const chain = connectCenters(voronoi.vertices, s.pole[1]);
const relaxed = chain.map(i => voronoi.vertices.p[i]).filter((p, i) => i%15 === 0 || i+1 === chain.length);
const relaxed = chain.map(i => voronoi.vertices.p[i]).filter((p, i) => i % 15 === 0 || i + 1 === chain.length);
paths.push([s.i, relaxed]);
function getHull(start, state, maxLake) {
@ -438,12 +610,12 @@
const q = queue.pop();
const nQ = cells.c[q].filter(c => cells.state[c] === state);
cells.c[q].forEach(function(c, d) {
cells.c[q].forEach(function (c, d) {
const passableLake = features[cells.f[c]].type === "lake" && features[cells.f[c]].cells < maxLake;
if (cells.b[c] || (cells.state[c] !== state && !passableLake)) {hull.add(cells.v[q][d]); return;}
if (cells.b[c] || (cells.state[c] !== state && !passableLake)) { hull.add(cells.v[q][d]); return; }
const nC = cells.c[c].filter(n => cells.state[n] === state);
const intersected = common(nQ, nC).length
if (hull.size > 20 && !intersected && !passableLake) {hull.add(cells.v[q][d]); return;}
if (hull.size > 20 && !intersected && !passableLake) { hull.add(cells.v[q][d]); return; }
if (used[c]) return;
used[c] = 1;
queue.push(c);
@ -455,7 +627,7 @@
function connectCenters(c, y) {
// check if vertex is inside the area
const inside = c.p.map(function(p) {
const inside = c.p.map(function (p) {
if (p[0] <= 0 || p[1] <= 0 || p[0] >= graphWidth || p[1] >= graphHeight) return false; // out of the screen
return used[findCell(p[0], p[1])];
});
@ -467,9 +639,9 @@
const start = pointsInside[d3.scan(pointsInside, (a, b) => (c.p[b][0] - c.p[a][0]) - (Math.abs(c.p[b][1] - y) - Math.abs(c.p[a][1] - y)) * h)]; // right point
// connect leftmost and rightmost points with shortest path
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
const queue = new PriorityQueue({ comparator: (a, b) => a.p - b.p });
const cost = [], from = [];
queue.queue({e: start, p: 0});
queue.queue({ e: start, p: 0 });
while (queue.length) {
const next = queue.dequeue(), n = next.e, p = next.p;
@ -481,7 +653,7 @@
if (from[v] || totalCost >= cost[v]) continue;
cost[v] = totalCost;
from[v] = n;
queue.queue({e: v, p: totalCost});
queue.queue({ e: v, p: totalCost });
}
}
@ -516,12 +688,12 @@
const s = states[p[0]];
if (list) {
t.select("#textPath_stateLabel"+id).remove();
g.select("#stateLabel"+id).remove();
t.select("#textPath_stateLabel" + id).remove();
g.select("#stateLabel" + id).remove();
}
const path = p[1].length > 1 ? lineGen(p[1]) : `M${p[1][0][0]-50},${p[1][0][1]}h${100}`;
const textPath = t.append("path").attr("d", path).attr("id", "textPath_stateLabel"+id);
const path = p[1].length > 1 ? lineGen(p[1]) : `M${p[1][0][0] - 50},${p[1][0][1]}h${100}`;
const textPath = t.append("path").attr("d", path).attr("id", "textPath_stateLabel" + id);
const pathLength = p[1].length > 1 ? textPath.node().getTotalLength() / letterLength : 0; // path length in letters
let lines = [], ratio = 100;
@ -543,25 +715,25 @@
// prolongate path if it's too short
if (pathLength && pathLength < lines[0].length) {
const points = p[1];
const f = points[0], l = points[points.length-1];
const f = points[0], l = points[points.length - 1];
const dx = l[0] - f[0], dy = l[1] - f[1];
const mod = Math.abs(letterLength * lines[0].length / dx) / 2;
points[0] = [rn(f[0] - dx * mod), rn(f[1] - dy * mod)];
points[points.length-1] = [rn(l[0] + dx * mod), rn(l[1] + dy * mod)];
points[points.length - 1] = [rn(l[0] + dx * mod), rn(l[1] + dy * mod)];
textPath.attr("d", round(lineGen(points)));
}
example.attr("font-size", ratio+"%");
example.attr("font-size", ratio + "%");
const top = (lines.length - 1) / -2; // y offset
const spans = lines.map((l, d) => {
example.text(l);
const left = example.node().getBBox().width / -2; // x offset
return `<tspan x="${left}px" dy="${d?1:top}em">${l}</tspan>`;
return `<tspan x="${left}px" dy="${d ? 1 : top}em">${l}</tspan>`;
});
const el = g.append("text").attr("id", "stateLabel"+id)
.append("textPath").attr("xlink:href", "#textPath_stateLabel"+id)
.attr("startOffset", "50%").attr("font-size", ratio+"%").node();
const el = g.append("text").attr("id", "stateLabel" + id)
.append("textPath").attr("xlink:href", "#textPath_stateLabel" + id)
.attr("startOffset", "50%").attr("font-size", ratio + "%").node();
el.insertAdjacentHTML("afterbegin", spans.join(""));
if (lines.length < 2) return;
@ -582,7 +754,7 @@
const left = example.node().getBBox().width / -2; // x offset
el.innerHTML = `<tspan x="${left}px">${name}</tspan>`;
ratio = Math.max(Math.min(rn(pathLength / name.length * 60), 130), 40);
el.setAttribute("font-size", ratio+"%");
el.setAttribute("font-size", ratio + "%");
});
example.remove();
@ -593,7 +765,7 @@
}
// calculate states data like area, population etc.
const collectStatistics = function() {
const collectStatistics = function () {
TIME && console.time("collectStatistics");
const cells = pack.cells, states = pack.states;
states.forEach(s => {
@ -628,7 +800,7 @@
TIME && console.timeEnd("collectStatistics");
}
const assignColors = function() {
const assignColors = function () {
TIME && console.time("assignColors");
const colors = ["#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3", "#a6d854", "#ffd92f"]; // d3.schemeSet2;
@ -654,50 +826,50 @@
}
// generate historical conflicts of each state
const generateCampaigns = function() {
const wars = {"War":6, "Conflict":2, "Campaign":4, "Invasion":2, "Rebellion":2, "Conquest":2, "Intervention":1, "Expedition":1, "Crusade":1};
const generateCampaigns = function () {
const wars = { "War": 6, "Conflict": 2, "Campaign": 4, "Invasion": 2, "Rebellion": 2, "Conquest": 2, "Intervention": 1, "Expedition": 1, "Crusade": 1 };
pack.states.forEach(s => {
if (!s.i || s.removed) return;
const n = s.neighbors.length ? s.neighbors : [0];
s.campaigns = n.map(i => {
const name = i && P(.8) ? pack.states[i].name : Names.getCultureShort(s.culture);
const start = gauss(options.year-100, 150, 1, options.year-6);
const start = gauss(options.year - 100, 150, 1, options.year - 6);
const end = start + gauss(4, 5, 1, options.year - start - 1);
return {name:getAdjective(name) + " " + rw(wars), start, end};
return { name: getAdjective(name) + " " + rw(wars), start, end };
}).sort((a, b) => a.start - b.start);
});
}
// generate Diplomatic Relationships
const generateDiplomacy = function() {
const generateDiplomacy = function () {
TIME && console.time("generateDiplomacy");
const cells = pack.cells, states = pack.states;
const chronicle = states[0].diplomacy = [];
const valid = states.filter(s => s.i && !states.removed);
const neibs = {"Ally":1, "Friendly":2, "Neutral":1, "Suspicion":10, "Rival":9}; // relations to neighbors
const neibsOfNeibs = {"Ally":10, "Friendly":8, "Neutral":5, "Suspicion":1}; // relations to neighbors of neighbors
const far = {"Friendly":1, "Neutral":12, "Suspicion":2, "Unknown":6}; // relations to other
const navals = {"Neutral":1, "Suspicion":2, "Rival":1, "Unknown":1}; // relations of naval powers
const neibs = { "Ally": 1, "Friendly": 2, "Neutral": 1, "Suspicion": 10, "Rival": 9 }; // relations to neighbors
const neibsOfNeibs = { "Ally": 10, "Friendly": 8, "Neutral": 5, "Suspicion": 1 }; // relations to neighbors of neighbors
const far = { "Friendly": 1, "Neutral": 12, "Suspicion": 2, "Unknown": 6 }; // relations to other
const navals = { "Neutral": 1, "Suspicion": 2, "Rival": 1, "Unknown": 1 }; // relations of naval powers
valid.forEach(s => s.diplomacy = new Array(states.length).fill("x")); // clear all relationships
if (valid.length < 2) return; // no states to renerate relations with
const areaMean = d3.mean(valid.map(s => s.area)); // avarage state area
// generic relations
for (let f=1; f < states.length; f++) {
for (let f = 1; f < states.length; f++) {
if (states[f].removed) continue;
if (states[f].diplomacy.includes("Vassal")) {
// Vassals copy relations from their Suzerains
const suzerain = states[f].diplomacy.indexOf("Vassal");
for (let i=1; i < states.length; i++) {
for (let i = 1; i < states.length; i++) {
if (i === f || i === suzerain) continue;
states[f].diplomacy[i] = states[suzerain].diplomacy[i];
if (states[suzerain].diplomacy[i] === "Suzerain") states[f].diplomacy[i] = "Ally";
for (let e=1; e < states.length; e++) {
for (let e = 1; e < states.length; e++) {
if (e === f || e === suzerain) continue;
if (states[e].diplomacy[suzerain] === "Suzerain" || states[e].diplomacy[suzerain] === "Vassal") continue;
states[e].diplomacy[f] = states[e].diplomacy[suzerain];
@ -706,7 +878,7 @@
continue;
}
for (let t=f+1; t < states.length; t++) {
for (let t = f + 1; t < states.length; t++) {
if (states[t].removed) continue;
if (states[t].diplomacy.includes("Vassal")) {
@ -729,7 +901,7 @@
}
// declare wars
for (let attacker=1; attacker < states.length; attacker++) {
for (let attacker = 1; attacker < states.length; attacker++) {
const ad = states[attacker].diplomacy; // attacker relations;
if (states[attacker].removed) continue;
if (!ad.includes("Rival")) continue; // no rivals to attack
@ -745,23 +917,27 @@
const dd = states[defender].diplomacy; // defender relations;
// start a war
const war = [`${an}-${trimVowels(dn)}ian War`,`${an} declared a war on its rival ${dn}`];
const war = [`${an}-${trimVowels(dn)}ian War`, `${an} declared a war on its rival ${dn}`];
const end = options.year;
const start = end - gauss(2, 2, 0, 5);
states[attacker].campaigns.push({name: `${trimVowels(dn)}ian War`, start, end});
states[defender].campaigns.push({name: `${trimVowels(an)}ian War`, start, end});
states[attacker].campaigns.push({ name: `${trimVowels(dn)}ian War`, start, end });
states[defender].campaigns.push({ name: `${trimVowels(an)}ian War`, start, end });
// attacker vassals join the war
ad.forEach((r, d) => {if (r === "Suzerain") {
attackers.push(d);
war.push(`${an}'s vassal ${states[d].name} joined the war on attackers side`);
}});
ad.forEach((r, d) => {
if (r === "Suzerain") {
attackers.push(d);
war.push(`${an}'s vassal ${states[d].name} joined the war on attackers side`);
}
});
// defender vassals join the war
dd.forEach((r, d) => {if (r === "Suzerain") {
defenders.push(d);
war.push(`${dn}'s vassal ${states[d].name} joined the war on defenders side`);
}});
dd.forEach((r, d) => {
if (r === "Suzerain") {
defenders.push(d);
war.push(`${dn}'s vassal ${states[d].name} joined the war on defenders side`);
}
});
ap = d3.sum(attackers.map(a => states[a].area * states[a].expansionism)); // attackers joined power
dp = d3.sum(defenders.map(d => states[d].area * states[d].expansionism)); // defender joined power
@ -791,9 +967,9 @@
ad.forEach((r, d) => {
if (r !== "Ally" || states[d].diplomacy.includes("Vassal") || defenders.includes(d)) return;
const name = states[d].name;
if (states[d].diplomacy[defender] !== "Rival" && (P(.2) || ap <= dp * 1.2)) {war.push(`${an}'s ally ${name} avoided entering the war`); return;}
if (states[d].diplomacy[defender] !== "Rival" && (P(.2) || ap <= dp * 1.2)) { war.push(`${an}'s ally ${name} avoided entering the war`); return; }
const allies = states[d].diplomacy.map((r, d) => r === "Ally" ? d : 0).filter(d => d);
if (allies.some(ally => defenders.includes(ally))) {war.push(`${an}'s ally ${name} did not join the war as its allies are in war on both sides`); return;};
if (allies.some(ally => defenders.includes(ally))) { war.push(`${an}'s ally ${name} did not join the war as its allies are in war on both sides`); return; };
attackers.push(d);
ap += states[d].area * states[d].expansionism;
@ -817,13 +993,13 @@
}
// select a forms for listed or all valid states
const defineStateForms = function(list) {
const defineStateForms = function (list) {
TIME && console.time("defineStateForms");
const states = pack.states.filter(s => s.i && !s.removed);
if (states.length < 1) return;
const generic = {Monarchy:25, Republic:2, Union:1};
const naval = {Monarchy:25, Republic:8, Union:3};
const generic = { Monarchy: 25, Republic: 2, Union: 1 };
const naval = { Monarchy: 25, Republic: 8, Union: 3 };
const median = d3.median(pack.states.map(s => s.area));
const empireMin = states.map(s => s.area).sort((a, b) => b - a)[Math.max(Math.ceil(states.length ** .4) - 2, 0)];
@ -834,17 +1010,17 @@
});
const monarchy = ["Duchy", "Grand Duchy", "Principality", "Kingdom", "Empire"]; // per expansionism tier
const republic = {Republic:75, Federation:4, Oligarchy:2, Tetrarchy:1, Triumvirate:1, Diarchy:1, "Trade Company":4, Junta:1}; // weighted random
const union = {Union:3, League:4, Confederation:1, "United Kingdom":1, "United Republic":1, "United Provinces":2, Commonwealth:1, Heptarchy:1}; // weighted random
const theocracy = {Theocracy: 20, Brotherhood:1, Thearchy:2, See:1};
const anarchy = {"Free Territory":2, Council:3, Commune:1, Community:1};
const republic = { Republic: 75, Federation: 4, Oligarchy: 2, Tetrarchy: 1, Triumvirate: 1, Diarchy: 1, "Trade Company": 4, Junta: 1 }; // weighted random
const union = { Union: 3, League: 4, Confederation: 1, "United Kingdom": 1, "United Republic": 1, "United Provinces": 2, Commonwealth: 1, Heptarchy: 1 }; // weighted random
const theocracy = { Theocracy: 20, Brotherhood: 1, Thearchy: 2, See: 1 };
const anarchy = { "Free Territory": 2, Council: 3, Commune: 1, Community: 1 };
for (const s of states) {
if (list && !list.includes(s.i)) continue;
const religion = pack.cells.religion[s.center];
const isTheocracy = religion && pack.religions[religion].expansion === "state" || (P(.1) && ["Organized", "Cult"].includes(pack.religions[religion].type));
const isAnarchy = P(.01 - expTiers[s.i]/500);
const isAnarchy = P(.01 - expTiers[s.i] / 500);
if (isTheocracy) s.form = "Theocracy";
else if (isAnarchy) s.form = "Anarchy";
@ -907,14 +1083,14 @@
// state forms requiring Adjective + Name, all other forms use scheme Form + Of + Name
const adjForms = ["Empire", "Sultanate", "Khaganate", "Shogunate", "Caliphate", "Despotate", "Theocracy", "Oligarchy", "Union", "Confederation", "Trade Company", "League", "Tetrarchy", "Triumvirate", "Diarchy", "Horde", "Marches"];
const getFullName = function(s) {
const getFullName = function (s) {
if (!s.formName) return s.name;
if (!s.name && s.formName) return "The " + s.formName;
const adjName = adjForms.includes(s.formName) && !(/-| /).test(s.name);
return adjName ? `${getAdjective(s.name)} ${s.formName}` : `${s.formName} of ${s.name}`;
}
const generateProvinces = function(regenerate) {
const generateProvinces = function (regenerate) {
TIME && console.time("generateProvinces");
const localSeed = regenerate ? Math.floor(Math.random() * 1e9).toString() : seed;
Math.random = aleaPRNG(localSeed);
@ -923,16 +1099,16 @@
const provinces = pack.provinces = [0];
cells.province = new Uint16Array(cells.i.length); // cell state
const percentage = +provincesInput.value;
if (states.length < 2 || !percentage) {states.forEach(s => s.provinces = []); return;} // no provinces
if (states.length < 2 || !percentage) { states.forEach(s => s.provinces = []); return; } // no provinces
const max = percentage == 100 ? 1000 : gauss(20, 5, 5, 100) * percentage ** .5; // max growth
const forms = {
Monarchy: {County:11, Earldom:3, Shire:1, Landgrave:1, Margrave:1, Barony:1},
Republic: {Province:6, Department:2, Governorate:2, District:1, Canton:1, Prefecture:1},
Theocracy: {Parish:3, Deanery:1},
Union: {Province:1, State:1, Canton:1, Republic:1, County:1, Council:1},
Anarchy: {Council:1, Commune:1, Community:1, Tribe:1},
Wild: {Territory:10, Land:5, Region:2, Tribe:1, Clan:1, Dependency:1, Area: 1}
Monarchy: { County: 11, Earldom: 3, Shire: 1, Landgrave: 1, Margrave: 1, Barony: 1 },
Republic: { Province: 6, Department: 2, Governorate: 2, District: 1, Canton: 1, Prefecture: 1 },
Theocracy: { Parish: 3, Deanery: 1 },
Union: { Province: 1, State: 1, Canton: 1, Republic: 1, County: 1, Council: 1 },
Anarchy: { Council: 1, Commune: 1, Community: 1, Tribe: 1 },
Wild: { Territory: 10, Land: 5, Region: 2, Tribe: 1, Clan: 1, Dependency: 1, Area: 1 }
}
// generate provinces for a selected burgs
@ -947,7 +1123,7 @@
const provincesNumber = Math.max(Math.ceil(stateBurgs.length * percentage / 100), 2);
const form = Object.assign({}, forms[s.form]);
for (let i=0; i < provincesNumber; i++) {
for (let i = 0; i < provincesNumber; i++) {
const province = provinces.length;
s.provinces.push(province);
const center = stateBurgs[i].cell;
@ -963,23 +1139,23 @@
const type = getType(center, burg.port);
const coa = COA.generate(stateBurgs[i].coa, kinship, null, type);
coa.shield = COA.getShield(c, s.i);
provinces.push({i:province, state:s.i, center, burg, name, formName, fullName, color, coa});
provinces.push({ i: province, state: s.i, center, burg, name, formName, fullName, color, coa });
}
});
// expand generated provinces
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
const queue = new PriorityQueue({ comparator: (a, b) => a.p - b.p });
const cost = [];
provinces.forEach(function(p) {
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});
queue.queue({ e: p.center, p: 0, province: p.i, state: p.state });
cost[p.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) {
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;
@ -990,7 +1166,7 @@
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});
queue.queue({ e, p: totalCost, province, state });
}
});
}
@ -1003,7 +1179,7 @@
if (adversaries.length < 2) continue;
const buddies = neibs.filter(c => c === cells.province[i]).length;
if (buddies.length > 2) continue;
const competitors = adversaries.map(p => adversaries.reduce((s, v) => v === p ? s+1 : s, 0));
const competitors = adversaries.map(p => adversaries.reduce((s, v) => v === p ? s + 1 : s, 0));
const max = d3.max(competitors);
if (buddies >= max) continue;
cells.province[i] = adversaries[competitors.indexOf(max)];
@ -1024,11 +1200,11 @@
// expand province
const cost = []; cost[center] = 1;
queue.queue({e:center, p:0});
queue.queue({ e: center, p: 0 });
while (queue.length) {
const next = queue.dequeue(), n = next.e, p = next.p;
cells.c[n].forEach(function(e) {
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;
@ -1039,7 +1215,7 @@
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});
queue.queue({ e, p: totalCost });
}
});
}
@ -1061,7 +1237,7 @@
const type = getType(center, burgs[burg]?.port);
const coa = COA.generate(s.coa, kinship, dominion, type);
coa.shield = COA.getShield(c, s.i);
provinces.push({i:province, state:s.i, center, burg, name, formName, fullName, color, coa});
provinces.push({ i: province, state: s.i, center, burg, name, formName, fullName, color, coa });
s.provinces.push(province);
// check if there is a land way within the same state between two cells
@ -1088,8 +1264,10 @@
TIME && console.timeEnd("generateProvinces");
}
return {generate, expandStates, normalizeStates, assignColors,
return {
generate, expandStates, normalizeStates, assignColors,
drawBurgs, specifyBurgs, defineBurgFeatures, getType, drawStateLabels, collectStatistics,
generateCampaigns, generateDiplomacy, defineStateForms, getFullName, generateProvinces, updateCultures};
generateCampaigns, generateDiplomacy, defineStateForms, getFullName, generateProvinces, updateCultures
};
})));