updated templates
This commit is contained in:
parent
51354fa168
commit
673772385d
@ -80,7 +80,7 @@ X |an inanimate object, an object of mystery, an uncertain quantity |
|
|||||||
|
|
||||||
## 1928 and Civil Rights Discrepancies
|
## 1928 and Civil Rights Discrepancies
|
||||||
|
|
||||||
Plotto was written in 1928 decades before the civil rights area in the US. Therefore the generated plots may seem to favor males and have some references to race.
|
Plotto was written in 1928 decades before the civil rights era in the US. Therefore the generated plots may seem to favor males and have some references to race.
|
||||||
|
|
||||||
### Protagonist Gender
|
### Protagonist Gender
|
||||||
|
|
||||||
|
1
data/names_female.json
Normal file
1
data/names_female.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
["SOPHIA","MADISON","MIA","EMMA","OLIVIA","ISABELLA","ABIGAIL","EMILY","AVA","SOFIA","ASHLEY","SARAH","SAMANTHA","LEAH","KAYLA","CHLOE"]
|
1
data/names_male.json
Normal file
1
data/names_male.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
["JOHN","JUSTIN","AIDEN","JACOB","JASON","ALEXANDER","WILLIAM","JOSEPH","MATTHEW","JAYDEN","BENJAMIN","GABRIEL","CHRISTOPHER","MASON","JAMES","LIAM","DAVID","SAMUEL","NOAH","MICHAEL","ANTHONY","CHRISTIAN","BRANDON","JOSHUA","LOGAN","ETHAN","RYAN","LUCAS","DANIEL","DYLAN","TYLER","NICHOLAS","JONATHAN","ANDREW","JACK","KEVIN","THOMAS"]
|
2467
data/plotto.json
2467
data/plotto.json
File diff suppressed because it is too large
Load Diff
58
fix.js
58
fix.js
@ -10,7 +10,7 @@ const jsonPath = require('jsonpath');
|
|||||||
const obj = require('./data/plotto.json');
|
const obj = require('./data/plotto.json');
|
||||||
const _ = require('underscore');
|
const _ = require('underscore');
|
||||||
|
|
||||||
//console.log(JSON.stringify(Object.keys(obj.conflicts), null, '\t'));
|
/*
|
||||||
let added = obj.conflicts;
|
let added = obj.conflicts;
|
||||||
for(let ck of Object.keys(obj.conflicts)) {
|
for(let ck of Object.keys(obj.conflicts)) {
|
||||||
let conflict = obj.conflicts[ck];
|
let conflict = obj.conflicts[ck];
|
||||||
@ -59,22 +59,76 @@ for(let ck of Object.keys(obj.conflicts)) {
|
|||||||
//console.log(idxs[i].name, ':', data);
|
//console.log(idxs[i].name, ':', data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
//console.log(JSON.stringify(added, null, '\t'));
|
//console.log(JSON.stringify(added, null, '\t'));
|
||||||
let allKeys = Object.keys(obj.conflicts);
|
let allKeys = Object.keys(obj.conflicts);
|
||||||
|
|
||||||
let stk = [];
|
let stk = [];
|
||||||
for(let o of jsonPath.query(obj, '$.conflicts.*')) {
|
for(let o of jsonPath.query(obj, '$.conflicts.*')) {
|
||||||
stk.push(o.conflictid);
|
stk.push(o.conflictid);
|
||||||
|
|
||||||
|
expandDesc(o);
|
||||||
|
|
||||||
|
/*
|
||||||
for(let arrt of ['leadIns', 'carryOns']) {
|
for(let arrt of ['leadIns', 'carryOns']) {
|
||||||
stk.push(arrt);
|
stk.push(arrt);
|
||||||
o[arrt] = expand(o[arrt]);
|
o[arrt] = expand(o[arrt]);
|
||||||
stk.pop();
|
stk.pop();
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
stk.pop();
|
stk.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function expandDesc(item) {
|
||||||
|
|
||||||
|
if(typeof(item.description) != "string") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rg = /\{([^}]+)\}/g;
|
||||||
|
let m = rg.exec(item.description);
|
||||||
|
if(!m) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let desc = item.description;
|
||||||
|
let repl = [];
|
||||||
|
let prevStart = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
|
||||||
|
let s = desc.slice(prevStart, m.index);
|
||||||
|
if(s) { repl.push(s); }
|
||||||
|
|
||||||
|
let refNodes = expand(_.map(m[1].split("|"), function(idstr) {
|
||||||
|
|
||||||
|
let addOp = idstr.split(';');
|
||||||
|
if(addOp.length > 1) {
|
||||||
|
return { op:'+', v:addOp};
|
||||||
|
}
|
||||||
|
|
||||||
|
return idstr;
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
repl.push(refNodes);
|
||||||
|
|
||||||
|
prevStart = m.index + m[0].length + 1;
|
||||||
|
|
||||||
|
} while((m = rg.exec(item.description)));
|
||||||
|
|
||||||
|
item.description = repl;
|
||||||
|
//console.log(item.conflictid, ':', desc);
|
||||||
|
//console.log('===', JSON.stringify(repl));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
function expand(item) {
|
function expand(item) {
|
||||||
|
|
||||||
|
if(typeof(item) == "string") {
|
||||||
|
item = fixToCh(item);
|
||||||
|
}
|
||||||
|
|
||||||
if(typeof(item) == "string") {
|
if(typeof(item) == "string") {
|
||||||
|
|
||||||
let ret = item;
|
let ret = item;
|
||||||
@ -137,7 +191,7 @@ function expand(item) {
|
|||||||
//console.log('OUTLIER!!', stk.join('/'), '[' + k + ']:', item, ' ---> ', JSON.stringify(ret), ' ____ ', JSON.stringify(ret2));
|
//console.log('OUTLIER!!', stk.join('/'), '[' + k + ']:', item, ' ---> ', JSON.stringify(ret), ' ____ ', JSON.stringify(ret2));
|
||||||
ret = ret2;
|
ret = ret2;
|
||||||
} else {
|
} else {
|
||||||
//console.log('!!!!!OUTLIER!!', stk.join('/'), '[' + k + ']:', item, ' ---> ', JSON.stringify(ret), ' ____ ', d);
|
console.log('!!!!!OUTLIER!!', stk.join('/'), '[' + k + ']:', item, ' ---> ', JSON.stringify(ret), ' ____ ', d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
254
index.js
254
index.js
@ -6,44 +6,145 @@ function regEscape(s) {
|
|||||||
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||||
};
|
};
|
||||||
|
|
||||||
function pick(rng, arr) {
|
//seedrandom doesn't expose the seed used, so generate one here
|
||||||
if(!arr) {
|
const defaultSeed = seedrandom()(); ;
|
||||||
return null;
|
const defaultRng = seedrandom(defaultSeed);
|
||||||
}
|
|
||||||
if(arr.length == 0)
|
const genderMap = {
|
||||||
return null;
|
"A": "male",
|
||||||
if(arr.length == 1)
|
"A-2": "male",
|
||||||
return arr[0];
|
"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) {
|
||||||
|
|
||||||
let min = 0;
|
return function randomNamer(characterSymbol, symbolDescription, gender) {
|
||||||
let max = arr.length;
|
|
||||||
let i =Math.floor(rng() * (max - min)) + min;
|
let arr;
|
||||||
let ret = arr[i];
|
if(gender == 'male') {
|
||||||
//console.log(i, ':', JSON.stringify(ret), '!!!!', JSON.stringify(arr));
|
arr = maleNames;
|
||||||
return ret;
|
} 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 {
|
class PlotGenerator {
|
||||||
|
|
||||||
constructor({ seed=null, flipGenders=undefined } = {}) {
|
constructor({ picker=undefined, namer=undefined, rng=undefined, flipGenders=undefined } = {}) {
|
||||||
//seedrandom doesn't expose the seed used, so generate one here
|
|
||||||
this._seed = seed || seedrandom()();
|
|
||||||
|
|
||||||
this._rng = seedrandom(this._seed);
|
|
||||||
|
|
||||||
if(flipGenders === undefined) {
|
this._namer = namer || createRandomNamer();
|
||||||
this._flipGenders = this._rng() < 0.5; //50% chance true/false
|
this._picker = picker || createRandomPicker();
|
||||||
} else {
|
this._flipGenders = flipGenders;
|
||||||
this._flipGenders = flipGenders;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get seed() { return this._seed; }
|
|
||||||
get flipGenders() { return this._flipGenders; }
|
get flipGenders() { return this._flipGenders; }
|
||||||
set flipGenders(flip) { this._flipGenders = flip; }
|
set flipGenders(flip) { this._flipGenders = flip; }
|
||||||
|
|
||||||
generate() {
|
generate() {
|
||||||
|
|
||||||
|
let flip = this._flipGenders;
|
||||||
|
if(flip === undefined) {
|
||||||
|
flip = this._picker([true, false], 'flip genders');
|
||||||
|
}
|
||||||
|
|
||||||
let rootTransform = {};
|
let rootTransform = {};
|
||||||
if(this._flipGenders) {
|
if(this._flipGenders) {
|
||||||
rootTransform = {
|
rootTransform = {
|
||||||
@ -67,21 +168,79 @@ class PlotGenerator {
|
|||||||
"B-9": "A-9"
|
"B-9": "A-9"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let preamble = pick(this._rng, plotto.masterClauseA);
|
let preamble = this._picker(plotto.masterClauseA, 'master clause A');
|
||||||
let resolution = pick(this._rng, plotto.masterClauseC);
|
let resolution = this._picker(plotto.masterClauseC, 'master clause C');
|
||||||
let masterPlot = pick(this._rng, plotto.masterClauseB);
|
let masterPlot = this._picker(plotto.masterClauseB, 'master clause B');
|
||||||
|
|
||||||
let subject = [masterPlot.group, ' / ', masterPlot.subgroup, ': ', masterPlot.description].join('');
|
let subject = [masterPlot.group, ' / ', masterPlot.subgroup, ': ', masterPlot.description].join('');
|
||||||
|
|
||||||
let conflict = plotto.conflicts[pick(this._rng, masterPlot.nodes)];
|
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 {
|
||||||
|
group: masterPlot.group,
|
||||||
|
subgroup: masterPlot.subgroup,
|
||||||
|
description: masterPlot.description,
|
||||||
|
cast: cast,
|
||||||
|
plot: [
|
||||||
|
preamble,
|
||||||
|
plot,
|
||||||
|
resolution
|
||||||
|
].join('\n\n').trim()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_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 ks = Object.keys(plotto.characters);
|
||||||
|
if(ks.length > 0) {
|
||||||
|
let pattern = '\\b(?:' + ks.map(function(s) {
|
||||||
|
return '(?:' + regEscape(s) + ')';
|
||||||
|
}).join('|') + ')(?=^|$| |,|\\.)';
|
||||||
|
|
||||||
|
let rg = new RegExp(pattern, 'g');
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
return [
|
|
||||||
subject,
|
|
||||||
preamble,
|
|
||||||
this._expand(conflict, rootTransform, {leadIns:1, carryOns:1}).replace(/\*/g, ''),
|
|
||||||
resolution
|
|
||||||
].join('\n\n').trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_expand(item, transform, ctx, start, end) {
|
_expand(item, transform, ctx, start, end) {
|
||||||
@ -105,9 +264,24 @@ class PlotGenerator {
|
|||||||
if(typeof(item) == "string") {
|
if(typeof(item) == "string") {
|
||||||
ret.push(this._expand(plotto.conflicts[item], null, ctx));
|
ret.push(this._expand(plotto.conflicts[item], null, ctx));
|
||||||
} else if(Array.isArray(item)) {
|
} else if(Array.isArray(item)) {
|
||||||
ret.push(this._expand(pick(this._rng, item), null, ctx));
|
ret.push(this._expand(this._picker(item, 'plot option'), null, ctx));
|
||||||
} else if(item.conflictid) {
|
} else if(item.conflictid) {
|
||||||
ret.push(item.description);
|
|
||||||
|
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) {
|
} else if(item.v) {
|
||||||
|
|
||||||
if(item.start || item.end) {
|
if(item.start || item.end) {
|
||||||
@ -161,5 +335,5 @@ class PlotGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = PlotGenerator;
|
module.exports = PlotGenerator;
|
||||||
|
module.exports.defaultSeed = defaultSeed;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "plottoriffic",
|
"name": "plottoriffic",
|
||||||
"version": "1.0.0",
|
"version": "2.0.0",
|
||||||
"description": "Generate plots based on Plotto: A New Method of Plot Suggestion for Writers of Creative Fiction",
|
"description": "Generate plots based on Plotto: A New Method of Plot Suggestion for Writers of Creative Fiction",
|
||||||
"keywords": ["procedural", "generative", "fiction", "story", "plot", "ES6"],
|
"keywords": ["procedural", "generative", "fiction", "story", "plot", "ES6"],
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user