// --------------------------------------------------------------------------- // UNDUM game library. Games and other modules using it should require it: // var undum = require('undum'); // // Version: 3.2.0-oreolek // --------------------------------------------------------------------------- var Random = require('./random.js'); var languages = require('./localize.coffee'); // --------------------------------------------------------------------------- // Infrastructure implementations /* Crockford's inherit function */ /* (Exported globally) */ Function.prototype.inherits = function(Parent) { var d = {}, p = (this.prototype = new Parent()); this.prototype.uber = function(name) { if (!(name in d)) d[name] = 0; var f, r, t = d[name], v = Parent.prototype; if (t) { while (t) { v = v.constructor.prototype; t -= 1; } f = v[name]; } else { f = p[name]; if (f == this[name]) { f = v[name]; } } d[name] += 1; r = f.apply(this, Array.prototype.slice.apply(arguments, [1])); d[name] -= 1; return r; }; return this; }; // Feature detection var hasLocalStorage = function() { var hasStorage = false; try { hasStorage = ('localStorage' in window) && window.localStorage !== null && window.localStorage !== undefined; } catch (err) { // Firefox with the "Always Ask" cookie accept setting // will throw an error when attempting to access localStorage hasStorage = false; } return hasStorage; }; // Assertion var AssertionError = function(message) { this.message = message; this.name = AssertionError; }; AssertionError.inherits(Error); var assert = function(expression, message) { if (!expression) { throw new AssertionError(message); } }; // ----------------------------------------------------------------------- // Types for Author Use // ----------------------------------------------------------------------- /* The game is split into situations, which respond to user * choices. Situation is the base type. It has three methods: * enter, act and exit, which you implement to perform any * processing and output any content. The default implementations * do nothing. * * You can either create your own type of Situation, and add * enter, act and/or exit functions to the prototype (see * SimpleSituation in this file for an example of that), or you * can give those functions in the opts parameter. The opts * parameter is an object. So you could write: * * var situation = Situation({ * entering: function(character, system, from) { * ... your implementation ... * } * }); * * If you pass in enter, act and/or exit through these options, * then they should have the same function signature as the full * function definitions, below. * * Note that SimpleSituation, a derived type of Situation, calls * passed in enter, act and exit functions AS WELL AS their normal * action. This is most often what you want: the normal behavior * plus a little extra custom behavior. If you want to override * the behavior of a SimpleSituation, you'll have to create a * derived type and set the enter, act and/or exit function on * their prototypes. In most cases, however, if you want to do * something completely different, it is better to derive your * type from this type: Situation, rather than one of its * children. * * In addition to enter, exit and act, the following options * related to implicit situation selection are available: * * optionText: a string or a function(character, system, * situation) which should return the label to put in an * option block where a link to this situation can be * chosen. The situation passed in is the situation where the * option block is being displayed. * * canView: a function(character, system, situation) which should * return true if this situation should be visible in an * option block in the given situation. * * canChoose: a function(character, system, situation) which should * return true if this situation should appear clickable in an * option block. Returning false allows you to present the * option but prevent it being selected. You may want to * indicate to the player that they need to collect some * important object before the option is available, for * example. * * tags: a list of tags for this situation, which can be used for * implicit situation selection. The tags can also be given as * space, tab or comma separated tags in a string. Note that, * when calling `getSituationIdChoices`, tags are prefixed with * a hash, but that should not be the case here. Just use the * plain tag name. * * priority: a numeric priority value (default = 1). When * selecting situations implicitly, higher priority situations * are considered first. * * frequency: a numeric relative frequency (default = 1), so 100 * would be 100 times more frequent. When there are more * options that can be displayed, situations will be selected * randomly based on their frequency. * * displayOrder: a numeric ordering value (default = 1). When * situations are selected implicitly, the results are ordered * by increasing displayOrder. */ var Situation = function(opts) { if (opts) { if (opts.entering) this._entering = opts.entering; if (opts.act) this._act = opts.act; // Options related to this situation being automatically // selected and displayed in a list of options. this._optionText = opts.optionText; this._canView = opts.canView || true; this._canChoose = opts.canChoose || true; this._priority = (opts.priority !== undefined) ? opts.priority : 1; this._frequency = (opts.frequency !== undefined) ? opts.frequency : 1; this._displayOrder = (opts.displayOrder !== undefined) ? opts.displayOrder : 1; // Tag are not stored with an underscore, because they are // accessed directy. They should not be context sensitive // (use the canView function to do context sensitive // manipulation). if (opts.tags !== undefined) { if ($.isArray(opts.tags)) { this.tags = opts.tags; } else { this.tags = opts.tags.split(/[ \t,]+/); } } else { this.tags = []; } } else { this._canView = true; this._canChoose = true; this._priority = 1; this._frequency = 1; this._displayOrder = 1; this.tags = []; } }; /* A function that takes action when we enter a situation. The * last parameter indicates the situation we have just left: it * may be null if this is the starting situation. Unlike the * exit() method, this method cannot prevent the transition * happening: its return value is ignored. */ Situation.prototype.entering = function(character, system, from) { if (this._entering) this._entering(character, system, from); }; /* A function that takes action when we carry out some action in a * situation that isn't intended to lead to a new situation. */ Situation.prototype.act = function(character, system, action) { if (this._act) this._act(character, system, action); }; /* Determines whether this situation should be contained within a * list of options generated automatically by the given * situation. */ Situation.prototype.canView = function(character, system, situation) { if ($.isFunction(this._canView)) { return this._canView(character, system, situation); } else { return this._canView; } }; /* Determines whether this situation should be clickable within a * list of options generated automatically by the given situation. */ Situation.prototype.canChoose = function(character, system, situation) { if ($.isFunction(this._canChoose)) { return this._canChoose(character, system, situation); } else { return this._canChoose; } }; /* Returns the text that should be used to display this situation * in an automatically generated list of choices. */ Situation.prototype.optionText = function(character, system, situation) { if ($.isFunction(this._optionText)) { return this._optionText(character, system, situation); } else { return this._optionText; } }; /* Returns the priority, frequency and displayOrder for this situation, * when being selected using `system.getSituationIdChoices`. */ Situation.prototype.choiceData = function(character, system, situation) { return { priority: this._priority, frequency: this._frequency, displayOrder: this._displayOrder }; }; /* A simple situation has a block of content that it displays when * the situation is entered. The content must be valid "Display * Content" (see `System.prototype.write` for a definition). This * constructor has options that control its behavior: * * heading: The optional `heading` will be used as a section title * before the content is displayed. The heading can be any * HTML string, it doesn't need to be "Display Content". If * the heading is not given, no heading will be displayed. If * a heading is given, and no optionText is specified (see * `Situation` for more information on `optionText`), then the * heading will also be used for the situation's option text. * * actions: This should be an object mapping action Ids to a * response. The response should either be "Display Content" * to display if this action is carried out, or it should be a * function(character, system, action) that will process the * action. * * choices: A list of situation ids and tags that, if given, will * be used to compile an implicit option block using * `getSituationIdChoices` (see that function for more details * of how this works). Tags in this list should be prefixed * with a hash # symbol, to distinguish them from situation * ids. If just a single tag or id is needed, it can be passed * in as a string without wrapping into a list. * * minChoices: If `choices` is given, and an implicit choice block * should be compiled, set this option to require at least * this number of options to be displayed. See * `getSituationIdChoices` for a description of the algorithm by * which this happens. If you do not specify the `choices` * option, then this option will be ignored. * * maxChoices: If `choices` is given, and an implicit choice block * should be compiled, set this option to require no more than * this number of options to be displayed. See * `getSituationIdChoices` for a description of the algorithm * by which this happens. If you do not specify the `choices` * option, then this option will be ignored. * * The remaining options in the `opts` parameter are the same as for * the base Situation. */ var SimpleSituation = function(content, opts) { Situation.call(this, opts); this.content = content; this.heading = opts && opts.heading; this.actions = opts && opts.actions; this.choices = opts && opts.choices; this.minChoices = opts && opts.minChoices; this.maxChoices = opts && opts.maxChoices; }; SimpleSituation.inherits(Situation); SimpleSituation.prototype.entering = function(character, system, from) { if (this.heading) { if ($.isFunction(this.heading)) { system.writeHeading(this.heading()); } else { system.writeHeading(this.heading); } } if (this._entering) this._entering(character, system, from); if (this.content) { if ($.isFunction(this.content)) { system.write(this.content()); } else { system.write(this.content); } } if (this.choices) { var choices = system.getSituationIdChoices(this.choices, this.minChoices, this.maxChoices); system.writeChoices(choices); } }; SimpleSituation.prototype.act = function(character, system, action) { var response = this.actions[action]; try { response(character, system, action); } catch (err) { if (response) system.write(response); } if (this._act) this._act(character, system, action); }; SimpleSituation.prototype.optionText = function(character, system, sitn) { var parentResult = Situation.prototype.optionText.call(this, character, system, sitn); if (parentResult === undefined) { return this.heading; } else { return parentResult; } }; /* Instances of this class define the qualities that characters * may possess. The title should be a string, and can contain * HTML. Options are passed in in the opts parameter. The * following options are available. * * priority - A string used to sort qualities within their * groups. When the system displays a list of qualities they * will be sorted by this string. If you don't give a * priority, then the title will be used, so you'll get * alphabetic order. Normally you either don't give a * priority, or else use a priority string containing 0-padded * numbers (e.g. "00001"). * * group - The Id of a group in which to display this * parameter. The corresponding group must be defined in * your `undum.game.qualityGroups` property. * * extraClasses - These classes will be attached to the
of the first paragraph, and ends with the
of * the last. So "Foo
" is valid, but "foo" is not. * * The content goes to the end of the page, unless you supply the * optional selector argument. If you do, the content appears * after the element that matches that selector. */ System.prototype.write = function(content, elementSelector) { doWrite(content, elementSelector, 'append', 'after'); }; /* Outputs the given content in a heading on the page. The content * supplied must be valid "Display Content". * * The content goes to the end of the page, unless you supply the * optional selector argument. If you do, the content appears * after the element that matches that selector. */ System.prototype.writeHeading = function(headingContent, elementSelector) { var heading = $("