| MAIN | NEWS | FAQS | LINKS | ARTICLES | WORKSHOP |
Send Mail To:Steve Register


Time Tracking - Gwidon S. Naskrent.
This article was originally written for Darren Hebden's RLNews


When creating a roguelike, more often than not you are presented with 
the challenging task of devising an interesting world in which the 
game takes place. And, naturally, most beings in most worlds, even if
they are immortal, need to keep the flow of time for personal, 
governmental (taxes) and religious (feasts) purposes. This article 
deals with issues commonly encountered upon adding a time dimension to
your game.

Most assuredly, you are not bound to design your time according to the
traditional sense, with seconds, minutes and hours. But for the 
simplicity of the discussion, we will assume that you operate in a 
framework that consists of time units that everyone knows - seconds 
that make minutes, minutes that make hours, hours that make days and 
days that make years. We'll also assume that typical Earth standards 
are in force, with the exception that a year is always 365 days 
(not 365.24624 as in reality).

After determining how are your time units structured, it is important 
to decide what within the game will you use to represent it. The 
simplest approach is to assign a different variable to each of the 
various units; a signed char (byte) to seconds, minutes and hours, and 
an unsigned int to days. This simple solution has, however, a 
significant drawback: you waste storage space. It's certainly not a 
big waste, but helps to forget about memory issues and develop bad 
programming habits.

Therefore, a more efficient way to approach our problem will be the
introduction of *scalar time*. Scalar time is an uniform figure; it
represents the amount of basic time units - in our case seconds - that
have elapsed from a specific point in time. This point can be purely
arbitrary, but should be antecedent to the start of your adventure, so
as to avoid 'negative time', if possible. The variable type for scalar
time should be a 32-bit int (it is a good idea to make it a signed 
int, to avoid problems with negative time). 32 bits are, surprisingly 
enough, sufficient to cover a period exceeding 68 Earth years, 
probably more than the longest lifetime of a typical adventurer in 
danger-ridden dungeons. If you still think it's not enough, you can 
make the variable unsigned and double the available period. You can 
even increase the variable length to 64 bits, and it will cover (gasp) 
some 292 trillion years. Now THAT should be enough even if your game 
starts at Big Bang...


Basic Timekeeping

We'll start the coding part by defining some basic quantities:

#define MINUTE 60
#define HOUR (60 * MINUTE)
#define DAY (24 * HOUR)
#define YEAR (365 * DAY)

These will be helpful in adding or substracting larger amounts of time
to/from our scalar clock.

Next, we must write a function that will do for us the dirty work of
calculating actual time representation. Programmers call that 
'breaking down' of the scalar value. Assuming the time units are 
always of equal length, we can achieve the task very easily without 
resorting to floating point arithmetics.

Here's a function I used in GSNband (slightly changed)

int break_scalar_time (int what)
{
	switch (what)
	{
	case SEC:	
	    return (sc_time % MINUTE);
	case MINUTE:
	    return ((sc_time / MINUTE) % 60);
	case HOUR:
	    return ((sc_time / HOUR) % 24);
	case DAY:
	    return ((sc_time / DAY) % 365);
	case YEAR:
	    return (sc_time / YEAR);
	default:
	return (0);
	}
}	    

(sc_time is my 32-bit time variable; SEC, MINUTE etc. are #defined modes
the function is called with; they may be any value you want).

In short, to calculate the remainder corresponding to any unit, we first
divide the scalar time by the amount of base units correspoding to that
unit (note that in the first case sc_time is equivalent to sc_time / 1),
and then take the modulus of it and the amount of units it takes to fill
the next higher unit. The last case does not include the modulus, since
there is no higher unit that a year, and we immediately know how many
years have passed on our clock.

This is a rather unoptimised function that will not work well in a 
compiling environement that generates lots of extra code for each 
function call. It that case, it is better to rewrite the function header 
using pointers, like this:

int break_scalar_time (int *sec, int *min, int *hour, int *day, int *year)

And then call it after having initialized the variables:

int sec, min, hour, day, year;
break_scalar_time (&sec, &min, &hour, &day, &year);

Yet another solution would be a structure to keep the information 
returned, or even better an union (since all the fields are of equal 
length).


Months and Weeks

The next thing you want is a calendar. Unless, of course, the inhabitants
of your world customarily refer to the likes of 'day 164 of the year'. 
For a calendar, you'll need months and perhaps weeks (if each months 
contains an equal number of weeks).

If you wanted to emulate the Gregorian calendar (without a leap day), 
your best bet would be to make two arrays like this:

char *month_names[12] =
{
   "January", "February" ... 
}

int month_table[12] = /* No. of first day of each month in a year (0-364) */
{  
   0, 31, 59 ...
};

(we're counting from 0 to be in common with the values break_scalar_time()
returns)

And then we can calculate the month and day of month like this (with the result
in out_str):

int day, month, i, j;
int year_day = break_scalar_time(DAY);
char *p;
char *out_str;

for (i=0; i <= 12; i++)
{
if (year_day >= month_table[i])
   {
      month = i;
      day = year_day - month_table[i] + 1;
      
      /* Take account of annoying English */
      p = "th";
      j = day % 10;
      if ((day / 10) == 1) /* nothing */;
      else if (j == 1) p = "st";
      else if (j == 2) p = "nd";
      else if (j == 3) p = "rd";

      (void)sprintf (out_str, "%d%s day of %s", day, p, month_names[i]);
    }
}


What To Do With Time?

After we've written our timekeeping routines, we must consider how 
much time will elapse while the player is doing something, IOW, how 
fast will the game time progress. If you game employs large wilderness 
areas, you will probably want to hasten time flow while the player is 
there. OTOH, in a dungeon settings time should pass more slowly.

First, define the area of the basic grid of your dungeon, if you 
haven't already. For example, in Angband this is 10 feet (somewhat 
over three metres). How fast can you expect the player to cover a 
distance of ten feet, moving at a somewhat cautious pace? What about 
combat - how long does a blow take? (I based time elapsed between blows 
on dexterity and weapon weight.) Do monsters act in a separate time 
piece or does time not move while they have a turn? How fast do you use 
magical items? Change levels? Eat something? (think about weight/volume)
Etc. etc. Providing for all actions the player may undertake is vital 
if you want the time flow to correspond, at least vaguely, to what 
you've been doing. 


Some other ideas employing the concept of (recurring) time:

- Shops that are open 7/11 or on certain weekdays
- NPCs that can be had only at specific hours (otherwise they sleep, 
  work, wander away etc.)
- Quests given may be restricted to a deadline
- Artifacts that activate a set number of times per day (or, if that's
  too sparse, per hour). Same goes for recharging time.
- Your character may have to spend a few hours, or even days, to learn
  new spells at a guild.
- Article prices fluctuate over time (probably in a random way, unless 
  you have an economy model handy. :)
- Delayed magical effects, like a retarded fireball or a time 
  bomb/grenade (the concept of delayed function calls probably merits 
  its own article)

Not to mention time travel...

Any questions, corrections or notes? Mail naskrent@artemida.amu.edu.pl
Copyright 2001 Steve Register.