pLast time, we saw how to make rooms and items in ScottKit. We used these to build a small but not completely trivial game with three rooms, in which you can move through from the start room to where a coin is found, and bring it back to where you started.
Now we’re going to see how to use actions to code up puzzles. Here’s how the map of the game is going to look when we’ve finished with it today:
As you can see, the rooms and exits are basically the same as the map for the previous iteration, But we’ve added a couple of items and — crucially — the first puzzle.
Changes to the map
The “=” sign in the north-south passage between the Dungeon and the Cell is meant to indicate some kind of block that needs to be removed. Why do I say the rooms and exits are basically the same as last time? Because there is one crucial difference: to make the door-opening puzzle work, there is no longer an exit south from the Dungeon to the Cell. That way is now blocked by a locked door, which we will need to open — and we can only do that if we have the key.
Changes to the items
If you were paying attention last time, you’ll already know how to add the key and the door to the game. But we will also need one more item: once we open to locked door, we need to replace it with an open door, so we’ll also create that item, but start it off out of the game:
item door "Locked door" at dungeon item key "Brass key" called "key" at chamber item door2 "Open door leads south" nowhere
Now we come to the actual actions. Here’s where it gets fun.
A ScottKit file can define any number of actions. When the user submits a command such as OPEN DOOR or EAT SUSHI, the game goes down the list of defined actions and executes the first one whose verb and noun (if any) match what’s specified — provided that all of the action’s associated conditions are also satisfied. If it finds a verb/noun match but the conditions are not satisfied, it just keeps on moving down the list looking for one where they are. Finally, if it falls off the bottom of the list, there are a small number of built-in actions that it performs: these are movement in the six cardinal directions, GET and DROP (or their synonyms).
We define an action using the keyword action followed by a verb and optionally a noun. (When only a verb is specified, any noun or none at all is a match.) Following this can come the keyword when followed by several conditions joined with and. (Due to limitations in the underlying game format, there is no or.) Finally comes the list of action results.
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
By happy coincidence, these happen to be precisely the actions we need to solve the puzzle in the present game.
The first of these actions will fire if the user types OPEN DOOR — but only if the door is in the same room as the player, and the key is not present (that is, neither carried nor in the room).
If the user types OPEN DOOR but one or both of those conditions is not satisfied, then the first action is passed over and the second OPEN DOOR action is tried. In this case, it fires if the door is in the same room as the player and, implicitly, the key is present. (We could have been more explicit, and coded the second action as action open door when here door and present key; but we didn’t need to because we only reach this OPEN DOOR action if the previous one failed its conditions.)
As you can see from this example, the order of actions in the file is important. If the two actions above were reversed, it would be possible to open the door even without the key. This is in contrast to room and items, which may appear in any order (subject only to the constraint that items may need to be explicitly located within rooms using at, since implicit positioning depends on the last-mentioned room.)
We need one more action to make the puzzle work: once the door is open, we want the player to be able to GO DOOR to move south from the Dungeon into the Cell:
action go door when here door2 goto cell look
So far, we’ve rather skimmed over the conditions in the actions above (here door and !present key). In fact there are only eleven available conditions — nineteen if you count the fact that eight of them can be negated. By far the most important of these conditions test the positions of the player and named items. They are:
- at ROOM — True if the player’s current room is ROOM, which must be the name of a room defined somewhere in the ScottKit file.
- carried ITEM — True if the player is carrying ITEM, which must be the name of an item defined somewhere in the ScottKit file.
- here ITEM — True if ITEM is in the player’s current room.
- present ITEM — True if ITEM is either being carried by the player or in the player’s current room (i.e. if either carried ITEM or here ITEM is true.)
- exists ITEM — True if ITEM is in the game (i.e. is not “nowhere”).
You can also test whether an item has been moved from its starting location (moved ITEM), or whether the player is carrying any items at all (loaded, with no argument). Annoyingly, there is no way to test whether the player is carrying more than a specified number of items.
All of these conditions can be negated by preceding them with an exclamation mark (“!“).
In the current game, we tested present key rather than carried key, because if the player happens to have dropped the key in this room earlier, it’s reasonable to be able to use it to open the door without tediously having to GET it first.
The remaining conditions test flags and counters, which we have not discussed yet but will return to in a future post. (The three conditions that can not be directly negated are the ones pertaining to counters.)
Since the great majority of the state of a ScottKit game is in the locations of the items, almost all testing of state is done using at, carried, here, present and sometimes exists.
Unlike the relatively limited vocabulary of conditions, there are 38 action results. Some of them (such as die and save_game) take no arguments; some (such as goto and destroy) take a single argument; and a few (like swap and put_with) take two arguments. The ones we’ve used here are:
- print STRING — prints the specified string, of course. Escape sequences are recognised for newline (“\n“) and tab (“\t“). Since double-quotes are used to enclose the string, they may not appear within it. So backquotes (“`“) are replaced by double quotes when they are printed.
- swap ITEM1 ITEM2 — exchanges the locations of the two named items. In this case, swap door door2 moves the locked door to “nowhere” (where the open door was), and the open door to the Dungeon (where the locked door was).
- look — redisplays the room description. That’s useful when the description has changed, in this case by the replacement of the locked door with the open one.
- goto ROOM — moves the player to the named room.
There are many more action results: you can read about them in the reference guide.
You may ask why the poor user has to GO DOOR after opening the door, instead of just going south. The answer is that rooms and items are immutable, and that includes rooms’ exits. There is no way to create a new exit. You can hack this by creating an action (action “s” when here door2 goto cell). But that won’t make South appear in the list of exits, so it’s more honest to treat the item as a destination.
Putting it together
Let’s finish up by tossing in a few standard verbs, tying them to their obvious implementation:
action score: score action inventory: inventory action look: look
The score, inventory and look action results are all primitives in Scott Adams games, but you still need to specify how the player invokes them, hence these actions. The colon that appears in each of these lines is required here because the action result immediately follows a verb with no specified noun: otherwise the parser would read the first line as specifying an action for the command SCORE SCORE.
I am afraid to say that in Scott Adams games, the definition of a treasure is “an item whose name begins with an asterisk”, and the definition of the score is “the percentage of treasures that have been placed in the designated room”. So one area of inflexibility is that you can’t easily implement your own scoring system (though there are workarounds). Anyway, this means we can now “win” the present game by depositing the coin in the start location and typing SCORE.
With this established, we can now show the source-code for the complete game:
action score: score action inventory: inventory action look: look room chamber "square chamber" exit east dungeon item sign "Sign says: leave treasure here, then say SCORE" room dungeon "gloomy dungeon" exit west chamber item door "Locked door" room cell "dungeon cell" exit north dungeon item coin "*Gold coin*" called "coin" item key "Brass key" called "key" at chamber 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 action "s" when here door2 goto cell look
And here’s how it plays:
ringo:tutorial mike$ scottkit -p t3.sck ScottKit, a Scott Adams game toolkit in Ruby. Release 1.2, (C) 2010-2017 Mike Taylor <firstname.lastname@example.org> Distributed under the GNU software license I'm in a square chamber Obvious exits: East. I can also see: Sign says: leave treasure here, then say SCORE, Brass key Tell me what to do ? e I'm in a gloomy dungeon Obvious exits: West. I can also see: Locked door Tell me what to do ? go door I can't do that yet. Tell me what to do ? open door It's locked. Tell me what to do ? w I'm in a square chamber Obvious exits: East. I can also see: Sign says: leave treasure here, then say SCORE, Brass key Tell me what to do ? get key O.K. Tell me what to do ? e I'm in a gloomy dungeon Obvious exits: West. I can also see: Locked door Tell me what to do ? open door OK I'm in a gloomy dungeon Obvious exits: 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. Tell me what to do ? n I'm in a gloomy dungeon Obvious exits: West. I can also see: Open door leads south Tell me what to do ? w I'm in a square chamber Obvious exits: East. I can also see: Sign says: leave treasure here, then say SCORE Tell me what to do ? score I've stored 0 treasures. On a scale of 0 to 100, that rates 0. 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. ringo:tutorial mike$
Next time: actions that happen on their own!