Darkness and light in ScottKit

Last time out, we learned how to have things happen to the player in ScottKit; that was the natural complement of the player doing things, which we covered the time before. Today, we’ll use that knowledge to deal with darkness, light, light sources, and their expiry and refilling. Here’s the expanded map:

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:

item lamp "old-fashioned brass lamp"
	called "lamp"

item lit_lamp "lit lamp"
	called "lamp" nowhere

item empty_lamp "empty lamp"
	called "lamp" nowhere

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:

occur when !flag 1
	print "Welcome to the Tutorial adventure."
	print "You must find a gold coin and store it."
	set_flag 1

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:

action light lamp when present lamp
	swap lamp lit_lamp
	print "OK, lamp is now lit and will burn for 10 turns."
	look

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.)

Here’s the code:

occur when flag 16
	clear_flag 16
	swap lit_lamp empty_lamp
	look
	comment "The engine sets flag 16 when the lamp runs out"

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:

start cave
treasury throne

action score: score
action inventory: inventory
action look: look

occur when !flag 1
	print "Welcome to the Tutorial adventure."
	print "You must find a gold coin and store it."
	set_flag 1

room cave "cave mouth"
	exit east chamber

room throne "gorgeously decorated throne room"
	exit south chamber

item sign "Sign says: leave treasure here, then say SCORE"

item lamp "old-fashioned brass lamp"
	called "lamp"

item lit_lamp "lit lamp"
	called "lamp" nowhere

item empty_lamp "empty lamp"
	called "lamp" nowhere

lightsource lit_lamp
lighttime 10

action light lamp when present lamp
	swap lamp lit_lamp
	print "OK, lamp is now lit and will burn for 10 turns."
	look

occur when flag 16
	clear_flag 16
	swap lit_lamp empty_lamp
	look
	comment "The engine sets flag 16 when the lamp runs out"

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."

room chamber "square chamber"
	exit east dungeon
	exit north throne
	exit west cave

# Flag 15 is on when and only when it is dark
occur when at chamber and flag 15
	clear_dark
	look

item cross "Wooden cross"
	called "cross"

room dungeon "gloomy dungeon"
	exit west chamber
	exit north crypt

occur when at dungeon and !flag 15
	set_dark
	look

occur 25% when at dungeon
	print "I smell something rotting to the north."

item door "Locked door"

item key "Brass key"
	called "key"
	at crypt

item door2 "Open door leads south"
	nowhere

action open door when here door and !present key
	print "It's locked."

action open door when here door
	swap door door2
	print OK
	look

action go door when here door2
	goto cell
	look

room cell "dungeon cell"
	exit north dungeon

item coin "*Gold coin*"
	called "coin"

room crypt "damp, dismal crypt"
	exit south dungeon

item vampire "Vampire"

occur when here vampire and carried cross
	print "Vampire cowers away from the cross!"

occur when here vampire and !carried cross
	print "Vampire looks hungrily at me."

occur 25% when here vampire and !carried cross
	print "Vampire bites me!  I'm dead!"
	game_over
	comment "vampire can attack unless cross is carried"

action get key when here vampire and !carried cross
	print "I'm not going anywhere near that vampire!"

verbgroup get take g
verbgroup drop leave
noungroup lamp lantern

And here’s how it plays:

$ 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.

Next time: we’ll look at counters.

Advertisements

2 responses to “Darkness and light in ScottKit

  1. Pingback: Occurrences (or daemons) in ScottKit | The Reinvigorated Programmer

  2. Pingback: Boilerplate for ScottKit games: standard actions | The Reinvigorated Programmer

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s