Section 28: Scope and what you can see

Contents Back Forward | ### 28. Scope and what you can see
| He cannot see beyond his own nose. Even the fingers he outstretches from it to the world are (as I shall suggest) often invisible to him. ...Max Beerbohm (1872--1956), of George Bernard Shaw Wherefore are these things hid? ...William Shakespeare (1564--1616), Twelfth Night Time to say what "in scope" means. This definition is one of the most important rules of play, because it decides what the player is allowed to refer to. You can investigate this in practice by compiling any game with the debugging suite of verbs included and typing "scope'' in different places: but here are the rules in full. The following are in scope: the player's immediate possessions; the 12 compass directions; if there is light (see Section 17), the objects in the same 'enclosure' as the player; if not, any objects in the `thedark` object; if the player is inside a dark `container`, then that container. The 'enclosure' of the player is usually the current location. Formally, it's the outermost object containing the player which remains visible -- for instance, if the player is in a transparent cabinet in a closed, huge cupboard in the Stores Room, then the enclosure is the huge cupboard. (Thus items in the huge cupboard are in scope, subject to the remaining rules, but other items in the Stores Room are not.) In addition, if an object is in scope then its immediate possessions are in scope, if it is 'see-through', which means that: the object has `supporter`, or the object has `transparent`, or the object is an `open` `container`. In addition, if an object is in scope then anything which it "adds to scope'' is also in scope.
| The player's possessions are in scope in a dark room -- so the player can still turn his lamp on. On the other hand, a player who puts the lamp on the ground and turns it off then loses the ability to turn it back on again, because it is out of scope. This can be changed; see below.
| Compass directions make sense as things. The player can always type something like "attack the south wall'' and the `before` rule for the room could trap the action `Attack s_obj` to make something unusual happen, if this is desired.
| The parser applies scope rules to all actors, not just the player. Thus "dwarf, drop sword'' will be accepted if the dwarf can see it, even if the player can't.
| The `concealed` attribute only hides objects from room descriptions, and doesn't remove them from scope. If you want things to be both concealed and unreferrable-to, put them somewhere else! Or give them an uncooperative `parse_name` routine.
| Actually, the above definition is not quite right, because the compass directions are not in scope when the player asks for a plural number of things, like "take all the knives"; this makes some of the parser's plural algorithms run faster. Also, for a
`multiexcept`
token, the other object is not in scope; and for a
`multiinside`
token, only objects in the other object are in scope. This makes "take everything from the cupboard'' work in the natural way.
| Two library routines are provided to enable you to see what's in scope and what isn't. The first, `TestScope(obj, actor)`, simply returns true or false according to whether or not `obj` is in scope. The second is `LoopOverScope(routine, actor)` and calls the given routine for each object in scope. In each case the `actor` given is optional; if it's omitted, scope is worked out for the player as usual.
| EXERCISE 79: (link to the answer)
| Implement the debugging suite's "scope'' verb, which lists all the objects currently in scope.
| EXERCISE 80: (link to the answer)
| Write a "megalook'' verb, which looks around and examines everything nearby. Formally, scope determines what you can talk about, which usually means what you can see. But what can you touch? Suppose a locked chest is inside a sealed glass cabinet. The Inform parser will allow the command "unlock chest with key'' and generate the appropriate action, `Unlock chest key`, because the chest is in scope, so the command at least makes sense. But it's impossible to carry out, because the player can't reach through the solid glass. So the library's routine for handling the `Unlock` action needs to enforce this. The library does this using a stricter rule called "touchability''. The rule is that you can touch anything in scope unless there's a closed container between you and it. This applies either if you're in the container, or if it is. Some purely visual actions don't require touchability -- `Examine` or `LookUnder`, for instance. But most actions are tactile, and so will many actions created by designers. If you want to make your own action routines enforce touchability, you can call the library routine `ObjectIsUntouchable(obj)`. This either returns `false` if there's no problem in touching `obj`, or returns `true` and prints a suitable message (such as "The solid glass cabinet is in the way.''). Thus, the first line of many of the library's action routines is:
        if (ObjectIsUntouchable(noun)) return;
You can also call `ObjectIsUntouchable(obj, true)` to simply return true or false, and print nothing, if you'd rather provide your own failure message. The rest of this section is about how to change the scope rules. As usual with Inform, you can change them globally, but it's more efficient and safer to work locally. To take a typical example: how do we allow the player to ask questions like the traditional "what is a grue'' ? The "grue'' part ought to be parsed as if it were a noun, so that we could distinguish between, say, a "garden grue'' and a "wild grue''. So it isn't good enough to look only at a single word. Here is one solution:
    Object questions "qs";
    [ QuerySub; print_ret (string) noun.description;
    ];
    [ Topic i;
      switch(scope_stage)
      {   1: rfalse;
          2: objectloop (i in questions) PlaceInScope(i); rtrue;
          3: "At the moment, even the simplest questions confuse you.";
      }
    ];
where the actual questions at any time are the current children of the `questions` object, like so:
    Object q1 "long count" questions
      with name "long" "count",
           description "The Long Count is the great Mayan cycle of time,
               which began in 3114 BC and will finish with the world's end
               in 2012 AD.";
and we also have a grammar line:
    Verb "what"
                    * "is"  scope=Topic              -> Query
                    * "was" scope=Topic              -> Query;
