Timesys

 

Advanced

Timekeeping System

for

HTML TADS

 


 

 

 

 

 

 



The Timesys Advanced Timekeeping System Author’s manual

 

Copyright 1999, 2000 by Kevin Forchione.

 

This manual is the copyrighted property of Kevin L. Forchione. Permission is granted to distribute this material by digital and/or physical means on the provision that the material is distributed in an unmodified form for non-profit purposes. It is prohibited to redistribute any sub-section from this material separately without the consent of the author. The author makes no warranty of any kind with respect to this material, and disclaims all warranties, including any implied warranties of merchantability or fitness for any particular purpose, or the continued accuracy of this manual for future versions of the product. I have made every attempt to make this documentation accurate, but will not be held responsible for any loss of productivity resulting from errors.

 

Manual version 3.1      (March 2000)

 

Written by Kevin L. Forchione,

c/o Lysseus Dream Ltd.,

1 Bramford Terrace,

23 Westfield Park

Redland

Bristol UK BS6 6LT.

 

e-mail:    Lysseus@msn.com

 

If you have any questions or queries about Timesys, you may either write or email me.

 

Timesys is Copyright 1999, 2000 by Kevin Forchione for Lysseus Dream Ltd.

 


Contents

 

Preface______________________________________________________________ i

Rewriting History_____________________________________________________ 1

Building From the Ground Up__________________________________________ 4

Advanced Timekeeping________________________________________________ 8

timesys: object______________________________________________________________ 8

timesys.timestring___________________________________________________________ 8

timesys.time________________________________________________________________ 8

timesys.timerate_____________________________________________________________ 8

timesys.timeDisplay( val, parm )_________________________________________________ 8

timesys.day________________________________________________________________ 9

timesys.dayDisplay( val, parm )_________________________________________________ 9

timesys.date________________________________________________________________ 9

timesys.dateDisplay( val, parm )_________________________________________________ 9

timesys.elapsetimeDisplay( val, parm )____________________________________________ 9

timesys.waittime____________________________________________________________ 10

timesys.waitquery__________________________________________________________ 10

timesys.waitqblock__________________________________________________________ 10

timesys.advance( incr )_______________________________________________________ 10

Time-related Functions_______________________________________________ 11

parsetime( cmd );___________________________________________________________ 12

settimesys( t, r, y, m, d );______________________________________________________ 12

gettimesys(  disp, val, lst )____________________________________________________ 13

advtimesys( incr, style )______________________________________________________ 13

stopwaiting( parm )__________________________________________________________ 14

Waiting Around_____________________________________________________ 15

Watching Time Go By________________________________________________ 17

Appendix___________________________________________________________ 17

Technical Issues___________________________________________________ 17

 

 

 

 


Preface

 

Timesys is an extension for HTML TADS’ default parsing/adventuring system contained within ADV.T. Games that use ADV.T can be modified to run with Timesys with minimal changes, and receive the immediate benefits of the system.

 

Timesys is a timekeeping system for HTMLTADS games. It provides a time-oriented alternative to the classical “turns”-style game display. It is particularly suited to mystery games, but can be used in any game that requires that the passage of time play a significant role in the game milieu.

 

Timesys implements an advanced time-oriented game system. Games can display date and time information on the status line, replacing the classic TADS turn/score display.

 

Ever wanted to make a mystery game in the style of Infocom's The Witness? Or an adventure that incorporated the passage of time into its milieu like Infocom's Sherlock: The Secret Of The Crown Jewels? Wanted to make a game that expanded the use of TADS standard waitVerb to handle extended periods like minutes or hours or days?

 

Timesys provides a simple 'plug-n-play' way of installing a sophisticated game clock into your games that does this and more. Taking advantage of HTML TADS powerful display capabilities and the sophisticated time-handling functions of Timesys you can revisit old favourites, giving them a new look and feel in a matter of minutes, or create new games designed to take full advantage of time-oriented game play.

 

A full copy of the Timesys system, including this manual can be found at Internet location:

 

            ftp.gmd.de in the /if-archive/programming/tads/examples directory

 


Freeware Software License

 

This is the Timesys Author's Manual. You may use and redistribute this software, free of charge, with the following restrictions:

 

1.      You must include this file and the copyright notice with all copies.

2.      You may not require or collect a fee for copies of this software, or any part of this software, that you give to other people.

3.      You may not include this software with any other software for which a fee is collected.

4.      You may not modify this software in any way, and each copy you make and distribute must be a full and complete copy of the software you originally received.

5.      Anyone to whom you give a copy of this software receives all of the same permissions that you did under this license.

 

You are permitted to create and distribute translations of this software into other layout languages or formats; any such translation shall be subject to the restrictions of this license as well. You may also make hardcopies of the documentation; hardcopies are also subject to these restrictions.

These files are distributed without warranty of any kind, including without limitation any warranties of merchantability or fitness for a particular purpose. THE READER ASSUMES FULL RISK AND RESPONSIBILITY FOR USE OF THESE FILES. UNDER NO CIRCUMSTANCES IS LYSSEUS DREAM, LTD. LIABLE FOR ANY DAMAGES, DIRECT OR INDIRECT, RESULTING FROM USE OF THIS DOCUMENTATION.

 

With the release of TADS 2.4.0 the following updates have been made to the timesys system:

 

