Files
StoryletManager/storymanager.js

154 lines
5.0 KiB
JavaScript

// Set up the general narrative manager
// -----------------------------------------------------------------------
var StoryManager = {};
StoryManager.storylets = {};
StoryManager.nextStoryletCache = [];
StoryManager.currentStorylet = Object.create(null);
StoryManager.getAllStorylets = function(tag=null) {
let allStorylets = [];
let storylet, storylets, boundStorylet;
for (let key in this.storylets) {
storylet = this.storylets[key];
if (tag === null || ("tags" in storylet && storylet.tags.includes(tag))) {
for (let boundStorylet of storylet.generate()) {
//boundStorylet = storylets[i];
if (!("priority" in boundStorylet)) boundStorylet.priority = 1;
allStorylets.push(boundStorylet);
}
}
}
return allStorylets;
}
StoryManager.getStorylets =
function(n=null, tag=null, selection="ordered", respect_interrupt=true) {
/*
Get n storylets, prioritizing the highest-priority ones first.
n: if not null, return at most n storylets
tag: Only get storylets matching this tag
respect_interrupt: if true, any interrupt storylet overrides n and priority
*/
let allStorylets;
allStorylets = this.getAllStorylets(tag);
// Check for interruptions
if (respect_interrupt) {
let interruptions = [];
for (let storylet of allStorylets)
if (storylet.interrupt) interruptions.push(storylet);
if (interruptions.length > 0) {
interruptions.sort((a, b) => b.priority - a.priority);
return [interruptions[0]];
}
}
// Return n or the max
if (n != null) {
n = Math.min(n, allStorylets.length);
if (n == 0) return [];
}
else n = allStorylets.length;
let selectedStorylets;
if (selection == "ordered")
selectedStorylets = this.sortByPriority(allStorylets, n);
else if (selection == "weighted")
selectedStorylets = this.weightedRandom(allStorylets, n);
return selectedStorylets;
}
// Get storylets strictly by priority
StoryManager.sortByPriority = function(allStorylets, n) {
let selectedStorylets = [];
// Select by strict priority; randomize among matching priority.
let priorities = Array.from(new Set(allStorylets.map(x => x.priority)));
priorities.sort((a, b) => b - a);
let currentPriority = 0;
while (selectedStorylets.length < n) {
let priorityStorylets = allStorylets.filter(s => s.priority==priorities[currentPriority]);
priorityStorylets.sort((a, b) => (Math.random() - Math.random()));
let nAdd = Math.min(n - selectedStorylets.length, priorityStorylets.length);
for (let i=0; i<nAdd; i++) selectedStorylets.push(priorityStorylets.pop());
currentPriority++;
}
return selectedStorylets;
}
// Get storylets via weighted random choice
StoryManager.weightedRandom = function(allStorylets, n) {
let selectedStorylets = [];
let sum, counter, index, rand;
while (selectedStorylets.length < n) {
sum = allStorylets.reduce((a, x) => a + x.priority, 0);
counter = 0;
rand = Math.random() * sum;
for (let i=0; i<allStorylets.length; i++) {
if (counter + allStorylets[i].priority > rand) {
selectedStorylets.push(allStorylets.splice(i, 1)[0]);
break;
}
counter += allStorylets[i].priority;
}
}
return selectedStorylets;
}
StoryManager.setCurrentStorylet = function(id) {
if (Object.hasOwn(StoryManager.nextStoryletCache, id)) {
StoryManager.currentStorylet = StoryManager.nextStoryletCache[id]
StoryManager.nextStoryletCache = Object.create(null);
} else {
console.log("Error: didn't find storylet cache entry: " + id);
}
}
StoryManager.getStoryletLinks = function(n, tag, selection) {
var links = _.map(StoryManager.getStorylets(n, tag, selection), function(storylet) {
// Maybe store the current storylet in a map generating a unique ID, then
// add some code below looking it up via that ID?
//
// How to keep storylets from building up over time?
// Clear them when a passage is left
var id = _.uniqueId("storylet-key_")
StoryManager.nextStoryletCache[id] = storylet;
return "<div onclick=\"window.SM.setCurrentStorylet('" + id + "');\" >[[" + storylet.description + "|" + storylet.passage + "]]<br></div>"
})
return _.reduce(links, function(memo, link) { return memo + link }, "")
}
// Set up macros
// ---------------------------------------------------------------
// Macro.add("getStoryletLinks", {
// handler: function() {
// let n, tag, selection;
// [n=null, tag=null, selection="ordered"] = this.args;
// State.temporary.nextStorylets = StoryManager.getStorylets(n, tag, selection);
// $(this.output).wiki(`\
// <<for _storylet range _nextStorylets>> \
// <<capture _storylet>> \
// [[_storylet.description|_storylet.passage][$currentStorylet=_storylet]]<br>
// <</capture>> \
// <</for>> \
// `)
// }
// });
// Macro.add("linkToNextStorylet", {
// handler: function() {
// let text, tag, selection;
// [text, tag=null, selection="weighted"] = this.args;
// State.temporary.nextStorylet = StoryManager.getStorylets(1, tag, selection)[0];
// $(this.output).wiki(`<<capture _nextStorylet>>[[${text}|_nextStorylet.passage][$currentStorylet=_nextStorylet]]<</capture>>`);
// }
// });
window.SM = StoryManager;