plottoriffic/index.js
Xe Iaso ec8ae7e063 bump to v2.3.0, fix modern node builds
Signed-off-by: Xe Iaso <me@christine.website>
2023-04-11 22:40:34 -04:00

337 lines
8.8 KiB
JavaScript

'use strict'
const seedrandom = require('seedrandom');
const plotto = require("./data/plotto.json");
function regEscape(s) { return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); };
//seedrandom doesn't expose the seed used, so generate one here
const defaultSeed = seedrandom()();
const defaultRng = seedrandom(defaultSeed);
const genderMap = {
"A": "male",
"A-2": "male",
"A-3": "male",
"A-4": "male",
"A-5": "male",
"A-6": "male",
"A-7": "male",
"A-8": "male",
"A-9": "male",
"B": "female",
"B-2": "female",
"B-3": "female",
"B-4": "female",
"B-5": "female",
"B-6": "female",
"B-7": "female",
"B-8": "female",
"B-9": "female",
"F-A": "father",
"M-A": "mother",
"BR-A": "male",
"SR-A": "female",
"SN-A": "male",
"D-A": "female",
"U-A": "male",
"AU-A": "female",
"CN-A": "male",
"NW-A": "male",
"NC-A": "female",
"GF-A": "male",
"GM-A": "female",
"SF-A": "male",
"SM-A": "female",
"GCH-A": "any",
"F-B": "male",
"M-B": "female",
"BR-B": "male",
"SR-B": "female",
"SN-B": "male",
"D-B": "female",
"U-B": "male",
"AU-B": "female",
"CN-B": "female",
"NW-B": "male",
"NC-B": "female",
"GF-B": "male",
"GM-B": "female",
"SF-B": "male",
"SM-B": "female",
"GCH-B": "any",
"BR": "male",
"SR": "female",
"SN": "male",
"D": "female",
"CN": "any",
"CH": "any",
"AX": "male",
"BX": "female",
"X": "none"
};
function createRandomPicker(rng = defaultRng) {
return function randomPicker(arr) {
if (!arr) {
return null;
}
if (arr.length == 0)
return null;
if (arr.length == 1)
return arr[0];
let min = 0;
let max = arr.length;
let i = Math.floor(rng() * (max - min)) + min;
let ret = arr[i];
return ret;
};
}
let maleNames = [];
let femaleNames = [];
function createRandomNamer(rng = defaultRng) {
return function randomNamer(characterSymbol, symbolDescription, gender) {
let arr;
if (gender == 'male') {
arr = maleNames;
} else if (gender == 'female') {
arr = femaleNames;
} else if (gender == 'any') {
if (rng() < 0.5) {
arr = maleNames;
} else {
arr = femaleNames;
}
} else {
return characterSymbol;
}
let min = 0;
let max = arr.length;
let i = Math.floor(rng() * (max - min)) + min;
let ret = arr[i];
arr.splice(i, 1);
return ret || characterSymbol;
};
}
class PlotGenerator {
constructor({ picker = undefined, namer = undefined, rng = undefined, flipGenders = undefined } = {}) {
this._namer = namer || createRandomNamer();
this._picker = picker || createRandomPicker();
this._flipGenders = flipGenders;
}
get flipGenders() { return this._flipGenders; }
set flipGenders(flip) { this._flipGenders = flip; }
generate() {
let flip = this._flipGenders;
if (flip === undefined) {
flip = this._picker([true, false], 'flip genders');
}
let rootTransform = {};
if (this._flipGenders) {
rootTransform = {
"A": "B",
"A-2": "B-2",
"A-3": "B-3",
"A-4": "B-4",
"A-5": "B-5",
"A-6": "B-6",
"A-7": "B-7",
"A-8": "B-8",
"A-9": "B-9",
"B": "A",
"B-2": "A-2",
"B-3": "A-3",
"B-4": "A-4",
"B-5": "A-5",
"B-6": "A-6",
"B-7": "A-7",
"B-8": "A-8",
"B-9": "A-9"
};
}
let preamble = this._picker(plotto.masterClauseA, 'master clause A');
let resolution = this._picker(plotto.masterClauseC, 'master clause C');
let masterPlot = this._picker(plotto.masterClauseB, 'master clause B');
let subject = [masterPlot.group, ' / ', masterPlot.subgroup, ': ', masterPlot.description].join('');
let conflict = plotto.conflicts[this._picker(masterPlot.nodes, 'main conflict')];
let cast = [];
let plot = this._expand(conflict, rootTransform, { leadIns: 1, carryOns: 1 }).replace(/\*/g, '');
plot = this._applyNames(plot, cast);
return {
subject,
group: masterPlot.group,
subgroup: masterPlot.subgroup,
description: masterPlot.description,
cast: cast,
plot: [
subject,
preamble,
plot,
resolution
].join('\n\n').trim()
};
}
_transformToRegex(o) {
let keys = Object.keys(o);
keys.sort(function (a, b) { return b.length - a.length; });
if (keys.length == 0) {
return null;
}
let pattern = '\\b(?:' + keys.map(function (s) { return '(?:' + regEscape(s) + ')'; }).join('|') + ')(?![a-z])';
return new RegExp(pattern, 'g');
}
_applyNames(text, cast) {
//reset default name lists
maleNames = require('./data/names_male.json').slice(0);
femaleNames = require('./data/names_female.json').slice(0);
//randomNamer(characterSymbol, symbolDescription, gender)
let nameCache = {};
let rg = this._transformToRegex(plotto.characters);
if (rg) {
text = text.replace(rg, (match) => {
if (!match || match.length == 0)
return '';
let t = plotto.characters[match];
if (!t) {
console.log('Could not replace match in template: ' + match);
return match;
}
let ret = nameCache[match];
if (!ret) {
ret = this._namer(match, t, genderMap[match]);
nameCache[match] = ret;
cast.push({
symbol: match,
name: ret,
description: t
});
}
return ret;
});
}
return text;
}
_expand(item, transform, ctx, start, end) {
let ret = [];
if (!item)
return 'NULL';
if (ctx.leadIns > 0 && item.leadIns) {
ctx.leadIns -= 1;
ret.push(this._expand(item.leadIns, null, ctx));
}
let carryon = null;
if (ctx.carryOns > 0 && item.carryOns) {
ctx.carryOns -= 1;
carryon = this._expand(item.carryOns, transform, ctx);
}
if (typeof (item) == "string") {
ret.push(this._expand(plotto.conflicts[item], null, ctx));
} else if (Array.isArray(item)) {
ret.push(this._expand(this._picker(item, 'plot option'), null, ctx));
} else if (item.conflictid) {
if (typeof (item.description) == "string") {
ret.push(item.description);
} else {
for (let subdesc of item.description) {
if (typeof (subdesc) == "string") {
ret.push(subdesc);
} else {
ret.push(this._expand(subdesc, null, ctx));
}
}
}
} else if (item.v) {
if (item.start || item.end) {
ret.push(this._expand(plotto.conflicts[item.v], item.tfm, ctx, item.start, item.end));
} else if (item.op == '+') {
for (let sub of item.v) {
ret.push(this._expand(sub, item.tfm, ctx));
}
} else {
ret.push(this._expand(item.v, item.tfm, ctx));
}
}
if (carryon) {
ret.push(carryon);
}
let str = ret.join('\n\n').trim();
if (transform) {
let rg = this._transformToRegex(transform);
if (rg) {
let cmp = {
before: str,
transform: transform
};
str = str.replace(rg, function (match) {
if (!match || match.length == 0)
return '';
let t = transform[match];
if (!t) {
console.log('Could not replace match in template: ' + match);
return match;
}
return t;
});
cmp.after = str;
//console.log(cmp);
}
}
return str;
}
}
module.exports = PlotGenerator;
module.exports.defaultSeed = defaultSeed;