·         The replacement of the parseWord() and parsePunct() functions with TADS built-in parserTokenize. This meant some small modification to the timesys parseTime() function.

·         Removal of the room.t file from the package. The modification of room.statusLine was cleaned up and logic consolidated into the timesys.t source.

·         Removal of the htmlBanner object. This object merely acted as a switch for toggling the statusLine. This attribute was moved to global.

·         General cleanup of the manual and sample source.

 


Changes with Version 2.0

 

With the release of TADS 2.4.0 the following updates have been made to the timesys system:

 

·         The replacement of the parseWord() and parsePunct() functions with TADS built-in parserTokenize. This meant some small modification to the timesys parseTime() function.

·         Removal of the room.t file from the package. The modification of room.statusLine was cleaned up and logic consolidated into the timesys.t source.

·         Removal of the htmlBanner object. This object merely acted as a switch for toggling the statusLine. This attribute was moved to global.

·         General cleanup of the manual and sample source.

 

Changes with Version 3.0

 

The release of version 3.0 has seen significant changes to the Timesys system! Timesys now takes full advantage of TADS' regular expression functionality that was introduced with TADS 2.3.0. Version 3.0 of Timesys does not use or require any of the built-in functions provided by TADS 2.4.0, yet because of its use of regular expressions it is the most flexible and powerful implementation of a time-oriented system currently available.

 

·         Changed all references to timeSys and parseTime() to the lower-case timesys and parsetime(). This should help eliminate errors due to case-sensitivity, as there are far fewer mixed-case objects, functions, and methods.

·         Replacement of parserTokenize() with regular expression matching functions reSearch() and reGetGroup(). This involved a complete re-write of the parsetime().

·         Incorporated #defines for parsetime limitations and gettimesys constants to make the system easier to work with. Authors can now set limits on how many hours or minutes the player can request to wait.

·         Conversion of preparse to preparse object with the inclusion of ppo.t source file. The timesys_ppo handles the reformat of the command, use of default timerate when waittime is unspecified, retrieves information needed for AGAIN processing, and does general syntax checking.

·         Simplification of the waitingVerbs Version 3.0 replaces ADV.T waitVerb converting it to waitingVerb class and adding vocabulary to allow it to handle 'wait', 'wait for', 'wait until', and 'z'.

·         Modified settimesys() and advtimesys() to take advantage of parsetime(). It is now possible to pass a time-format expression in the parameters, which should make the functions easier to use and understand.

·         General cleanup of the manual and sample source.

 

Changes with Version 3.0.1

 

Version 3.0.1 adds an additional parameter to the timesys object’s xxxxxDisplay() methods and the gettimesys() function that allows values to be formatted into timesys time, day, date, and elapsetime formats.

 

Additionally, all calls using gettimesys() now use the timesys constants defined in version 3.0.

 

Changes with 3.0.2

 

This release was a minor change to the room status line, enabling non-HTML run-times to display timesys banners when the game has been compiled using USE_HTML_STATUS.

 

Changes with 3.1

 

The calculation of the base date was changed to correctly incorporate leap years. The settimesys() function was simplified to accept the date as y, m, d parameters. Finally the package was converted to #pragma C+ and the __TIMESYS_MODULE defines checked for at the beginning of the module.

 


Acknowledgements

 

Much gratitude is owed to Michael J. Roberts, author of TADS, whose efforts have enabled others to put their dreams and ideas into the works of Interactive Fiction. It is hoped that this manual will provide both instruction and food for the imagination.

 

Ditch Day Drifter is Copyright © 1990-1999 by Michael J. Roberts.

 

The Gold Skull  is Copyright © 1992-1999 by Michael J. Roberts.

 


Required System Files

 

In addition to requiring HTML TADS the Timesys system is composed of the following two files:

 

timesys.t               This file contains the timesys object and the routines and grammar necessary to manage the passage of time in the game. This file also contains the parsing routines found in ParseWrd.t, which is used in the Timesys.t parsetime(). It is used to convert the player’s input stream into a list of parsed ‘words’ that greatly simplify the coding of game time logic allowing for the analysis of complex grammatical structures.

 

ppo.t                     This file implements preparse objects. This file is not required but is included to provide the author with more flexibility in implementing Timesys. If ppo.t is not #include'd then Timesys will revert to the use of  a conventional TADS preparse() function as it did in versions earlier than 3.0.

 

Notice: With version 2.0 the room.t file is no longer necessary. (See Changes to Version 2.0 above.) With version 3.0 the addition of ppo.t automatically implements the use of preparse objects.

 

Additional Files

 

Along with these files timesys.zip contains the following files:

 

Sample.t         Coding required to adapt Ditch Day Drifter to a time-oriented game.

 

 

Sample2.t       Source file for A Very Simple Game, demonstrating some of the features of Timesys.

 

 

 

 


Rewriting History

 

Requirements

 

The system has been designed to be as modular as possible: the desire being the ability to “plug-n-play” with minimal changes necessary to existing code. In addition to requiring HTML TADS the following statements should be included in your game source file:

 

#define USE_HTML_STATUS

#include <ppo.t>                // optional, using preparse objects

#include <timesys.t>

 

Along with the modification of the ADV.T class room definition’s HTML status line, the timesys.t file will replace your game’s turncount() and scoreRank() in order to progress and report the time.

 

