Intrinsic Functions: "tads-io" Function Set

The "tads-io" function set provides access to the user interface provided by the TADS 3 Interpreter host applications, as well as file input/output functions.  The "tads-io" function set is available only in T3 implementations that are hosted within a TADS 3 Interpreter environment, so a program that uses this function set will only run in a TADS Interpreter.

 

The "tads-io" function set is separate from the "tads-gen" function set to allow programmers to choose an alternative input/output and user interface function set, if desired, while still using the more general "tads-gen" functions.  Most programs will probably want to use the "tads-gen" set because of the many data conversion and manipulation functions it contains, even if they're using an alternative user interface.

 

To use the "tads-io" function set in a program, #include "tadsio.h", or simply #include "tads.h" (which includes both "tadsio.h" and "tadsgen.h" for the full set of TADS intrinsics).

 

Banner functions – refer to the Banner API documentation for information on the banner functions, which allow the program to divide the display into several independent subwindows.

 

clearScreen() – clear the main console window, if possible.  The actual effect of this function varies by system; some interpreters clear the window, some display enough newlines to scroll any existing text off the top of the window, and some ignore the call completely.

 

flushOutput() - immediately flushes text to the output.  When you display output using tadsSay(), the text you write isn't necessarily displayed immediately, because the output formatter generally buffers text internally; the exact details of the output formatter's internal buffering vary by platform.  The flushOutput() function tells the output formatter to display any buffered text immediately.  It is never necessary to call this function, because the formatter automatically flushes its buffers before waiting for user input.  It is, however, sometimes desirable to be able to display buffered output explicitly; for example, if your program is going to perform some computation that will take a while, you might want to ensure that the user sees a "please wait" message before the long-running computation begins.


This function takes no arguments and returns no value.

 

getLocalCharSet(which) – returns a string giving the name of the active local character set selected by which, which can have one of the following values:

 

 

If which is not one of the above values, the function returns nil.
 
The character set name returned can be used to create a CharacterSet object to perform character-to-byte and byte-to-character mappings.
 

inputDialog(icon, prompt, buttons, defaultButton, cancelButton) – display a dialog and wait for the user to respond.  This works the same as TADS 2's inputdialog() function, but note that the names of the constants have been changed.

 

The icon constants are:

 

 

The buttons constants are:

 

 

The button label constants are:

 

 

inputEvent(timeout?) – wait for an event, with the optional timeout, in milliseconds.  If the timeout value is missing or nil, there is no timeout, so the function waits indefinitely for an event.  Uses the same result code as TADS 2's inputevent() function, but note that the names of the constants have changed:

 

 

inputFile(prompt, dialogType, fileType, flags) – display a file selector dialog and wait for the user to respond.  This works the same as TADS 2's askfile() function, except that the flags argument is now mandatory, the ASKFILE_EXT_RESULT flag is no longer used, and the list-style extended return value is always used (this is why ASKFILE_EXT_RESULT was deleted: the extended results format is now the only option).  Note also that the names of the constants have changed.

 

The constants for dialogType are:

 

 

The constants for fileType are:

 

 

The returned status codes are:

 

 

There are no flags currently defined, so flags should always be set to zero for compatibility with any flags added in the future.

 

The return value is a list.  The first element is always the integer result code.  If a filename was successfully obtained, the second element is the filename string.

 

inputKey() – read a keystroke from the user.  Waits for the user to press a key, then returns a string with the key the user pressed.  This function uses the same result codes as the TADS 2 inputkey() function.

 

inputLine() – read a line of text input from the user.  Returns the text of the input as a string.  (The returned string will not contain a newline character.)  Returns nil if the console is at end-of-file, which usually indicates that the user has closed the interpreter application.

 

inputLineCancel(reset) – cancels an editing session interrupted by a timeout.  This function must be called after inputLineTimeout() returns the InEvtTimeout event code if any display input or output is to be performed before another call to inputLineTimeout().  This function terminates the editing session, making any changes to the visual display that would have occurred if the user had terminated the command entry by pressing the Return key or some equivalent action.  For example, this function changes the display by starting a new line of text after the line that was being edited.

 

