[ Go to Stephen's Entry Page ]

[ TI99/4a Articles index | TI Book front page |    TI Resources Page |   PC99 Programs ||    TI*MES 29 ||   TI*MES 31 ]
contact - please use subject="pc99 page" to avoid spam trap!

Jump to:
Console Only Corner- Error proofing
Rambles:
Unlistable basic programs and making them listable
Reviews: Tris module   ||   Balloon Wars   ||   TI Base Version 3
Chaotic Bifurcations as Sound
Graphics: Gingerbread Men   ||   Connett Circles

This web page contains the text of articles for owners of the TI-99/4a from Issue 30 of TI*MES. It is of use to users of the TI-99/4a emulators.

Items from TI*MES Issue 30, Autumn 1990

Basic programming: Error Proofing

CONSOLE ONLY CORNER by PETER WALKER

When writing a program it is always worth remembering that it may be used by others and they night not necessarily react in the same way as you, the programmer. It is therefore worthwhile taking a few precautionary steps and employing what is generally called defensive programming.

Firstly do document your program so that others know what it does and how to operate it. Better still, try to provide on-screen help and, if this is long, make its viewing an option at the beginning of the program.

Secondly, make sure that as far as possible any run-time errors are trapped so that the program doesn't crash or emit 'warnings'. I'm not here talking about programming errors - we must assume that these have been eliminated. What is less often thought about is that the program operator will, when his/her input is requested, react in a way that you hadn't predicted.

A simple example is would be where an input is required and the screen displays: "Input number". If the next statement is an INPUT or ACCEPT AT then what is clearly missing is the phrase ".. and then press <ENTER>". You mustn't assume that everyone knows to do this and in any event it is often the case that a single digit number is input by a CALL KEY which doesn't require the use of the Enter key.

Another problem arises with the following 300 PRINT "Press Yes or No"
What is the operator supposed to do?
Enter "Yes" or press "Y"
(or the negative opption)'? Now look at this:
300 PRINT "Continue? Press Y/N "
310 CALL KEY(5,K,V)
320 IF V-1 THEN 310
330 IF K=89 THEN 500 ELSE 600
The problem here is that, if the alpha lock key was up, then pressing "Y" will set K to 121 not 89 and so the wrong branch will be taken. One way round this would be:
330 IF (K=89) + (K=121) THEN 500 ELSE 600
(ExBas owners could use IF K=89 OR K=121 THEN...)
However, a far neater way is to use
310 CALL KEY(3,K,V)
Key unit 3 interprets all key presses as upper case, so only K=89 needs to be tested.

If the operator is asked to pick from a menu of, say, 4 items by pressing 1, 2, 3 or 4 then you might program this as follows:
400 CALL KEY (3,K,V)
410 IF V-1 THEN 400
420 IF (K<49)+(K>52) THEN 400
430 ON K-48 GOTO 500,600,700,800
The check in line 420 ensures that no key press other than 1-4 will be allowed to proceed to 430 where any other value would cause an error. Another approach to input checking is as follows:
400 CALL KEY (3, K, V)
410 IF V-1 THEN 400
420 ON POS("1234",CHR$(K),1)+1 GOTO 400,500,600,700,800
This method is neater and also works well when the menu options are not numbers but letters eg A,B,C,D (Use ON POS("ABCD"..) and is the only sensible checking method when the letters are not alphabetically consecutive such as C,R,Q (As in Continue, Return or Quit).

A different sort of problem arises when you use numbers on a menu but the choices exceed 9. One cannot then use CALL KEY, but rather INPUT or ACCEPT AT must be used to input a one or two digit number. Error trapping could be implemented as follows:
400 INPUT "Input No then press <Enter> ":N
410 IF (N<1)+(N>12) then 400
This is not as neat as the CALL KEY trap since input of, say, 13 will cause the Input line to be repeated. A further problem is caused if the operator inputs a string instead of a number. This causes a 'honk' and a STRING-NUMBER MISMATCH warning which won't stop the program but is not a friendly way of trapping the problem. (As you can see we are talking about "Idiot Proofing" here!).

For those with ExBas, a better way of trapping unwanted key presses is through the use of validating with ACCEPT AT. For example:
400 DISPLAY AT (1,1) : " Input No then press Enter"
410 ACCEPT AT(1,29)VALIDATE(NUMERIC):N

This ensures that only numbers can be input. But there is still one last possibility of error. If the operator presses <Enter> before inputting anything, a string-number mismatch will again occur since the numeric N cannot be equal to "". If you want to trap against this, you can use the following:
410 ACCEPT AT(1,29)VALIDATE(NUMERIC) :NS
420 IF N$="" THEN 410 ELSE N=VAL(N$)

It is not just when inputting choices that errors can occur. It is always worth ensuring that 'divide by zero' errors are trapped:-
200 IF N=0 THEN 500
210 X=Y/N

Less well known is an error that can occur with the ASC function This, as you will recall, returns the ASCII value of the first character of a string eg:
200 N=ASC(N$)

If N$ is "ALFIE" then N becomes 65. But if N$ is the nul string "" then the ASC function will cause the program to crash, so it worth trapping as follows:
190 IF N$= "" THEN N= -1 :: GOTO 210
200 N=ASC(N$)

If you do have ExBas then you have another powerful method of error trapping through the statements ON ERROR, RETURN and CALL ERR. However, I have seen even experienced programmers use these incorrectly so it is worth explaining in detail how they should be used.

The statement ON ERROR 1000 sets up a condition such that any subsequent error, instead of halting the program, will cause the program to branch to a subroutine at line 1000. There are two important facts to remember. Firstly, an error branch is a subroutine, that is, it is like GOSUB rather than GOTO. Such a branch must return to the calling program via a RETURN statement. As we will see however, there are 3 forms of RETURN used with ON ERRO R instead of the single version used with GOSUB. Secondly, the branch condition to line 1000 remains valid until either:
another ON ERROR is executed
or
an error causes the branching to the specified line

In other words, ON ERROR sets up a 'pending' condition that is cancelled once used. So, if you want to continue error trapping after an error has occurred, you must execute another ON ERROR.

However, you are not forced to do this, contrary to what one might glean from many of the manuals.

Nor is it necessarily the best idea to rely on resetting the ON ERROR condition within the error subroutine itself, since the target routine might depend on where the subroutine returns to.

Lets look at the structure of a typical error trap:
100 ON ERROR 1000
110 REM YOUR PROGRAM
120 A=B/C
130 PRINT A
140 GOTO 140

1000 PRINT "DIVIDE BY ZERO!"
1010 A=X
1020 RETURN NEXT

At line 100 we protect against division by zero at line 120. When C=0 an error occurs and the program branches to line 1000 and a default for A is set. RETURN NEXT causes the routine to return to line 130, the next line after the one where the error occurred. There are 3 types of RETURN:-
RETURN - this returns to re-execute the line where the error occurred
RETURN NEXT - this returns to the line after that which caused the error.
RETURN XXX - this returns to a specific line XXX.

Now the important issue is this: given that the error subroutine must return in an appropriate way for the error that occurred, you mustn't expect to use one subroutine that can recover from all the errors that night happen. So you may want to have specific subroutines protecting specific lines where errors might arise. For example:-
100 ON ERROR 1000
110 REM 1000 recovers errors from here forward.

200 ON ERROR 1500
210 REM 1500 recovers from here onwards

To clarify the issue of when the three types of RETURN should be used, study the following:-
100 ON ERROR 1000
110 INPUT "N? ":N
120 PRINT "N SQUARED IS ";N*N
140 PRINT "SQUARE ROOT N IS ";SQR(N)
150 PRINT "LOG N IS ";LOG(N)
160 GOTO 110

1000 ON ERROR 1000
1010 RETURN NEXT

In this example if the value of N causes any error to occur, the faulty line is passed over. Try with N= -1.
RETURN alone is used here:

100 ON ERROR 1000
110 INPUT "FILE? ": F$
120 OPEN #1:F$, INPUT
130 .....

1000 PRINT "CAN'T OPEN FILE ";F$
1010 INPUT "TRY AGAIN ":F$
1020 ON ERROR 1000
1030 RETURN

If the file can't be opened at line 120, then the filename is requested again at line 1010, control is returned to line 120.
Finally, RETURN XXX might be used as follows
100 ON ERROR 1000
110 REM START OF THE PROGRAM
120 ...

1000 PRINT "An error has occurred - program will restart"
1010 RETURN 100

Notice here that the error trap is reset outside the subroutine.


Go to top of page

So, we have seen that the error recovery subroutine needs to be compatible with the error that called it. There is another way around this problem, which involves the use of CALL ERR. This routine returns the error type and line number where the error occurred. It is then possible to return in a manner appropriate to the error. For example:-
100 ON ERROR 1000
110 REM SEE NOTES BELOW

1000 CALL ERR(A,B,C,D)
! D IS LINE NUMBER
1010 IF D=210 THEN X=2 :: RETURN 200
1020 IF D=300 THEN PRINT "WRONG!" :: ON ERROR 1200 :: RETURN
1030 IF D=500 THEN RESTORE #1 :: RETURN NEXT
1040 PRINT "ERROR ";A;" AT LINE ";D :: STOP

In this example, the right RETURN is provided for errors occurring at lines 210, 300 & 500. Other errors cause a printout and stop. An important point to note is that the line numbers 210, 300 and 500 in lines 1010, 1020 and 1030 are not affected by a RESEQUENCE, so either avoid resequencing or remember to adjust these line nunbers yourself after a resequence.

Well, I hope that explains how to remove or contain errors in your programs.

Peter Walker

Rambles by Stephen Shaw

Unlistable Basic Programs and fixing them

Did you ever want to make an Extended Basic program unlistable and unalterable but still playable and saveable?` This is a bit of fun programming to see how to exploit the way our computer stores its programs.

Original programs from Tidewater Newsletter, Ken Woodcock - I took them from WORDPLAY June 1990:

Your computer stores all the basic program lines in one block; and then in a separate block, a line number table, which identifies where in memory each line number can be found- the line number table is in numeric order, the actual program lines are stored in the order you enter them, which may not be in numeric order!

To make a program unlistable all you need do is change all the length bytes in the LNT! The following program will set all length bytes to zero and requires XB and 32k ram.

First load your program. If it uses lines 1 and 2, resequence it!

Then MERGE in (or type in) the following code:
1 CALL. INIT :: CALL PEEK(-31952,A,B,C,D) :: SL=C*256+D-65539 :: EL=A*256+B-65536 :: FOR X=SL TO EL STEP -4 :: CAL L PEEK(X,E,F,G,H) :: ADD=G*256+H-65536 :: PRINT E*256+F
2 CALL LOAD(ADD-1,0) :: NEXT X :: STOP :: !@P-

[ TISHUG has a slightly modified format of this listing as follows:
1 CALL INIT :: CALL PEEK(-31952,A,B,C,D) :: SL=C*256+D-65539 :: EL=A*256+B-65536 :: FOR X=SL TO EL STEP -4 :: CA LL PEEK(X+2,G,H) :: CALL LOAD(G*256+H-65537,0) :: NEXT X ]


What is it doing?
First PEEK four bytes from -31952 for the top and bottom addresses of the line number table. (Line 1, obtain SL and EL).

Then step through these -four bytes at a time- the first two bytes (E,F) are the line number, then the next two which we are going to use (G,H) are the address at which the program line starts. We look at the address where the program line starts LESS ONE (-65537 instead of -65536) because this initial byte is the actual line length. It is NOT used to execute a line, which is in tokenised form, but it is required when we LIST a program as the tokens have to be “undone" and it is easier to put a length indicator in than install a more intelligent untokeniser.

The first byte of the memory address which the Number Table points to tells the computer how long the line is in bytes- this is only used to list the program or to handle editing functions. When a program is running, a zero value byte terminates each program line and the LNT is only used to locate the start of the program line. So we are able to make a program unlistable and uneditable without preventing it running!

Now RUN the amended program.
Now in command mode type in 1 [enter], 2 [enter] to remove the extra lines.
Now save using a different file name if to disk or a different tape if to cassette!!!

And you can now RUN the program, no trouble- try it. BUT... what happens if you try typing EDIT 100 or LIST

What... you saved the amended program over your original? Not to worry you can get it back.

We could alter all the length bytes to the maximum possible, which would certainly allow the program to be LISTed again (change the value 0 in line 2 above to 255),
1 CALL INIT :: CALL PEEK(-31952,A,B,C,D) :: SL=C*256+D-65539 :: EL=A*256+B-65536 :: FOR X=SL TO EL STEP -4 :: CALL PEEK(X+2,G,H) :: CALL LOAD(G*256+H-65537,255) :: NEXT X

but editing could still be problematical, so lets reset the length bytes to what they should be!

Looking for a zero byte is a start, but not the end, as a zero byte may also occur in certain cases in the program line. The simplest thing to do is to look for a zero byte, then look at the line number table to see if the value obtained is an actual start of a program line. So lets do it...

First load the corrupted program- you cannot list it to see if it uses lines 1 to 6, so RESequence it for safety! then MERGE in the following lines:
1 CALL INIT :: CALL PEEK(-31952,A,B,C,D) :: SL=C*256+D-65539 :: EL=A*256+B-65536 :: FOR X=SL TO EL STEP -4 :: CALL PEEK(X,E,F,G,H) :: ADD=G*256+H-65536 :: PRINT E*256+F
2 I=1 :: CALL PEEK(ADD-1,V) :: IF V THEN 6
3 CALL PEEK(ADD+I,V,W) :: IF V THEN I=I+1 :: GOTO 3
4 FOR Y= SL TO EL STEP -4 :: CALL PEEK(Y,E,E,E,F) :: IF E*256+F-65536=ADD+I+2 OR =0 OR ADD+I>-3 THEN CALL LOAD(ADD-1,I+1):: GO TO 6
5 NEXT Y :: I=I+1 :: GOTO 3
6 NEXT X :: STOP :: !@P-

Some of those lines are a little long by the way- when your console honks at you to Say it won't take any more characters THANK YOU, you press ENTER, then bring the line back with EDIT N or N (FCTN X), move the cursor to the end of the line and carry on typing.

Run this amended program- it takes a little longer this time!!! and when it has finished your program is uncorrupted, lines 1 to 6 can be removed and the program saved, listable and editable.

Too slow? From an anonymous author, a machine code fix! This comes from the Sydney News Digest of July 1990 and requires assembly WITHOUT the C option:


* This program re-enters the
* line length values within
* a Basic program.
*
* For use when a program is
* corrupted or protected by
* having false line length
* values.
*
      DEF  UNFIX
      AORG >2500
UNFIX LWPI  USRWS
      MOV  @>8330,R1
      MOV  @>8332,R2
      C    R1,R2
      JHE  FIN
*
      INC  R2
      S    R1,R2
      SRL  R2,1
      CI   R2,BUFMAX
      JGT  FIN
*
      SRL  R2,1
      MOV  R2,@BUFLEN
      INCT R1
      LI   R4,BUFF
*
LOOP  MOVB *R1+,*R4+
      MOVB *R1+,*R4
      DEC  R4
      DEC  *R4
      INCT R4
      INCT R1
      DEC  R2
      JNE  LOOP
      LI   R5,>FFE8
AGAIN MOV @BUFLEN,R3
      LI   R2,ZER0
      LI   R1,BUFF
      DECT R1
*
NEXT  INCT R1
      C    *R1,*R2
      JL   SKIP
      MOV  R1,R2
SKIP  DEC  R3
      JNE  NEXT
*
      MOV  *R2,R6
      MOV  *R1,*R2
      S    R6,R5
      DEC  R5
      SWPB R5
      MOVB R5,*R6
      MOV  R6,R5
      DEC  @BUFLEN
      JNE  AGAIN
*
FIN   LWPI >83E0
      CLR  R0
      MOVB R0,@>837C
      RT
*
USRWS  BSS  32
BUFLEN BSS  2
ZERO   DATA >0000
BUFMAX EQU  >1800
BUFF   BSS  BUFMAX
*
      END


Load that offensive Basic program that causes your computer to lock-up when you try to list it. Then type CALL INIT :: CALL LOAD("DSK1.OBJECT") [ENTER]
Next execute the machine code routine by typing
CALL LINK("UNFIX") [ENTER]

When the cursor re-appears on the screen you will be able to LIST or edit the program.
Check the start of the program to see if those few lines listed earlier are still there.
If so, then delete these lines before you re-save the program otherwise it will reset line length values each time you run the program.

A great deal faster! This machine code is for loading with ExBas ONLY! Why do we need to have the length right for editting? 'Cos if the length byte is 255 bytes, when you delete (or edit) the lines, the computer will delete 255 bytes, and if the line is shorter, you are going to lose other data, probably partial lines, which will be very sad!


Go to top of page

Review: Tris module

MODULE REVIEW: TRIS- Famous Game! Asgard Software, 1939. Main author Jim Reis

who cannot know this game? The TI module is a perfectly good emulation, in which tetromino shapes fall, can be rotated and dropped, and must be formed into a solid horizontal line. You score by how quickly you line the shapes up and drop them into place. Game ends when the shapes stack up to the top due to your having left empty spaces in horizontal lines. This is very similar to the Hulpke disk version, except that you may not amend the "next shape" (which is optionally displayed in the module version) and the module version benefits by automatically increasing in speed as play progresses- Hulpkes version plays constantly at the same level.

A good module for unexpanded owners who cannot play the Hulpke disk version, this is also a good module ior all games players Music and sound are credited to Ken Gilliland, Bruce Harrison, Jim Reiss. HOW GOOD IS IT? My wife -Cathy- has not played a module game before- she is hooked on this one, belting the living daylights out of a joystick and not infrequent *expletive deleted*s! My six year old doesn't get too high a score at the easiest level but still likes a go. I play it. Look- if you ever play games this is one tor you. If you have never played a game on your TI, this is still one ior you. Buy it. OK!
Recommended

Review- Disk- Balloon Wars

GAME REVIEW- DISK- BALLOON WARS- 1985. ASGARD SOFTWARE Written by John Morrison.

Quite an old game this one, I've seen it advertised but not reviewed. Its an ExBas game, but uses disk: data files, so is not suitable for tape.

My disk was quite unusual, having a disk jacket a trifle floppier than the disk inside it. I was surprised when my drive read it (and copied it onto a more substantial disk!). The program is list protected but most disk owners know what to do about that!

Your task is to pilot a hot air balloon from screen left to right across several screens, using fuel as you go! There are enemy troops below with a nasty habit of firing at you- you may avoid their shots and you may bomb them. On some screens, after removing the enemy, you may land for repairs and restocking.

The documentation is in error- it is joystick down to release a bomb, and joystick up to drop a sandbag (not vice versa).

This program is not exactly sophisticated., and the graphics are very simple, but it is an interesting game which presents some simple strategic decisions to be made. If you like a game to can get into quickly, you'll like this one.

Review- Disk- TI Base Vn 3

UTILITY REVIEW- TI BASE VERSION THREE- DISK. TEXAMENTS. US$25+ $9 p and p.

We have already covered earlier versions, this one is fully compatible with earlier one but spreads its wings in many new directions!

Of particular interest is the ability to place command files into VDP memory them as memory images and save and load them as memory images - for really rapid operation, more so if you are using a ram disk for the actual data! There is also the availability of using this option to create macros- making up your own commands from the ones supplied.

The report generator is possibly misnamed, it is a database of command files, and while it can certainly be used to produce screen and printed formats, it is not limited to that use. The major limit would be the number of commands you can fit into the database, but with the macro facility already indicated above, this additional facility gives the programmer considerable power to hide TI Base behind a fast and friendly user interface- the user need never know TI base is in use, apart from the loading screen!

Inverse characters are available, but the documentation is incomplete. You can turn all text inverse with the command SET INVERSE ON but you may also mix normal and inverse text by leaving SET INVERSE OFF and inserting characters with ASCII codes 128 higher than usua1 - eg an inverse A is character 193 instead of 65. These characters are used on Epson printers as the italic set. You cannot enter a character 193 into a command fle using TI Writer, you must use the TI Base editor which has another new option, familiar to PC users as the ALT key: hold down CTRL and key in the three numbers of the character you want (eg 193) and that character will appear on screen.

(As the TI Base editor is limited to small command files, we have in the disk library a utility to merge such command files into longer ones). Of course, as a program/language becomes more powerfu1 it tends to become harder to use, and indeed, to become underused as few bother, but TI Base can be used without command fi1es at all, for the early owner, leading upwards to a very professional database presentation for the more experienced programmer who wishes to impress (very often it's the experienced programmers who leave the stitches showing!).

If you want a database, this is the one! Can work with one SSSD disk drive, works better with two disk drives, and is happy with ram disks or hard drives.

Graphics for The Missing Link

gingerbread man - simplest form An issue of Rambles without fractal programs would be amiss, so here are are some fractal programs for you.

There is a program which gives an audio representation of bifurcation.

Then we have a program which draws the little character alongside - take a look at him and work out a program to draw him! Can't do it? Look at the listing. Odd! Variables X & Y can take almost any value to start - high initial values mean you need a lower multiplier for A & B.

And finally, a program that draws wallpaper, composed entirely of circles- start with a SIDE of say 20 and gradually work up to say 2000. The pattern will recede and different detail will become apparent.


Go to top of page

Bifurcated graphics realised as sounds

From an article BIFURCATED SOUNDS by Leon Heller in Issue 10 of FRACTAL REPORT. Based on a book by Becker and Dorfler regarding presentation of values generated by a Feigenbaun type system... what the heck, a DIFFERENT way of producing random sequences of tunes instead of cycling round CALL SOUND(-100,1l0+RND*1000,4)
100 ON WARNING STOP
120 RANDOMIZE
140 CALL CLEAR :: PRINT "ANY KEY FOR NEW PATTERN" : :
160 X=RND :: PRINT "X =";X
180 R=3.2+RND :: PRINT "G FACT =";R: "IF >3.57 PATTERN IS CHAOTIC"
200 FT=1000
220 FB=111
240 EF=360+230*RND
260 PRINT "EF= ";EF
280 D=70+RND*100
300 PRINT
320 X=R*X*(1-X) :: N=EF*X :: IF N>550 OR N<56 THEN 140 ELSE CALL SOUND(-D,N*2,6*RND)
340 CALL KEY(0,A,B) :: IF B=1 THEN 140 ELSE 320
360 END

Also from Issue 10 of Fractal Report, by Tom Marlow, based on R Denaney, this requires THE MISSING LINK as it stands but readily converted to any utillity allowing bit map graphics
90 ! GINGERBREAD MAN 1 four gingerbread men
100 CALL LINK("CLEAR")
110 ! H=240 :: V=180
120 X=8.56
130 Y=3.76
140 FOR L=1 TO 5000
150 NX=1-Y+ABS(X):: NY=X :: X=NX :: Y=NY
160 A=100+X*8-Y*8
170 B=70+X*8+Y*8
180 CALL LINK("PIXEL",A,B)
190 NEXT L
200 CALL LINK("PRINT",180,180,"END")
210 GOTO 210

OR TRY:

90 ! GINGERBREAD MAN 2
100 CALL LINK("CLEAR")
110 ! H=240 :: V=180
120 X=9*RND
130 Y=X+2*RND-2*RND
140 FOR L=1 TO 3300
150 NX=1-Y+ABS(X):: NY=X :: X=NX :: Y=NY
160 A=100+X*7-Y*7
170 B=70+X*7+Y*7
180 CALL LINK("PIXEL",A,B)
190 NEXT L
200 CALL LINK("PRINT",180,180,"END")
210 GOTO 210

ALSO

90 ! GINGERBREAD MAN 3
100 CALL LINK("CLEAR")
140 X= -0.100000000001 ! needs the 10x 0's and final 1!
150 Y=0
180 FOR L=1 T0 5299
190 NX=1-Y+ABS(X):: NY=X :: X=NX :: Y=NY
200 A=100+X*14-Y*14
210 B=60+X*14+Y*14
220 CALL LINK("PIXEL",A,B)
230 NEXT L
240 CALL LINK("PRlNT",180,180,"END")
260 GOTO 260

And finally a little wallpaper

100 REM CIRCLES
110 REM JE CONNETT/PWH MOON/S SHAW 1990
120 SIDE=20 ! anything from 10 to 2000 but avoid multiples of 150
130 REM
140 CALL LINK("CLEAR")
150 FOR I=l TO 150 :: FOR J=1 T0 150
160 X=I*SIDE/150 :: Y=J*SIDE/150 :: C=INT(X*X+Y*Y) :: D=C/2 :: IF D-INT(D)>0.1 THEN 180
170 CALL LINK("PIXEL",I+20,J+20)
180 NEXT J :: NEXT I
190 PIC=PIC+1 :: A$="DSK2."&STR$(PIC)
199 REM OMIT LINE 200 IF YOU DON'T WANT TO SAVE
200 CALL LINK("SAVEP",A$)
210 SIDE=SIDE*1.2 :: GOTO 140
220 END


Go to top of page

[ TI Book front page | TI Resources Page | PC99 Review | PC99 Programs ]