§22   Miscellaneous constants, scoring, quotations

For when the One Great Scorer comes
To write against your name,
He marks – not that you won or lost –
But how you played the game.
— Grantland Rice (1880–1954), Alumnus Football

There are some constants which, if defined in your code before the library files are included, change the standard game rules or tell the Inform library about your game. Two such constants appeared back in §4: the strings of text Story and Headline.

Constant Story "ZORK II";
Constant Headline "^An Interactive Plagiarism^
             Copyright (c) 1995 by Ivan O. Ideas.^";

The library won't allow the player to carry an indefinite number of objects. As was mentioned in §21, the limit is the value of capacity for the current player-object, which you're free to vary during play. The library sets up the capacity of the usual player-object to be equal to a constant called MAX_CARRIED, which is normally 100. But you can define it differently, and ‘Ruins’ does:

Constant MAX_CARRIED = 7;

For these purposes a container counts as only one object, even if it contains hundreds of other objects.

Many games, perhaps too many, involve collecting vast miscellanies of items until a use has been found for each. A small value of MAX_CARRIED will then annoy the player unreasonably, whereas a large one will stretch plausibility. The standard resolution is to give the player a sack for carrying spare objects, and the Inform library provides a feature whereby the designer can nominate a given container to be this “sack”:

Object satchel "satchel"
  with description "Big and with a smile painted on it.",
       name 'satchel', article 'your',
       when_closed "Your satchel lies on the floor.",
       when_open "Your satchel lies open on the floor.",
  has  container open openable;
Constant SACK_OBJECT = satchel;

(This is from ‘Toyshop’: the ‘Ruins’ have been sacked too many times as it is.) The convenience this offers is that the game will now automatically put old, least-used objects away into the sack as the game progresses, provided the sack is still being carried:

>get biscuit
(putting the old striped scarf into the canvas rucksack to make room)
Taken.

The “Invisiclues” hints of some of the reissued Infocom games sometimes included a category called “For Your Amusement”, listing some of the improbable things the game can do. Only the victorious should read this, as it might spoil surprises for anyone else. You can, optionally, provide such “amusing” information by defining the constant…

Constant AMUSING_PROVIDED;

… and also providing an entry point routine called Amusing. For a player who has won the game, but not one who has merely died, the usual question

Would you like to RESTART, RESTORE a saved game or QUIT?

will then become

Would you like to RESTART, RESTORE a saved game, see some suggestions for AMUSING things to do or QUIT?

(The best way to provide such suggestions, if there are many, is to use a menu system like that described in §44.) One of the worst-kept secrets of the Inform library is that an option not mentioned by this question is to type “undo”, which will undo the last move and restore the player to life. If you feel that this option should be mentioned, define the constant:

Constant DEATH_MENTION_UNDO;

Finally, this end-of-game question will also mention the possibility of typing “full” to see a full score breakdown, if tasks are provided (see below).

The other constants you are allowed to define help keep the score. There are two scoring systems provided by the library, side by side: you can use both or neither. You can always do what you like to the library's score variable in any case, though the “fullscore” verb might not then fully account for what's happened. Whatever scoring system you use, you should define MAX_SCORE, as ‘Ruins’ for instance does by declaring:

Constant MAX_SCORE = 30;

This is the value which the library tells to the player as the maximum score attainable in text like:

You have so far scored 0 out of a possible 30, in 1 turn.

Note that the library does not check that this is the actual maximum score it's possible to clock up: and nor does it cause the game to be automatically won if the maximum is achieved. The game is won when and only when deadflag is set to 2 (see §21), regardless of score.

