This commit is contained in:
Azgaar 2019-09-23 22:57:23 +03:00
parent 5320279289
commit 729d91c053
11 changed files with 557 additions and 193 deletions

480
main.js
View file

@ -646,7 +646,7 @@ function generateSeed() {
// Place points to calculate Voronoi diagram
function placePoints() {
console.time("placePoints");
const cellsDesired = 10000 * densityInput.value; // generate 10k points for graphSize = 1
const cellsDesired = 10000 * densityInput.value; // generate 10k points for each densityInput point
const spacing = grid.spacing = rn(Math.sqrt(graphWidth * graphHeight / cellsDesired), 2); // spacing between points before jirrering
grid.boundary = getBoundaryPoints(graphWidth, graphHeight, spacing);
grid.points = getJitteredGrid(graphWidth, graphHeight, spacing); // jittered square grid
@ -1175,163 +1175,7 @@ function rankCells() {
console.timeEnd('rankCells');
}
// add a some zones
function addZones(number = 1) {
console.time("addZones");
const data = [], cells = pack.cells, states = pack.states, burgs = pack.burgs;
const used = new Uint8Array(cells.i.length); // to store used cells
if (Math.random() < .8 * number) addRebels(); // rebels along a state border
for (let i=0; i < rn(Math.random() * 1.8 * number); i++) addDisease(); // disease starting in a random city
for (let i=0; i < rn(Math.random() * 1.8 * number); i++) addDisaster(); // disaster starting in a random city
for (let i=0; i < rn(Math.random() * 1.8 * number); i++) addEruption(); // volcanic eruption afecing cells aroung volcanoes
for (let i=0; i < rn(Math.random() * 1.4 * number); i++) addFault(); // fault line
function addRebels() {
const state = states.find(s => s.i && s.neighbors.size > 0 && s.neighbors.values().next().value);
if (!state) return;
const neib = state.neighbors.values().next().value;
const cellsArray = cells.i.filter(i => cells.state[i] === state.i && cells.c[i].some(c => cells.state[c] === neib));
const rebels = rw({"Rebels":5, "Insurgents":2, "Recusants":1,
"Mutineers":1, "Rioters":1, "Dissenters":1, "Secessionists":1,
"Insurrection":2, "Rebellion":1, "Conspiracy":2});
const name = getAdjective(states[neib].name) + " " + rebels;
data.push({name, type:"Rebels", cells:cellsArray, fill:"url(#hatch3)"});
}
function addDisease() {
const burg = ra(burgs.filter(b => b.i && !b.removed)); // random burg
if (!burg) return;
const cellsArray = [], cost = [], power = rand(20, 40);
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
queue.queue({e:burg.cell, p: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;
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 (!cost[e] || p < cost[e]) {
cost[e] = p;
queue.queue({e, p});
}
});
}
const adjective = () => ra(["Great", "Silent", "Severe", "Blind", "Unknown", "Loud", "Deadly", "Burning", "Bloody", "Brutal", "Fatal"]);
const animal = () => ra(["Ape", "Bear", "Boar", "Cat", "Cow", "Dog", "Pig", "Fox", "Bird", "Horse", "Rat", "Raven", "Sheep", "Spider", "Wolf"]);
const color = () => ra(["Golden", "White", "Black", "Red", "Pink", "Purple", "Blue", "Green", "Yellow", "Amber", "Orange", "Brown", "Grey"]);
const type = rw({"Fever":5, "Pestilence":2, "Flu":2, "Pox":2, "Smallpox":2, "Plague":4, "Cholera":2, "Ague":1, "Dropsy":1, "Leprosy":2});
const name = rw({[color()]:4, [animal()]:2, [adjective()]:1}) + " " + type;
data.push({name, type:"Disease", cells:cellsArray, fill:"url(#hatch12)"});
}
function addDisaster() {
const burg = ra(burgs.filter(b => b.i && !b.removed)); // random burg
if (!burg) return;
const cellsArray = [], cost = [], power = rand(5, 28);
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
queue.queue({e:burg.cell, p: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;
cells.c[next.e].forEach(function(e) {
const c = rand(1, 10);
const p = next.p + c;
if (p > power) return;
if (!cost[e] || p < cost[e]) {
cost[e] = p;
queue.queue({e, p});
}
});
}
// Avalanche, Tsunami
const type = rw({"Famine":5, "Drought":3, "Dearth":1, "Earthquake":3, "Tornadoes":1, "Wildfires":1, "Flood":3});
const name = getAdjective(burg.name) + " " + type;
data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch5)"});
}
function addEruption() {
const volcanoes = [];
markers.selectAll("use[data-id='#marker_volcano']").each(function() {
volcanoes.push(this.dataset.cell);
});
if (!volcanoes.length) return;
const cell = +ra(volcanoes);
const id = markers.select("use[data-cell='"+cell+"']").attr("id");
const note = notes.filter(n => n.id === id);
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);
while (queue.length) {
const q = Math.random() < .5 ? queue.shift() : queue.pop();
cellsArray.push(q);
if (cellsArray.length > power) break;
cells.c[q].forEach(e => {
if (used[e]) return;
used[e] = 1;
queue.push(e);
});
}
data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch7)"});
}
function addFault() {
const elevated = cells.i.filter(i => cells.h[i] > 50 && cells.h[i] < 70 && !used[i]);
if (!elevated.length) return;
const cell = ra(elevated);
const cellsArray = [], queue = [cell], power = rand(3, 15);
while (queue.length) {
const q = queue.pop();
cellsArray.push(q);
if (cellsArray.length > power) break;
cells.c[q].forEach(e => {
if (used[e]) return;
used[e] = 1;
queue.push(e);
});
}
const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
const name = proper + " Fault";
data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch2)"});
}
void function drawZones() {
zones.selectAll("g").data(data).enter().append("g")
.attr("id", (d, i) => "zone"+i).attr("data-description", d => d.name).attr("data-type", d => d.type)
.attr("data-cells", d => d.cells.join(",")).attr("fill", d => d.fill)
.selectAll("polygon").data(d => d.cells).enter().append("polygon")
.attr("points", d => getPackPolygon(d)).attr("id", function(d) {return this.parentNode.id+"_"+d});
}()
console.timeEnd("addZones");
}
// add some markers as an example
// generate some markers
function addMarkers(number = 1) {
console.time("addMarkers");
const cells = pack.cells;
@ -1536,6 +1380,326 @@ function addMarkers(number = 1) {
console.timeEnd("addMarkers");
}
// regenerate some zones
function addZones(number = 1) {
console.time("addZones");
const data = [], cells = pack.cells, states = pack.states, burgs = pack.burgs;
const used = new Uint8Array(cells.i.length); // to store used cells
for (let i=0; i < rn(Math.random() * 1.8 * number); i++) addInvasion(); // invasion of enemy lands
for (let i=0; i < rn(Math.random() * 1.6 * number); i++) addRebels(); // rebels along a state border
for (let i=0; i < rn(Math.random() * 1.6 * number); i++) addProselytism(); // proselitism of organized religion
for (let i=0; i < rn(Math.random() * 1.6 * number); i++) addCrusade(); // crusade on heresy lands
for (let i=0; i < rn(Math.random() * 1.8 * number); i++) addDisease(); // disease starting in a random city
for (let i=0; i < rn(Math.random() * 1.4 * number); i++) addDisaster(); // disaster starting in a random city
for (let i=0; i < rn(Math.random() * 1.4 * number); i++) addEruption(); // volcanic eruption aroung volcano
for (let i=0; i < rn(Math.random() * 1.0 * number); i++) addAvalanche(); // avalanche impacting highland road
for (let i=0; i < rn(Math.random() * 1.4 * number); i++) addFault(); // fault line in elevated areas
for (let i=0; i < rn(Math.random() * 1.4 * number); i++) addFlood() // flood on river banks
for (let i=0; i < rn(Math.random() * 1.2 * number); i++) addTsunami() // tsunami starting near coast
function addInvasion() {
const atWar = states.filter(s => s.diplomacy.some(d => d === "Enemy"));
if (!atWar.length) return;
const invader = ra(atWar);
const target = invader.diplomacy.findIndex(d => d === "Enemy");
const cell = ra(cells.i.filter(i => cells.state[i] === target && cells.c[i].some(c => cells.state[c] === invader.i)));
if (!cell) return;
const cellsArray = [], queue = [cell], power = rand(5, 30);
while (queue.length) {
const q = Math.random() < .4 ? queue.shift() : queue.pop();
cellsArray.push(q);
if (cellsArray.length > power) break;
cells.c[q].forEach(e => {
if (used[e]) return;
if (cells.state[e] !== target) return;
used[e] = 1;
queue.push(e);
});
}
const invasion = rw({"Invasion":4, "Occupation":3, "Raid":2, "Conquest":2,
"Subjugation":1, "Foray":1, "Irruption":1, "Incursion":2, "Pillage":1, "Intervention":1});
const name = getAdjective(invader.name) + " " + invasion;
data.push({name, type:"Invasion", cells:cellsArray, fill:"url(#hatch1)"});
}
function addRebels() {
const state = ra(states.filter(s => s.i && Array.from(s.neighbors).some(n => n)));
if (!state) return;
const neib = ra(Array.from(state.neighbors).filter(n => n));
const cell = cells.i.find(i => cells.state[i] === state.i && cells.c[i].some(c => cells.state[c] === neib));
const cellsArray = [], queue = [cell], power = rand(10, 30);
while (queue.length) {
const q = queue.shift();
cellsArray.push(q);
if (cellsArray.length > power) break;
cells.c[q].forEach(e => {
if (used[e]) return;
if (cells.state[e] !== state.i) return;
used[e] = 1;
if (e%4 !== 0 && !cells.c[e].some(c => cells.state[c] === neib)) return;
queue.push(e);
});
}
const rebels = rw({"Rebels":5, "Insurgents":2, "Recusants":1,
"Mutineers":1, "Rioters":1, "Dissenters":1, "Secessionists":1,
"Insurrection":2, "Rebellion":1, "Conspiracy":2});
const name = getAdjective(states[neib].name) + " " + rebels;
data.push({name, type:"Rebels", cells:cellsArray, fill:"url(#hatch3)"});
}
function addProselytism() {
const organized = ra(pack.religions.filter(r => r.type === "Organized"));
if (!organized) return;
const cell = ra(cells.i.filter(i => cells.religion[i] && cells.religion[i] !== organized.i && cells.c[i].some(c => cells.religion[c] === organized.i)));
if (!cell) return;
const target = cells.religion[cell];
const cellsArray = [], queue = [cell], power = rand(10, 30);
while (queue.length) {
const q = queue.shift();
cellsArray.push(q);
if (cellsArray.length > power) break;
cells.c[q].forEach(e => {
if (used[e]) return;
if (cells.religion[e] !== target) return;
if (cells.h[e] < 20) return;
used[e] = 1;
//if (e%2 !== 0 && !cells.c[e].some(c => cells.state[c] === neib)) return;
queue.push(e);
});
}
const name = getAdjective(organized.name.split(" ")[0]) + " Proselytism";
data.push({name, type:"Proselytism", cells:cellsArray, fill:"url(#hatch6)"});
}
function addCrusade() {
const heresy = ra(pack.religions.filter(r => r.type === "Heresy"));
if (!heresy) return;
const cellsArray = cells.i.filter(i => !used[i] && cells.religion[i] === heresy.i);
if (!cellsArray.length) return;
cellsArray.forEach(i => used[i] = 1);
const name = getAdjective(heresy.name.split(" ")[0]) + " Crusade";
data.push({name, type:"Crusade", cells:cellsArray, fill:"url(#hatch6)"});
}
function addDisease() {
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});
while (queue.length) {
const next = queue.dequeue();
if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e);
used[next.e] = 1;
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 (!cost[e] || p < cost[e]) {
cost[e] = p;
queue.queue({e, p});
}
});
}
const adjective = () => ra(["Great", "Silent", "Severe", "Blind", "Unknown", "Loud", "Deadly", "Burning", "Bloody", "Brutal", "Fatal"]);
const animal = () => ra(["Ape", "Bear", "Boar", "Cat", "Cow", "Dog", "Pig", "Fox", "Bird", "Horse", "Rat", "Raven", "Sheep", "Spider", "Wolf"]);
const color = () => ra(["Golden", "White", "Black", "Red", "Pink", "Purple", "Blue", "Green", "Yellow", "Amber", "Orange", "Brown", "Grey"]);
const type = rw({"Fever":5, "Pestilence":2, "Flu":2, "Pox":2, "Smallpox":2, "Plague":4, "Cholera":2, "Ague":1, "Dropsy":1, "Leprosy":2});
const name = rw({[color()]:4, [animal()]:2, [adjective()]:1}) + " " + type;
data.push({name, type:"Disease", cells:cellsArray, fill:"url(#hatch12)"});
}
function addDisaster() {
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});
while (queue.length) {
const next = queue.dequeue();
if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e);
used[next.e] = 1;
cells.c[next.e].forEach(function(e) {
const c = rand(1, 10);
const p = next.p + c;
if (p > power) return;
if (!cost[e] || p < cost[e]) {
cost[e] = p;
queue.queue({e, p});
}
});
}
const type = rw({"Famine":5, "Dearth":1, "Drought":3, "Earthquake":3, "Tornadoes":1, "Wildfires":1});
const name = getAdjective(burg.name) + " " + type;
data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch5)"});
}
function addEruption() {
const volcanoes = [];
markers.selectAll("use[data-id='#marker_volcano']").each(function() {
volcanoes.push(this.dataset.cell);
});
if (!volcanoes.length) return;
const cell = +ra(volcanoes);
const id = markers.select("use[data-cell='"+cell+"']").attr("id");
const note = notes.filter(n => n.id === id);
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);
while (queue.length) {
const q = Math.random() < .5 ? queue.shift() : queue.pop();
cellsArray.push(q);
if (cellsArray.length > power) break;
cells.c[q].forEach(e => {
if (used[e]) return;
used[e] = 1;
queue.push(e);
});
}
data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch7)"});
}
function addAvalanche() {
const roads = cells.i.filter(i => !used[i] && cells.road[i] && cells.h[i] >= 70);
if (!roads.length) return;
const cell = +ra(roads);
const cellsArray = [], queue = [cell], power = rand(3, 15);
while (queue.length) {
const q = Math.random() < .3 ? queue.shift() : queue.pop();
cellsArray.push(q);
if (cellsArray.length > power) break;
cells.c[q].forEach(e => {
if (used[e] || cells.h[e] < 65) return;
used[e] = 1;
queue.push(e);
});
}
const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
const name = proper + " Avalanche";
data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch5)"});
}
function addFault() {
const elevated = cells.i.filter(i => !used[i] && cells.h[i] > 50 && cells.h[i] < 70);
if (!elevated.length) return;
const cell = ra(elevated);
const cellsArray = [], queue = [cell], power = rand(3, 15);
while (queue.length) {
const q = queue.pop();
if (cells.h[q] >= 20) cellsArray.push(q);
if (cellsArray.length > power) break;
cells.c[q].forEach(e => {
if (used[e] || cells.r[e]) return;
used[e] = 1;
queue.push(e);
});
}
const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
const name = proper + " Fault";
data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch2)"});
}
function addFlood() {
const fl = cells.fl.filter(fl => fl), meanFlux = d3.mean(fl), maxFlux = d3.max(fl), flux = (maxFlux - meanFlux) / 2 + meanFlux;
const rivers = cells.i.filter(i => !used[i] && cells.h[i] < 50 && cells.r[i] && cells.fl[i] > flux && cells.burg[i]);
if (!rivers.length) return;
const cell = +ra(rivers), river = cells.r[cell];
const cellsArray = [], queue = [cell], power = rand(5, 30);
while (queue.length) {
const q = queue.pop();
cellsArray.push(q);
if (cellsArray.length > power) break;
cells.c[q].forEach(e => {
if (used[e] || cells.h[e] < 20 || cells.r[e] !== river || cells.h[e] > 50 || cells.fl[e] < meanFlux) return;
used[e] = 1;
queue.push(e);
});
}
const name = getAdjective(burgs[cells.burg[cell]].name) + " Flood";
data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch13)"});
}
function addTsunami() {
const coastal = cells.i.filter(i => !used[i] && cells.t[i] === -1 && pack.features[cells.f[i]].type !== "lake");
if (!coastal.length) return;
const cell = +ra(coastal);
const cellsArray = [], queue = [cell], power = rand(10, 30);
while (queue.length) {
const q = queue.shift();
if (cells.t[q] === 1) cellsArray.push(q);
if (cellsArray.length > power) break;
cells.c[q].forEach(e => {
if (used[e]) return;
if (cells.h[e] >= 20 && !cells.t[e]) return;
if (pack.features[cells.f[e]].type === "lake") return;
used[e] = 1;
queue.push(e);
});
}
const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
const name = proper + " Tsunami";
data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch13)"});
}
void function drawZones() {
zones.selectAll("g").data(data).enter().append("g")
.attr("id", (d, i) => "zone"+i).attr("data-description", d => d.name).attr("data-type", d => d.type)
.attr("data-cells", d => d.cells.join(",")).attr("fill", d => d.fill)
.selectAll("polygon").data(d => d.cells).enter().append("polygon")
.attr("points", d => getPackPolygon(d)).attr("id", function(d) {return this.parentNode.id+"_"+d});
}()
console.timeEnd("addZones");
}
// show map stats on generation complete
function showStatistics() {
const template = templateInput.value;