Changes to the map

There’s just one new room, the cave mouth, where the game now begins. We’ve added two new items — the lamp and a lamp-refilling station.

But there are actually two more new items that do not initially appear in the game: they represent different states of the lamp. It starts out unlit; it can then be lit, which will enable us to see in the dark; and after some period of time, it will run out, and become empty. Since individual items are immutable, we represent these state changes by defining three separate items, of which only the initial state is actually in the game:

We will want to write our actions and occurrences to ensure that exactly one of these is in the game at any one time: when one of them leaves the game, another must enter, and vice versa.

How does the game know which of these is the light source? We can tell it, using the lightsource keyword. And we can also specify how many turns the light-source lasts, using the lighttime keyword:

lightsource lit_lamp
lighttime 10

(Ten turns is a short time for a lamp to last. But this is a small, tutorial game, and we want to demonstrate lamp exhaustion as well as illumination.)

Flags

We’re going to use flags in handling light and darkness. But before we leap into that, let’s take a quick look at how flags work more generally.

Each game has 32 binary flags whose state can change during the game. (They are all initially clear.) With only two exceptions, which we will get to later, they have no inherent meaning: only the meaning that a specific game attributes to them. For example, let’s use this facility to display a welcome message at the start of the game:

This is pretty simple: we’ve chosen to use flag 1 to mean “Welcome message has been displayed”. Like all flags, it is initially false. We use an occurrence to indicate that when it’s unset, we print the welcome message, and then set the flag. (If we didn’t set it, then the message would be printed before every turn.)

A short digression on state

The state of a Scott Adams game is actually very simple: it consists of:

the locations of each item

the values of all 32 flags

the player’s present location

how much longer the light-source will last

the values of 16 counters

an indication of which of these counters is current

the values of 16 saved room-numbers

an indication of which of these saved room-numbers is current

We’ve not yet discussed counters or saved room. I hope to get to them in a subsequent post, but for now you can safely ignore them. For our present purposes, then, the entire game-state consists of user location, item locations, flag values, and how long the light will last.

Darkness (and light)

Here comes the nasty part: I said above that with only two exceptions flags have no inherent meaning. One of those exceptions is that flag 15 is special, and indicates whether or not it is dark. The engine that plays games (ScottKit, ScottFree, or what have you) knows this, and will not display location descriptions if it’s dark. It will also kill you if you try to go in a direction where there is no exit, though you can move safely in the dark if you know the map well enough to avoid walking into walls.

So when the player enters a dark area, we need to tell the game by setting flag 15; and when we leave a dark area, we need to clear that flag. We can do this using the regular set_flag and clear_flag instructions. Or, as a short-cut, we can use the set_dark and clear_dark actions. The former is exactly equivalent to set_flag 15, the latter to clear_flag 15.

In our present game, we want the dungeon and everything beyond it (the crypt and the cell) to be dark; and everywhere else is light. The topology is simple here: there’s only one way in and one way out of the darkness, so we can just notice when we’re at the first dark location (the dungeon) or the first location outside there (the chamber) and set the flag accordingly. Here’s a first attempt:

occur when at chamber
clear_dark
look
occur when at dungeon
set_dark
look

Can you see what’s wrong? Take a moment to think about it before reading on.

With this as written, on every turn taken in these rooms — even if all we do is take inventory or pick up or drop an item — the dark flag will be redundantly reset (which doesn’t matter) and the room will be redescribed (which does).

This is a fine example of the kind of bug that’s easy to inadvertently create with these games. Once you twig, it’s obvious what’s going on; but it’s not so obvious when all that happens is that you keep seeing room descriptions. Anyway, the fix is simple: only fire these occurrences if we are changing the darkness state. We can check flag 15 and see whether we’ve just moved from light into darkness, or vice versa:

occur when at chamber and flag 15
clear_dark
look
occur when at dungeon and !flag 15
set_dark
look

If there are multiple entrances to or exits from a dark area, you need to remember to reset the light flag for all of them. That includes magical exits: for example, in Adventureland, you can escape from underground locations to the above-group meadow by saying a magic word when you have the flying carpet. The action for this includes a clear_dark instruction.

Seeing in the dark

You can see in the dark only when the light-source is present: that is, when you’re carrying it or it’s in the same room as you. There is exactly one light-source in the game. (Internally, it’s always item number 9, but you don’t need to worry about that: the ScottKit compiler takes care of putting it in the right place in the object file.)

By default, the light-source lasts forever But it can be configured to last for a finite number of moves, as specified by the lighttime keyword. These moves only count down while the light-source is in the game. The usual way to model this is to have multiple items, as we saw above: an unlit lamp which is initially in the game, and a lit lamp which is nowhere. When the player is ready to start using the lamp, a LIGHT LAMP action swaps the two items. The unlit lamp is removed from the game, the lit lamp replaces it, and it starts emitting light (and counting down). Here’s how it looks in the game we’re building:

What actually happens when the light-source expires? Actually, not very much. The item is not destroyed, and continues to emit light. The engine signals the expiry by setting flag 16 — and this, of course, is the second and last of the flags that has inherent meaning.

