Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added license and readme.

  • Loading branch information...
commit 823742e559ff6cefd436b962caec8556fcf7a44e 1 parent e38ea7b
@idmillington authored
View
22 LICENSE
@@ -0,0 +1,22 @@
+MIT License
+----
+
+Copyright (c) 2009-2010 Ian Millington
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
View
140 README.md
@@ -0,0 +1,140 @@
+# Undum
+
+Undum is a game framework for building a sophisticated form of
+hypertext interactive fiction.
+
+If that means nothing to you, then let's go back a few steps. Remember
+those Choose Your Own Adventure, or Fighting Fantasy books? Where you
+got to choose what your character does next? Well if you think of that
+in a web-page you have hypertext interactive fiction, or HIF. Instead
+of turning to a particular page, you click a link, and the next bit of
+content appears.
+
+The problem is that those kinds of games are pretty limited. Every
+time the player does something, the story could go in different
+directions. So the author has to either write masses of branches, or
+else the decisions you make as a player have to be relatively short
+lived. If you played CYOA books you'll know that the wrong move either
+ended the story pretty quickly, or else it didn't really matter what
+you did because you'd end up at the same place.
+
+To beat this limitation, Undum allows you to make the output
+dynamic. It allows you to keep track of what has happened to the
+character (any kinds of data, in fact), and to then change the text
+that gets output accordingly. Effectively it is like writing a CYOA
+page that is different each time you read it. This allows for far
+richer and more rewarding game design.
+
+Undum is a pure client client-side library. It consists of a HTML file
+and three Javascript files. The HTML file uses a nice bit of styling,
+so there's a bunch of CSS and images in the default package too, but
+that can be replaced if you want. To create your own game, you edit
+the HTML file a little (mainly just changing the title and author),
+and edit one of the Javascript files.
+
+Because the game is written in Javascript, you get the full power of a
+dynamic and efficient programming language. This isn't a CYOA
+scripting system with limited functionality. You can take control of
+anything you want. Or you can just keep things simple using a bunch of
+simple functions provided by Undum.
+
+
+## Compatibility
+
+Undum is designed for HTML5 and CSS3 browsers. It has been tested on
+Firefox 3.6, Chrome 5, and Safari 5. Older browsers may work okay too,
+but some of the animation won't work, the styles may render poorly,
+and saving and loading of games is unlikely to work. Anyone who wants
+to hack around with it and make it work more widely is welcome. Just
+fork this project on Github.
+
+The local storage system on some browsers does not work when loading a
+page from your hard drive. To test your game when developing it, you
+may want to start up a simple local webserver. I have found that
+Chrome seems to reliably provide local storage for local
+development. It also has excellent Javascript debugging tools.
+
+
+## Getting Started
+
+1. Download Undum. Use the 'download source' link in the top right of
+ this page.
+
+2. Unzip Undum somewhere on your hard-drive.
+
+3. Open tutorial.html in your browser, and play through the tutorial
+ (see Progress, below).
+
+4. Copy tutorial.html to a file that reflects your game name.
+
+5. Edit your HTML file and add the title, author and description of
+ the game you want to write. At the bottom of the file change the
+ name of `tutorial.game.js` to something else (by convention
+ *your-game-name*`.game.js`.
+
+6. Copy `tutorial.game.js` to the file name you chose in the last
+ step. Open it and begin creating your game.
+
+The source code for all the files is heavily commented, so if you get
+stuck, go in and read it.
+
+
+## Deploying
+
+To deploy your game, just upload the `index.html` and the `media`
+folder to your webserver. You can serve several games with the same
+look and feel from the same directory. Just rename `index.html` to a
+different name for each game, and have each HTML file load the correct
+`.game.js` file at the end. The remaining files will be reused.
+
+For example, if you had 3 games: `episode1`, `episode2`, and
+`christmas-special`. You'd have a directory structure:
+
+ episode1.html
+ episode2.html
+ christmas-special.html
+ media/
+ css/ ...
+ img/ ...
+ js/
+ jquery-1.4.2.min.js
+ undum.js
+ episode1.game.js
+ episode2.game.js
+ christmas-special.game.js
+
+
+## Progress
+
+My plan (if I get some time) is to make the very, very terse example
+game into something that acts as a real tutorial as well as a
+smoke-test. For now, I apologise that the "tutorial" is nothing of the
+kind, just a shell used to test all major functions of the
+code. Still, I think if you look at the code you'll see how just about
+everything works. If you're interested in helping me put together
+something on that front, let me know.
+
+Also on my plan is some reference documentation. So you don't have to
+go through the source code (no matter how well documented it is).
+
+
+
+## Undum
+
+The name `undum` came from a little project that preceded this code
+base. In 1998 I put together a simple browser based game. It was
+narrative, but used the grind-based mechanics of games such as
+Farmville and Mafia Wars. Because of the grinding, I called it
+Carborundum, which I found I couldn't type at speed, so it became
+Undum. The code has changed out of all recognition since them, as the
+grind-based game moved to Flash. But the name stuck for the Javascript
+framework.
+
+
+## License
+
+The code is distributed under the MIT license. This permits you to
+modify and use it, even for commercial use. The only stipulation is
+that you keep the original copyright message associated with the
+code. This appears as the bottom line in the HTML file. A copy of the
+MIT license is found in the LICENSE file.
View
94 media/js/tutorial.game.js
@@ -14,40 +14,82 @@ var FUDGE_WORDS = [
/* The situations that the game can be in. Each has a unique ID. */
undum.game.situations = {
start: new undum.ActionSituation(
- "<p>It was a\
- dark and stormy night. <span class='transient'>And nothing was\
- stirring, not even a <a href='leave'>mouse</a>.</span> You could\
- <a class='sticky' href='start/light'>light a candle</a>, to see \
- what's <a href='/' class='raw'>going on</a>. Or you could\
- <a href='leave'>leave</a>.</p>",
+ "<p>Welcome to the UNDUM tutorial. This text was generated by\
+ the <em>enter</em> method of the first situation. All interaction \
+ takes place in a situation. You can think of it either as a 'Room' in\
+ regular IF (although it is less flexible than that), or as a 'Page'\
+ in a Choose Your Own Adventure book (though it is more flexible than\
+ that.<p>\
+ \
+ <p>Two things happen in a situation. You either\
+ <a href='leave'>leave</a> the situation for another one, or else\
+ you carry out some <a href='./act'>action</a>. Actions perform\
+ some processing, they may display some results, but\
+ ultimately they put you back into the same situation\
+ again.</p>\
+ \
+ <p class='transient'>This paragraph gives you some\
+ options to click. Because the paragraph HTML tag has the\
+ <em>transient</em> CSS class, they are removed when you move\
+ out of this situation, so\
+ that the text that remains is more pleasing to read. To stop \
+ the player using links from old situations, you'll also notice \
+ that the links in the previous paragraph turn into plain text \
+ when you <a href='leave'>go</a> (you can stop this behavior by \
+ giving a link the <em>sticky</em> CSS class - particularly useful \
+ for offsite links). So what now? You could\
+ <a href='./act'>carry out an action</a>, use a link to go\
+ <a href='http://news.bbc.co.uk/' class='raw'>off site</a>\
+ (offsite links could be styled differently, but currently aren't).\
+ Or you could <a href='leave'>go to the next situation</a>.\
+ </p>",
// Responses to action codes:
{
- light: function(character, system, action) {
- system.animateQuality(
- "luck", character.qualities.luck + 1, {
- from: 0.6,
- to: 0.2
- }
+ act: function(character, system, action) {
+ system.scrollHere();
+ system.write(
+ "<p>You have carried out an action. This doesn't change\
+ the situation, but may modify the character in some\
+ way, and may generate some output. In this case\
+ I am increasing your 'luck' score, and you should see\
+ both an animation below, and the score changing in the\
+ qualities list to the right.</p>"
);
- system.setQuality('blessed', 0);
- return "<p>The lamp flickers into life.</p>";
+ system.animateQuality("luck", character.qualities.luck + 1);
+ system.write(
+ "<p>I am also removing the 'novice' quality (you can\
+ see it go from the character panel in the top right.</p>"
+ );
+ system.setQuality('novice', 0);
+
+ // We don't have to return content here, we could just do
+ // another write and return null. Either way has the same
+ // effect.
+ return "<p>And that's us done for this action. Because we end\
+ up in the same situation, you can click the action to \
+ repeat it. If you want to limit the user so they can't \
+ do the same thing again, it is best to send the user to \
+ another situation.</p>";
}
},
// Other options and function overrides:
{
- heading: "Where You Start",
+ heading: "Starting Out with Undum",
exit: function(character, system, from) {
- system.setQuality("magic", 1);
- system.setCharacterText("You feel all magical!");
+ system.setQuality("inspiration", 1);
+ system.setCharacterText(
+ "<p>You feel all inspired, why not have a go?</p>"
+ );
return true;
},
}
),
leave: new undum.SimpleSituation(
- "<p>You have left, goodbye.</p>"
+ "<p>This is the end of this tutorial. Have a dig through the code, \
+ particularly <em>undum.js</em>, for more details.</p>"
)
};
@@ -70,11 +112,11 @@ undum.game.qualities = {
"Luck", FUDGE_WORDS, {offset:-4, priority:"0003", group:'stats'}
),
- magic: new undum.IntegerQuality(
- "Magic", {priority:"0001", group:'magic'}
+ inspiration: new undum.IntegerQuality(
+ "Inspiration", {priority:"0001", group:'progress'}
),
- blessed: new undum.OnOffQuality(
- "Blessed", {priority:"0002", group:'magic', onValue:"&#10003;"}
+ novice: new undum.OnOffQuality(
+ "Novice", {priority:"0002", group:'progress', onValue:"&#10003;"}
)
};
@@ -85,8 +127,8 @@ undum.game.qualities = {
* the end. It is an error to have a quality definition belong to a
* non-existent group. */
undum.game.qualityGroups = {
- stats: new undum.QualityGroup(null, {priority:"0000"}),
- magic: new undum.QualityGroup('Magic', {priority:"0001"})
+ stats: new undum.QualityGroup(null, {priority:"0001"}),
+ progress: new undum.QualityGroup('Progress', {priority:"0002"})
};
// ---------------------------------------------------------------------------
@@ -96,6 +138,6 @@ undum.game.init = function(character, system) {
character.qualities.skill = 12;
character.qualities.stamina = 12;
character.qualities.luck = 0;
- character.qualities.blessed = 1;
- system.setCharacterText("Hello.");
+ character.qualities.novice = 1;
+ system.setCharacterText("You are starting on an exciting journey.");
};
View
424 media/js/undum.js
@@ -6,7 +6,9 @@
(function() {
// -----------------------------------------------------------------------
- // Infrastructure Implementations
+ // 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 */
@@ -40,10 +42,42 @@
}
// -----------------------------------------------------------------------
- // Types for user instantiation.
+ // Types for Author Use
// -----------------------------------------------------------------------
- /* The game is split into situations. */
+ /* 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 or ActionSituation in this file for examples 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 (SimpleSituation and
+ * ActionSituation), 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 or ActionSituation,
+ * 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;
@@ -74,8 +108,15 @@
else return true;
};
- /* A simple situation just displays its text when the situation is
- * entered. The heading is optional. */
+ /* 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). It
+ * has an optional `heading` (in the opts parameter) that 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". The remaining options in the opts parameter are the
+ * same as for Situation.
+ */
var SimpleSituation = function(content, opts) {
Situation.call(this, opts);
this.content = content;
@@ -89,11 +130,18 @@
};
/* An action situation is just like a simple situation, only it
- * has a number of fixed responses to internal actions. The
- * actions parameter should be an object mapping the action id to
- * the text response. Responses can be either a
- * function(character, system, action) that returns a string of
- * content, or just the raw string of content. */
+ * has a number of responses to internal actions that can occur
+ * without leaving the situation. The actions parameter should be
+ * an object mapping the action id to a response. Responses can be
+ * either a function(character, system, action) that returns a
+ * string of content, or just the raw string of content. In either
+ * case the content must be valid "Display Content" (see
+ * `System.prototype.write` for a definition). If you give a
+ * function, then the function may return null if it has no output
+ * to send (e.g. it could use system.prototype.write internally to
+ * write its output). Valid options in the opts parameter are the
+ * same as for SimpleSituation.
+ */
var ActionSituation = function(content, actions, opts) {
SimpleSituation.call(this, content, opts);
this.actions = actions;
@@ -105,21 +153,42 @@
response = response(character, system, action);
} catch (err) {
}
- system.write(response);
+ 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. The priority, if given, is a string used to sort
- * qualities within their groups. If you don't give a priority,
- * then the title will be used. Normally you either don't give a
- * priority, or else use a priority string containing 0-padded
- * numbers (e.g. "00001"), because numbers sort before
- * letters. The group parameter allows you to specify that
- * this definition sits within a particular group. All qualities
- * without a group are placed at the end of the list. */
+ * 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. If a group is given, then it must be defined in
+ * your `undum.game.qualityGroups` property. If no group is
+ * given, then the quality will be placed at the end of the
+ * list.
+ *
+ * extraClasses - These classes will be attached to the <div> tag
+ * that surrounds the quality when it is displayed. A common
+ * use for this is to add icons representing the quality. In
+ * your CSS define a class for each icon, then pass those
+ * classes into the appropriate quality definitions.
+ *
+ * One key purpose of QualityDefinition is to format the quality
+ * value for display. Quality values are always stored as numeric
+ * values, but may be displayed in words or symbols. A number of
+ * sub-types of QualityDefinition are given that format their
+ * values in different ways.
+ */
var QualityDefinition = function(title, opts) {
var myOpts = $.extend({
priority: title,
@@ -140,8 +209,9 @@
return value.toString();
};
- /* A Quality that is always displayed as the floor of the current
- * value. */
+ /* A quality that is always displayed as the nearest integer of
+ * the current value, rounded down. Options (in the opts
+ * parameter) are the same as for QualityDefinition. */
var IntegerQuality = function(title, opts) {
QualityDefinition.call(this, title, opts);
};
@@ -152,23 +222,35 @@
/* A quality that displays its full numeric value, including
* decimal component. This is actually a trivial wrapper around
- * the QualityDefinition class, which formats in the same way. */
+ * the QualityDefinition class, which formats in the same
+ * way. Options (in the opts parameter) are the same as for
+ * QualityDefinition. */
var NumericQuality = function(title, opts) {
QualityDefinition.call(this, title, opts);
};
NumericQuality.inherits(QualityDefinition);
/* A quality that displays its values as one of a set of
- * words. These map to the integer component of the corresponding
- * quality value. With offset=0 (the default), then a value of 0
- * will map to the first word, and so on. If offset is non-zero
- * then the value given will correspond to the first word in the
- * list. So if offset=4, then the first word in the list will be
- * used for value=4. Words outside the range of the values given
- * will be constructed from the limits of the values given and an
- * integer modifier. So if the words are 'low', 'high' with no
- * offset, a value of 2 will be 'high+1' and -2 will be
- * 'low-2'. */
+ * words. The quality value is first rounded down to the nearest
+ * integer, then this value is used to select a word to
+ * display. The offset parameter (optionally passed in as part of
+ * the opts object) controls what number maps to what word.
+ *
+ * The following options (in the opts parameter) are available:
+ *
+ * offset - With offset=0 (the default), the quantity value of 0
+ * will map to the first word, and so on. If offset is
+ * non-zero then the value given will correspond to the first
+ * word in the list. So if offset=4, then the first word in
+ * the list will be used for value=4.
+ *
+ * Other options are the same as for QualityDefinition.
+ *
+ * Words outside the range of the values given will be constructed
+ * from the limits of the values given and an integer modifier. So
+ * if the words are 'low', 'high' with no offset, a value of 2
+ * will be 'high+1' and -2 will be 'low-2'.
+ */
var WordScaleQuality = function(title, values, opts) {
var myOpts = $.extend({
offset: null
@@ -192,7 +274,11 @@
};
/* An on/off quality that removes itself from the quality list if
- * it is not present. */
+ * it has a zero value. If it has a non-zero value, its value
+ * field is usually left empty, but you can specify your own
+ * string to display as the `onValue` parameter of the opts
+ * object. Other options (in the opts parameter) are the same as
+ * for QualityDefinition. */
var OnOffQuality = function(title, opts) {
var myOpts = $.extend({
onValue: ""
@@ -207,7 +293,8 @@
};
/* Defines a group of qualities that should be displayed together,
- * before any miscellaneous qualities. */
+ * before any miscellaneous qualities. These should be defined in
+ * the `undum.game.qualityGroups` parameter. */
var QualityGroup = function(title, opts) {
var myOpts = $.extend({
priority: title,
@@ -218,30 +305,66 @@
this.extraClasses = myOpts.extraClasses;
};
+
// -----------------------------------------------------------------------
- // Internal Types.
+ // Types Passed to Situations
// -----------------------------------------------------------------------
- /* The interface from Undum to situations. */
+ /* A system object is passed into the enter, act and exit
+ * functions of each situation. It is used to interact with the
+ * UI. */
var System = function () {
};
- /* Outputs regular content to the page. The content supplied MUST
- * begin and end with HTML start/end tags. You could have several
- * paragraphs, however, as long as the content starts with the <p>
- * of the first paragraph, and ends with the </p> of the last. */
+
+ /* Outputs regular content to the page. The content supplied must
+ * be valid "Display Content".
+ *
+ * "Display Content" is any HTML string that begins with a HTML
+ * start tag, ends with either an end or a closed tag, and is a
+ * valid and self-contained snippet of HTML. Note that the string
+ * doesn't have to consist of only one HTML tag. You could have
+ * several paragraphs, for example, as long as the content starts
+ * with the <p> of the first paragraph, and ends with the </p> of
+ * the last. So "<p>Foo</p><img src='bar'>" is valid, but "foo<img
+ * src='bar'>" is not.
+ */
System.prototype.write = function(content) {
var output = augmentLinks(content);
- $('#content').append(output);
+ var content = $('#content').append(output);
};
+
+ /* Call this method before doing a chunk of writing, so that the
+ * client will elegantly scroll to that location. This doesn't
+ * happen automatically, because you may want to write several
+ * chunks in one go, and it would be annoying to scroll to the
+ * bottom of those. */
+ System.prototype.scrollHere = function() {
+ setTimeout(function() {
+ var body = $("body,html");
+ var content = $("#content");
+ body.animate(
+ {scrollTop:content.scrollTop() + content.height()},
+ 500
+ );
+ }, 0);
+ };
+
/* Begins a new heading on the page. You could write headings
* using write, manually wrapping them in the appropriate
- * HTML. But it is strongly recommended that you use this method,
- * as in the future headings may receive additional processing for
- * indexing and javascript hooks. Do not wrap the content you pass
- * into this function in a HTML heading tag. That will be done for
- * you. The extraClasses parameter is there if you need to give
- * the resulting heading additional CSS classes; it should be an
- * array of strings. */
+ * HTML. But it is strongly recommended that you use this method.
+ * In the future headings may receive additional processing for
+ * indexing and javascript hooks.
+ *
+ * There is no need to return "Display Cotnent" from this method,
+ * any content will do. Do not wrap the content you pass into this
+ * function in a HTML heading tag. That will be done for you. You
+ * can, however, use other tags, such as <em> and <span> in your
+ * heading.
+ *
+ * The extraClasses parameter is there if you need to give the
+ * resulting heading additional CSS classes; it should be an array
+ * of strings.
+ */
System.prototype.writeHeading = function(content, extraClasses) {
var h = $("<h1>").html(content);
if (extraClasses) {
@@ -253,13 +376,15 @@
};
/* Call this to change the character text: the text in the right
- * toolbar before the qualities list. The value you give can
- * contain links to situations.
+ * 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($("<div>").html(content));
+ var newContent = augmentLinks(content);
if (block.is(':visible')) {
block.fadeOut(250, function() {
block.html(newContent);
@@ -271,10 +396,10 @@
}
};
- /* Call this to change the value of a character quality. Don't do
- * this by directly changing the quality, because that will not
- * update the UI. Because the character's sandbox isn't displayed,
- * you can modify that directly. */
+ /* 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;
@@ -315,9 +440,42 @@
}
showHighlight(qualityBlock);
};
- /* Changes a quality to a new value, but also shows an animation
- * of the change. This probably only makes sense for qualities
- * that are numeric. */
+
+ /* 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) {
// Overload default options.
var myOpts = $.extend({
@@ -391,34 +549,132 @@
this.setQuality(quality, newValue);
};
- /* Constructor for character data. */
+ /* 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 = {
- // -----------------------------------------------------------------------
- // Internal Data Structures
- // -----------------------------------------------------------------------
+ // Situations
- /* The global system object. */
- var system = new System();
+ /* 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 data structure holding the content for the game. This
- * should be static data. Anything that might change should be
- * stored in the character. */
- var game = {
+ /* The unique id of the situation to enter at the start of a
+ * new game. */
start: "start",
- situations: {},
- /* We can define these functions to do global processing. */
+
+
+ // 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,
+
+ /* 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);
+ */
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. Like that method, it shoudld return true if
+ * it wants the transition to go ahead, or false to stop
+ * it. 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 Structures
+ // -----------------------------------------------------------------------
+
+ /* The global system object. */
+ var system = new System();
+
/* This is the character data that gets saved. It isn't the
* character that the situations see, it holds other internal data
* too. */
@@ -569,7 +825,17 @@
if (action) {
var situation = getCurrentSituation();
if (situation) {
+ if (game.beforeAction) {
+ game.beforeAction(
+ character, system, sysChar.current, action
+ );
+ }
situation.act(character, system, action);
+ if (game.afterAction) {
+ game.afterAction(
+ character, system, sysChar.current, action
+ );
+ }
}
}
@@ -585,9 +851,13 @@
// Notify the exiting situation, exit if we've finished or if
// we're not allowed to enter the new situation.
- if (!oldSituation || !oldSituation.exit(
- character, system, newSituationId
- )) return;
+ if (!oldSituation) return;
+ if (!oldSituation.exit(character, system, newSituationId)) return;
+ if (game.exit) {
+ if (!game.exit(character, system, oldSituationId, newSituationId)){
+ return;
+ }
+ }
// Remove links and transient sections.
$('#content a').each(function (index, element) {
@@ -602,6 +872,9 @@
// Notify the incoming situation, unless we're ending.
if (newSituation) {
+ if (game.enter) {
+ game.enter(character, system, oldSituationId, newSituationId);
+ }
newSituation.enter(character, system, oldSituationId);
}
};
@@ -660,6 +933,9 @@
var situation = getCurrentSituation();
// assert(situation);
+ if (game.enter) {
+ game.enter(character, system, null, sysChar.current);
+ }
situation.enter(character, system, null);
showQualities();
};
@@ -693,7 +969,7 @@
};
// -----------------------------------------------------------------------
- // Setup and API
+ // Setup
// -----------------------------------------------------------------------
/* Export our API. */
View
6 index.html → tutorial.html
@@ -44,9 +44,9 @@
<div class="label">
<!-- Game Title: edit this -->
- <h1>The Instant Alchemist <span class='fancy'>&amp;</span><br>
- His Sanguine Servant</h1>
- <h2>by Ian Duncan</h2>
+ <h1>The Undum Tutorial <span class='fancy'>&amp;</span><br>
+ Interactive Example</h1>
+ <h2>by I D Millington</h2>
<!-- End of Game Title -->
<p class="click_message">click to begin</p>
Please sign in to comment.
Something went wrong with that request. Please try again.