Please read the player instructions first for a brief introduction to FATE.

This tutorial will walk you through a simple FATE game that makes use of many of the engine's features.

Note: A fairly good understanding of JavaScript is assumed, including closures, arrow functions, first class functions (functions that can be stored in variables and passed as parameters), and the use of this and new with functions.

FATE.load("Tutorial", "living_room", function() {
  FATE.setVerbs(['look', 'examine', 'open', '\n', 'take', 'drop', 'inventory', '\n', 'go', 'sit', 'wait']);

  FATE.addRoom('living_room', function() {
    this.$name = 'Living Room';

    this.$triggers = {
      'look': () => "You are in the living room. Your easy chair beckons to you from across the room.\n"+
                    "There is a mouse hole in the wall. Dang mice...\n\n"+

                    "The door to the north leads to the kitchen."
    };

    this.$enterRoom = () => {
      return this.$triggers['look']();
    };

    this.$exits = {
      'north': 'kitchen'
    };
  });

  FATE.addRoom('kitchen', function() {
    this.$name = 'Kitchen';

    this.$triggers = {
      'look': () => "You are in the kitchen.\n"+
                    "There is a refrigerator on the far side of the room.\n\n"+

                    "To the south is the living room."
    };

    this.$exits = {
      'south|living room': 'living_room'
    };
  });
});

The bare minimum FATE game will look something like the above. It consists of 2 rooms, with the ability to move between them. It's functional, but beyond the initial "wow" factor of having a little world to move around in, there's not much to it. Let's go through it step by step, and then we'll start adding things.