The simpler scoring system awards points for the first time certain objects are picked up, and the first time certain places are entered. (As long as there is light to see by: no points unless you can recognise that you've arrived somewhere interesting.) To make an item or a place carry a points bonus, give it the attribute scored. You may also want to vary the amounts of these bonuses by defining two constants:

OBJECT_SCORE points for picking up a scored object (normally 4);
ROOM_SCORE   points for entering a scored room (normally 5)

The more elaborate scoring system keeps track of which “tasks” the player has accomplished. These are only present if the constant TASKS_PROVIDED is defined, and then the further constant NUMBER_TASKS should indicate how many tasks have to be accomplished. If this value is N, then the tasks are numbered 0, 1, 2, …, N − 1. The number of points gained by solving each task must be defined in a -> array with N entries called task_scores, like so:

Constant TASKS_PROVIDED;
Constant NUMBER_TASKS = 5;
Constant MAX_SCORE = 25;
Array task_scores -> 3 7 3 5 7;

Thus task 0 scores three points, task 1 scores seven points and so on. Since the entries in a -> array have to be numbers between 0 and 255, no task can have a negative score or a score higher than 255. Besides a points score, each task has a name, and these are printed by an entry point routine called PrintTaskName. For instance (‘Toyshop’):

[ PrintTaskName task_number;
  switch (task_number) {
      0: "eating a sweet";
      1: "driving the car";
      2: "shutting out the draught";
      3: "building a tower of four";
      4: "seeing which way the mantelpiece leans";
  }
];

Finally, the game's source code should call Achieved(task_number) to tell the library that the given task has been completed. If this task has been completed before, the library will do nothing: if not, the library will award the appropriate number of points. The verb “full” will give a full score breakdown including the achieved task in all future listings.

When points are awarded by a call to Achieved, or by the player picking up a scored object, or visiting a scored place, or simply by the source code itself altering the score variable, no text is printed at the time. Instead, the library will normally notice at the end of the turn in question that the score has changed, and will print a message like:

[Your score has gone up by three points.]

Not all players like this feature, so it can be turned on and off with the “notify” verb, but by default it is on. The designer can also turn the feature off and on: it is off if the library's variable notify_mode is false, on if it is true.

Another (optional) entry point routine, called PrintRank, gets the chance to print text additional to the score. It's called PrintRank because the traditional “something additional” is a ranking based on the current score. Here is ‘Ruins’:

[ PrintRank;
  print ", earning you the rank of ";
  if (score == 30) "Director of the Carnegie Institution.";
  if (score >= 20) "Archaeologist.";
  if (score >= 10) "Curiosity-seeker.";
  if (score >= 5) "Explorer.";
  "Tourist.";
];

Besides the score breakdown, two more verbs are usually provided to the player: “objects” and “places”. The former lists off all the objects handled by the player and where they are now; the latter lists all the places visited by the player. In some game designs, these verbs will cause problems: you can get rid of them both by defining the constant NO_PLACES.

EXERCISE 57
Suppose one single room object is used internally for the 64 squares of a gigantic chessboard, each of which is a different location to the player. Then “places” is likely to result in only the last-visited square being listed. Fix this.

The rest of this section runs through some simple “special effects” which are often included in games. See Chapter VII for much more on this, and in particular see §44 for using the "Menus.h" library extension.

The first effect is hardly special at all: to ask the player a yes/no question. To do this, print up the question and then call the library routine YesOrNo, which returns true/false accordingly.

The status line is perhaps the most distinctive feature of Infocom games in play. This is the (usually highlighted) bar across the top of the screen. Usually, the game automatically prints the current game location, and either the time or the score and number of turns taken. It has the score/turns format unless the directive

Statusline time;

has been written in the program, in which case the game's 24-hour clock is displayed. See §20 for more on time-keeping.

If you want to change this, you need to Replace the parser's DrawStatusLine routine. This requires some assembly language programming: there are several examples of altered status lines in the exercises to §42.

Many games contain quotations, produced with box statements like so:

box "I might repeat to myself, slowly and soothingly,"
    "a list of quotations beautiful from minds profound;"
    "if I can remember any of the damn things."
    ""
    "-- Dorothy Parker";

A snag with printing such boxes is that if you do it in the middle of a turn then it will probably scroll half-off the screen by the time the game finishes printing for the turn. The right time to do so is just after the prompt (usually “>”) is printed, when the screen will definitely scroll no more. You could use the Prompt: slot in LibraryMessages to achieve this (see §25), but a more convenient way is to put your box-printing into the entry point routine AfterPrompt, which is called at this time in every turn.

EXERCISE 58
Devise a class Quotation, so that calling QuoteFrom(Q) for any quotation Q will cause it to be displayed at the end of the current turn, provided it hasn't been quoted before.

REFERENCES
‘Advent’ contains ranks and an Amusing reward (but doesn't use either of the scoring systems provided by the library, instead working by hand).   ‘Balances’ uses scored objects (for its cubes).   ‘Toyshop’ has tasks, as above.   ‘Adventureland’ uses its TimePasses entry point to recalculate the score every turn (and watch for victory).