Optimized Systems Software
Last month we explored the possibilities inherent in the fact that Atari BASIC supports addressable DATA statements. This month we will tackle a related subject. It will be fairly obvious that the techniques presented in these two articles can help one write a fast yet complex adventure game on the Atari. However, it would be most interesting to me to see what other uses you readers make of these ideas, so start those cards and letters coming!
Nonexistent Subroutines... Or Having Fun While GOSUBing Nowhere
Purists should probably not read this section: we will plumb depths that advocates of structured programming would never sink to. Just as Atari BASIC allows the code RESTORE <expression >, we can also use GOTO <expression> and GOSUB <expression>. (Don't worry about the notation: <anything> just means that "anything" is an English language word instead of a reserved BASIC word.) Allowing GOTO and GOSUB to refer to arbitrary expressions instead of absolute line numbers is unusual in BASICs (and well nigh impossible in most other languages), so perhaps the power of this capability has never been fully realized. We will try to make a few inroads.
Before we get into the more exotic part of our discussion, let us note the most obvious advantage of "line number expressions": self-documenting code. How much more meaningful it is to be able to code GOSUB CALCULATEPAY instead of GOSUB 13250 !! Admittedly, with Atari BASIC one must first have written LET CALCULATEPAY = 13250 ; but that is a small price to pay for the added readability. Fair warning: there is one drawback to this trick. Atari BASIC allows only 128 different variable names. Normally that is a very big number, but naming every subroutine or section of code can eat up variables in a hurry. Be judicious in your choice of which routines are worth naming.
Now it is time for our main topic. And, thus, time for an example program. Study the example carefully before continuing with this text.
Lines 1000-1030 are simply set-up and initialization, one time operations. Lines 2000-2260 constitute the main loop of the program (in fact, in this simplistic example, this is an endless loop). First, the user is asked for a verb. If the INPUT verb matches one in the DATA list, it is assigned an appropriate verb number. The process is repeated for a noun. In an actual adventure game, the user would presumably be asked for "VERB NOUN" via a single INPUT; the program would then have to parse the request into the separate words.
At line 2200-2260 we exhibit the trick that this article is all about. To understand what happens, let us follow through what would happen if the user had requested "KISS BABY." "KISS" is verb number 2 and "BABY" is noun number 3, so at line 2220 we attempt to GOSUB to line 10000 + 2* 1000 + 3*10; that is we attempt to GOSUB 12030. Lo and behold, line 12030 causes the message "YOU HAVE JUST BEEN ELECTED MAYOR" to print out. When the routine RETURNs, we GOTO LOOP and do this all over again.
But wait: suppose the user had typed "LOOK BABY." That phrase evaluates to verb 1 and noun 3, so we try to GOSUB 11030 (10000+ 1*1000 + 3*10). But there is no line 11030! All is not lost: note that on line 2210 we first TRAPped to line 2250. The attempt to GOSUB 11030 will trigger the TRAP and we indeed will continue execution at line 2250. Here, we attempt to GOSUB to line 10000 + 1*1000, effectively ignoring the noun. We succeed in executing line 11000, the "default" routine for the verb "LOOK," and find that the computer sees "NOTHING SPECIAL" about this baby.
The power implicit here is perhaps not obvious. But consider how easy it is to add new verbs and nouns to this program. Consider how easy it is to provide for as many or as few special verb-noun combinations as you wish. And, finally, look how little code is used!
Note that this program expects there will be a routine for each valid verb (it's only sensible: why have a verb in the DATA if it doesn't do anything)? Another TRAP statement, at line 2250, could allow for omitted verbs. By the way, with the program written as it is, there is no way to get to line 2250 with the error TRAP system still active. Atari BASIC always resets any TRAP when it is triggered (this is so that you can't accidentally fall into endless TRAP loops).
The techniques discussed in this and prior articles have actually been used to write a "PICO-ADVENTURE." The most amazing aspect of the program is the speed with which it responds: it seems as fast or faster than even machine language adventures. Try it. Let us know about your efforts.
There follow two program lines that can be added to any existing Atari BASIC program and which, when executed, will make a program virtually unLISTable. The first line simply changes the names of all your variables to RETURN characters, and can be used with or without the second line. The second line actually produces a BASIC SAVEd program that can only be RUN — not LISTed, LOADed, etc.
Atari BASIC version:
32766 FOR I = PEEK(130) + 256*PEEK(131) TO PEEK(132) + 256*PEEK(133) : POKE 1, 155 : NEXT I 32767 POKE PEEK(138) + 256*PEEK(139) + 2, 0 : SAVE "<filename>" : NEWBASIC A + version:
32766 for i = dpeek(130) to dpeek(132) : poke i, 155 : next i 32767 poke dpeek(138) + 2, 0 : save "<filename>" : new
To use these gems, simply enter them and then type GOTO 32766. The line numbers are not important, but the second line must be the last line of the program. To use the resulting program, simply type RUN "<filename>". The program should not end with STOP or END; instead, it should exit via NEW (yes, "NEW" can be used from within a program). The <filename> may be "C:", but CLOAD will not work (you must use RUN"C:").
VARIABLE, VARIBLE, VARABLE
Perhaps one of the more common mistakes when using the long variable names allowed by Atari BASIC is to make a typo when entering the name (I tend to leave off the plural, "s"). How to know you have committed this sin? Try the following program segment:
Using Atari BASIC:
32700 I = 0: FOR J = PEEK(130) + 256*PEEK(131) TO PEEK(132) + 256*PEEK(133)-1 32710 IF PEEK(J) < 128 THEN PRINT CHR$(PEEK(J)); : GOTO 32730 32720 PRINT CHR$(PEEK(J)-128) : I = I + 1 32730 NEXT J : PRINT : PRINT I ; " VARIABLES IN USE" : STOP
Using BASIC A + :
32700 i = 0 : for J = dpeek(130) to dpeek(132 + 1 : print chr$(peek(j) & 127) ; 32710 if peek(j) > 128 : print: i = i+ 1 : endif : nextj 32720 print: print i ; " variables in use" : stop
It would be advisable to LIST this program segment to DISK or CASSETTE and then ENTER it into any program that needs it. To use, simply type GOTO 32700 ; all your variables will be listed and a count displayed. Obviously, this output could be sent to the printer by first OPEN #7, 8, 0, "P:" and then replacing all the PRINTs with PRINT #7. If you do this, it is advisable to CLOSE #7 before the STOP.
1000 REM ***** SET UP ******** 1010 DIM VERB$(4), NOUN$(4), TEST$(4) 1020 VERBDATA = 9000 : NOUNDATA = 9100 1030 LOOP = 2000 2000 REM ***** MAIN LOOP ****** 2010 PRINT "GIVE ME A VERB "; : INPUT VERB$ 2020 RESTORE VERBDATA : VERB = 0 2030 FOR CNT = 1 TO 3 : REM CHANGE TO MATCH DATA 2040 READ TEST$ 2050 IF TEST$ = VERB$ THEN VERB = CNT : CNT = 99 2060 NEXT CNT 2070 IF NOT VERB THEN PRINT "INVALID VERB" :. GOTO LOOP 2100 REM VERB DONE, DO NOUN 2110 PRINT "GIVE ME A NOUN "; : INPUT NOUN$ 2120 RESTORE NOUNDATA : NOUN = 0 2130 FOR CNT = 1 TO 3 : REM CHANGE TO MATCH DATA 2140 READ TEST$ 2150 IF TEST$ = NOUN$ THEN NOUN = CNT : CNT = 99 2160 NEXT CNT 2170 IF NOT NOUN THEN PRINT "INVALID NOUN" : GOTO LOOP 2200 REM ***** THE TRICKY STUFF ***** 2210 TRAP 2250 2220 GOSUB 10000 + VERB*1000 + NOUN*10 2230 GOTO LOOP 2240 REM WE GET TO 2250 ONLY ON TRAP 2250 GOSUB 10000 + VERB*1000 2260 GOTO LOOP 9000 REM A LIST OF ALL VERBS 9010 DATA LOOK, KISS, DROP 9100 REM A LIST OF ALL NOUNS 9110 DATA ROOM, BEAR, BABY 10000 REM *************************** 10010 REM * THE VERB-NOUN ACTION * 10020 REM *************************** 11000 REM >>> LOOK <<< 11001 PRINT "I SEE NOTHING SPECIAL" : RETURN 11010 PRINT "I SEE A WINDOW AND A DOOR" : RETURN 12000 REM >>> KISS <<< 12001 PRINT "THAT'S SILLY...BUT SMACK" : RETURN 12020 PRINT "BEAR BITES OFF YOUR LIPS" : RETURN 12030 PRINT "YOU HAVE JUST BEEN ELECTED MAYOR" : RETURN 13000 REM >>> DROP <<< 13001 PRINT "HOW? I COULDN'T HAVE LIFTED THAT." : RETURN 13030 PRINT "IT'S A BOUNCING BABY BOY!!" : RETURN