The reset argument indicates whether or not inputLineTimeout() should forget the editing state that was in effect when the timeout occurred.  If reset is true, then the next call to inputLineTimeout() will start with a blank input line; if reset is nil, then the next call to inputLineTimeout() will re-display the line of text that was under construction when the timeout occurred, and will restore the editing state (cursor position, selected text range, and so on) that was in effect.

 

inputLineTimeout(timeout?) – read a line of text input from the user, with an optional timeout given in milliseconds.  See the section on real-time input below for examples of how to use this function.  If timeout is missing or is nil, there is no time limit on the input.

 

This function might not be implemented on every platform, because some platforms do not have the necessary operating system features to support it.  If a platform does not support the timeout feature, this function will return InEvtNotimeout immediately upon invocation if timeout is given as a non-nil value.

 

The return value is a list, the first element of which gives an event code (from the same set of codes that inputEvent() returns); the meaning of any additional elements varies according to the event code.  The possible event codes are:

 

 

When this function returns the InEvtTimeout event code, the caller must not perform any display input or output operations until after calling inputLineCancel(), with the single exception that the caller can call inputLineTimeout() again with no intervening call to inputLineCancel().

 

After a timeout occurs, if inputLineTimeout() is called again with no intervening call to inputLineCancel(), then inputLineTimeout() resumes editing the interrupted command line.  In this case, there is no visible effect of the timeout; from the user’s perspective, the timeout never occurred.

 

If a timeout occurs and inputLineCancel(nil) is subsequently called, then inputLineTimeout() is called again, the new call to inputLineTimeout() re-displays the command line as it was at the time of interruption, and then allows the user to resume editing where they left off.  In this case, there is a visible change to the display, in that the command line is re-displayed; however, all of the editing state (cursor position, selected text range, history recall position, and so on) is duplicated from the previous editing session.  So, although the user will see that editing was interrupted, the user can continue editing the command line exactly where they left off.

 

When this function is called without the timeout argument, or with nil as the timeout value, it is similar to inputLine(), in that it allows the user to edit a line of text, with no upper limit on how long to wait until the user finishes.  However, this function differs from inputLine() in one important respect: if the preceding call to inputLineTimeout() ended with the timeout expiring, and no intervening call to inputLineCancel(true) was made since the timeout occurred, this function will resume editing of the interrupted command line.

 

logConsoleClose(handle) – closes the given console.  This function closes the operating system file, so no further text can be written to the console after this function is called.

 

logConsoleCreate(filename, charset, width) – creates a "log console."  A log console is a special system object that behaves much like the main game window, except that all of the text written to a log console is captured in a text file rather than being displayed.  filename is the name of the file to write; any existing file with the same name will be overwritten.  charset is either a CharacterSet object or a string giving the name of a character set; the text in the log file will be written in the selected character set.  width is the maximum width, in text columns, for the text written to the file; the console will automatically word-wrap the written text to this width.  The return value is a "handle," which identifies the new console in calls to other logConsoleXxx functions; if the return value is nil, the system was unable to create the console.

 

In most cases, the character set should simply be given as getLocalCharSet(CharsetDisplay).  This will ensure that the text in the file uses the same character set as the local system default for displaying text on the screen.

 

