# Some Common Input/Output Issues

by Eric Eve

## (Adapted for Adv3Lite by Eric Eve)

There is some overlap between this article and the Library Manual section on Some Output and Input Issues, but also some additional material as well as a different presentation of the material that does overlap.

## Introduction

For the most part, input and output in TADS 3 is pretty straightforward. The player types commands at the command prompt and presses the RETURN or ENTER key, and your game responds. You output text to the screen using a string in double-quotes such as "this will be shown on screen" or else by using the say() function, and the library contributes to the output with its own standard messages and responses. For many games this is enough, and there's no need to complicate matters any further.

Some games, however, may occasionally need to do more, such as pausing the output for dramatic effect, clearing the screen, waiting for a keypress, or asking the player to input something other than a command at the regular command prompt. TADS 3 provides a set of i/o functions to handle this, which the System Manual documents in the section on the tads-io Function Set. If you've tried to use some of these functions, however, you may have found that they don't work in quite the way you expected. In fact, they may not be the best tools to get the job done (and if you want to compile your game for the Web UI they won't work at all). This article will briefly explore some of the alternatives.

## The inputManager

The tads-io function set includes the functions morePrompt(), which displays a MORE prompt and waits for the player to press a key; inputKey(), which waits for the player to press a key and returns the key pressed, and inputLine(), which reads a whole line of text from the player (up to the point the player presses the ENTER or RETURN key) and then returns the string entered.

Since adv3Lite does not use a transcript in the way that adv3 does, using these functions is not so problematic. In adv3 they don't entirely work as you want them to. For example, if in adv3 you try placing morePrompt() in a the middle of some text to create a dramatic pause, what actually happens is that all your text is displayed, and only then the does the more prompt appear, at the end, and not in the middle where you actually wanted it. E.g. you might write code like:

    "The evil baby-eating villain points his splat-gun at you, pulls the trigger and...";
    morePrompt();
    "... suffers a sudden fatal heart attack! ";

But you then find the more prompt is actually displayed after the second line of text, defeating your dramatic pause.

This problem does not occur in adv3Lite, but it is still preferable to use inputManager methods rather than the tadsio functions in case you subsequently want to convert your game to work with the WebUI interface.

Our previous example then becomes:

    "The evil baby-eating villain points his splat-gun at you, pulls the trigger and...";
    inputManager.pauseForMore(true);
    "... suffers a sudden fatal heart attack! ";

So rather than use the three tads-io functions mentioned above, it's generally better to use the corresponding inputManager methods:

- Instead of morePrompt() use inputManager.pauseForMore();
- Instead of inputKey(promptFunc?) use inputManager.getKey();
- Instead of inputLine() use inputManager.getInputLine(promptFunc?);

The promptFunc parameter can be supplied either as an anonymous function that displays a prompt or simply as a single-quoted string. For `getInputLine()` and `getKey()` (or their macro equivalents - see below) this parameter can be omitted altogether; you can simply display a prompt string before calling the method.

For the most common cases you can use the following macros:

- more expands to inputManager.pauseForMore()
- input(x) expands to inputManager.getInputLine(x)
- waitKey(x) expands to inputManager.getKey(x)

You shouldn't use the built-in function `clearScreen()`, just as you shouldn't use any other built-in I/O functions. Use `cls()` instead - this will work correctly in both Adv3Lite and Adv3LiteWeb.

## The About Box and Clearing the Screen

There may be points in the game when you want to clear the screen: for example, at the end of an extended prologue, or to mark some other dramatic change of scene. The tads-io function set includea a clearscreen() function for this purpose, and you may well have been tempted to use it.

You may also have come across the <ABOUTBOX> tag than can be used to create an about box that displays when a player selects Help|About from an interpreter window. For example, the following would create a fairly general purpose plain-vanilla about box:

    "<ABOUTBOX>
     <CENTER>
     <<versionInfo.name.toUpper()>>\b
     <<versionInfo.byline>>\b
     Version <<versionInfo.version>>\b
     </CENTER>
     </ABOUTBOX>";

These seemingly disparate points are actually related. For if you have ever used clearscreen() to clear the screen, you may have noticed that it causes your about box to disappear. Clearing the screen also clears the about box.

There is a straightforward solution; you just need to do two things:

1. Don't use clearscreen(); use the library function in cls() instead.
2. Use gameMain.setAboutBox() to define your about box, e.g.:

    gameMain:GameMainDef
        initialPlayerChar = me

        setAboutBox()
        {
          "<ABOUTBOX>
           <CENTER>
           <<versionInfo.name.toUpper()>>\b
           <<versionInfo.byline>>\b
           Version <<versionInfo.version>>\b
           </CENTER>
           </ABOUTBOX>";
        }
    ;

Finally, if you are intending to compile your game for the Web UI you must use cls() rather than clearscreen(), since clearscreen() is another tads-io function that won't work with the Web UI. Neither, for that matter, will <ABOUTBOX>.

## The mainOutputStream

The complement to the inputManager object is the mainOutputStream object, which has a number of methods that can sometimes be useful. However, the situations in which they're likely to prove useful are rather less common than the ones we've been looking at up to now, and the treatment of them is a bit more advanced. The main things we'll be discussing in this section are how to capture the output of a routine (such a double-quoted string, or a method that displays something) in a single-quoted string so we can examine it or manipulate it, and how to test whether a routine actually output anything to the display. If these aren't of any immediate interest you may want to skip to the final piece on typographical quotes right at the end.

### captureOutput()

Surprisingly enough, the captureOutput() method allows you to capture output (i.e. store what would have been output to the screen in a variable or property instead). To do this you can call mainOutputStream.captureOutput(func, [args]). This takes a function pointer as its first argument, and the arguments to that function (if any) as its remaining arguments. It returns the string that would have been output to the screen. The 'function pointer' can typically be an anonymous function, and often a short-form one (if you're not sure what that means, see the section on Anonymous Functions in the System Manual).

For example, the statement:

    local utterance = mainOutputStream.captureOutput(
                             {: "The rain in Spain stays mainly in the plain. " });

Would result in utterance containing the string 'The rain in Spain stays mainly in the plain. ', without anything being output to the screen.

That particular example may not be particularly useful, but it serves to illustrate the principle. To take develop a slightly more elaborate example, suppose we needed to get at the value of a property defined as a double-quoted string, such as the desc property of a Thing, in order to manipulate it in some way. We might, for example, have defined a number of objects like:

    redBall: Thing 'red ball; big round bouncy'
      "It's a big bouncy object, red and round. "
    ;

    blueBall: Thing  'blue ball; small round bouncy'
      "It's a small bouncy ball, blue and <<flattened ? 'flat' : 'round'>>"
      flattened = nil
    ;

Now suppose that for some bizarre reason we need to construct a list of all the objects in our game whose descriptions contain the words 'round' and 'bouncy'. The problem is that there's no obvious way to manipulate double-quoted strings; the only thing TADS 3 lets us do with them is display them. It's bad enough in the case of the red ball, since TADS 3 provides no means of searching a double-quoted string for a substring; in the case of the blue ball it looks even trickier, since the blue ball's description depends on the value of its flattened property. The way it's defined makes it effectively equivalent to a method for printing different strings under different circumstances, and one can hardly look for a substring of a method.

But one can look for substrings in the output that a double-quoted string or method would display. All we need do to do is to capture the output of these desc() methods or properties in a single-quoted string, which we can then manipulate. This can be achieved like so:

    roundBouncy()
    {
      local vec = new Vector(10);

      forEachInstance(Thing, new function(obj) {
           local str = mainOutputStream.captureOutput({: obj.desc() });
           local toks = Tokenizer.tokenize(str);
           if(toks.indexWhich({tok: getTokOrig(tok) == 'round'})
             && toks.indexWhich({tok: getTokOrig(tok) == 'bouncy'}))
           {
             vec.append(obj);
           }
       }
      );

      return vec.toList();
    }

Don't worry if you don't understand all this code; if some parts look a bit puzzling they're probably the parts that are just providing shortcut ways to loop over collections of objects and tokens to find the ones we want. The important thing is that we can use

    local str = mainOutputStream.captureOutput({: obj.desc() });

to convert whatever obj.desc() outputs into a single-quoted string that we can then inspect and manipulate as we please.

Note that if we know for sure that a property holds a simple double-quoted string, and not a method that prints a double-quoted string, or a double-quoted string that contains an embedded expression (with the << >> syntax), then we could obtain the equivalent single-quoted string with:

    local str = obj.getMethod(&desc);

But if, as in the example above, we can't be sure that the property or method whose string value we want doesn't hold a method, then it's much safer to use the string capture technique just described.

### watchForOutput()

Another thing mainOutputStream can help with is determining whether anything has been output at all. For this you use its watchForOutput() method. You use it by calling mainOutputStream.watchForOutput(func), which returns true if calling func() caused something to be displayed and nil otherwise.

For example, suppose you define a class with a specialMessage property, which may contain either a double-quoted string, or a routine that displays a string, or a single-quoted string, or a routine that returned a single-quoted string, or a number, and we want it displayed appropriately whichever of these ways the property is defined. If specialMessage property is a double-quoted string or a routine that prints a string, then all we need to do is invoke it. If it contains a single-quoted string or a number, then we need to take further action to display its contents. We could do it like this:

     class SpecialThing : Thing
        specialMessage = nil
        showSpecialMessage
        {
          local val;
          local hasDisplayed = mainOutputStream.watchForOutput( {: val = specialMessage } );
          if(!hasDisplayed)
           switch(dataType(val))
           {
             case TypeSString:
             case TypeInt: "<<val>><.p>"; break;
             case TypeTrue: "True<.p>"; break;
             default: "Nothing to report. ";
           }
        }
     ;

     specialBall: SpecialThing 'ball'
        specialMessage = "It's a special ball.<.p>"
     ;

     specialStick: SpecialThing 'stick'
        specialMessage = 'It\'s a stick. '
     ;

     specialNumber: SpecialThing 'number'
        specialMessage = 532
     ;

     DefineIAction(Test)
       execAction(c)
       {
         specialBall.showSpecialMessage;
         specialStick.showSpecialMessage;
         specialNumber.showSpecialMessage;
       }
     ;

     VerbRule(Test)
      'test'
      : VerbProduction
      action = Test
     ;

In this case, issuing the command TEST will result in the display:

       >test
       It's a special ball.
       It's a stick.
       532

Although in this case we could have tested for dataType(val) being of kind TypeDString, that wouldn't work in the more general case in which specialMessage was a routine that may or may not display something, e.g:

     specialBall: SpecialThing 'ball'
        specialMessage
         {
           if(fooVal < 2)
             "It's an ordinary ball. ";
           else if(fooVal == 2)
             "It's a special ball.&lt;.p&gt;";
           else if(fooVal > 10)
             "It's a very special ball. ";
           return 'It\'s a moderately special ball. ';
         }
        fooVal = 4
     ;

In this highly artificial example, specialBall.specialMessage always returns 'It\'s a moderately special ball. ' but may also display a different string as well depending on the value of specialBall.fooVal. Calling specialBall.showSpecialMessage will cause 'It\'s a moderately special ball. ' to be displayed if and only if fooVal is between 3 and 9 inclusive; otherwise one of the other messages will be displayed instead.

### Output Filters

If you look up the OutputManager class in the Library Reference Manual (or in output.t), you'll see that it has a number of methods for adding and removing Output Filters: addOutputFilter(filter), addOutputFilterBelow (newFilter, existingFilter), and removeOutputFilter(filter). An OutputFilter is simply an object that defines a filterText() method that can process any string passed to it and then pass it on to the next filter (or, finally, to the display). Both captureOutput() and watchForOutput() work by temporarily installing an OutputFilter and then removing it again when it's done its job. The library also installs a number of OutputFilters to carry out tasks such as managing the transcript and interpreting style tags. For the most part you can leave the TADS 3 library to employ the OutputFilters it uses automatically, and you may write dozens of TADS 3 games without ever worrying about this mechanism.

It may, however, be useful to know that it's there, just in case the time should come when you want to manipulate the output in ways that go beyond what the normal display mechanism offers (e.g. to summarize a series of action reports into one summary report). This is not something we shall attempt to go into here; the point is just to note that OutputFilters may turn out to be something you could use sometime in the future.

### Displaying Preformatted Text

Sometimes in a game it's useful to be able to dispay a diagram or picture made up from ordinary text characters, perhaps as a crude map, along the lines of:

    +---   -----------
    |    *         +  \
    |    *     |   |   \
    +-------------------

If we try to do this in TADS 3, however, we that find the normally helpful output formatting, designed to produce good-looking textual output, will do its best to defeat us. In particular, runs of consecutive dashes will be converted to m-dashes or n-dashes, and runs of consecutive spaces will be converted to a single space.

The conversion of consecutive dashes into m-dashes and n-dashes is carried out by an OutputFilter called the typograhicalOutputFilter. This in not the only OutputFilter that might frustrate our efforts. We can bypass all the OutputFilters by ussing the function aoiSay(txt), but we still need a way to circumvent the effects of compressing multiple spaces and having our diagram messed up by a variable spaced font.

The library function showPreformatted(lst) can handle all this for us, where lst is a list of strings we want displayed with line breaks between each element of the list. This allows us to display our diagram like this:

    local diag = [
        '+---   -----------',
        '|    *         +  \\',
        '|    *     |   |   \\',
        '+-------------------'
      ];

    showPreformatted(lst)

In case you're wondering about the extra backslashes at the end of the second and third lines of `lst`, these are needed to produce a single backslash rather than having the backslash escape the closing single quote mark, as it would have done if we only had one backslash there.

We can also use `howPreformatted(str)` to display a single string, where str is supplied as a single-quoted string, for example:

      showPreformatted('--- +++ *** ---');

TADS 3 Technical Manual
Table of Contents | Fundamentals > Some Common Input/Output Issues
Prev: Object-Oriented Programming Overview Next: Using Build Configurations