Home Reference Source

Scripting Choices

In this guide:

Custom option text

This section builds on the code from the previous section.

You might have noticed that when you first "Enter the maze" and then "Go to E," one of the options is still "Enter the maze," which takes you back to Cell A.

We can fix this using a little bit of JavaScript. situation.optionText can be either a string, or a function! (You can see a reference for all the situation values here.)

It looks like this:

{
  id: 'my-situation',
  optionText: function(model, hostSituation) {
    return 'a string';
  }
}

The model argument will be explained later. hostSituation is the situation that is presenting the choice.

Here's how we can use that in our game. (From here on out, when we're just modifying one situation, we'll just list out the code for that situation, instead of the whole game.)

jumbogrove.jumbogrove('#game', {
  id: 'maze-game',
  situations: [
    /* ... */
    { id: 'A',

      /* THIS IS THE IMPORTANT PART */
      optionText: function(model, hostSituation) {
        if (hostSituation.id === 'start') {
          return 'Enter the maze';
        } else {
          return 'Go to A';
        }
      },
      /* end important part */

      content: "You are standing in cell A.",
      choices: ['E'] },
    /* ... */
  ]
})

We can also add some spice to our teleporter cells. Instead of "Go to X", they can say "Teleport to X" if they are being presented as a choice from another teleporter situation.

There is a method on situation objects, Situation.hasTag(), that we can use to quickly determine if the "host" situation (the situation presenting the choice) has a particular tag.

jumbogrove.jumbogrove('#game', {
  id: 'maze-game',
  situations: [
    /* ... */
      {
        id: 'D',
        tags: ['teleporter'],
        optionText: function(model, host) {
          if (host.hasTag('teleporter')) {
            return 'Teleport to D';
          } else {
            return 'Go to D';
          }
        },
        content: "You are standing in cell D. There is a teleporter here.",
        choices: ['H', '#teleporter']
      },
    /* ... */
      {
        id: 'L',
        tags: ['teleporter'],
        optionText: function(model, host) {
          if (host.hasTag('teleporter')) {
            return 'Teleport to L';
          } else {
            return 'Go to L';
          }
        },
        content: "You are standing in cell L. There is a teleporter here.",
        choices: ['K', 'P', '#teleporter']
      },
      {
        id: 'M',
        tags: ['teleporter'],
        optionText: function(model, host) {
          if (host.hasTag('teleporter')) {
            return 'Teleport to M';
          } else {
            return 'Go to M';
          }
        },
        content: "You are standing in cell M. There is a teleporter here.",
        choices: ['N', '#teleporter']
      },
    /* ... */
  ]
})

If you're an experienced JavaScript programmer you might want to consolidate that logic a bit. Go right ahead!

Here are the changes in action. You can walk to cell D by visiting cells AEFBCGHD, where you can see the teleporter options.

Remembering things

Let's design a new maze. It will be physically smaller, but it will require the player to find a key to solve.

+-------+-------+
|       |       |
|  key  | exit   
|       |       |
+-     -+- door-+
|       |       |
| start   empty |
|       |       |
+-------+-------+

We want our logic to look something like this:

To accomplish this task, we need to use three new features of situations. Each one is a function you can provide which is called by Jumbo Grove when something happens or a decision needs to be made.

And finally, we need to make use of the model. The model object does a lot of things. What we care about right now is the model.globalState object.

model.globalState

This object may contain anything you like, as long as it is safe too convert it to JSON. globalState is part of your game's save file.

Things that can be converted to JSON include:

Everything else is unsafe to use.

Tiny maze example

Here's our tiny maze game in full:

jumbogrove.jumbogrove('#game', {
  id: 'prison-escape',
  // You can specify the initial value of globalState like this
  globalState: {
    playerHasKey: false,
  },
  situations: [
    {
      id: 'start',
      optionText: 'Return to your bedroom',
      content: `
        You are standing in a cold, damp cell. A straw mattress lies in
        the corner.
      `,
      choices: ['key-room', 'door-room'],
    },

    {
      id: 'key-room',
      // In the ASCII map I drew this as a whole room, but the flavor
      // text just calls it a hole in the wall.
      optionText: 'Inspect hole in the wall',
      getCanSee: function(model, host) {
        // only visible if player doesn't already have the key
        return model.globalState.playerHasKey === false;
      },
      enter: function(model, ui, from) {
        model.globalState.playerHasKey = true;
      },
      content: `
        There is a key hidden in the hole!
      `,
      choices: ['start']
    },

    {
      id: 'door-room',
      optionText: 'Walk to the other end of the room',
      content: `
        There is a locked door here.
      `,
      choices: ['start', 'win-the-game']
    },

    {
      id: 'win-the-game',
      optionText: 'Unlock the door',
      getCanChoose: function(model, host) {
        // only pickable if player DOES have the key
        return model.globalState.playerHasKey === true;
      },
      content: `
        You walk out into the hallway. You're free!

        # Game Over
      `
    }

  ]
});

Priority

Imagine you're making a game about a high school crush. The player is at a party, and may talk to anyone. But when their crush walks in the room, all they can do is stammer and stare.

We could write a getCanSee function for every person you talk to, but it's much simpler to use situation.priority!

When a situation's choices have different priorities, it only shows choices of situations with the highest priority.

So if situations A, B, and C have priority: 0, but situations E and F have priority: 1, then only E and F will be presented.

All situations have priority 0 by default.

Here's a small demonstration:

jumbogrove.jumbogrove('#game', {
  id: 'party-crush',
  globalState: {
    turnsUntilCrushEnters: 2,
    hasTalkedToJeff: false,
    hasTalkedToXiao: false,
    hasTalkedToMegan: false,
    hasTalkedToMarta: false
  },
  situations: [
    {
      id: 'start',
      content: "You have entered a pretty chill party.",
      choices: ['#talk-to-someone'],
    },

    {
      id: 'jeff', tags: ['talk-to-someone'],
      optionText: "Talk to Jeff",
      getCanSee: function(model) { return !model.globalState.hasTalkedToJeff; },
      enter: function(model) {
        model.globalState.hasTalkedToJeff = true;
        model.globalState.turnsUntilCrushEnters -= 1;
      },
      content: "You catch up with Jeff. He aced his math test.",
      choices: ['#talk-to-someone']
    },

    {
      id: 'xiao', tags: ['talk-to-someone'],
      optionText: "Talk to Xiao",
      getCanSee: function(model) { return !model.globalState.hasTalkedToXiao; },
      enter: function(model) {
        model.globalState.hasTalkedToXiao = true;
        model.globalState.turnsUntilCrushEnters -= 1;
      },
      content: "You catch up with Xiao. He scored the winning goal at a football game.",
      choices: ['#talk-to-someone']
    },

    {
      id: 'megan', tags: ['talk-to-someone'],
      optionText: "Talk to Megan",
      getCanSee: function(model) { return !model.globalState.hasTalkedToMegan; },
      enter: function(model) {
        model.globalState.hasTalkedToMegan = true;
        model.globalState.turnsUntilCrushEnters -= 1;
      },
      content: "You catch up with Megan. She tells you about a short story she wrote.",
      choices: ['#talk-to-someone']
    },

    {
      id: 'marta', tags: ['talk-to-someone'],
      optionText: "Talk to Marta",
      getCanSee: function(model) { return !model.globalState.hasTalkedToMarta; },
      enter: function(model) {
        model.globalState.hasTalkedToMarta = true;
        model.globalState.turnsUntilCrushEnters -= 1;
      },
      content: "You catch up with Marta. She just beat her personal best deadlift.",
      choices: ['#talk-to-someone']
    },

    {
      id: 'crush', tags: ['talk-to-someone'],
      optionText: "Your crush is here",
      priority: 1,
      getCanSee: function(model) {
        return model.globalState.turnsUntilCrushEnters <= 0;
      },
      content: `
      You see your crush enter the party. You immediately forget how to speak.
      You stammer uncontrollably, trying to excuse yourself while you escape
      out the back door.

      # The End
      `
    }

  ]
});