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
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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!
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.
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.
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\">";
}
;
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.
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:
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 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.
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”.
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 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.
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')
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.).
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.
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.
This attribute holds
the amount of time in minutes that the player has requested to wait, as
converted from his input stream by parsetime().
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.)
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.
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.
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:
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.
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.
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
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.
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
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.
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.
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.
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.