Unless your coding modifies these functions no further action is required for them. If you do need to replace these routines then you must copy the code contained in timesys.t into your functions so that the program can communicate with the timesys object.

 

If your game provides a preparse() function, you will need to replace the one provided by timesys.t and add it’s code to your own. If you have #include ppo.t in your source listing you have only to convert your existing preparse() function into a preparse object.

 

Your game’s init() or commonInit() function will need to call the settimesys() function provided by timesys.t. This function allows you to set the game’s initial date and time values, along with a default time rate.

 

Previous versions of Timesys experienced run-time errors if the various date and time information was not initialised. Version 3.0 defaults to 'Saturday, 1 January 2000 12:00 am' with a timerate of 1 minute.

 

Optional Features

 

Different status line banners can be achieved through the use of the gettimesys() function and the modification of room. The gettimesys() function is capable of reformatting time, day, date, and elapsed time in a variety of ways. You can even utilise this function from your source code to create clock and calendar objects within the game or provide the information to actors for the purposes of decision-making and dialogue.

 

With the advtimesys() function you can control the hands of time from within your code, making use of time-elapsing events such as travel, sleeping, or periods of unconsciousness.

 

You can utilise the timesys.events() method called each time the clock is advanced, in order to affect global time-related events, such as game lighting for night and day transitions, the striking of the hour by the city’s clock, weather effects, etc.

 

If your game makes use of the waitingVerb class provided by timesys you may wish to implement events that request the halting of a waiting process (perhaps with the classic "Do you wish to continue waiting? [Y/N]" option). You can use the stopwaiting() function to issue such requests from your program.

 

Finally, time may always run forward, but the settimesys() function is capable of transporting the player to any time period since the birth of Christ. Travel back to the days of King Arthur, Galileo, or Darwin--or fling yourself into the 23rd century on an adventure to the stars!

 

Adapting Ditch Day Drifter

 

For the purpose of example, let's adapt Ditch Day Drifter[1] to be time-oriented. We'll do the minimal amount of changes necessary to do this.

 

It takes only 6 easy steps!

 

1.      Create a new source file: call it SAMPLE.T and include the following lines of code at the top:

 

#define USE_HTML_STATUS

#include <ditch.t>                // the Ditch Day source

#include <timesys.t>       // The Timesys Advanced Time System

 

