top expert

a biting cat website

interlude: tracking player objectives

Before continuing with scenes: keeping track of player goals.

what am i doing.

As a reminder: I am a beginning Inform 7 author, and there are probably better ways to do most of the things that I do here. But that’s kind of the point! You don’t have to be an amazing programmer to make an Inform 7 game. Inform is for everyone.

Scenes, as I’ve already said, are a way to articulate a plot or carve out dramatic moments in an Inform 7 game. At a lower level, they can also serve as replacements for in-game timers and the like. If we commit to using scenes throughout a work, we can use transitions to give helpful information to the player. If we had a nice bit of reusable code like this, for instance, it would help us keep track of player objectives:

earthquake is a scene.
earthquake begins when the player is in the danger zone for the first time.

when earthquake begins:
update the current high-level objective;
add a relevant task.

This isn’t very useful as-is, since the exciting bits, “update the current…” and “add a relevant task” aren’t built into inform. They’re definitions that I need to set up for myself. Lots of authors have their own take on tracking goals, but I’ll be riffing on two examples from the Inform 7 documentation, “Boch” and “Ibid.

My first attempt at a task list was pretty bad. That was in Repeat the Ending, and it was a simple variable number, updated in play, that could be used to look up a bit of printable text in a table. Bad, because it was inflexible, yes, but also because I could have skipped the table and just maintained the goal as a “text that varies,” updating it as needed during action processing. History might repeat itself in today’s post, but hopefully I’ve made some incremental improvements. What would the goals be?

  • Compatible with scenes (already done, since scenes are built in)
  • Track high-level, overarching goals
  • Track lower-level objectives (tasks) as well
  • Take advantage of the existing scoring system (based on the “Bosch” example linked above)

Looking at this, I think I probably want one table for big goals, and another for tasks. Likewise, to keep things clean, I prefer setting up separate definitions for each.

Now, Bosch is the model, so let’s consider some of its code:

Table of Valuable Actions
relevant action point value turn stamp
taking the emerald leaf 15 -1
eating the gilded lily 5 -1

The maximum score is 25.

After doing something:
repeat through Table of Valuable Actions:
if the current action is the relevant action entry and turn stamp entry is less than 0:
now the turn stamp entry is the turn count;
increase the score by the point value entry;
continue the action.

This is exciting code! We have a really big rule (“after doing something”) that will fire after every successful action (this is why we don’t use “instead” for everything!!!!!), checking the current action against the “relevant action” entries in the “table of valuable actions.” It’s easy to see how this tactic might help us do all kinds of things. If points are awarded, the turn stamp entry will be updated from its default value to the current in-game turn count. “Ibid.” (linked above) also keeps track of first-time, productive lookups via a time stamp.

We could easily use something like this to update our task list! However, let’s look more closely at the actions in the table. There isn’t a lot to them. Rather, they are actions and objects, without any descriptive circumstances. It doesn’t matter where the player eats the lily, for instance, just that they eat it. What if they needed to eat it in a specific room? Or while holding a glass of sparkling water. The actions in the table are what are called “stored actions,” whereby specific actions (let’s think of them as player commands) can be handled as values. A player can type in “eat the gilded lily” but not–at least not in any Inform 7 game I will ever write–“eat the gilded lily while the glass of sparkling water is carried by the player.” That “while” phrase is programmer stuff and isn’t appropriate to stored actions.

What this all means is that we are, by default, pretty limited by the stored action requirement. Sure, we can mitigate this somewhat by doing rigorous action processing. We can be sure we are shutting down actions with “instead” and “check” when appropriate so that “after” rules never fire. We should be doing that anyway, but we will need to be careful, since those rules will be spread out through code, even if we try to stay organized. My solution is this: let’s make up some unique, very readable actions that we can use. Let’s say we want to hand out some points and update a task list when a player enters the “Dungeon Entrance” for the first time.

TS is a scene.
TS begins when the player is in pathway to desire for the first time.

when TS begins:
try earning a point.

earning a point is an action applying to nothing.

OK. Now we add “earning a point” to our table of stored actions.

