Timekeeping
Keith Schleiffer
Annandale, VA
The friendly computer guide that comes in the box with your VIC 20 mentions several interesting features that the casual reader can easily miss. In my most recent rereading, I discovered the time-keeping feature of the VIC. The computer can keep real clock time, count elapsed time, or time controlled pauses during program execution.
The clock is available as the reserved variables TI and TI$. TI actually counts time passing. TI$ is a string variable, which depicts this time count in HHMMSS format (hours, minutes, and seconds, without any punctuation) on a twenty-four hour clock.
How does the VIC do this timekeeping? When the computer is first turned on, the timekeeper initializes at 000000 (midnight). You can then set it to act as a clock by assigning to TI$ a string representing the correct time. For instance, if I initialize the timekeeper as a clock at 1:29:30 in the afternoon, I would enter the statement:
TI$ = "132930"
The VIC would convert this to 48570 seconds after midnight, multiply by sixty, and assign:
TI = 2914200
and continue counting from there. TI is counted in one-sixtieth second intervals; that is, when TI has increased by sixty, one second has passed. The time count is kept in memory locations 160, 161, and 162.
Once you have set the correct time, you can check it whenever you wish by entering:
PRINT TI$
and the VIC will display the time, again in HHMMSS format. I like to set TI$ to keep clock time, and check it occasionally, so my wife doesn't have to complain about getting less attention than the computer. The timekeeper can be used in programming to control operations at scheduled times during the day, such as periodic data-collection from an experiment, or to control your lights in a household security program.
To use the VIC to count elapsed time, you cannot start and stop the time counter. To get around this problem, you must run a second variable to count time in parallel with TI, then stop counting with that second variable when the timed period is over. The following program uses the "hit any key" concept to start and stop timing:
100 GET A$ : IF A$ = "" THEN 100 110 TS = TI 120 PRINT "TIMING" 130 TC = TI : GET A$ : IF A$ = ""THEN 130 140 TE = (INT((TC-TS)/6 + 0.5))/10 150 GOSUB 400 : PRINT T$ 160 END 400 REM CONVERTS SECONDS TO HH : MM : SS.S FORMAT 410 H1 = INT (((TE/60/60/24)-(INT(TE/60/60/24)))*24) 420 B1 = STR$(H1) 430 H$ = MID$(B$, 2, 2) : IF H1<10 THEN H$ = "0" + MID$(B$, 2, 1) 440 T3 = TE-(H1 * 60 * 24) 450 M1 = INT(((T3/60/60) - (INT(T3/60/60)))*60) 460 B$ = STR$(M1) 470 M$ = MID$(B$, 2, 2) : IF M1<10 THEN M$ = "0" + MID$(B$, 2, 1) 480 T2 = T3 - (M1*60) 490 S1 = INT(((T2/60)-(INT(TE/60)))*60) 500 B$ = STR$(S1) 510 S$ = MID$(BS, 2, 4) : IF S1<10 THEN S$ = "0" + MID$(B$, 2, 3) 520 T$ = H$ + ":" + M$ + ":" : RETURN
Line 130 converts TE to the elapsed time in seconds and rounds off to the nearest tenth. The subroutine starting at line 400 will convert this to "clock" display, complete with colons in HH:MM:SS.S format, down to tenths of seconds. A simpler approach would use TI$ by assigning to it the elapsed time value and immediately printing it:
140 TE = TC-TS 150 TI = TE : PRINT TI$ : END
You won't want to use this method if you are using TI as a real clock, or if you're relying on the time-keeper to track more than one period at once.
You can use the timekeeper for the scoreboard in a game, either by displaying stopwatch time or TI$, to show time passing, or by calculating time remaining and displaying a countdown timer. The followinng program is a version of the countdown timer.
100 PL = 5 : REM PERIOD LENGTH 5 MINUTES 110 PS = TI : REM PERIOD STARTS NOW 120 PF = PS + PL * 60 : REM PERIOD FINISH TIME 130 TR = PF-TI : REM TIME REMAINING 140 GOSUB 400 150 PRINT "[clr]" T$ 160 IF TI <PF THEN 130 170 END 400 REM CONVERTS SECONDS TO MM : SS FORMAT 440 T3 = INT (TR / 60 + 0.5) 450 M1 = INT(((T3 / 60 / 60) - (INT(T3 / 60 / 60))) * 60) 460 B$ = STR$(M1) 470 M$ = MID$(B$, 2, 2) : IF M1<10 THEN M$ = "0" + MID$(B$, 2, 1) 480 T2 = T3-(M1 * 60)490 S1 = INT(((T2/60) - (INT(TE/60))) * 60) 500 B$ = STR$(S1) 510 S$ = MID$(B$,2,2): IF S1<10 THEN S$ = "0" + MID$(B$,2,1) 520 T$ = MS + ":" + S$: RETURN
The most valuable feature of the timekeeper is the ability to control the length of pauses made during execution, independent of the program lines being executed. The friendly computer guide shows how to make delays by using a FOR ... NEXT loop with the statements:
FOR I = 1 TO 100 : NEXT I
The major problem with this method is that it ties up the whole program while you pause. You can insert program lines for execution during the loop, but then some guesswork and experimenting will be necessary every time you program to obtain the desired pause. Frequently you will have to compromise between the statements you want to execute and the time you can allot to the pause. Finally, if the lines executed during the pause contain the decisions with varying amounts of program to be executed based on the decision, the length of the pause becomes unpredictable.
Getting Control Over Pause
The timekeeper counts independently, on a steady basis, and allows you to assume control of the length of a pause, while permitting other parts of the program to continue. To do this you simply note the time the pause begins and add the desired pause length, giving the time the pause will end. An IF decision watches for the clock to exceed that end time, and you can run other parts of the program while the pause is in progress. The decision watching for the end of the pause must be made with a reasonable frequency, so the number of statements you can execute between repetitions of the end-time decision will depend on how long the pause is and how exact you want the measurement of the pause to be.
As a very conservative rule-of-thumb, allow twenty eighty-character (multiple statement) program lines to reach the end-time decision at an interval of about ten percent of the total pause length. For example, if I pause for about ten seconds, I can allow up to one second, or about twenty program lines. Similarly, a two-second pause will allow up to four program lines between repetitions of the end-time decision. You can use a greater number of lines if they do not contain several statements each.
These time estimates are very rough: do some experimenting yourself to find how many statements you can squeeze in and still get accurate control of the pause length. Once you have established some rules for yourself, they should be useful in all your programming.
As an example of the pause, let's say that I'm writing a game program in which we explore a dungeon. If someone casts a magic spell of darkness, then I want to give no visual clues for the length of the spell–say twenty seconds–while the action of the program continues. The following segment of a program will provide that effect:
100 DEF FN PS(T2) = TI + (T2 * 60) 350 REM THE SPELL IS CAST 360 GOSUB 900 : P1 = FN PS(20) 370 REM P1 = TIME TO END BLACKOUT 380 REM THE 390 REM PROGRAM 400 REM CONTINUES 410 REM RUNNING 420 REM WITH A 430 REM BLACK 440 REM SCREEN 490 REM (UP TO FORTY PROGRAM LINES) 775 IF TI>P1 THEN GOSUB 902 : GOTO 800 780 GOTO 380 800 END 900 POKE 36879,8 : FOR I = 38400 TO 38906 : POKE I,0 : NEXT I 905 RETURN : REM BLACKOUT MAKER 920 POKE 36879,78 : RETURN : REM BLACKOUT LIFTER
This application uses the function PS to relate the desired pause length (T2) to a future time value (P1) which defines the end of the blackout.
Another application of the pause timer can limit how often I may perform an action. I'm writing a game in which the player fires a laser cannon that takes five seconds to recharge before it can be fired again. The line which times the firing interval looks like this:
350 IF PEEK(197) = 35 AND TI>P1 THEN GOSUB 800 : P1 = TI + (5*60) 800 RETURN: REM VISUAL AND SOUND EFFECT FOR LASER FIRING
Here there is no need to worry about running the end-time decision within a set interval – the next time I want to fire the cannon, the logical AND in the decision checks to see if it has recharged. This pause method can also be used in an education program, to limit how soon the student may answer after a question appears, or may try a second time after an incorrect first answer has been entered.
If you're interested in converting existing programs to timekeeper pauses, the statement:
FOR I = 1 TO 100 : NEXT I
is worth about eight counts on the timekeepeer, or 0.13 seconds. There will be some difference between this statement and a longer loop. For instance, modifying the statement to:
FOR I = 1 TO 1000 : NEXT I
This is worth 72 counts, or 1.2 seconds, not the eighty counts one might expect. This is because of the "overhead" time needed to establish the loop during execution. There may even be differences between machines. You can check your own timing with this simple program:
10 BT = TI 20 FOR J = 1 TO 1000 : NEXT J 30 FT = TI 40 ET = FT - BT 50 PRINT ET, ET/60
This displays the time passed in both counts and seconds. Try varying the length of the loop in line 20 to get a general idea of what the "overhead" time is on your computer.
You need to do nothing to the timer to use it as a basis for pauses. However, if you have the VIC on for long periods, or if you set TI$ to keep clock time and run the program near midnight, be careful: if the pause starts before midnight and ends after, you may never reach the end of the pause, since the clock resets to 000000 at midnight. You can put in additional statements to watch for this problem and compensate for it; you can have the program reset the clock to 000000 before timing any pauses; or you can ignore the possibility and hope for the best. The third option, technically unsound as it is, requires the least effort and presents no great threat.
These pause techniques have two important features: controllable pause lengths and the ability to run other, unrelated parts of the program while the pause is in effect. When you develop a program, you can select a length of pause that will not change as you add, change, remove, or relocate program statements. The pause can also be lengthened or shortened to suit your needs, without major changes in the program itself. You have made your pause independent of the program that contains it. At the same time, you can execute lines of an unrelated portion of the program while the pause is in progress, making the program independent of the pause it executes. The timekeeper in the VIC gives the programmer much better control of realism in his game and simulation programs.