Note that the `questions` and `q1` objects are out of the game for every other purpose. The name "qs'' doesn't matter, as it will never appear; the individual questions are named so that the parser might be able to say "Which do you mean, the long count or the short count?'' if the player asked "what is the count''. When the parser reaches
`scope=Topic`
, it calls the `Topic` routine with the variable `scope_stage` set to 1. The routine should return 1 (true) if it is prepared to allow multiple objects to be accepted here, and 0 (false) otherwise: as we don't want "what is everything'' to list all the questions and answers in the game, we return false. A little later on in its machinations, the parser again calls `Topic` with `scope_stage` now set to 2. `Topic` is now obliged to tell the parser which objects are to be in scope. It can call two parser routines to do this.
    ScopeWithin(object)
puts everything inside the object into scope, though not the object itself;
    PlaceInScope(object)
puts just a single object into scope. It is perfectly legal to declare something in scope that "would have been in scope anyway": or even something which is in a different room altogether from the actor concerned, say at the other end of a telephone line. Our scope routine `Topic` should then return 0 -- (false) to carry on with the usual scope rules, so that everything that would usually be in scope still is, or 1 -- (true) to tell the parser not to put any more objects into scope. So at `scope_stage` 2 it is quite permissible to do nothing but return false, whereupon the usual rules apply. `Topic` returns true because it wants only question topics to be in scope, not question topics together with the usual miscellany near the player. This is enough to deal with "what is the long count''. If on the other hand the player typed "what is the lgon cnout'', the error message which the parser would usually produce ("You can't see any such thing'') would be unsatisfactory. So if parsing failed at this token, then `Topic` is called at `scope_stage` 3 to print out a suitable error message. It must provide one.
| Note that `ScopeWithin(object)` extends the scope down through its possessions according to the usual rules, i.e., depending on their transparency, whether they're containers and so on. The definition of `Topic` above shows how to put just the direct possessions into scope.
| EXERCISE 81: (link to the answer)
| Write a token which puts everything in scope, so that you could have a debugging "purloin'' verb which could take anything, regardless of where it was and the rules applying to it. Changing the global definition of scope should be done cautiously (there may be unanticipated side effects); bear in mind that scope decisions need to be taken often -- every time an object token is parsed, so perhaps five to ten times in every game turn -- and hence moderately quickly. The global definition can be tampered with by providing the entry point
    InScope(actor)
where the `actor` is usually the player, but not always. If the routine decides that a particular object should be in scope for the actor, it should execute `PlaceInScope` and `ScopeWithin` just as above, and return true or false, as if it were at `scope_stage` 2. Thus, it is vital to return false in circumstances when you don't want to intervene.
| The token
`scope=`<Routine>
takes precedence over `InScope`, which will only be reached if the routine returns false to signify 'carry on'.
| There are seven reasons why `InScope` might be being called; the `scope_reason` variable is set to the current one:
| `PARSING_REASON` The usual one. Note that `action_to_be` holds `NULL` in the early stages (before the verb has been decided) and later on the action which would result from a successful match. `TALKING_REASON` Working out which objects are in scope for being spoken to (see the end of Section 16 for exercises using this). `EACHTURN_REASON` When running `each_turn` routines for anything nearby, at the end of each turn. `REACT_BEFORE_REASON` When running `react_before`. `REACT_AFTER_REASON` When running `react_after`. `TESTSCOPE_REASON` When performing a `TestScope`. `LOOPOVERSCOPE_REASON` When performing a `LoopOverScope`.
| Here are some examples. Firstly, as promised, how to change the rule that "things you've just dropped disappear in the dark":
    [ InScope person i;
      if (person==player && location==thedark)
          objectloop (i near player)
              if (i has moved)
                  PlaceInScope(i);
      rfalse;
    ];
With this routine added, the objects in the dark room the player is in are in scope only if they have `moved` (that is, have been held by the player in the past); and even then, are in scope only to the player.
| EXERCISE 82: (link to the answer)
| Construct a long room divided by a glass window. Room descriptions on either side should describe what's in view on the other; the window should be lookable-through; objects on the far side should be in scope, but not manipulable; and everything should cope well if one side is in darkness.
| EXERCISE 83: (link to the answer)
| Code the following puzzle. In an initially dark room there is a light switch. Provided you've seen the switch at some time in the past, you can turn it on and off -- but before you've ever seen it, you can't. Inside the room is nothing you can see, but you can hear a dwarf breathing. If you tell the dwarf to turn the light on, he will.
| Alternatively, it may contain a routine. This routine can then call `AddToScope(x)` to put any object `x` into scope. It may not, however, call `ScopeWithin` or any other scoping routines.
| Scope addition does not occur for an object moved into scope by an explicit call to `PlaceInScope`, since this must allow complete freedom in scope selections. But it does happen when objects are moved in scope by calls to `ScopeWithin(domain)`.
| EXERCISE 84: (link to the answer)
| (From the tiny example game 'A Nasal Twinge'.) Give the player a nose, which is always in scope and can be held, reducing the player's carrying capacity.
| EXERCISE 85: (link to the answer)
| (Likewise.) Create a portable sterilising machine, with a "go'' button, a top which things can be put on and an inside to hold objects for sterilisation. (Thus it is a container, a supporter and a possessor of sub-objects all at once.)
| EXERCISE 86: (link to the answer)
| Create a red sticky label which the player can affix to any object in the game. (Hint: use `InScope`, not `add_to_scope`.)
| REFERENCES: 'Balances' uses
`scope =` <routine>
tokens for legible spells and memorised spells. See also the exercises at the end of Section 16 for further scope trickery.

Contents / Back / Forward
Chapter I / Chapter II / Chapter III / Chapter IV / Chapter V / Chapter VI / Appendix

Mechanically translated to HTML from third edition as revised 16 May 1997. Copyright © Graham Nelson 1993, 1994, 1995, 1996, 1997: all rights reserved.