It’s up to the game to notice when this has happened, and make the appropriate adjustments. In the present game, we clear flag 16 (so the same occurrence doesn’t keep happening), and swap the lit lamp out for an empty lamp — which is why we made three lamp items earlier. (It doesn’t suffice just to swap the lit lamp for the unlit lamp item that we started with, as the player could then simply re-light it.)

Is all lost when the light-source expires? Not necessarily. The game can provide for the lamp to be refilled. There is a refill_lamp instruction which both resets the light-time to its initial value and (rather non-orthogonally) places the light-source item in the user’s inventory. As a result, it’s important that when implementing lamp-refilling, your action destroys the empty lamp item.

Here’s how it looks in this tutorial game, which lets you refill the lamp any number of times at the lamp-refilling station:

item station "lamp-refilling station" at cave
action refill lamp when here station and present empty_lamp
destroy empty_lamp
refill_lamp
print "The lamp is now full and lit."

In some games, the number of lamp refills is limited. The simplest way to impose such a limitation is to provide for only a single refill by removing the refilling resource: for example, we could destroy station in the present game. A more sophisticated approach would be to limit the player to some finite number of refills using a counter — but since we’ve not discussed counters yet, we’ll skip over that for now.

Putting it together

Before we show the complete source-code for the present iteration of our little game, let’s add another convenience feature. It’s nice to be able to use the verb TAKE as well as GET, and I also find it useful to have the single-letter abbreviation G for GET. Similarly, we’d like to be able to use LEAVE as well as DROP. And it’s also nice if we can refer to the LAMP as a LANTERN. We can define groups of equivalent verbs and nouns using verbgroup and noungroup respectively.

verbgroup get take g
verbgroup leave drop
noungroup lamp lantern

You can put any number of nouns or verbs in a class.

So, with all this established, here is the source code for the game as it stands at the moment:

$ scottkit -p t5.sck
ScottKit, a Scott Adams game toolkit in Ruby.
(C) 2010-2017 Mike Taylor <mike@miketaylor.org.uk>
Distributed under the GNU GPL version 2 license,
I'm in a cave mouth
Obvious exits: East.
I can also see: lamp-refilling station
Welcome to the Tutorial adventure.
You must find a gold coin and store it.
Tell me what to do ? e
I'm in a square chamber
Obvious exits: North, East, West.
I can also see: Wooden cross
Tell me what to do ? e
I can't see. It is too dark!
Tell me what to do ? w
Dangerous to move in the dark!
I'm in a square chamber
Obvious exits: North, East, West.
I can also see: Wooden cross
Tell me what to do ? n
I'm in a gorgeously decorated throne room
Obvious exits: South.
I can also see: Sign says: leave treasure here, then say SCORE, old-fashioned brass lamp
Tell me what to do ? get lamp
O.K.
Tell me what to do ? s
I'm in a square chamber
Obvious exits: North, East, West.
I can also see: Wooden cross
Tell me what to do ? e
I can't see. It is too dark!
Tell me what to do ? light lamp
OK, lamp is now lit and will burn for 10 turns.
I'm in a gloomy dungeon
Obvious exits: North, West.
I can also see: Locked door
Tell me what to do ? w
I'm in a square chamber
Obvious exits: North, East, West.
I can also see: Wooden cross
Tell me what to do ? g cross
O.K.
Tell me what to do ? e
I'm in a gloomy dungeon
Obvious exits: North, West.
I can also see: Locked door
Tell me what to do ? n
Your light is growing dim.
Vampire cowers away from the cross!
I'm in a damp, dismal crypt
Obvious exits: South.
I can also see: Brass key, Vampire
Tell me what to do ? get vampire
You use word(s) I don't know!
Vampire cowers away from the cross!
Tell me what to do ? get cross
It's beyond my power to do that.
Vampire cowers away from the cross!
Tell me what to do ? get key
O.K.
Vampire cowers away from the cross!
Tell me what to do ? s
I smell something rotting to the north.
I'm in a gloomy dungeon
Obvious exits: North, West.
I can also see: Locked door
Tell me what to do ? open door
OK
Your light has run out
I can't see. It is too dark!
Tell me what to do ? w
Dangerous to move in the dark!
I'm in a square chamber
Obvious exits: North, East, West.
Tell me what to do ? w
I'm in a cave mouth
Obvious exits: East.
I can also see: lamp-refilling station
Tell me what to do ? refill lamp
The lamp is now full and lit.
Tell me what to do ? e
I'm in a square chamber
Obvious exits: North, East, West.
Tell me what to do ? e
I smell something rotting to the north.
I'm in a gloomy dungeon
Obvious exits: North, West.
I can also see: Open door leads south
Tell me what to do ? go door
I'm in a dungeon cell
Obvious exits: North.
I can also see: *Gold coin*
Tell me what to do ? get coin
O.K.
Your light is growing dim.
Tell me what to do ? n
I'm in a gloomy dungeon
Obvious exits: North, West.
I can also see: Open door leads south
Tell me what to do ? w
I'm in a square chamber
Obvious exits: North, East, West.
Tell me what to do ? n
I'm in a gorgeously decorated throne room
Obvious exits: South.
I can also see: Sign says: leave treasure here, then say SCORE
Tell me what to do ? drop coin
O.K.
Tell me what to do ? score
I've stored 1 treasures. On a scale of 0 to 100, that rates 100.
Well done.
The game is now over.