1
0
Fork 0
mirror of https://github.com/Oreolek/raconteur.git synced 2024-04-25 21:49:22 +03:00

Add package.json, set up for npm

This commit is contained in:
Bruno Dias 2015-04-10 18:18:44 -03:00
parent 8239392a89
commit 860ad6dd79
5 changed files with 568 additions and 1 deletions

1
.gitignore vendored
View file

@ -1,6 +1,5 @@
node_modules/*
!node_modules/undularity
!node_modules/undum
devel/build/**
game/**
*.sublime-workspace

417
lib/situation.js Normal file
View file

@ -0,0 +1,417 @@
var undum = require('undum'),
md = require('markdown-it'),
$ = require('jquery');
/* ---------------------------------------------------------------------------
Undularity is a rethought API for Undum, featuring more usable interfaces
which coalesce as a DSL for defining Undum stories.
----------------------------------------------------------------------------*/
/* ---------------------------------------------------------------------------
Helper functions
----------------------------------------------------------------------------*/
/*
Normalises the whitespace on a string. So the indentation level of the
first line will become 0. FIXME: This isn't quite ideal. Need to figure out
a better way of preventing strings in source code ending up interpreted as
<pre> blocks.
*/
String.prototype.normaliseTabs = function () {
let lines = this.split('\n');
let indent = lines[0].match(/^\s+/) || lines[1].match(/^\s+/);
if (!indent) return this;
return lines.map( s => s.replace(new RegExp('^' + indent), '')).join('\n');
};
/* Agnostic Call */
/*
Many properties in Undularity can be either a String, or a function that
takes some objects from the game state (character, system, and the current
situation) and returns a String. Or in Haskell terms:
String | (CharacterObject -> SystemObject -> SituationString -> String)
fcall() is added to the prototypes of both String and Function to handle
these situations. When called on a Function, it's an alias for
Function#call(); when called on a String, it only returns the string itself,
discarding any input.
*/
Function.prototype.fcall = Function.prototype.call;
String.prototype.fcall = function () {return this;};
/*
Markdown renderer, defined with options.
*/
var markdown = new md({
typographer: true, // Use smart quotes.
html: true // Passthrough html.
});
/*
Ensures a string is a HTML string, by wrapping it in span tags.
*/
String.prototype.spanWrap = function () {
return `<span>${this}</span>`;
};
/*
Adds the "fade" class to a htmlString.
*/
String.prototype.fade = function () {
return $(this).addClass('fade');
};
/* Situations ----------------------------------------------------------------
The prototype UndularitySituation is the basic spec for situations
created with Undularity. It should be able to handle any use case for Undum.
Properties:
(In addition to properties inherited from undum.Situation)
actions :: {key: (character, system, from) -> null}
An object containing definitions for actions, which are called when an
action without a special marker (writer, inserter, replacer) is called
when the situation is current, usually by clicking an action link.
after :: (character, system, from) -> null
A function that is called right after printing the content of the
situation. Useful for housekeeping tasks (Such as changing character
stats) or implementing custom behaviour in general.
before :: (character, system, from) -> null
Similar to after, but called first
choices :: [String]
A list of situation names and/or tags that can be listed as choices for
this situation. That list will be further filtered by CanView and
CanChoose.
content :: markdownString | (character, system, from) -> markdownString
The main content of the situation, printed when the situation is entered.
visited :: Number
Defaults to 0. Incremented every time the situation is entered.
writers :: {key: markdownString | (character, system, from) -> markdownString}
An object containing definitions for special actions called by inserter,
writer, and replacer links. Note that the content of writer links will be
interpreted as a regular markdownString, while the content of replacer
and inserter links, on the assumption that it's meant to be written into
an existing paragraph, will be interpreted as a inline markdown.
*/
var UndularitySituation = function (spec) {
undum.Situation.call(this, spec);
// Add all properties of the spec to the object, indiscriminately.
Object.keys(spec).forEach( key => {
if (this[key] === undefined) {
this[key] = spec[key];
}
});
this.visited = 0;
};
UndularitySituation.inherits(undum.Situation);
/*
Undum calls Situation.enter every time a situation is entered, and
passes it three arguments; The character object, the system object,
and a string referencing the previous situation, or null if there is
none (ie, for the starting situation).
Undularity's version of enter is set up to fulfill most use cases.
*/
UndularitySituation.prototype.enter = function (character, system, f) {
this.visited++;
if (this.before) this.before(character, system, f);
if (this.content) {
system.write(
markdown.render(
this.content.fcall(this, character, system, f).normaliseTabs()));
}
if (this.after) this.after(character, system, f);
if (this.choices) {
let choices = system.getSituationIdChoices(this.choices,
this.minChoices, this.maxChoices);
system.writeChoices(choices);
}
};
/*
Situation.prototype.act() is called by Undum whenever an action link
(Ie, a link that doesn't point at another situation or an external URL) is
clicked.
Undularity's version of act() is set up to implement commonly used
functionality: "writer" links, "replacer" links, "inserter" links, and
generic "action" links that call functions which access the underlying
Undum API.
*/
UndularitySituation.prototype.act = function (character, system, action) {
var actionClass,
self = this;
var responses = {
writer: function (ref) {
if (self.writers[ref] === undefined) {
console.log(self);
throw new Error("Tried to call undefined writer:" + ref);
}
system.write(
markdown.render(
self.writers[ref].fcall(self, character, system, action)
).fade());
},
replacer: function (ref) {
if (self.writers[ref] === undefined) {
throw new Error("Tried to call undefined replacer:" + ref);
}
system.replaceWith(
markdown.renderInline(
self.writers[ref].fcall(self, character, system, action)
).spanWrap().fade(), `#${ref}`);
},
inserter: function (ref) {
if (self.writers[ref] === undefined) {
throw new Error("Tried to call undefined inserter:" + ref);
}
system.writeInto(
markdown.renderInline(
self.writers[ref].fcall(self, character, system, action)
).spanWrap().fade(), `#${ref}`);
}
};
if (actionClass = action.match(/^_(\w+)_(.+)$/)) {
responses[actionClass[1]](actionClass[2]);
} else if (self.actions.hasOwnProperty(action)) {
self.actions[action].call(self, character, system, action);
} else {
throw new Err(`Action "${action}" attempted with no corresponding` +
'action in current situation.');
}
};
/* Element Helpers */
/*
While you can write HTML elements by hand, those helpers make it easier to
place anchors (especially with special purpose) and spans.
They define a monadic interface:
a.id('my-link').content('link').writer('my-ref')
-> <a id="my-link" href="./_writer_my-ref">link</a>
span -> <span></span>
The object supplies a toString() method that outputs the tag. Element
helpers are immutable, so theoretically this should be safe from side
effects. This interface allows the safe definition of templates which
can be used and modified at will, for instance:
let mySpanClass = span.class('myclass'); // -> <span class="myclass"></span>
mySpanClass.content("Hello!"); // -> <span class="myclass">Hello!</span>
Methods on an elementHelper return a new elementHelper that inherits from
itself. Since those objects (should be) immutable by being frozen when they
are created, this is semantically equivalent to returning a copy but more
efficient in terms
Methods:
class :: String -> elementHelper
Returns a new elementHelper with the given class added.
classes :: [String] -> elementHelper
Returns a new elementHelper with the given classes.
id :: String -> elementHelper
Returns a new elementHelper with the given id.
*/
var elementHelper = function (element) {
this.element = element;
this._classes = [];
};
var elementSetterGen = function (prop) {
return function (value) {
return Object.freeze(Object.create(this, {
[prop]: {value}
}));
};
};
elementHelper.prototype.classes = function (newClasses) {
return Object.freeze(Object.create(this, {
_classes: {
value: newClasses
}
}));
};
elementHelper.prototype.class = function (newClass) {
return this.classes(this._classes.concat(newClass));
};
elementHelper.prototype.id = elementSetterGen("_id");
elementHelper.prototype.type = elementSetterGen("_linkType");
elementHelper.prototype.content = elementSetterGen("_content");
elementHelper.prototype.ref = elementSetterGen("_ref");
elementHelper.prototype.url = elementHelper.prototype.ref;
elementHelper.prototype.situation = elementHelper.prototype.ref;
elementHelper.prototype.once = () => this.class('once');
var linkTypeGen = function (type) {
return function (ref) {
return this.type(type).ref(ref);
}
};
elementHelper.prototype.writer = linkTypeGen("writer");
elementHelper.prototype.replacer = linkTypeGen("replacer");
elementHelper.prototype.inserter = linkTypeGen("inserter");
elementHelper.prototype.action = linkTypeGen("action");
elementHelper.prototype.toString = function () {
var classes = "",
classString = "",
idString = "",
hrefString= "",
contentString = "";
if (this._classes) {
classes += this._classes.join(' ');
}
if (this._linkType) {
classes += (' ' + this._linkType);
}
if (classes) {
classString = ` class="${classes}"`;
}
if (this._id) {
idString = ` id="${this._id}"`;
}
if (this.element === "a") {
if (this._linkType) {
if (this._linkType === "action") {
hrefString = ` href="./${this._ref}"`;
} else {
hrefString = ` href="./_${this._linkType}_${this._ref}"`;
}
} else {
hrefString = ` href="${this._ref}"`;
}
}
if (this._content) {
contentString = markdown.renderInline(this._content);
}
return `<${this.element}${classString}${idString}${hrefString}>${contentString}</${this.element}>`;
};
var a_proto = Object.freeze(new elementHelper("a"));
var span_proto = Object.freeze(new elementHelper("span"));
var a = function (content) {
if (content) return a_proto.content(content);
return a_proto;
};
var span = function (content) {
if (content) return span_proto.content(content);
return span_proto;
};
/*
Quality definition function
Meant to be called only once in the main story source file, this definition
is passed a spec to define qualities. The spec is an object containing quality
groups as objects, which contain qualities that themselves hold definitions.
*/
var qualities = function (spec) {
Object.keys(spec).forEach(function(group) {
/* The special "name" and "options" properties are passed on. */
var groupName = (spec[group].name === undefined) ? null : spec[group].name;
var groupOpts = (spec[group].options === undefined) ? {} : spec[group].options;
undum.game.qualityGroups[group] = new undum.QualityGroup(groupName, groupOpts);
Object.keys(spec[group]).forEach(function(quality) {
if (quality === "name" || quality === "options") return;
undum.game.qualities[quality] = spec[group][quality](group);
});
});
};
var qualityShim = {
integer: "IntegerQuality",
nonZeroInteger: "NonZeroIntegerQuality",
numeric: "NumericQuality",
fudgeAdjectives: "FudgeAdjectivesQuality",
onOff: "OnOffQuality",
yesNo: "YesNoQuality"
};
Object.keys(qualityShim).forEach(function (key) {
qualities[key] = function (title, spec={}) {
return function (group) {
spec.group = group;
return new undum[qualityShim[key]](title, spec);
};
};
});
/*
WordScaleQuality has a different interface (naughty!) so it has to be
defined by hand.
*/
qualities.wordScale = function (title, words, spec={}) {
return function (group) {
spec.group = group;
return new undum.WordScaleQuality(title, words, spec);
};
};
module.exports = function (name, spec) {
spec.name = name;
undum.game.situations[name] = new UndularitySituation(spec);
};
module.exports.a = a;
module.exports.span = span;
module.exports.qualities = qualities;