FATE.load("Tutorial", "living_room", function() {

The first thing we do is tell the FATE engine to load our game. Essentially, a FATE game is just a configuration function for the FATE engine itself, which is a global object attached to the browser window.

Note: FATE games are just .js files that contain a call to FATE.load. Using "Load From File" on the menu will dynamically load the uploaded script into the browser and execute it, thus configuring FATE and getting the engine ready to run your game.

FATE.load takes 3 parameters:

  1. The name of the game (used for validating save files, mainly).
  2. The ID of the room the game should start in.
  3. The setup function that configures the engine for play (this is where all our code goes).
FATE.setVerbs(['look', 'examine', 'open', '\n', 'take', 'drop', 'inventory', '\n', 'go', 'sit', 'wait']);

Next, we set up some verbs.

FATE.setVerbs takes 1 parameter:

  1. An array of strings representing each word you want to appear in the verb window beneath the main game window.

These will stay under the main game window until we call FATE.setVerbs again. A \n just tells FATE to insert a line break in the verb window and is purely aesthetic.

Note: If you don't call FATE.setVerbs, there will be no verbs at all in the verb window! Maybe that's what you want, but maybe not.

Next is the definition of the first room:

FATE.addRoom('living_room', function() {
  this.$name = 'Living Room';

  this.$triggers = {
    'look': () => "You are in the living room. Your easy chair beckons to you from across the room.\n"+
                  "There is a mouse hole in the wall. Dang mice...\n\n"+

                  "The door to the north leads to the kitchen."
  };

  this.$enterRoom = () => {
    return this.$triggers['look']();
  };

  this.$exits = {
    'north': 'kitchen'
  };
});

FATE.addRoom takes 2 parameters:

  1. A string ID for the room, this is how you will refer to the room whenever you need to access it in the FATE engine.
  2. A constructor function. This must be a standard function, not an arrow function, as it will be instantiated with the new keyword and stored by FATE.

There are certain special member variables (or "hooks") that FATE uses to interact with rooms, items, and actors. All of these start with a $.

$name
This will be displayed in the header when in the room by default. It can also be a function, in case we want some logic to determine what gets displayed there.

$triggers
Triggers are one of the most important aspects of any FATE game. $triggers is an object in which each key represents a command entered by the player, and the value paired with that key is a function which returns a response that will be displayed in the main game window.

When a player enters a command, the $triggers of the current room is one of the places FATE will look for a match for that command, and call the associated function when it finds one.

The response returned by a trigger, or by any function called in response to a command (such as $enterRoom, described below) can either be a simple string, or an object:

{
  content: "A string to display in the main game window.",
  header: "(Optional) a string to display in the header.",
  clear: true // (Optional) A boolean that determines whether to clear the main game window.
}

Note: If no string or response object is returned by a trigger function or standard command, it will not count as a "response" and a standard failure message will be displayed. This will happen even if a function executes logic, changes values, or has some effect on your FATE game.

Note: If multiple functions return responses, the values of header, and clear will be set by the first response. content, or plain text responses, will be joined togther with newlines between.

$enterRoom
is a function called by FATE whenever the player moves into the room. It returns a response to be displayed in the main game window on entry.

In this room, all $enterRoom does is call the function at $triggers['look'], having the same effect that entering the command "look" would.

Note: There is also an $exitRoom. FATE will call this function (if it exists) when the player leaves a room, right before calling $enterRoom for the player's new location. Keep in mind that FATE.currentRoom, where the id of the current room is stored, will have already been updated to the new room when $exitRoom is called.

$exits
is a little like $triggers, except the value stored is the ID of a room. When a standard move command is issued by the player, the $exits of the current room is where FATE looks for a match in order to move the player to the room indicated.

living_room has only a single trigger: "look", which returns some descriptive text, and one exit: "north", which points to the room with ID "kitchen".

Let's look at the next room.

FATE.addRoom('kitchen', function() {
  this.$name = 'Kitchen';

  this.$triggers = {
    'look': () => "You are in the kitchen.\n"+
                  "There is a refrigerator on the far side of the room.\n\n"+

                  "To the south is the living room."
  };

  this.$exits = {
    'south|living room': 'living_room'
  };
});

It's almost the same, but with a few key differences.

First, there's no $enterRoom! FATE is set up, in this case, to look for a $trigger using the standard "look" command and use that as the $enterRoom function. So here, the same thing will be displayed when we enter the kitchen as when the player enters "look" as a command while in the kitchen.

"But," you may ask, "what is a standard look or move command?"

Most commands in your FATE game are completely determined by you. However, there are 5 that the engine is also set up to handle, because they are so intimately tied up with how FATE works. They are look, move, take, drop, and inventory.

The standard "look" command does what we just discussed, it is the command that FATE will use as the default trigger when an explicit $enterRoom is not defined.

The "move" command will cause FATE to try to exit the currrent room and enter a new room based on whatever comes afterward. So "go south" will look for an $exit in the current room under the key "south".

Note: A move from one room to another can also be triggered by simply entering the exit key and nothing else as a command. So "south" would have the same result as "go south" in the above example.

Similarly, the "take" command will look for an item in the current room's inventory referred to by whatever follows "take" in the entered command and move it into the players inventory. (I.e. "take lantern" would cause FATE to look for an item referred to as "lantern", remove it from the room's inventory, and add it to the player's inventory).

The "drop" command is the reverse. It looks for something in the player's inventory and move it to the room.

The "inventory" command will list any items the player current has.

Note: "take", "drop", and "inventory" will be covered in more detail once we add items.

So, what are these "standard" commands? Do they always have to be the same words? No! There is a configuration object, FATE.config, that lets you choose what triggers these standard commands, as well as allowing you a few other options. It looks like this by default:

config: {
  tickOnFail: true,
  clearOnRoomChange: true,
  failMessage: "I don't understand.",
  takeCommand: "take",
  dropCommand: "drop",
  moveCommand: "go",
  lookCommand: "look",
  inventoryCommand: "inventory",
  listInventoryConfig: {
      intro: 'You have:',
      nothing: "You aren't carrying anything.",
      prefix: null,
      suffix: null,
      outro: null
  },
  listRoomInventoryConfig: {
      intro: '\n',
      nothing: null,
      prefix: 'There is',
      suffix: 'here.',
      outro: null
  },
  listRoomActorsConfig: {
      intro: '\n',
      nothing: null,
      prefix: null,
      suffix: 'is here.',
      outro: null
  }
}

Note: There's a lot here, but for now we're just focused on the *command fields.

So if you were to write FATE.config.lookCommand = "peek" at the top of your FATE game, then "peek" would be the trigger FATE used to supply missing $enterRoom functions.

You should set up FATE.config inside the top level of the configuration function for your FATE game. It should not be changed dynamically over the course of the game. At least in the case of lookCommand, the configuration is used during setup, and a change during gameplay would have no effect.

Note: Keep in mind that the standard commands we've been discussing have lower priority than any specific trigger that is triggered when a command is entered. It's only if no global trigger exists, and no room, item, or actor has a matching trigger that these standard commands will be attempted.

So much for why we don't need an explicit $enterRoom in the kitchen! You'll also notice something different about the $exits for the kitchen:

this.$exits = {
  'south|living room': 'living_room'
};

This doesn't mean the player has to enter that weird string to go to the living room. It means you can enter either south or living room to go to the living room. This syntax can also be used in $triggers, and there are a few other possibilities as well. For example:

this.$triggers = {
  ...
  '[look,open] refrigerator': () => "Nothing in there! You should really go shopping."
}

means that both the commands look refrigerator and open refrigerator will activate this trigger. There's also things like 'take {this}' and use {knife}, but we need to add items before that makes sense.

Note: You can also use wildcards by inserting * into a trigger, e.g. * * refrigerator. This trigger matches any string, followed by any string, followed by "refrigerator", and passes the wildcard strings into the trigger function in the order they appear in the trigger key. So the function for this trigger should look like: (a, b) => {...}

So let's add an item. We'll add one right above the kitchen, and add it to the kitchen's inventory. So our game looks like this now:

FATE.load("Tutorial", "living_room", function() {
  FATE.setVerbs(['look', 'examine', 'open', '\n', 'take', 'drop', 'inventory', '\n', 'go', 'sit', 'wait']);

  FATE.addRoom('living_room', function() {
    this.$name = 'Living Room';

    this.$triggers = {
      'look': () => "You are in the living room. Your easy chair beckons to you from across the room.\n"+
                    "There is a mouse hole in the wall. Dang mice...\n\n"+

                    "The door to the north leads to the kitchen."+

                    FATE.listInventory('living_room')
    };

    this.$enterRoom = () => {
      return this.$triggers['look']();
    };

    this.$exits = {
      'north': 'kitchen'
    };
  });

  FATE.addItem('hand_towel', function() {
    this.$name = 'hand towel';
    this.$terms = ['towel', 'hand towel'];
    this.$triggers = {
      'look {this}': () => "It's a clean hand towel with a happy sunflower printed on it.",
      'look sunflower': () => "It's a very happy sunflower."
    };
  });

  FATE.addRoom('kitchen', function() {
    this.$name = 'Kitchen';
    this.$inventory = ['hand_towel'];
    this.$triggers = {
      'look': () => "You are in the kitchen.\n"+
                    "There is a refrigerator on the far side of the room.\n\n"+

                    "To the south is the living room."+

                    FATE.listInventory('kitchen'),
      '[look,open] refrigerator': () => "Nothing in there! You should really go shopping."
    };

    this.$exits = {
      'south|living room': 'living_room'  
    };
  });
});

FATE.addItem takes 2 parameters just like addRoom:

  1. A string ID for the item, which is how you'll refer to the item in your code.
  2. The function that defines the item (this will be instantiated with new just like a room).

Items have a $name hook and $triggers just like rooms. $name can be a function or a string and is how the item will be referred to when listed in the player's inventory or a room inventory. An item's $triggers will be checked for matching commands when that item is in the player's inventory or in the inventory of the room the player is currently in.

Items have an additional hook however: $terms.

This is an array of strings that tells FATE when an item has been referred to in a command. This applies to the take command, the drop command, and any triggers that use the curly brace syntax mentioned earlier.

In our game, for instance, take towel and take hand towel would both cause FATE to look in the inventory of the current room for the hand_towel item and transfer it to the player's inventory if it was there. And, in hand_towel's triggers you'll notice 'look {this}', which means the command look followed by any term that referes to this item will match this trigger. (so look towel and look hand towel).

Note: You could also use something like look {hand_towel} (or whatever the ID of the item is) in any other $triggers object (such as that of the current room, or another item in the room), and that would work the same way.

Note: You can change the terms that refer to an item by changing the contents of the $terms array, or just assigning a new array to the variable.

There are a couple other changes we've added to the game to make our item work. You'll notice we've added the $inventory variable to the kitchen. This is just an array of item ids that tells FATE those items are in the room. If an item does not appear in any room's $inventory array, it will not have a location when the game starts. Also, make sure you've added the item to FATE before you add the room that contains it, otherwise FATE won't be able to find the item you're talking about!

Note: Items don't need to start with locations. You might very well want an item to be inaccessable at the start of a game. For instance, maybe when you use an axe on a tree, a log gets added to the room's inventory.

We've also added some calls to FATE.listInventory in each room's look trigger. listInventory just prints out all the items currently in some inventory. It takes 2 parameters:

  1. The ID of the room that you want to list the contents of (or the string '{inventory}' if you want to list what the player has on them).
  2. An optional configuration object that can have one or more of the following fields:
    {
     intro: '',
     nothing: '',
     prefix: '',
     suffix: '',
     outro: ''
    }
    

You may notice that there are a few of these configuration objects in the FATE.config field mentioned above. Those default configurations can be changed, and will be used when none is passed in specifically. listInventoryConfig is used by default when the player's inventory is listed, listRoomInventoryConfig is used when a room's inventory is listed, and listRoomActorsConfig is used when the actors currently in a room are listed (more on that when we cover actors).

Note: If you don't want any defaults, you can just assign an empty object ({}) to these config fields.

FATE does not assume when you'll want to list what's in a room, because you may want to handle that in different ways. (What if the room is pitch black without a lantern?!) So if you want room contents to be listed whenever you look in a room, you'll have to make a call to listInventory in each room's look command, like we have in our game here.

Now we can move between rooms and we have an item we can pick up. Let's add something to do with the item. We'll do this by making some additions to the kitchen.

FATE.addRoom('kitchen', function() {
    this.$name = 'Kitchen';

    this.$data = {
        fridge_wiped: false
    };

    this.$inventory = ['hand_towel'];
    this.$triggers = {
        'look': () => "You are in the kitchen.\n"+
                      `There is a ${this.$data.fridge_wiped ? 'shiny' : 'dirty'} refrigerator on the far side of the room.\n\n`+

                      "To the south is the living room."+

                      FATE.listInventory('kitchen'),
        '[look,open] refrigerator': () => "Nothing in there! You should really go shopping.",
        '{hand_towel} refrigerator': () => {
            if (!FATE.haveItem('hand_towel')) {
                return "You don't have a hand towel.";
            }

            if (this.$data.fridge_wiped) {
                return "It's clean enough. Don't get fussy.";
            }

            this.$data.fridge_wiped = true;
            return "You wipe down the dirty fridge.";
        }
    };

    this.$exits = {
        'south|living room': 'living_room'  
    };
});

First, check out

this.$data = {
  fridge_wiped: false
};

If a $data hook is attached to any room, item, or actor, the contents of that field will be saved when the player saves the game state, and loaded when the player loads the game state. So, values you want to persist across play sessions should be put in something's $data field.

Note: There is also FATE.data (with no $), which is a place to store any global data you want to persist.

Note: Since the data stored in these fields will be serialized, make sure not to store functions, or references to the data field itself.

Next, you'll see we've introduced some logic into the look trigger for the kitchen that will print a different adjective to describe the refrigerator based on whether our data flag is true or false.

Then, the main addition is the new trigger:

'{hand_towel} refrigerator': () => {
  if (!FATE.haveItem('hand_towel')) {
    return "You don't have a hand towel.";
  }

  if (this.$data.fridge_wiped) {
    return "It's clean enough. Don't get fussy.";
  }

  this.$data.fridge_wiped = true;
  return "You wipe down the dirty fridge.";
}

If the player enters a command like towel refrigerator, the first thing we do is call FATE.haveItem which takes the ID of an item and returns true if that item is currently in the player's inventory. If we don't have the towel, we can't wipe the fridge.

Then, we check the state of our data flag. If we've already wiped the fridge, we won't do it again.

Finally, if we have the towel and haven't wiped the fridge, then we set our flag to true and tell the player they cleaned the fridge.

There are other handy item related functions we can use:

At this point we've covered enough to make a basic adventure. We can make rooms with state, we can make items with state, and we can interact with those items and rooms. FATE still has a few more features I'd like to cover here though. First, let's finally talk about actors.

Actors, like items and rooms, are defined by a function call. FATE.addActor takes the ID of the actor, and a function that will be instantiated and stored by FATE.

Actors have a $name, $terms, and $triggers, they can also have a $data field like rooms and items. Additonally they have a $location, which is the room they are in, and an $active flag.

Optionally, they can have $actBefore and / or $actAfter hooks, which FATE will call either before or after a command is processed as long as the actor's $active flag is set to true. It's these hooks that make actors special, as it allows them to act independently of the player. Let's add an actor.

FATE.addActor('mouse', function() {
  this.$name = 'that darn mouse';
  this.$terms = ['mouse'];
  this.$location = 'kitchen';
  this.$data = {
    scared: false
  };

  this.$active = true;

  this.changeLocation = () => {
    let location;
    switch (this.$location) {
      case 'kitchen':
        location = 'living_room';
        break;
      case 'living_room':
        location = 'kitchen'
        break;
    }
    FATE.teleportActor('mouse', location);
  };

  this.$triggers = {
    'look {this}': () => "\"My nemesis...\"",
    'examine {this}': () => "Even after close examination, you are unable to fathom the mouse's depravity.",
    'take {this}': () => {
      this.changeLocation();
      this.$data.scared = true;


      return "\"Squeek!\" says the mouse as it scurries away."
    }
  }

  this.$actAfter = () => {  
    if (this.$data.scared) {
      this.$data.scared = false;
      return '';
    }

    if (Math.random() < 0.5) {
      return '';
    }

    this.changeLocation();
    return this.$location === FATE.currentRoom ? "The mouse scurries into the room, doubtless up to no good." :
                                                 "The mouse scurries out of the room.";
  }
});

As you can see, $triggers, $name, $terms, and $data all operate the same as they do for rooms and items. We can use {this} in a trigger to refer to the actor itself (by any of its terms), or use {mouse} to refer to this actor in another trigger. We set the actor's location to the kitchen (by ID), and set it to active so that its $actAfter function will be called after a command is processed.

Note: We also have a function called changeLocation. This is just a normal function used internally by the actor's code. There's nothing to stop you from adding things like this to any items, rooms, etc. as necessary.

The other interesting points are that, if we try to take the mouse (remember that the trigger will only be evaluated when the mouse is in the room, just like an item), it will set itself to "scared" and move to the other room, printing some text to indicate the move.

When $actAfter is called, it freezes for a turn if it is scared. Otherwise, half the time it stays where it is, and the other half of the time it changes rooms, printing out an appropriate message depending on whether it is coming into or leaving the room the player is in.

Note: We've also added calls to FATE.listActors to our rooms, which operates just like FATE.listInvetory and uses FATE.config.listRoomActorsConfig for its default configuration settings.

We move the mouse with FATE.teleportActor, just like moving an item with FATE.teleportItem. There are a few other useful functions related to actors:

Note: An actor does not need to have a location. If it is active it will still have its before / after hooks called without one. It just won't show up when FATE.listActors is called, and its triggers will never be checked against entered commands until it has a location.

Lastly, let's go over some other useful functions and wrap up by explaining the rest of the FATE.config object.

Note: It's also possible to simply change FATE.currentRoom. If, say, you wanted to magicaly whisk the player away to somewhere. Keep in mind though that nothing will be triggered by doing this, and anything evaluated after the change during the current turn will treat the newly changed current room as the current room.

At this point, it might occur to you that a lot of triggers and functions might return responses for a single command. How does FATE handle this possiblity?

FATE collects responses and compiles them all together, adding a new line between each. They are compiled in the following order:

  1. Any registered global "before" command.
  2. Any active actors' $actBefore hooks.
  3. Any results of triggers registered:
    1. In the current room (with no wildcards)
    2. In any active actors (with no wildcards)
    3. In any item in the current room (with no wildcards)
    4. in any item the player has (with no wildcards)
    5. As global triggers (with no wildcards)
    6. In the current room (with wildcards)
    7. In any active actors (with wildcards
    8. In any item in the current room (with wildcards)
    9. in any item the player has (with wildcards)
    10. As global triggers (with wildcards)
  4. If no triggers have returned a result yet, FATE then tries standard commands
    1. Take
    2. Drop
    3. Inventory
    4. Move
  5. Then, any registered global "after" command.
  6. Any active actors' $actAfter hooks.

As shown above, FATE.config looks like this by default:

{
    tickOnFail: true,
    clearOnRoomChange: true,
    failMessage: "I don't understand.",
    takeCommand: "take",
    dropCommand: "drop",
    moveCommand: "go",
    lookCommand: "look",
    inventoryCommand: "inventory",
    listInventoryConfig: {
        intro: 'You have:',
        nothing: "You aren't carrying anything.",
        prefix: null,
        suffix: null,
        outro: null
    },
    listRoomInventoryConfig: {
        intro: '\n',
        nothing: null,
        prefix: 'There is',
        suffix: 'here.',
        outro: null
    },
    listRoomActorsConfig: {
        intro: '\n',
        nothing: null,
        prefix: null,
        suffix: 'is here.',
        outro: null
    }
};

We've covered most of what's here, but let's wrap up by filling in the gaps.

Note: FATE.ticks is a potentially very powerful tool in FATE games. Reading it directly in your game code can make it possible to have timed events, day/night cycles, and schedules. It can even be manipulated directly although, of course, you'll have to be careful!

And that's it for the tutorial. You can load up this little game we've walked through by selecting "Tutorial" from the list of Built-in Games in FATE.

You'll notice there's no "win" or "lose" state in it. That's simply because a win or lose state in a FATE game is entirely up to the author. Are there "points" like in Zork? Can you die? Does the game end at all? That's up to you. As a hint, a "win" or "lose" state could easily just be a room you get sent to that you can't get out of without restarting the game.

You can also look directly at the engine code by visiting this link if you want to poke around and try things out. Or look here for the source of a longer, more elaborate game. (I recommend playing it first though, so you don't spoil it for yourself!)

Any feedback, criticism, or questions are welcome, about this guide or FATE in general. I'd also love to take a look at any games you make! I'll even consider them for inclusion in the Built-in Games list if that's something you're interested in.

You can reach me at johnhiner@wholebeangames.com.