Table of Valuable Actions (continued)
relevant action point value turn stamp
earning a point 5 -1

We came here to update a task list, though. How can we capitalize on this code to do so? Let’s look at the high-level objective first. We start this as a definition:

to update the current high-level objective (this is the goal-setting rule):

Unlike the task list, let’s assume that they player can only have one high-level objective at a time. I think I’ll make the goal a value assigned to the player.

big picture objective is a kind of value.
the thief has a big picture objective.
the big picture objectives are defined by the table of high-level goals.

I can build a table to keep track of the goals, along with some other useful stuff. Here are the columns I chose:

table of high-level goals
big picture objective inciting act feedback initiated completed original order
formlessness -- -- 1 -1 1
escape the earthquake thief becoming sighted "[a text for becoming sighted]" -1 -1 2
travel through time thief returning to darkness "[a text for returning to darkness]" -1 -1 3

Here’s what I have in mind with these columns:

  • big picture objective: the actual value, written in plain English.
  • inciting act: the stored action we will reference when updating these goals.
  • feedback: descriptive text that will print when the player checks their current objective.
  • initiated: the equivalent of the “turn stamp” in the above examples. Updates when the inciting act is performed during action processing.
  • completed: updated when a new goal is set.
  • original order: an index number to track the original ordering of rows. Not likely to be used, since I don’t plan to sort or re-sort the rows. I always include this somewhere, just in case.

Let’s think about a rule that would let us disable the current objective and set a new objective. Disabling the current one is pretty straightforward. We’ll find it in the table by checking against the player’s current big picture objective.

to update the current high-level objective (this is the goal-setting rule):
let the old goal be the big picture objective of the player;
choose row with a big picture objective of the old goal from the table of high-level goals;
now the completed entry is the turn count;

We haven’t really disabled it yet; we’ve just marked it complete in our table by updating its completed entry. We’ll need to check the current action to assign a new value to the player, as well as updating the relevant turn count in the table.

	let the CA be the current action;
choose row with an inciting act of CA from the table of high-level goals;
now the initiated entry is the turn count;
now the big picture objective of the player is the big picture objective entry;
say "a simple notification message, informing the player of their new goal.".

We’re halfway there! We still need to think about more tasky things, but we have a model for tracking activities. Our starting rule, as a reminder, was this:

when earthquake begins:
update the current high-level objective;
add a relevant task.

We have the objective but not the task. A problem: where is the current action in this rule? Without it, nothing connects back to the relevant value in our table. Out of the box, our code can’t work in exactly this way.

but wait.

Say we want to keep pursuing an automated update of goals when specific scenes begin. Unless I’ve missed something, this won’t work:

When a scene begins:

Neither will this as kinds don’t apply to scenes (AFAIK):

an important moment is a kind of scene.

However, we can do this:

a scene can be important.

That makes rules like this possible:

when an important scene begins:
update the current high-level objective;

Since our code relies on the current action, though, we’d have to go back and find something else to check. The current scene, perhaps? Inform 7 doesn’t do that out of the box, so you’re taking some rather serious risks if you want to go that route (multiple scenes can be active at once, starting and stopping independently). I think this can be managed in a smaller game, but I’ll be sticking with stored actions for a large project like this. If you’re curious, you can look at the code for a small project here.

For next time, I’ll try to think about the flow like this:

becoming sighted is an action applying to nothing.
becoming sighted is a momentous occasion.

when earthquake begins:
try becoming sighted.

every turn when the current action is a momentous occasion:
update the current high-level objective.

This is still a work in progress, I’m sure I’ll think of ways to improve as I go. There are no checks to see if a goal is marked complete already, for instance. This is just something that is working at the moment!

an important note: “after” rules will end the current action by default, stopping any “report” rules as well as other after rules. So you may need to consider processing order. You can also keep things moving by adding the phrase “continue the action” to your “after” rule.

next.

I’ll wrap up this little detour by discussing a system for tracking tasks. There will be some minor tweaks to accommodate some new requirements, but hopefully we will be in familiar territory.