2.      Replace the init() function: Include the source code from ditch.t init() and the necessary "\H+"; qualifier for HTML TADS (The minimal amount of changes necessary to accommodate Timesys. Since Ditch Day Drifter is pre-HTML TADS it will need a few modifications to bring it up to speed. You will need to add the #define USE_HTML_STATUS for used in toggling between standard and HTML TADS). Also include the following statement:

 

settimesys( '11:50 P.M.', 1, 1999, 12, 31 );   

 

Note the 1ST parameter is in single-quotes. This initialises the time to "Friday, 31 December 1999 11:50 pm".

 

3.      Replace the scoreRank() function: Include the source from ditch.t scoreRank() and the code found in the scoreRank() of timesys.t (this allows us to toggle between classic TADS SCORE messages and Timesys SCORE messages.)

 

4.      Replace the turncount() function: Since Ditch Day Drifter doesn't alter the ADV.T turncount() function, nothing needs to be done for this step. It will automatically use the Timesys function.

 

5.      Replace the preparse() function: Since Ditch Day Drifter doesn't alter the TADS preparse() function, nothing needs to be done for this step. It will automatically use the Timesys function. We don't even need to #include the ppo.t file, as timesys.t will automatically handle the replacement.

 

6.      Compile SAMPLE.T with the HTML TADS compiler and run: (See source code SAMPLE.T included with Timesys.) Try toggling the different banner modes with the BANNER command. Try WAIT 10 MINUTES and see what happens. Also notice the SCORE message. You can now play Ditch Day Drifter in either time-oriented or "turns" mode.

 

Ditch Day Drifter wasn't written to take advantage of time-oriented game play. In the next chapter we'll develop a simple game from to demonstrate and utilise a few more of Timesys' special features.

 


Building From the Ground Up

 

This chapter introduces some of the concepts behind writing a program to use Timesys from scratch by expanding upon the same steps used for adapting an existing game to time-orientation. While the example we will develop is very simple, the game is a complete, fully-working time-oriented program.

 

A Very Simple Game

 

To show how easy it is to develop a Timesys game in HTML TADS, we will start off by constructing a Timesys version of the simple game, The Gold Skull[2] presented in Chapter 1 of the TADS 2.0 manual, adapting it to Timesys and then adding a few features. This will hopefully give you an idea of the potential of a time-oriented game, and help in later sections to know how the various elements of the Timesys system can fit into a game.

 

You should already be familiar with the process of entering TADS scripts into a word processor and saving in ASCII format for compilation by the TADS compiler. You should also be familiar with the requirements of HTML TADS. In general, modifications will be made using the modify and replace keywords. A full version of the sample game can be found in SAMPLE2.T

 

First we create the SAMPLE2.T file with the required HTML definition, the ADV.T and STD.T files:

 

#define USE_HTML_STATUS

#include <adv.t>

#include <std.t>

#include <ppo.t>

#include <timesys.t>

 

We want our game to be sensitive to the lighting conditions of day and night. The simplest way to do this is with a class of room for when the location is out of doors. Since this location must be sensitive to light and dark it will have to be a darkroom. We will affect the lighting in these locations, but we will also provide messages warning the player about changes it the lighting.

 

For our example let's create the class:

 

class outdoors: darkroom

       lightsOn = nil             // starts initially in the dark

       lightlevel  = 0            // 0=night 1=sunrise 2=day 3=sunset

       lighting( time ) =

       {

              if ( time >= 390 and time < 420 ) // sunrise 6:30 am

              {

                     if ( self.lightsOn = nil )

                     {

"Visibility increases in the gathering light of a new day. ";

                           self.lightsOn := true;

                           self.lightlevel := 1;

                           stopwaiting( true );

                     }

              }

else if ( time >= 420 and time < 1170 ) 

// day 7:00 am to 7:30 pm

{

if ( self.lightlevel = 1 )

                     {

"The sun comes up, as much as it ever comes up here. ";

                           self.lightsOn := true;

                           self.lightlevel := 2;

                           stopwaiting( true );

                     }

              }

              else if ( time >= 1170 and time < 1200 )

// sunset 7:30 pm

{

       if ( self.lightlevel = 2 )

                     {

"Daylight begins to fade, soon it will be dark. ";

                           self.lightlevel := 3;

                           stopwaiting( true );

                     }

              }

              else                       // night 8:00 pm to 6:30 am

              {

                     if ( self.lightsOn = true )

                     {

                           "Darkness falls and the mists come in. ";

                           self.lightsOn := nil;

                           self.lightlevel := 0;

                           stopwaiting( true );

                     }

              }     

       }

;

 

Now we need a way for Timesys to communicate the time to this class. To do this we modify timesys.events to call the class' lighting method.

modify timesys

       events =

       {

              outdoors.lighting( self.time );  

       }

;

 

We'll give the player a brass lamp they carry with them, for times when they're in the dark.

brassLamp: lightsource

       location = startroom

       sdesc = "lamp"

       noun = 'lamp' 'lantern' 'light'

       adjective = 'battered' 'old' 'brass'

ldesc = "It is a battered old brass lamp that has accompanied you on many adventures in the past. "

       islit = true              

       verDoTurnon( actor ) =

       {

              if ( self.islit )

              {

                     "But the lamp is already lit!";

              }

       }

       doTurnon( actor ) =

       {

              self.islit := true;

              "You turn up the wick. The lamp emits a warm glow that

              lights up the area around you. ";

       }

       verDoTurnoff( actor ) =

       {

              if ( not self.islit )

              {

                     "The lamp is already out.";

              }

       }

       doTurnoff( actor ) =

       {

              self.islit := nil;

              "You turn down the wick. The lamp goes dark. ";

       }

;

 

And now for some familiar territory with a new twist. We adapt the ldesc for our locations to reflect the changing light conditions. We do this minimally, based on whether it is day or not (The assumption being that dusk and dawn, being transitional states, do not have enough intensity to be described.)

startroom: outdoors

       sdesc = "Outside cave"

       ldesc =

       {

               "You're standing ";

              switch( self.lightlevel )

              {

                     case 2:

                           " in the bright sunlight ";

                           break;

                     default:

                           break;

              }

              "just outside of a large, dark, foreboding cave, which

              lies to the north. ";

       }

/* the room called "cave" lies to the north */

north = cave        

;

 

cave: outdoors

       sdesc = "Cave"

       ldesc =

       {

              "You're inside a dark and musty cave. ";

              switch( self.lightlevel )

              {

                     case 2: "Sunlight pours in from a passage to

                           the south. ";

                           break;

default: "In the shadowy gloom you can just make out a passage to the south. ";

       break;

              }

       }

       south = startroom

;

 

Now we can add items to the game. They are the traditional pedestal, rock and skull, with the only difference being that the skull is now coded to advance the time, instead killing the player as it did in the original demonstration game.

pedestal: surface,  fixeditem

       sdesc = "pedestal"

       noun = 'pedestal'

       location = cave

;

goldSkull: item

       sdesc = "gold skull"

       noun = 'skull' 'head'

       adjective = 'gold'

       location = pedestal

       doTake (actor) =

       {

if ( self.location <> pedestal or smallRock.location = pedestal )

              {

                     pass doTake;

              }

              else

              {

                     "As you lift the skull, a volley of poisonous

                     Arrows is shot from the walls! You try to

                     Dodge the arrows, but they take you by surprise! \b

Suddenly everything starts to spin. You pass out and fall to the ground. Sometime later you awaken and stumble back to your feet. ";

// set time ahead 6 hours

advtimesys( '6 hours', nil );

}

}

;

 

smallRock: item

       sdesc = "small rock"

       noun = 'rock'

       adjective = 'small'

       location = cave

;

Lastly, we replace the commonInit() function, so that we can set the time, and add a little colour to the display.

 replace commonInit: function

{

"\H+";

  /*

   *  The \H+ bit is the magical incantation that

   *  instructs the TADS interpreter that henceforth

   *  we want HTML to be parsed as HTML rather than

   *  displayed as text for the user to puzzle over.

   */

 

       // set the window title

"<TITLE>Timesys: Advanced Timekeeping System Sample2</TITLE>";   

 

       // display the game's name and version number

 

       settimesys( '5:00 A.M.', 5, 1887, 6, 17 );

 

       "<BODY BGCOLOR=\"#0000AA\" TEXT=\"#FFFFFF\">";

}

;

 


Advanced Timekeeping

 

Overview

 

The system utilises a basic calendar system derived from a Julian date. Though not directly displayed in the standard Timesys status, it is incremented as the time advances and allows the author the flexibility of programming time-specific events that can be activated in the timesys.events() as well as in other places of the code.

 

timesys: object

 

The timesys object contains attributes and methods used to keep track of the passage of time for the game. It also contains mechanisms for the display of the time and date in various formats on the game status line (sometimes called the game banner).

 

The following are attributes and methods of timesys:

 

timesys.timestring

 

This is the last time-string parsed from the timesys_ppo. This string is the reSearch() return list's 3rd occurrence and is used solely for the purpose of AGAIN processing.

 

timesys.time

 

Timesys has a 1440-minute clock which represents minutes from midnight starting at 0 (12:00 am), and progressing through 1439 (11:59 pm). The representation of the time in hours and minutes, with a.m. and p.m. indicators is used for status line display only.

 

timesys.timerate

 

The timerate is the default rate in minutes that the time is advanced with the processing of normal game activities (i.e. the execution of non-system, non-waitingVerb commands.) It should be a positive integer representing the number of minutes to advance each "turn”.

 

timesys.timeDisplay( val, parm )

 

This function formats val for time display purposes. Parm should be either true or nil. If nil, it will return a string with the time in hh:mm tt format. If the parameter is true then a list will be returned with the following values:

 

            ret [ 1 ]             string containing timesys.time

ret [ 2 ]             string in hh:mm tt format (i.e. 12:00 am)

            ret [ 3 ]             string in hh:mm 24-hour format, no leading zero

            ret [ 4 ]             string containing the hours in hh format, no leading zero

            ret [ 5 ]             string containing the minutes in mm format, no leading zero

            ret [ 6 ]             string of the am / pm indicator[3]         

 

timesys.day

 

Timesys has a numerical representation of the day that is based on computations used by its calendar. The corresponding values are: 0 = Sunday, 2 =Monday, 3 = Tuesday, etc. The day is progressed when the time passes 11:59 pm.

 

timesys.dayDisplay( val, parm )

 

This function formats val for day display purposes. Parm should be either true or nil. If nil, it will return a string with the day in dddddddd format (i.e. 'Thursday'). If the parameter is true then a list will be returned with the following values:

 

            ret [ 1 ]             string containing timesys.day

ret [ 2 ]             string in dddddddd format (i.e. 'Friday')

            ret [ 3 ]             string in ddd format (i.e. 'Thu')           

 

timesys.date

 

In order to maintain its calendar timesys converts Julian values to a base figure that can be easily incremented (See appendix A for the calculation algorithm.).

 

timesys.dateDisplay( val, parm )

 

For authors who wish to incorporate more information into their games. Formats val for date display purposes. If the parm is nil this function provides the full date as a string in the default format of dd mmmmm yyyy (i.e. 20 September 1929). If the parameter is true then a list will be returned with the following values:

 

ret [ 1 ]             string containing timesys.date

ret [ 2 ]             string in dd mmmmm yyyy format (i.e. 20 September 1929)

ret [ 3 ]             string in mmmmm dd yyyy format (i.e. September 20 1929)

ret [ 4 ]             string in yyyy format (i.e. '1929')

            ret [ 5 ]             string in mmmmm format (i.e. 'September') 

            ret [ 6 ]             string in ddd format (i.e. '365'), no leading zeros

            ret [ 7 ]             string in dd format (i.e. '20' )

timesys.elapsetime

 

The elapsetime attribute holds the total elapsed game time. It is used mainly for printing the SCORE message. When the default timerate is 1 then this value will equal the global.turnsofar value, but when timerate > 1 then this value will reflect the accumulated game time, not the number of turns the player has taken.

 

timesys.elapsetimeDisplay( val, parm )

 

Formats val for elapsed time display purposes. If parm is nil this function provides the elapsed time as a string in the default format of dd 'days' and hh 'hours' and mm 'minutes' (i.e. 2 days and 4 hours and 10 minutes). The function will not display any portion that is zero, except in the case where no time has elapsed, in which case it will display '0 minutes'. If the parameter is true then a list will be returned with the following values:

 

ret [ 1 ]             string containing the timesys.elapsetime

ret [ 2 ]             string containing the default display string

ret [ 3 ]             string containing the elapsed time in days

ret [ 4 ]             string containing the elapsed time in hours   

timesys.waiting

This attribute is a Boolean (true/nil) indicator of whether the system is performing a waiting process.

 

timesys.waittime

 

This attribute holds the amount of time in minutes that the player has requested to wait, as converted from his input stream by parsetime().

 

timesys.waitquery

 

This attribute is a Boolean (true/nil) indicator of whether the system is to be sent a request asking the player whether or not they wish to stop the waiting process.

 

timesys.waitqblock

 

This attribute is a Boolean (true/nil) indicator of whether the system is to process an advtimesys() message. It is used to block the issue of any stopwaiting() request, thus insuring that the advtimesys() processes to completion.

 

timesys.advance( incr )

 

This method handles the advancement of the time, day, date and elapsed time. The parameter should be the amount of time in minutes you wish to advance.

 

timesys.events

 

This method is called each time timesys.time is incremented. This allows the game author to affect events globally, such as lighting and weather effects. You should replace this function in your game source with the necessary code using the modify statement. Note that the method takes no argument.

 

Some games may require that the village clock strikes the hour, or that the sky pour down with rain on a certain day. You may wish to implement a system of lighting in your game that effects the global.lightsource[] making it dangerous for the player to go out at night, but safe during the day. Timesys provides an entry-point into these kinds of options with this method.


Time-related Functions

 

Although there are a fair number of attributes and methods in the timesys object, there are only a handful of functions necessary to communicate with the object.

 

Time-formats

 

The following regular expression search strings are used to decide if a string contains a valid time-format:

 

       '([0-9]?[0-9]):([0-9][0-9]) *([ap]%.?m%.?)?'

       '([0-9]*[0-9]) hours? (and)? ([0-9]*[0-9]) *minutes?'

       '([0-9]*[0-9]) *hours?'

       '([0-9]*[0-9]) *minutes?'

 

Also included in the list are the following:

 

       '(midnight)|(noon)'

'([0-9]?[0-9]) *([ap]%.?m%.?)'

'([0-9]*[0-9])'

 

These are special cases and are used by parsetime() to reformat an expression into a recognised time-format.

 

Even though we may have a valid time-format we may not have a valid time. The regular expressions will narrow down the range of invalid values that might sneak into calculations, but the parsetime() function goes further in providing simple edits to try and trap invalid time formats.

 

Limiting Hours and Minutes In parsetime()

 

As you can see from the regular expressions listed above, it would be perfectly valid to enter a command  such as the following:

 

>Wait 100 hours and 720 minutes.

 

This would, however, cause serious response time problems, even on the quickest of machines. Timesys therefore requires that limits be placed on the size of the hours and minutes a player can specify. These limits, however, can be modified through the use of #defines.

 

#define PARSETIME_LIM_HH 24

#define PARSETIME_LIM_MM 59

 

These are the Timesys default values for the maximum hour and minute when parsing commands with the hh hour<s> mm minute<s> style syntax. The parsetime() function will return nil for any hour greater than PARSETIME_LIM_HH and any minute larger than PARSETIME_LIM_MM.

 

If the author wishes, they may set these limits to any value at their discretion by #defines placed before the #include of Timesys.t.

 

Parsing the Time

 

Timesys provides access to its time parsing function, returning useful information to the author about the passed command. The function will find the first occurrence of a valid time-format within a string and calculate a timesys.waittime for it.

 

parsetime( cmd );

 

Use this function when you wish check a string for a valid time-format or retrieve information related to the time-format. The parameters are as follows:

           

cmd     (single-quoted) string you wish to check for a valid time-format or for which you wish information.

 

If the cmd does not contain a valid time-format the function will return nil; otherwise it will return a list composed of the following:

 

ret [ 1 ]             a list composed of the return values for the reSearch() for the cmd.

ret [ 2 ]             the calculated waittime

ret [ 3 ]             if cmd was an o'clock value (or midnight/noon) then this will be the time of cmd in minutes since midnight; if the cmd was an amount (hours or minutes) then there will be no ret[3] element in the return list.

 

Setting the Date and Time

 

Timesys allows you to initialise the game clock through its settimesys() function. This function should normally be included in your game's init() or commonInit(), but can be executed from any point where you desire to change the functioning of the game clock.

 

settimesys( t, r, y, m, d );

 

Use this function when you wish to initialise the timesys time clock. The parameters are as follows:

           

t           time of day in minutes past midnight. For example 0 = 12:00 am ( midnight ); 300 = 5:00 am; 720 = 12:00 pm ( noon ); 1020 = 5:00 pm. Using this format t should be an integer between 0 and 1439.

 

Time of day as time-format string. For example: '9:35 p.m.' or 'midnight' or '7 p.m.'. Using this format t should be a valid time-format in single-quotes

 

r           default rate per turn. This is the time in minutes that each action that the player makes (excluding waiting and system commands) takes to execute in game time. It is also used as the default value for the Wait command.

 

The rate should be a positive integer.

 

y          this value is the numeric value for the year in yyyy format.

 

m         this value is the numeric value for the month in mm format.

 

d          this value is the numeric value for the day in dd format.

 

 

Displaying The Time In Various Formats

 

While it's possible to play games using the default formats provided in timesys.t, you will no doubt wish to create your own game banners, utilizing the power of HTML TADS more fully than the basic displays provided. While you could access the timesys display methods directly, the system provides a simple, all-purpose routine that can return the default display or a list of string formats you can manipulate.

 

gettimesys(  disp, val, lst )

 

This function calls the various timesys Display methods, and can be used by the author to print date time strings or retrieve them in list format. The values of the parameters are as follows:

 

disp     display format corresponding to one of timesys' Display methods. The values passed should be: 'time', 'day', 'date' or 'elapse'. These values should be in single quotes. Use of TS_TIME, TS_DAY, TS_DATE, and TS_ELAPSE is recommended.

 

val       value to be formatted. This should correspond to the requested disp parameter and the corresponding timesys time, day, date, or elapsetime attribute.

 

lst        determines whether the value returned is in the form of the default display string or a list. Nil returns the default string, while true will return the corresponding list (See Chapter 2: Advanced Timekeeping for details.)

 

Making Time Advance through Game Puzzles

 

Imagine that you are Dr. Watson in Infocom's Sherlock Holmes: The Secret of the Crown Jewels. You enter into Holmes's bedroom and riffle through the equipment on the workbench. There is an ampoule sitting on the bench with some mysterious writing on it. Foolishly you open the ampoule, breathing in the noxious gasses and awakening hours later…

 

Timesys provides a function for realising this scenario. It will allow you to progress the time clock, day, and date from your code.

 

advtimesys( incr, style )

 

Allows the author to advance the time from within his game code. This function will not request a time advancement if a wait process is active. It also sets the timesys.waitqblock to true until the process has finished. The purpose of this function is to allow the game to request a waiting process. Since this waiting process is not initiated on behalf of the player it is expected to run to completion, unless the game terminates beforehand. The parameters have the following meaning:

 

incr     time in minutes of the requested wait.

The time should be an integer from 1 to 1440 (all other times will default to 1).

 

time of day as time-format string. For example: '9:35 p.m.'

 

The time-format string should be in single-quotes

 

style    a Boolean (true/nil) indicator that determines whether the rundaemons() and runfuses() functions are executed. If true then they are. If nil they are not. In either case the time is advanced and timesys.events is called.

 

Alerting The Player To Time-specific Events

 

Timesys allows the author to issue a request to the system to stop the waiting process. This should be issued by daemon- or fuse-driven events. For example, imagine:

 

a.      You are sitting lotus-style in your pagoda, waiting for illumination when a small band of ninjas burst in upon you, assassination on their minds.

 

b.      You’re 200 hundred feet beneath the waves, waiting for a rare sea anemone to open when the gauge on your air tank starts to flash low oxygen.

 

When the daemon reaches a certain point, or the fuse expires, it can send the timesys object a request to stop waiting. This can take one of two forms, allowing the player to decide whether to continue the waiting process, or terminating it without offering the player a choice. You can do either of these with the following function:

 

stopwaiting( parm )

 

If the passed parameter is true then the system will generate the message “Do you wish to stop waiting? [Y/N] and await the player’s input. If the parameter is nil then the waiting

process is terminated without issuing the stop waiting query.

 


Waiting Around

 

Normal game actions will expend the default number of minutes specified by the author’s settimesys(); For example, if the author has entered a rate of 5, then each command entered by the player will take 5 minutes of game time. This default number expends only 1 turn per time advance.

 

This means that if you request a timerate of 5 minutes then with each execution of turncount() the timesys clock will advance 5 minutes.

 

In mystery games and certain other types of adventure games the author may desire that the player remain in a certain location for a number of turns (time) before something will happen. It may be the case that certain events are time-driven: with things happening according to a time schedule. It is often convenient in these cases for the player to be able to wait.

 

The player is able to request waiting during game play in several ways. The following are all valid wait requests:

 

            Wait

            Wait 10

            Wait 10 minutes

            Wait for 3 hours and 10 minutes

            Wait until 12:00 a.m.

            Wait until 7 p.m.

            Wait until midnight

 

The waitingVerb class, when used in conjunction with parsetime() allows for the following syntax:

 

            Wait|z <until|for>  <time-format>

 

The syntax for a valid time-format are:

 

nn <MINUTE|MINUTES|HOUR|HOURS>

nn HOUR|HOURS <AND> nn MINUTE|MINUTES

            HH:MM  <AM|A.M|A.M.|PM|P.M|P.M.>

            HH AM|A.M|A.M.|PM|P.M|P.M.

            NOON|MIDNIGHT

 

Note that if the player issues the command <Wait 10> or <Wait for 10> the system advances the time in minutes. Each minute of waiting executes the game’s rundaemons() and runfuses(). This allows events to happen in the background. A wait of 60 minutes or 1 hour will execute rundaemons() and runfuses() 60 times. For this reason it is not possible to wait more than 24 hours at a time. Timesys waittime values longer than 24 hours are set to the 24 hour maximum.

 

Also, notice that if the player enters <Wait> then the system uses the default rate and advances timesys.time that many minutes, but executes rundaemons() and runfuses() only once. If you choose a default timerate > 1 then you should be aware that <Wait> and <Wait n> where n = timerate will have different effects in your game due to the execution of daemons and fuses.

 

Play It Again, Sam

 

Timesys is able to handle the player's request for AGAIN or G following a WAIT command, allowing the use of this shortcut. In the case of "wait until" the AGAIN pays special attention to whether the original command indicated an am/pm with the time. In that case the AGAIN will generate a waiting process of 24 hours. If the player's original input string did not contain an am/pm indicator then the waiting process advances the time 12 hours

 


Watching Time Go By

 

About the room.statusLine

 

With HTML TADS the appearance of game text can be manipulated with more ease and control than ever before. Timesys has begun to take advantage of this with its modification of the room.statusLine. Minor modifications have been made to the statusLine method to display a Timesys' basic and more advanced status lines.

 

Additionally the bannerVerb can be used to toggle between these two forms of time-oriented display and the classic TADS "turns/score" status line.

 


Appendix

 

Technical Issues

 

There are three technical issues that need to be discussed for completeness.

 

a.      The processing of the AGAIN verb

b.      Processing commands following WAIT in the input string

c.      Handling WAIT FOR and WAIT UNTIL with invalid time amounts stated

 

Processing the Again Verb

 

TADS works on the basis that AGAIN process the command as it has been translated into objects by the parser. This means that when the player types AGAIN the command processed will not be the original input string, but the waitVerb object that was used by the parser with the last command execution.

 

Therefore the original command is not re-evaluated and timesys would be forced to use the waittime value that it had previously calculated for computing the length of the waiting process. In the case of “wait for amount” this wouldn’t a problem, however, in the case of “wait until o'clock” the waittime value should be either 720 (in the case of “wait until 9:00) or 1440 (in the case of “wait until 9:00 am). This would be incorrect most of the time.

 

To remedy this timesys_ppo saves its parsed time-format each time in the timesys.timestring attribute. At the conclusion of waitingVerb's action() method the timesys.waittime is reset to 0. It is checked at the start of each waiting process to determine the likelihood that timesys_ppo has been called to calculate a new waittime. If the waittime is 0 at the beginning of waitingVerb.action() then parsetime() is called using the timesys.timestring to recalculate a new waittime.

 

Processing Commands Following WAIT In The Input String.

 

Because the waitingVerb handles the processing of rundaemons() and  runfuses() during a waiting process it issues an abort when finished in order to bypass running these functions again when control returns to TADS. Consequently commands issued in the player’s input string after the WAIT command are not processed.

 

Timesys version 3.0 has syntax-checking in timesys_ppo to detect this and issues an error message, terminating the command.

 

Handling Wait with Invalid Time Amounts

 

The use of TADS' regular expressions allows for a high degree of syntax-checking. With regular expressions it is possible to guaranty the precision of the time-format. It remains only to prevent the player (or author) from entering into prolonged waiting processes. This is done through one central location: parsetime(). The function automatically limits the waittime to a 24-hour period.

 

Parsing Time Methodology

 

Because the waitingVerb must process a value that is neither a direct or indirect object we need a special process to handle this. So we must use either preparse()or preparseCmd(). With Timesys version 1.0 was developed using preparse() in the tradition of other time-based systems[4], but not without giving consideration to the use of preparseCmd()[5]

 

The use of preparseCmd()

 

While preparseCmd() offers the advantage of providing a parser-derived command list and passes the function only 1 command at a time, it does offer some drawbacks.

 

A command such as:

 

            Wait until 9:30 pm

 

Where the compoundWord ‘wait’ ‘until’ ‘xyzzy’ has been defined will be receive a the list:

           

            [ ‘xyzzy’           ‘9’         ‘,’          ‘30’       ‘pm’ ]   // ‘:’ is translated to ‘,’

 

For its first iteration it must return either true (process the input asis), false (get a new input stream), or a wordlist. The second iteration must not return a word list, and so must in some way be identified as having already been processed. Normally this involves modifying the original word list command.

 

            [ ‘yzzyx’ ]

 

parsetime() must take the remaining elements of the list and convert them into a waittime value. This value must also be referenced by the verb attribute of a verb object.

 

Which means that our new verb ‘yzzyx’ if entered by the player will have the following effect:

 

a.      it will not go through the preparseCmd() because the function assumes that it was created there.

 

b.      It will function as a command because it references a verb, and hence will attempt to kick off a waiting process, the inadvertent success of which will be dependent upon whether a waittime value has been previously calculated.

 

The Use Of preparse()

 

Unlike preparseCmd this function is only called once, and is passed the player’s input string, leaving the author to manipulate it as s/he will. The drawbacks are as follows:

 

a.      The input string may contain multiple commands. It will then be necessary to keep the entire input string intact, while manipulating the input that relates to the waiting process.

 

b.      Locating and manipulating the input string is more complicated than with preparseCmd() which pre-packaged the statement for us. The first stage of the parsetime() function uses regular expressions to strip out valid time-formats and process them. It then returns information back to timesys_ppo to help reformat the command string.

 

So that an input string such as:

 

Ø                  Wait until 9:30 then pull the lever.

 

Must be converted into a configuration similar to the following:

 

‘wait then pull the lever.’

 

Because of the abort issued by the wait command the remainder of the input would never be evaluated. We therefore issue the error message:

 

       [There appear to be extra words or commands after the WAIT command.]

 

No conversion of the verb needs to be done and the player, entering “wait” or “wait for” or “wait until” will find his command going through the parsetime() where the function must determine if it has been provided with sufficient information to calculate a waittime value.

 

 



[1] Ditch Day Drifter is Copyright © 1990-1999 by Michael J. Roberts and can be found in the TADS directory of ftp.gmd.de

[2] The Gold Skull is Copyright © 1992 by Michael J. Roberts and is discussed in Chapter 1 of the TADS Author's Manual.

[3] Note that Timesys represents a.m. and p.m. as either 'am' and 'pm', 'a.m' and 'p.m' or 'a.m.' and 'p.m.'. This is one of the benefits of the use of regular expressions, as the period would normally terminate a command and cause syntax problems.

[4] The author has had extensive experience in developing time-based systems for Inform (See the Inform library extension Timesys @ ftp.gmd.de).

[5] At this time there are plans to incorporate the new Regular Expressions introduced in TADS 2.3.0 into version 3.0.