130
lib/tools.js Normal file
View file

@ -0,0 +1,130 @@
/*
Undularity Tools
Those functions are not a core part of Undularity, but provide some
general functionality that relates to adaptive text generation.
This is provided partly as a helper to less technical users, and as
a convenience for authors.
*/
/* Monkey patching */
/* Array.prototype.shuffle() */
/*
Shuffles an array. It can use Undum's random number generator implementation,
so it expects a System.rnd object to be passed into it. If one isn't
supplied, it will use Math.Random instead.
This is an implementation of the Fischer-Yates (Knuth) shuffle.
Returns the shuffled array.
*/
Array.prototype.shuffle = function (system) {
var rng = (system) ? system.rnd.random : Math.random;
// slice() clones the array. Object members are copied by reference,
// beware.
var newArr = this.slice();
var m = newArr.length;
while (m) {
let i = Math.floor(rng() * m --);
let t = newArr[m];
newArr[m] = newArr[i];
newArr[i] = t;
}
return newArr;
};
/*
oneOf()
Takes an array and returns an object with several methods. Each method
returns an iterator which iterates over the array in a specific way:
inOrder()
Returns the array items in order.
cycling()
Returns the array items in order, cycling back to the first item when
it runs out.
stopping()
Returns the array items in order, then repeats the last item when it
runs out.
randomly()
Returns the array items at random. Takes a system object, for consistent
randomness. Will never return the same item twice in a row.
trulyAtRandom()
Returns the array items purely at random. Takes a system object, for
consistent randomness.
inRandomOrder()
Returns the array items in a random order. Takes a system object, for
consistent randomness.
*/
var oneOf = function (ary) {
return {
inOrder () {
var i = 0;
return function () {
return ary[i++];
};
},
cycling () {
var i = 0;
return function () {
if (i >= ary.length) i = 0;
return ary[i++];
};
},
stopping () {
var i = 0;
return function () {
if (i >= ary.length) i = ary.length - 1;
return ary[i++];
}
},
randomly (system) {
var rng = (system) ? system.random : Math.random,
last;
return function () {
var i;
do {
i = Math.floor(rng() * ary.length);
} while (i === last);
last = i;
return ary[i];
};
},
trulyAtRandom (system) {
var rng = (system) ? system.random : Math.random;
return function () {
return ary[Math.floor(rng() * ary.length)];
};
},
inRandomOrder (system) {
var shuffled = ary.shuffle(system),
i = 0;
return function () {
return shuffled[i++];
};
}
};
};
exports.oneOf = oneOf;

1
node_modules/undularity generated vendored Symbolic link
View file

@ -0,0 +1 @@
lib

20
package.json Normal file
View file

@ -0,0 +1,20 @@
{
"name": "undularity",
"version": "0.1.0",
"description": "A friendly API framework for building hypertext interactive fiction with Undum",
"license": "MIT",
"author": { "name": "Bruno Dias" },
"files": ["lib"],
"dependencies": {
"jquery": "~2.1.3",
"markdown-it": "~4.1.0",
"undum": "git://github.com/sequitur/undum.git#commonjs"
},
"devDependencies": {
"gulp": "~3.8.11",
"browserify": "~9.0.8",
"babelify": "~6.0.2",
"vinyl-source-stream": "~1.1.0",
"gulp-less": "~3.0.2"
}
}