If the given file cannot be created (because the name is invalid, for example, or because there's no space on disk), a FileCreationException is thrown.  The "file safety" level must allow the operation, otherwise a FileSafetyException is thrown.

 

Log consoles are in some ways similar to text files based on the File intrinsic class.  The difference is that text written to a File object is written character-for-character exactly as you specify.  In contrast, the text written to a log console is processed the same way as text displayed to the player: HTML markups are processed (although, in a log console, only the text-only subset of HTML can be used, regardless of the kind of interpreter being used), the text is word-wrapped (to the fixed width given when the log console is created), excess whitespace is removed, and so on.

 

Log consoles are also similar to the log files created with setLogFile().  The only difference is that setLogFile() can only capture text that is also displayed to the main game window; a log console has no display component at all, so you can use a log console to capture text exclusively to a file, without also showing it to the user.

 

logConsoleSay(handle, ...) – writes the given arguments to the given log console.  This behaves just like say(), but writes the text to the given log console instead of to the main game window.

 

morePrompt() – display the MORE prompt on the main console window, and wait for the user to respond.  This can be used when you want to pause execution and wait for the user to acknowledge some output before proceeding.

 

resExists(resname) – check to see if the given resource can be found.  Returns true if the resource is present, nil if not.  For HTML TADS 3, this looks for an HTML resource; text-only TADS 3 interpreters always return nil for this function, since they don't use multi-media resources at all.  The resource name should be specified as a URL-style name.  The interpreter will look for the resource using the same searching rules that it uses for normal resource loading; the HTML interpreter will thus look for the resource bundled into the image file, in any external resource files (image.3r0 through image.3r9), and finally in an external file whose name is derived from the URL according to local system conventions.

 

setLogFile(fname, logType?) – turn on logging of the console output to a file whose name is given by fname.  If fname is nil, this turns off the specified type of logging, if it's currently in effect.  The logType argument specifies the type of logging to perform:

 

 

setScriptFile(filename, flags?) – start reading commands from the script file, or, if filename is nil, close any current script file.  The optional flags value lets you specify how to read the script file:

 

If the flags argument isn't specified, a default value of zero will be used, so none of the flags will be set.

 

When the end of the file is reached, the interpreter will automatically close the file and return to normal keyboard input, so calling this function with filename set to nil isn't necessary unless you want to stop reading the script file early.

 

statusMode(mode) – set the status line mode.  This can be used to control the status line in non-HTML mode and for text-only (non-HTML) interpreters.  The status mode controls where text is displayed.  In status mode StatModeNormal, text is displayed in the main text area.  In status mode StatModeStatus, text is displayed to the left portion of the status line.  Other status modes are not currently used; any text written to the console in other status modes is not displayed anywhere.

 

To write to the status line in non-HTML mode and on text-only interpreters, set the status mode to StatModeStatus, write the status line text as though it were ordinary display output, and set the status mode back to StatModeNormal:

 

   statusMode(StatModeStatus);
   "Loud Room";
   statusMode(StatModeNormal);

 

statusRight(txt) – write the text string txt to the right half of the status line.  This can be used to control the right portion of the status line in non-HTML mode and on text-only interpreters.

 

systemInfo(infoType, ...) – retrieve information about the TADS 3 application environment.  This function works the same way it did in TADS 2.  The infoType constants are:

 

o          SysInfoIClassText – character-mode text-only interpreter, such as Unix TADS or MS-DOS TADS.  These interpreters use a single, fixed-pitch font, cannot display any graphics, and support only the text-only HTML subset.

o          SysInfoIClassTextGUI – GUI text-only interpreter, such as WinTADS or MacTADS.  These interpreters behave essentially the same as character-mode interpreters, but run on graphical operating systems and thus might use proportional fonts, boldface text, and so on.  These interpreters cannot display graphics explicitly, but might use some graphics automatically (for drawing window borders, for example).  These support only the text-only HTML subset.

o          SysInfoIClassHTML – a full HTML interpreter running on a graphical platform, such as HTML TADS for Windows, or HyperTADS for Macintosh.  These interpreters can use multiple fonts of varying sizes, including proportional fonts, can display graphics and play sounds, and recognize the full HTML TADS markup language.

 

tadsSay(val, ...) – display one or more values.  Each value is displayed on the console, starting with the first argument; the displayed values are not separated by any spaces or other delimiters.  Each value can be a string, an integer (in which case its decimal representation is displayed), or nil, in which case nothing is displayed.  The function throws a run-time error (BAD_TYPE_BIF) for any other type.  No return value.

 

timeDelay(delay_ms) – pause execution for the given number of milliseconds.  Note that the precision of system timers varies, so the actual delay might differ from the exact time specified on some systems according to the available hardware timer precision.

Real-Time Input

A real-time event is an event that occurs at a particular point in time; for example, an event programmed to occur at 9:00 PM is a real-time event, because it’s scheduled according to time in the real world.  It is more typical to schedule a real-time event to occur after some number of seconds or minutes has elapsed than to schedule one for a particular time on the clock on the wall, but elapsed-time events are also real-time events, because they depend on the passage of time in the real world.

 

In most programs (such as adventure games) that take their input from a command line, reading a command line from the user stops everything until the user finishes entering the command (by pressing Return, or something similar).  This is known as a “blocking” operation, because the operation blocks the program’s progress until the command line is finished: the program simply waits as long as it takes for the user to type the command line and press the Return key.  For this reason, command-line programs don’t usually incorporate real-time events, and most programs with real-time events don’t use command lines.

 

TADS 3 has the ability to mix command-line input and real-time events, thanks to the inputLineTimeout() function.  This function works a lot like the ordinary inputLine() function, which reads a line of text from the keyboard and returns a string containing the text, but inputLineTimeout() has the additional feature of letting you specify a time limit, called a “timeout.”  A timeout is simply a maximum real-time interval; when the interval expires, inputLineTimeout() returns, even if the user hasn’t finished editing the command line.  The function returns information that lets you tell whether or not the user finished editing the command before the timeout expired.

 

At the simplest level, you could imagine a game that imposes a time limit for typing certain commands.  For example, a sadistic game designer might want to design a traditional adventure game maze, with the novel twist that the player has to move out of each room within ten seconds of real time or face some penalty, such as being moved back to the start of the maze.  To do this, you could use inputLineTimeout() with a timeout value of 10000 (ten thousand milliseconds equals ten seconds), imposing the penalty if the function ever returns with a timeout.

 

This type of use of inputLineTimeout() would not win many admirers, but fortunately it’s not at all the scenario for which this function was designed.  In fact, the key feature of the function is that it not only allows you to interrupt a command line, but also allows you to resume an interrupted command line.  This is crucial: because you can resume an interrupted command line, you can write your program so that it continues to process events in real time, even while the user is editing a command; the user’s editing and your real-time events can proceed in parallel, with neither blocking the other.

 

There are three possible ways to use inputLineTimeout().

 

Scenario 1: Limited-Time Input.  This is the real-time-maze scenario described above, where the program solicits command-line input from the user, but only allows the user a limited amount of time to complete the input.  In this scenario, when the time limit expires, the user’s chance to enter a command has ended: the program does not allow the user to resume editing the command later.

 

This is the simplest scenario, because the program unconditionally cancels the input when it times out.  To do this, you simply call the function inputLineCancel() when a timeout occurs.  Here’s how this looks:

 

  /* read a command, with a 10-second time limit */
  local result = inputLineTimeout(10000);
  if (result[1] == InEvtTimeout)
  {
    /* timed out - cancel input and forget the buffer */
    inputLineCancel(true);
 
    /*
     *  gloat about defeating the user with our clever time
     *  limit ruse, using the spelling enjoyed by usenet
     *  posters everywhere
     */
    "Ha, ha!  You LOOSE!";
    // etc
  }
  else
  {
    /* darn, they were fast enough */
    // move to the new location, etc
  }

 

Scenario 2: Internal Computation Only, with Resumed Editing.  Sometimes you'll want to perform some operation at a particular time, but the operation won't perform any display operations.  For example, suppose you're writing a detective game, and you have one character in the game who moves around according to a real-time schedule.  When you're about to read an input line, you can check the character's schedule, calculate the delay until the character's next move, and then use that delay as the timeout value for inputLineTimeout().

 

If inputLineTimeout() returns a timeout event, you'd move your character according to the schedule.  Now, suppose the character isn't in sight of the player character at any point during the scheduled travel.  In this case, you wouldn't want to display anything about the character's travel: everything happens behind the scenes.  So, you need to perform the event in real time, so that the character moves to its new location on schedule, but as far as the player is concerned, nothing happened.

 

In this case, you'd simply call inputLineTimeout() again after moving the character.  The function would pick up where it left off, with absolutely no effects visible to the player.  Nothing on the display changes in this case, so the player simply thinks they've been editing the same command all along.

 

The code for this is easy, as long as we take for granted that we know when the character's next move occurs.

 

  /* show the initial prompt */
  ">";
 
  /* keep looping forever */
  for (;;)
  {
    local delay;
    local result;
 
    /* calculate the interval until the next travel */
    delay = actor.nextMoveTime - getTime(GetTimeTicks);
 
    /* ask for input, waiting no longer than the timeout */
    evt = inputLineTimeout(delay);
 
    /* if we timed out, move the character */
    if (result[1] == InEvtTimeout)
    {
      /* time to go */
      actor.performNextTravel();
    }
    else
    {
      /* they entered a command - handle it */
      processCommand(result[2]);
 
      /* show the prompt again */
      ">";

    }
  }
 
Note that it's legal to update banner windows during interrupted input.  You could use code just like the example above, substituting banner window displays for the "performNextTravel" call.  For example, you could keep a running real-time clock in a banner window, updating it at each input timeout.  As long as you're not updating the main window, where the input editing session is taking place, it's not necessary to cancel input editing (as described in the next scenario).

 

Scenario 3: Interruption with a Displayed Message, then Resumed Editing.  This is the most complex situation, but in many ways the most interesting.  In this scenario, we want to tell the user about something that happened during the real-time event, but we still want to let the user go back to editing the command line after we finish processing the event.

 

Our detective example above fits this scenario when the traveling actor is in sight of the player character, because in this case we want to tell the user that the traveling actor has departed or arrived.  Once we've described the departure or arrival, though, we want to let the user continue editing the command, because the interruption doesn't necessarily change what they would have typed, and (unlike Scenario 1) doesn't take away the user's chance to type a command.

 

In this situation, we must use the inputLineCancel() function, passing nil as the reset argument.  This function tells the system that we are not processing Scenario 2; in particular, it tells the system that it will not be able to pretend that the interruption never happened.  The reason we must differentiate this case from Scenario 2 is that when inputLineTimeout() returns with a timeout, the system optimistically keeps everything on the screen and in memory in a state where it could resume editing the same command later.  This means that any display operations - even something as simple as displaying a string of text - would leave things terribly confused, because the system is holding everything ready for more command line editing.  To tell the system that we wish to give up our right to resume editing with complete transparency, and in exchange receive the right to perform other display operations, we use inputLineCancel(nil).

 

Note that we use nil for reset argument to inputLineCancel() in this scenario.  This is because we wish to resume editing the command line later.  This might seem confusing - if we want to resume editing the command later, why are we canceling in the first place?  The solution to this seeming contradiction is that canceling and resetting are not the same thing.  Canceling, which is what inputLineCancel() does regardless of the reset argument, simply tells the system to give up hope for transparently resuming editing.  Resetting, which only occurs when the reset argument to inputLineCancel() is true, tells the system to throw away all information about editing.  So, when you cancel without resetting, you tell the system that you won't transparently resume editing, but that you still wish to resume editing later, albeit not transparently.

 

Here's what this looks like to the user.  First, here's the way the screen looks when the user is first presented with the command line:

 

What do you, the detective, want to do next?

>|

 

(That vertical bar, "|", is meant to represent the cursor, where text the user types is inserted.)  Now, the user starts typing a command, and the screen looks like this after a bit:

 

What do you, the detective, want to do next?

>look at the v|

 

Now, at this point, our timeout expires, and we discover that it's time to move Miss Marmalade into the same room where the player character is located.  We call inputLineCancel(nil) to tell the system that we wish to perform some output operations in lieu of resuming editing transparently, then we add our displayed messages.  Finally, we call inputLineTimeout() again to resume editing.  Here's what the user sees:

 

What do you, the detective, want to do next?

>look at the v

 

Miss Marmalade enters from the north.  She pretends

not to see you, busying herself with rummaging for

something in her purse.

 

>look at the v|

 

Notice that the partially-constructed command line now appears twice on the screen - once before the interruption, and again after.  The first copy is no longer the active command line; it's left on screen only to maintain continuity, so that the user isn't startled by the text suddenly disappearing.  After this, we see the text displayed during the real-time interruption, and finally we see the new command line, where we've reinstated the text the user has entered so far, as well as the original cursor position.

 

This is what we mean by non-transparent resumption of editing.  The resumption isn't transparent, in that the user can plainly see on the screen that the editing was interrupted.  We are nonetheless resuming the editing.  If the user had their eyes closed, they could keep editing the command without knowing that we interrupted them, because the cursor position and all other editing state is the same as it was before the interruption; the only thing that has changed is that more text is on the screen than when we started.

 

The code for this case is almost exactly the same as the example code in Scenario 2, with two changes.  First, we must call inputLineCancel(nil) before we display the message about Miss Marmalde moving; we'd put this call just before the call to actor.performNextTravel().  Second, we'd have to re-display the ">" prompt before we resume editing the command; we could do this before the call to inputLineTimeout().  Note that the adv3 library's input/output managers handle all of this for you automatically, as long as you always go through the inputManager methods to read timed input: if you're using the inputManager methods, you'll never have to worry about whether or not you display anything during an editing interruption.