// --------------------------------------------------------------------------- // UNDUM game library. This file needs to be supplemented with a game // file (conventionally called "your-game-name.game.js" which will // define the content of the game. // --------------------------------------------------------------------------- (function() { // ----------------------------------------------------------------------- // Internal Infrastructure Implementations [NB: These have to be // at the top, because we use them below, but you can safely // ignore them and skip down to the next section.] // ----------------------------------------------------------------------- /* Crockford's inherit function */ 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() { return ('localStorage' in window) && window.localStorage !== null; }; var isMobileDevice = function() { return (navigator.userAgent.toLowerCase().search( /iphone|ipad|palm|blackberry|android/ ) >= 0 || $("html").width() <= 640); }; // 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({ * enter: 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 the derived types of Situation (current * SimpleSituation), call passed in 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. */ var Situation = function(opts) { if (opts) { if (opts.enter) this._enter = opts.enter; if (opts.act) this._act = opts.act; if (opts.exit) this._exit = opts.exit; } }; /* 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.enter = function(character, system, from) { if (this._enter) this._enter(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); }; /* A function that takes action when we exit a situation. The last * parameter indicates the situation we are going to. */ Situation.prototype.exit = function(character, system, to) { if (this._exit) this._exit(character, system, to); }; /* 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. * * 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. * * 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; }; SimpleSituation.inherits(Situation); SimpleSituation.prototype.enter = function(character, system, from) { if (this.heading) { if ($.isFunction(this.heading)) { system.writeHeading(this.heading()); } else { system.writeHeading(this.heading); } } if (this.content) { if ($.isFunction(this.content)) { system.write(this.content()); } else { system.write(this.content); } } if (this._enter) this._enter(character, system, from); }; 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); }; /* 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 regular content to the page. The content supplied must * be valid "Display Content". * * The content goes to the beginning 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.writeBefore = function(content, elementSelector) { doWrite(content, elementSelector, 'prepend', 'before'); }; /* Carries out the given situation change or action, as if it were * in a link that has been clicked. This allows you to do * procedural transitions. You might have an action that builds up * the character's strength, and depletes their magic. When the * magic is all gone, you can force a situation change by calling * this method. */ System.prototype.doLink = function(code) { processLink(code); }; /* Turns any links that target the given href into plain * text. This can be used to remove action options when an action * is no longer available. It is used automatically when you give * a link the 'once' class. */ System.prototype.clearLinks = function(code) { $("a[href='" + code + "']").each(function(index, element) { var a = $(element); a.replaceWith($("").addClass("ex_link").html(a.html())); }); }; /* Call this to change the character text: the text in the right * toolbar before the qualities list. This text is designed to be * a short description of the current state of your character. The * content you give should be "Display Content" (see * `System.prototype.write` for the definition). */ System.prototype.setCharacterText = function(content) { var block = $("#character_text_content"); var oldContent = block.html(); var newContent = augmentLinks(content); if (interactive && block.is(':visible')) { block.fadeOut(250, function() { block.html(newContent); block.fadeIn(750); }); showHighlight($("#character_text")); } else { block.html(newContent); } }; /* Call this to change the value of a character quality. Don't * directly change quality values, because that will not update * the UI. (You can change any data in the character's sandbox * directly, however, since that isn't displayed). */ System.prototype.setQuality = function(quality, newValue) { var oldValue = character.qualities[quality]; character.qualities[quality] = newValue; if (!interactive) return; // Work out how to display the values. var newDisplay = newValue.toString(); var qualityDefinition = game.qualities[quality]; if (qualityDefinition) { newDisplay = qualityDefinition.format(character, newValue); } // Add the data block, if we need it. var qualityBlock = $("#q_"+quality); if (qualityBlock.size() <= 0) { if (newDisplay === null) return; qualityBlock = addQualityBlock(quality).hide().fadeIn(500); } else { // Do nothing if there's nothing to do. if (oldValue == newValue) return; // Change the value. if (newDisplay === null) { // Remove the block, and possibly the whole group, if // it is the last quality in the group. var toRemove = null; var groupBlock = qualityBlock.parents('.quality_group'); if (groupBlock.find('.quality').size() <= 1) { toRemove = groupBlock; } else { toRemove = qualityBlock; } toRemove.fadeOut(1000, function() { toRemove.remove(); }); } else { var valBlock = qualityBlock.find("[data-attr='value']"); valBlock.fadeOut(250, function() { valBlock.html(newDisplay); valBlock.fadeIn(750); }); } } showHighlight(qualityBlock); }; /* Changes a quality to a new value, but also shows a progress bar * animation of the change. This probably only makes sense for * qualities that are numeric, especially ones that the player is * grinding to increase. The quality and newValue parameters are * as for setQuality. The progress bar is controlled by the * following options in the opts parameter: * * from - The proportion along the progress bar where the * animation starts. Defaults to 0, valid range is 0-1. * * to - The proportion along the progress bar where the * animation ends. Defaults to 1, valid range is 0-1. * * showValue - If true (the default) then the new value of the * quality is displayed above the progress bar. * * displayValue - If this is given, and showValue is true, then * the displayValue is used above the progress bar. If this * isn't given, and showValue is true, then the display value * will be calculated from the QualityDefinition, as * normal. This option is useful for qualities that don't have * a definition, because they don't normally appear in the UI. * * title - The title of the progress bar. If this is not given, * then the title of the quality is used. As for displayValue * this is primarily used when the progress bar doesn't have a * QualityDefinition, and therefore doesn't have a title. * * leftLabel, rightLabel - Underneath the progress bar you can * place two labels at the left and right extent of the * track. These can help to give scale to the bar. So if the * bar signifies going from 10.2 to 10.5, you might label the * left and right extents with "10" and "11" respectively. If * these are not given, then the labels will be omitted. */ System.prototype.animateQuality = function(quality, newValue, opts) { var currentValue = character.qualities[quality]; if (!currentValue) currentValue = 0; // Change the base UI. this.setQuality(quality, newValue); if (!interactive) return; // Overload default options. var myOpts = { from: 0, to: 1, title: null, showValue: true, displayValue: null, leftLabel: null, rightLabel: null }; if (newValue < currentValue) { myOpts.from = 1; myOpts.to = 0; } $.extend(myOpts, opts); // Run through the quality definition. var qualityDefinition = game.qualities[quality]; if (qualityDefinition) { // Work out how to display the value if (myOpts.displayValue === null) { myOpts.displayValue = qualityDefinition.format( character, newValue ); } // Use the title. if (myOpts.title === null) { myOpts.title = qualityDefinition.title; } } // Create the animated bar. var totalWidth = 496; var bar = $("#ui_library #progress_bar").clone(); bar.removeAttr("id"); var widthElement = bar.find("[data-attr='width']"); widthElement.css('width', myOpts.from*totalWidth); // Configure its labels var titleLabel = bar.find("[data-attr='name']"); var valueLabel = bar.find("[data-attr='value']"); var leftLabel = bar.find("[data-attr='left_label']"); var rightLabel = bar.find("[data-attr='right_label']"); if (myOpts.title) { titleLabel.html(myOpts.title); } else { titleLabel.remove(); } if (myOpts.showValue && myOpts.displayValue !== null) { valueLabel.html(myOpts.displayValue); } else { valueLabel.remove(); } if (myOpts.leftLabel) { leftLabel.html(myOpts.leftLabel); } else { leftLabel.remove(); } if (myOpts.rightLabel) { rightLabel.html(myOpts.rightLabel); } else { rightLabel.remove(); } $('#content').append(bar); // Start the animation setTimeout(function() { widthElement.animate( {'width': myOpts.to*totalWidth}, 1000, function() { // After a moment to allow the bar to be read, we can // remove it. setTimeout(function() { if (mobile) { bar.fadeOut(1500, function() {$(this).remove();}); } else { bar.animate({opacity: 0}, 1500). slideUp(500, function() { $(this).remove(); }); } }, 2000); } ); }, 500); }; /* The character that is passed into each situation is of this * form. * * The `qualities` data member maps the Ids of each quality to its * current value. When implementing enter, act or exit functions, * you should consider this to be read-only. Make all * modifications through `System.prototype.setQuality`, or * `System.prototype.animateQuality`. In your `init` function, you * can set these values directly. * * The `sandbox` data member is designed to allow your code to * track any data it needs to. The only proviso is that the data * structure should be serializable into JSON (this means it must * only consist of primitive types [objects, arrays, numbers, * booleans, strings], and it must not contain circular series of * references). The data in the sandbox is not displayed in the * UI, although you are free to use it to create suitable output * for the player.. */ var Character = function() { this.qualities = {}; this.sandbox = {}; }; /* The data structure holding the content for the game. By default * this holds nothing. It is this data structure that is populated * in the `.game.js` file. Each element in the structure is * commented, below. * * This should be static data that never changes through the * course of the game. It is never saved, so anything that might * change should be stored in the character. */ var game = { // Situations /* An object mapping from the unique id of each situation, to * the situation object itself. This is the heart of the game * specification. */ situations: {}, /* The unique id of the situation to enter at the start of a * new game. */ start: "start", // Quality display definitions /* An object mapping the unique id of each quality to its * QualityDefinition. You don't need definitions for every * quality, but only qualities in this mapping will be * displayed in the character box of the UI. */ qualities: {}, /* Qualities can have an optional group Id. This maps those * Ids to the group definitions that says how to format its * qualities. */ qualityGroups: {}, // Hooks /* This function is called at the start of the game. It is * normally overridden to provide initial character creation * (setting initial quality values, setting the * character-text. This is optional, however, as set-up * processing could also be done by the first situation's * enter function. If this function is given it should have * the signature function(character, system). */ init: null, /* This function is called before entering any new * situation. It is called before the corresponding situation * has its `enter` method called. It can be used to implement * timed triggers, but is totally optional. If this function * is given it should have the signature: * * function(character, system, oldSituationId, newSituationId); */ enter: null, /* Hook for when the situation has already been carried out and printed. * The signature is * * function( character, system, oldSituationId, newSituationId ); */ afterEnter: null, /* This function is called before carrying out any action in * any situation. It is called before the corresponding * situation has its `act` method called. If this optional * function is given it should have the signature: * * function(character, system, situationId, actionId); * * If the function returns true, then it is indicating that it * has consumed the action, and the action will not be passed * on to the situation. Note that this is the only one of * these global handlers that can consume the event. */ beforeAction: null, /* This function is called after carrying out any action in * any situation. It is called after the corresponding * situation has its `act` method called. If this optional * function is given it should have the signature: * * function(character, system, situationId, actionId); */ afterAction: null, /* This function is called after leaving any situation. It is * called after the corresponding situation has its `exit` * method called. If this optional function is given it should * have the signature: * * function(character, system, oldSituationId, newSituationId); */ exit: null }; // ======================================================================= // Code below doesn't form part of the public API for UNDUM, so // you shouldn't find you need to use it. // ----------------------------------------------------------------------- // Internal Data // ----------------------------------------------------------------------- /* The global system object. */ var system = new System(); /* This is the data on the player's progress that gets saved. */ var progress = { // A random seed string, used internally to make random // sequences predictable. seed: null, // Keeps track of the links clicked, and when. sequence: [], // The time when the progress was saved. saveTime: null }; /* The Id of the current situation the player is in. */ var current = null; /* This is the current character. It should be reconstructable * from the above progress data. */ var character = null; /* Tracks whether we're in interactive mode or batch mode. */ var interactive = true; /* Tracks whether we're mobile or not. */ var mobile = isMobileDevice(); /* The system time when the game was initialized. */ var startTime; /* The stack of links, resulting from the last action, still be to * resolved. */ var linkStack = null // ----------------------------------------------------------------------- // Utility Functions // ----------------------------------------------------------------------- var getCurrentSituation = function() { if (current) { return game.situations[current]; } else { return null; } }; /* Outputs regular content to the page. Used by write and * writeBefore, the last two arguments control what jQuery methods * are used to add the content. */ var doWrite = function(content, selector, addMethod, appendMethod) { continueOutputTransaction(); var output = augmentLinks(content); var element; if (selector) element = $(selector); if (!element) { $('#content')[addMethod](output); } else { element[appendMethod](output); } /* We want to scroll this new element to the bottom of the screen. * while still being visible. The easiest way is to find the * top edge of the *following* element and move that exactly * to the bottom (while still ensuring that this element is fully * visible.) */ var nextel = output.last().next(); var scrollPoint; if (!nextel.length) scrollPoint = $("#content").height() + $("#title").height() + 60; else scrollPoint = nextel.offset().top - $(window).height(); if (scrollPoint > output.offset().top) scrollPoint = output.offset().top; scrollStack[scrollStack.length-1] = scrollPoint; }; /* Gets the unique id used to identify saved games. */ var getSaveId = function() { return 'undum_'+game.id+"_"+game.version; } /* Adds the quality blocks to the character tools. */ var showQualities = function() { $("#qualities").empty(); for (var qualityId in character.qualities) { addQualityBlock(qualityId); } }; /* Fades in and out a highlight on the given element. */ var showHighlight = function(domElement) { var highlight = domElement.find(".highlight"); if (highlight.size() <= 0) { highlight = $('"+"no_local_storage".l()+"
"); startGame(); } // Display the "click to begin" message. (We do this in code // so that, if Javascript is off, it doesn't happen.) $(".click_message").show(); // Show the game when we click on the title. $("#title").one('click', function() { $("#content_wrapper, #legal").fadeIn(500); $("#tools_wrapper").fadeIn(2000); $("#title").css("cursor", "default"); $("#title .click_message").fadeOut(250); if (mobile) { $("#toolbar").slideDown(500); $("#menu").show(); } }); // Any point that an option list appears, its options are its // first links. $("ul.options li, #menu li").live('click', function(event) { // Make option clicks pass through to their first link. var link = $("a", this); if (link.length > 0) { $(link.get(0)).click(); } }); // Switch between the two UIs as we resize. var resize = function() { // Work out if we're mobile or not. var wasMobile = mobile; mobile = isMobileDevice(); if (wasMobile != mobile) { var showing = !$(".click_message").is(":visible"); if (mobile) { var menu = $("#menu") if (showing) { $("#toolbar").show(); menu.show(); } menu.css('top', -menu.height()-52); // Go to the story view. $("#character_panel, #info_panel").hide(); } else { // Use the full width version $("#toolbar").hide(); $("#menu").hide(); if (showing) { // Display the side bars $("#tools_wrapper").show(); } $("#character_panel, #info_panel").show(); } $("#title").show(); if (showing) $("#content_wrapper").show(); } }; $(window).bind('resize', resize); resize(); // Handle display of the menu and resizing: used on mobile // devices and an small screens. initMenu(); }); var initMenu = function() { var menu = $("#menu"); var menuVisible = false; var open = function() { menu.animate({top:48}, 500); menuVisible = true; }; var close = function() { menu.animate({top:-menu.height()-52}, 250); menuVisible = false; }; menu.css('top', -menu.height()-52); // Slide up and down on clicks from the main button. $("#menu-button").click(function(event) { event.preventDefault(); event.stopPropagation(); if (menuVisible) { close(); } else { open(); } return false; }); // Register for clicks on the individual menu items: show the // relevant item. $("#menu a").click(function(event) { event.preventDefault(); event.stopPropagation(); var target = $($(this).attr('href')); if (!target.is(":visible")) { // Fade out those we don't want. $("#menu a").each(function() { var href = $(this).attr('href'); if (href != target) { $(href).fadeOut(250); } }); // Fade in our target setTimeout(function() { target.fadeIn(500); }, 250); } close(); return false; }); }; // ----------------------------------------------------------------------- // Contributed Code // ----------------------------------------------------------------------- // Internationalization support based on the code provided by Oreolek. (function() { var codesToTry = {}; /* Compiles a list of fallback languages to try if the given code * doesn't have the message we need. Caches it for future use. */ var getCodesToTry = function(languageCode) { var codeArray; if (codeArray = codesToTry[languageCode]) return codeArray; codeArray = []; if (languageCode in undum.language) { codeArray.push(languageCode); } var elements = languageCode.split('-'); for (var i = elements.length-2; i > 0; i--) { var thisCode = elements.slice(0, i).join('-'); if (thisCode in undum.language) { codeArray.push(thisCode); } } codeArray.push(""); codesToTry[languageCode] = codeArray; return codeArray; }; var lookup = function(languageCode, message) { var languageData = undum.language[languageCode]; if (!languageData) return null; return languageData[message]; }; var localize = function(languageCode, message) { var localized, thisCode; var languageCodes = getCodesToTry(languageCode); for (var i = 0; i < languageCodes.length; i++) { thisCode = languageCodes[i]; if (localized = lookup(thisCode, message)) return localized; } return message; }; // API String.prototype.l = function(args) { // Get lang attribute from html tag. var lang = $("html").attr("lang"); // Find the localized form. var localized = localize(lang, this); // Merge in any replacement content. if (args) { for (var name in args) { localized = localized.replace( new RegExp("\\{"+name+"\\}"), args[name] ); } } return localized; }; })(); // Random Number generation based on seedrandom.js code by David Bau. // Copyright 2010 David Bau, all rights reserved. // // Redistribution and use in source and binary forms, with or // without modification, are permitted provided that the following // conditions are met: // // 1. Redistributions of source code must retain the above // copyright notice, this list of conditions and the // following disclaimer. // // 2. Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the // following disclaimer in the documentation and/or other // materials provided with the distribution. // // 3. Neither the name of this module nor the names of its // contributors may be used to endorse or promote products // derived from this software without specific prior written // permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND // CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, // EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. var Random = (function() { // Within this closure function the code is basically // David's. Undum's custom extensions are added to the // prototype outside of this function. var width = 256; var chunks = 6; var significanceExponent = 52; var startdenom = Math.pow(width, chunks); var significance = Math.pow(2, significanceExponent); var overflow = significance * 2; var Random = function(seed) { this.random = null; if (!seed) throw { name: "RandomSeedError", message: "random_seed_error".l() }; var key = []; mixkey(seed, key); var arc4 = new ARC4(key); this.random = function() { var n = arc4.g(chunks); var d = startdenom; var x = 0; while (n < significance) { n = (n + x) * width; d *= width; x = arc4.g(1); } while (n >= overflow) { n /= 2; d /= 2; x >>>= 1; } return (n + x) / d; }; }; // Helper type. var ARC4 = function(key) { var t, u, me = this, keylen = key.length; var i = 0, j = me.i = me.j = me.m = 0; me.S = []; me.c = []; if (!keylen) { key = [keylen++]; } while (i < width) { me.S[i] = i++; } for (i = 0; i < width; i++) { t = me.S[i]; j = lowbits(j + t + key[i % keylen]); u = me.S[j]; me.S[i] = u; me.S[j] = t; } me.g = function getnext(count) { var s = me.S; var i = lowbits(me.i + 1); var t = s[i]; var j = lowbits(me.j + t); var u = s[j]; s[i] = u; s[j] = t; var r = s[lowbits(t + u)]; while (--count) { i = lowbits(i + 1); t = s[i]; j = lowbits(j + t); u = s[j]; s[i] = u; s[j] = t; r = r * width + s[lowbits(t + u)]; } me.i = i; me.j = j; return r; }; me.g(width); }; // Helper functions. var mixkey = function(seed, key) { seed += ''; var smear = 0; for (var j = 0; j < seed.length; j++) { var lb = lowbits(j); smear ^= key[lb]; key[lb] = lowbits(smear*19 + seed.charCodeAt(j)); } seed = ''; for (j in key) { seed += String.fromCharCode(key[j]); } return seed; }; var lowbits = function(n) { return n & (width - 1); }; return Random; })(); /* Returns a random floating point number between zero and * one. NB: The prototype implementation below just throws an * error, it will be overridden in each Random object when the * seed has been correctly configured. */ Random.prototype.random = function() { throw new { name:"RandomError", message: "random_error".l() }; }; /* Returns an integer between the given min and max values, * inclusive. */ Random.prototype.randomInt = function(min, max) { return min + Math.floor((max-min+1)*this.random()); }; /* Returns the result of rolling n dice with dx sides, and adding * plus. */ Random.prototype.dice = function(n, dx, plus) { var result = 0; for (var i = 0; i < n; i++) { result += this.randomInt(1, dx); } if (plus) result += plus; return result; }; /* Returns the result of rolling n averaging dice (i.e. 6 sided dice * with sides 2,3,3,4,4,5). And adding plus. */ Random.prototype.aveDice = (function() { var mapping = [2,3,3,4,4,5]; return function(n, plus) { var result = 0; for (var i = 0; i < n; i++) { result += mapping[this.randomInt(0, 5)]; } if (plus) result += plus; return result; }; })(); /* Returns a dice-roll result from the given string dice * specification. The specification should be of the form xdy+z, * where the x component and z component are optional. This rolls * x dice of with y sides, and adds z to the result, the z * component can also be negative: xdy-z. The y component can be * either a number of sides, or can be the special values 'F', for * a fudge die (with 3 sides, +,0,-), '%' for a 100 sided die, or * 'A' for an averaging die (with sides 2,3,3,4,4,5). */ Random.prototype.diceString = (function() { var diceRe = /^([1-9][0-9]*)?d([%FA]|[1-9][0-9]*)([-+][1-9][0-9]*)?$/; return function(def) { var match = def.match(diceRe); if (!match) { throw new Error( "dice_string_error".l({string:def}) ); } var num = match[1]?parseInt(match[1], 10):1; var sides; var bonus = match[3]?parseInt(match[3], 10):0; switch (match[2]) { case 'A': return this.aveDice(num, bonus); case 'F': sides = 3; bonus -= num*2; break; case '%': sides = 100; default: sides = parseInt(match[2], 10); break; } return this.dice(num, sides, bonus); }; })(); // ----------------------------------------------------------------------- // Default Messages // ----------------------------------------------------------------------- var en = { terrible: "terrible", poor: "poor", mediocre: "mediocre", fair: "fair", good: "good", great: "great", superb: "superb", yes: "yes", no: "no", no_group_definition: "Couldn't find a group definition for {id}.", link_not_valid: "The link '{link}' doesn't appear to be valid.", link_no_action: "A link with a situation of '.', must have an action.", unknown_situation: "You can't move to an unknown situation: {id}.", erase_message: "This will permanently delete this character and immediately return you to the start of the game. Are you sure?", no_current_situation: "I can't display, because we don't have a current situation.", no_local_storage: "No local storage available.", random_seed_error: "You must provide a valid random seed.", random_error: "Initialize the Random with a non-empty seed before use.", dice_string_error: "Couldn't interpret your dice string: '{string}'." }; // Set this data as both the default fallback language, and the english // preferred language. undum.language[""] = en; undum.language["en"] = en; })();