lobus
Version:
A Ranvier-compatible input-event library, based on Inquirer and choose-your-own-adventure-style branching.
131 lines (112 loc) • 3.36 kB
JavaScript
const Scenario = require('./Scenario');
/**
* @typedef {Object} ChoicesConfig
* @property {Object} scenarios
* @property {EventEmitter} socket
* @property {Function} say
*/
/**
* @typedef {Object} Choices
* @augments {ChoicesConfig}
* @property {Object} decisions
*/
module.exports = class Choices {
/**
* Create a set of Scenarios and Choices to make.
* @param {ChoicesConfig} config defining scenarios, socket, and say
*/
constructor({scenarios, socket, say}) {
if (!scenarios || !scenarios.length) {
throw new Error('Your choices must include an Array of at least one scenario.');
}
if (!socket || !socket.emit) {
throw new Error('You must specify a socket that is EventEmitter-like.');
}
if (!say || typeof say !== 'function') {
throw new Error('You must specify a valid say function.');
}
this.scenarios = scenarios;
this.socket = socket;
this.say = say;
this.decisions = {};
}
/**
* Reduces the series of scenarios to a single promise that resolves then the user is done making decisions.
*
* @returns {Promise}
*/
decideAll() {
return this.scenarios.reduce(
(previous, scenario) => previous.then(this.decide.bind(this, scenario)),
Promise.resolve()
);
}
/**
* Allows the user to make a decision about a single scenario.
* @param {Scenario} scenario
* @returns {Promise}
*/
decide(scenario) {
return new Promise((resolve) => {
const predicate = scenario.prerequisite && scenario.prerequisite.bind(scenario);
const shouldAsk = predicate ? predicate(Object.assign({}, this.decisions)) : true;
if (!shouldAsk) {
this.decisions[scenario.name] = false;
return resolve();
}
if (scenario.decided) {
return resolve();
}
const redo = () => this.decide(scenario).then(resolve);
this.say('');
this.say(scenario.title);
if (scenario.description) {
this.say(scenario.description);
}
this.say('');
const validChoices = scenario.choices
.filter((choice) => choice.prerequisite.call(this, this.decisions));
validChoices.forEach((choice, i) =>
this.say(`| <cyan>[${i + 1}]</cyan> ${choice.description}`)
);
this.say('|\r\n`-> ');
this.socket.once('data', (data) => {
const input = parseInt(data, 10) - 1;
if (isNaN(input) || !validChoices[input]) {
this.say('Invalid selection...');
return redo();
}
const selection = validChoices[input];
selection.effect.call(this, scenario);
this.decisions[scenario.name] = selection.id;
resolve();
});
});
}
/**
* Factory function to create a new scenario.
*
* @static
* @param {String} name
* @param {ScenarioConfig} config
* @returns {Scenario}
*/
static createScenario(name, config) {
return new Scenario(name, config);
}
/**
* Creates and runs through a new set of Choices.
* @param {ChoicesConfig} config
* @returns {Promise}
*/
static run(config) {
let choices;
try {
choices = new Choices(config);
} catch(e) {
console.log(e);
return Promise.resolve('Failed, please contact an Admin.');
}
return choices.decideAll.call(choices);
}
}