COMPUTE! ISSUE 80 / JANUARY 1987 / PAGE 92

INSIGHT: Atari

Bill Wilkinson

Controlling Keyboard Input

One of the most powerful features of the BASIC language is the INPUT statement. Consider: This single statement allows you to ask the user for numbers or strings and allows the user to do full-screen editing as he or she enters data. Yet all this power has its price. The INPUT statement is extremely vulnerable and can easily cause programs to crash.

For example, if your program is expecting a number and the user types a string—KABLOOEY. Admittedly, you can (and should) TRAP this kind of error. But what about the user who delights in using the cursor-control keys to move all over the screen? Or the one who hits the CLEAR key after your program has gone to the trouble to put 20 lines of information on the screen? For a truly professional-looking program, you probably want the capability to restrict data entry to only those characters which you are expecting. And the INPUT statement just won’t work for this.

BASIC Has The Answer

Fear not; good old BASIC has another answer. Most BASICs provide a way to get a single key from the keyboard, and Atari BASIC is no exception. You simply OPEN the K: device on some channel, and then GET characters (actually, bytes) one at a time from that channel. Since the characters you get this way are not even echoed on the screen, you have a chance to filter the user’s keystrokes and ignore or alter those you don’t want. For example, if you want only digits (for a numeric input), you could ignore all non-numeric characters.

Sidelight: This type of problem is not unique to BASIC programmers. Any programmer using a language which accepts input from a screen editor as is built into the Atari eight-bit machines will have to decide whether to go to the trouble to use methods similar to those I am about to describe.

The program here is an example which will help familiarize you with restricted keyboard input. Let’s take a closer look at some of its inner workings.

First, because we’re using Atari BASIC, I have violated my own rules and placed the major subroutine near the start of the program, with the mainline code following. I did this because this major subroutine is very speed-critical, and every little thing that can be done to make it run faster is a help. Anyway, line 1030 immediately sends us to the main code, which starts at line 1400. Notice the OPEN of the keyboard device in line 1430 and the allocation of some strings in line 1440.

Then, after clearing the screen, we begin the main loop of this little example. In line 1500 we simply READ from some DATA statements to find the position, size, and type of a field on the screen which is to receive our ministrations. We have two special cases to take care of here: First, if the type code is an asterisk, we have exhausted our DATA. (In a larger, real-world program, this would probably indicate that it’s time to save the contents of our various fields to disk, and so on.) Second, if the length code is zero, the string which is the last item in each DATA statement is actually information to be displayed on the screen, so this particular DATA statement does not cause any input processing (see lines 1530 and 1540).

Assuming that we do have a field which requires formatted input processing, line 1520 causes the main subroutine at line 1100 to be called. This subroutine (in line 1100) displays a line of dots on the screen which is intended to tell the user the maximum size of the data he or she is supposed to input. Line 1110 is a bit of a trick: Since Atari BASIC does not allow us to PUT to channel 0 (the screen), we cheat and use nonexistent channel 16, which just happens to be translated by BASIC and the OS into (you guessed it) channel 0. And the two PUTs cause a cursor-right and then cursor-left movement to take place. (We do this because the POSITION statement does not actually move the cursor—the OS waits for a subsequent character output before moving it. These cursor movements get the cursor to the right location without actually changing the display.)

Special Cases

The CNT variable simply counts the characters we have passed so far. It can never be less than zero or more than the maximum length of the field we are currently working on. Then, within the loop, we get a single keystroke. Lines 1150 and 1160 combine to cause either an ESCape key or a RETURN key to force an exit from our formatted input routine. And line 1170 takes care of the special case of the backspace character (check any Atari BASIC or OS reference book to see which ATASCII codes normally perform various editing functions).

Finally, in line 1180 (where we convert lowercase letters into uppercase ones—certainly an optional process), we begin to start our testing. This rather simplistic example program provides for only three types of fields: All alphabetic fields (designated by an A type code), all numeric fields (designated by an N type code), and “everything goes” fields (designated by the E type code). Lines 1190 and 1200 validate the A and N types, respectively.

Note that we also restrict the number of characters to the maximum (line 1210—and PUT #16,253 simply sounds the bell on your Atari). If all is going well, we add the character the user typed to our collected field (line 1230) and go back for another character. Lines 1250–1290 are used to handle a backspace key. You should play with the PUTs a bit to figure out what they are doing. I will mention, however, that line 1260 serves to reduce the size of FIELD$ by a character.

And that’s about it. You’re welcome to type in this program and try it, but don’t expect it to do much. It is intended solely to get you started in using formatted input, so it doesn’t demonstrate what you can do with this nice formatted data once you have gotten it. (For example, once you have gotten a numeric-only field entered into FIELD$, how do you convert it to a number? Take a look at the VAL function.)

Also, the very simplistic nature of my three field types (A, N, and E) means that some desirable features are missing. For example, try typing in a name containing a space for that first NAME field. Or try entering a decimal point as part of the ZIP CODE. To be really flexible, this program should handle a dozen or so different data-entry formats. But now the sad truth comes out.

Atari BASIC is just too slow to do anything really fancy in the formatted entry subroutine. I have some much more exotic versions of this program written in BASIC XL and BASIC XE, but with Atari BASIC they tend to bog down way too soon. Still, for a particular program you should be able to develop four or five different types to be handled, and still maintain reasonable speed. And that is probably adequate.

So play with this program and these concepts; improve it and add features. I’ll even give you a few hints on directions to take. For example, what happens when you add this line?

1165 IF KEY=125 THEN 1100

Or how about “normalizing” a numeric input which includes a possible decimal point? For example, suppose you have a dollars-and-cents field. What should your program do if the user enters just dollars, with no decimal point and no cents? Or suppose the user enters three or more digits after the decimal point—what should you do?

Formatted Screen Data Entry

1000 REM
1010 REM PROGRAM TO SHOW PROTECTED INPUT
1020 REM
1030 GOTO 1400:REM (TO MAKE SUBROUTINES FASTER)
1040 REM
1050 REM MAIN SUBROUTINE:
1060 REM .  X,Y ARE SCREEN POSITION OF FIELD
1070 REM .  L IS MAXIMUM LENGTH OF FIELD
1080 REM .  TYPE$ IS ONE CHARACTER FIELD TYPE CODE
1090 REM
1100 POSITION X,Y:PRINT FILL$(1,L);
1110 POSITION X,Y:PUT #16,31:PUT #16,30
1120 CNT=0:FIELD$=""
1130 REM MAJOR LOOP
1140 GET #1,KEY
1150 IF KEY>127 THEN KEY=KEY-128
1160 IF KEY=27 THEN RETURN
1170 IF KEY=126 THEN 1250
1180 IF KEY>96 THEN KEY=KEY-32:REM (LOWER CASE GOES TO UPPER)
1190 IF TYPE$="A" THEN IF KEY<65 OR KEY>90 THEN PUT #16,253:GOTO 1130
1200 IF TYPE$="N" THEN IF KEY<48 OR KEY>57 THEN PUT #16,253:GOTO 1130
1210 IF CNT>=L THEN PUT #16,253:GOTO 1130
1220 PUT #16,KEY
1230 CNT=CNT+1:FIELD$(CNT)=CHR$(KEY)
1240 GOTO 1130
1250 IF CNT=0 THEN PUT #16,253:GOTO 1130
1260 FIELD$(CNT)=""
1270 PUT #16,KEY:CNT=CNT-1
1280 PUT #16,46:PUT #16,30
1290 GOTO 1130
1300 REM
1310 REM THE MAIN CODE
1320 REM
1330 REM IN THIS SAMPLE PROGRAM, WE DEFINE THE FIELDS
1340 REM VIA DATA STATEMENTS
1350 REM
1360 REM IN A MORE COMPLEX PROGRAM, THE INFO MIGHT COME
1370 REM FROM A FILE
1380 REM
1390 REM
1400 REM
1410 REM === INITIALIZATION ===
1420 REM
1430 OPEN #1,4,0,"K:"
1440 DIM FIELD$(40),TYPE$(1),FILL$(40)
1450 FILL$="........................................"
1460 GRAPHICS 0
1470 REM
1480 REM NOW A SIMPLE LOOP TO GET DATA FOR FIELDS ON SCREEN
1490 REM
1500 READ X,Y,L,FIELD$:TYPE$=FIELD$
1510 IF TYPE$="*" THEN POSITION 2,20:STOP
1520 IF L<>0 THEN GOSUB 1100:GOTO 1500
1530 POSITION X,Y:PRINT FIELD$;
1540 GOTO 1500
1550 REM
1560 REM IN A REAL PROGRAM, THE DATA FROM THE
1570 REM FIELDS WOULD NOW BE MANIPULATED IN SOME
1580 REM WAY (PERHAPS PLACED IN A DISK FILE)
1590 REM ==========================
1600 REM
1610 REM
1620 REM DATA TO DEFINE THE FIELDS FOLLOW
1630 REM
1640 DATA 7,2,0,NAME
1650 DATA 3,4,0,ZIP CODE
1660 DATA 2,7,0,MISCELLANEOUS COMMENTS:
1670 DATA 12,2,20,A
1680 DATA 12,4,5,N
1690 DATA 4,8,30,E
1700 DATA 7,15,0,=== THAT'S ALL FOLKS ===
1710 DATA 0,0,0,*
COMPUTE! ISSUE 81 / FEBRUARY 1987 / PAGE 73

INSIGHT: Atari

Bill Wilkinson

Number-Base Conversions

This column was prompted by a letter in COMPUTE!’s letters column, in which the author asked for a program to convert decimal numbers to binary. “Why?” I asked myself, “do all these conversion programs work with only one pair of bases (for example, base 10 to base 2)?” Answer: because few realize that a more general program is almost as easy these specific ones. Don’t believe me? Keep reading.

Number Bases

You probably learned about number bases back in third or fourth grade, though you might not have realized that’s what you were learning. Specifically, you likely were taught that the number 735 represented “seven hundreds, three tens, and five ones.” The fact that digits in a number represent powers of ten is kind of an accident. If humans were normally bom with only three fingers and a thumb on each hand, you can bet that 735 would have meant “seven sixty-fours, three eights, and five ones” (that is, we would have used base 8).

Since computers are “born” with only two “fingers,” their natural tendency is to use base 2, also known as binary numbers or notation. (A computer’s “fingers” are its memory cells, but each cell can remember only off or on, equivalent in function to counting on two human fingers.) Yet you seldom see a computer memory dump printed in binary, simply because such a printout would be gigantic! Binary numbers take up a lot of room compared to equivalent decimal numbers. Instead, because of the neat way that powers of two can be grouped together, we tend to see computer memory represented in either octal (base 8) or hexadecimal (base 16) notation.

One thing you may have noticed is that a base’s number is the same as the number of counting symbols needed to represent it. Thus base 10 uses 0–9. Base 8 uses only 0–7. What about bases beyond 10, such as base 16, the hexadecimal base most often used in microcomputer work? Doesn’t it need 16 counting symbols? Yes, indeed, and the symbols most commonly used are 0–9, followed by A–F. (Why not use completely new symbols for the digits beyond 9? Simple: Early computer printers had only 64 different symbols available, so uppercase letters were used.)

Why Hex?

Sidelight: Since we are working on computers that tend to work with bytes, and since a byte can have a value from 0 to 255 (decimal), base 256 notation would seem to be a logical choice. But now we can see why it is not used—humans would be forced to learn 256 unique digit symbols! Still, there are two “nybbles” in each byte, and a nybble can have a value from 0 to 15 (decimal), so hexadecimal (base 16) notation is a very logical alternative.

Now, when you see a hexadecimal number such as A88C, what does it mean? Well, you can read that as “A four-thousand-ninety-sixes, 8 two-hundred-fifty-sixes, 8 sixteens, and C ones.” In turn though, A and C may be read in decimal as 10 and 12, respectively. Whew! Now how about base 19?

Confused? Don’t worry, help is at hand. Program 1 consists of a short main program followed by two special-purpose subroutines. These routines are designed to make it easy to allow entry and display of any number using any base or pair of bases. The first one (from line 9200 to line 9330) takes a number in variable N and converts it to a string in variable N$ using the number base given by the variable BASE. The second routine (lines 9400–9560) performs the reverse operation, converting a string in N$ (which is supposed to be a number in BASE notation) and converting it to N for use as a number anywhere in BASIC.

Try it. Type in the main code and the subroutines and try the various options. And use some bizarre number bases, such as 13 or 37 or 53. In keeping with the tradition of hexadecimal numbering, the digit symbols used are 0–9 (same as decimal for the first ten symbols), followed by A-Z, and then a-z (good enough for anything up to base 62!).

So now I have one set of routines which take care of all conversions. And it’s kind of fun. You could even make a game of it: Try to make two English words “equal” by changing bases! For example, RIB base 35 equals some animal (which happens to enjoy ribs) in some other base. Can you find the animal word and its base? Maybe tricks like this could make a hard-to-break encryption scheme? (This can really cause you to lose sleep!)

Be Just A Bit Wiser

I couldn’t quit with simple number conversions, of course. One of the handy features of most higher-level languages is (usually) the presence of operators which do bitwise operations. I like such operators so much I put them into the first of the advanced Atari-compatible BASICs we did, way back in 1981. Unfortunately, Atari BASIC does not have bitwise operators. In Atari BASIC, operators such as AND and OR always perform logical comparisons rather than bitwise comparisons. Though, in fairness, I should point out that there are occasions where Atari’s logical operators are worth as much as or more than bitwise operators. Some authors have agreed with me to the extent that they have written machine language USR calls for use in their BASIC programs. But this is beyond the ken of most BASIC users.

Fortunately, bitwise operators can be implemented in Atari BASIC. And that’s exactly the purpose of the subroutines of lines 9000 through 9090 (bitwise AND) and lines 9100 through 9190 (bitwise OR) in Program 2. I don’t have space in this column to explain the theory and operation of bitwise operators, but we can quickly look at one example of their use.

Suppose you want to perform some subroutine only when the user of your program hits the SELECT key. Further, suppose that in your program it is legitimate and possible that the user may be pushing down either (or both) the START and OPTION keys at that same time as SELECT. If you look in most any good reference book (COMPUTE!’s Mapping the Atari, for example), you will find a little table something like this:

Push
this key
PEEK(53279)
decimal
shows this
binary
START 6 110
SELECT 5 101
OPTION 3 011

Here we have listed the binary values (even though you could have run Program 1 to convert the decimal values yourself) to show clearly what the console keys are doing: Each of those three keys changes a single bit of the specified address from 1 to 0 when it is pushed. So, we would like a way to isolate the state of the middle bit (of the three) to test for SELECT being pressed. No sooner said than done.

In most languages, you would use something equivalent to this:

SELECTPUSHED = NOT (PEEK(53279) AND 2)

In Atari BASIC, you can do it the way I did it in Program 2. Enough said?

Finally, there is Program 3. You can not use this program by itself. You must first add all four of the subroutines (on lines numbered 9000 or greater) from Programs 1 and 2. Be sure to keep those subroutines handy so they can be used by Program 3 or, I hope, by some of your own programs. (Remember, if you LIST a range of lines to disk or cassette, you can use ENTER to merge them with a program in memory.)

Program 3 is a catchall. It allows you to enter two numbers using two (optionally) different number bases. It then allows you to choose a number base for display purposes and shows you the conversions of the two numbers along with the results of bitwise ANDing them and bitwise ORing them. For a thorough understanding of bitwise operations, you might choose base 2 (binary) for all input and output. Happy hacking.

Program 1: Base Converter
100 REM ***** PROGRAM TO DEMONSTRATE
110 REM ***** NUMBER BASE CONVERSION
120 REM
130 DIM N$(40):REM (MUST BE AT LEAST 32)
140 REM
200 ? :? :PRINT "BASE FOR INPUT":INPUT BASEIN
210 PRINT "NUMBER ";:INPUT N$
220 BASE=BASEIN:GOSUB 9400:DECIMAL=N
230 IF N<0 THEN PRINT "GO PS":GOTO 200
240 PRINT "BASE FOR OUTPUT";:INPUT BASE
250 PRINT
260 PRINT N$;" BASE ";BASEIN;"
270 GOSUB 9200:PRINT N$;" BASE ";BASE;"
280 PRINT DECIMAL;" BASE 10"
290 GOTO 200
9200 REM ***** CONVERT N TO N$ USING GIVEN BASE
9210 REM ENTER:  N,BASE
9220 REM USES:   DIG$,DIGIT,WORK,TEMP
9230 TRAP 9230
9240 DIM DIG$(62)
9250 DIG$="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
9260 N$="00000000000000000000000000000000"
9270 WORK=N
9280 FOR DIGIT=32 TO 1 STEP -1
9290 TEMP=INT(WORK/BASE):WORK=WORK-TEMP*BASE
9300 N$(DIGIT,DIGIT)=DIG$(WORK+1)
9310 WORK=TEMP:IF WORK THEN NEXT DIGIT
9320 IF N$(1,1)="0" THEN N$=N$(2):GOTO 9320
9330 RETURN
9400 REM ***** CONVERT N$ TO N USING GIVEN BASE
9410 REM ENTER:  N$ HAS PRESUMED NUMBER IN STRING FORM
9420 REM .       BASE IS BASE TO USE
9430 REM EXIT:   N HAS NUMBER IN INTERNAL FORM 0 IF ERROR)
9440 REM USES:   DIGIT,TEMP
9450 REM NOTE:   DIGITS GOTO BASE 66, IN ORDER
9460 REM .       0 .. 9 , A .. Z , A .. Z
9470 IF N$(1,1)="0" THEN N$=N$(2):GOTO 9470
9480 N=0
9490 FOR DIGIT=1 TO LEN(N$)
9500 TEMP=ASC(N$(DIGIT)>=48:IF TEMP<13 THEN N=-1:RETURN
9510 IF TEMP>9 THEN TEMP=TEMP-7:IF TEMP<10 THEN N=1:RETURN
9520 IF TEMP>33 THEN TEMP=TEMP-6:IF TEMP<36 THEN N=1:RETURN
9530 IF TEMP>BASE THEN N =-1:RETURN
9540 N=N*BASE+TEMP
9330 NEXT DIGIT
9560 RETURN
Program 2: Bitwise Operations
100 REM ***** PROGRAM TO SHOW STATE
110 REM ***** OF CONSOLE KEYS AND
120 REM ***** DEMONSTRATE BITWISE AND
130 X=PEEK(53279):IF X=7 THEN 130
140 Y=1:GOSUB 9000
150 IF  NOT XANDY THEN PRINT "START",
160 Y=2:GOSUB 9000
170 IF  NOT XANDY THEN PRINT "SELECT",
180 Y=4:GOSUB 9000
190 IF  NOT XANDY THEN PRINT "OPTION",
200 PRINT
210 IF PEEK(53279)=X THEN 210
220 GOTO 100
9000 REM **** REM BITWISE AND
9010 REM ENTER:  X,Y
9020 REM EXIT:   XANDY IS X AND Y
9030 REM USES:   TEMPX,TEMPY,MASK
9040 TEMPX=X:TEMPY=Y:XANDY=0:MASK=1
9050 TEMPX=INT(TEMPX)/2:TEMPY=INT(TEMPY)/2
9060 IF TEMPX=0 OR TEMPY=0 THEN RETURN
9070 IF TEMPX<>INT(TEMPX) AND TEMPY<>INT(TEMPY) THEN XANDY=XANDY+MASK
9080 MASK=MASK+MASK
9090 GOTO 9050
9100 REM **** BITWISE OR
9110 REM ENTER:  X,Y
9120 REM EXIT:   XORY IS X OR Y
9130 REM USES:   TEMPX,TEMPY,MASK
9140 TEMPX=X:TEMPY=Y:XORY=0:MASK=1
9150 TEMPX=INT(TEMPX)/2:TEMPY=INT(TEMPY)/2
9160 IF TEMPX=0 AND TEMPY=0 THEN RETURN
9170 IF TEMPX<>INT(TEMPX) OR TEMPY<>INT(TEMPY) THEN XORY=XORY+MASK
9180 MASK=MASK+MASK
9190 GOTO 9150
Program 3: Subroutine Demo
100 REM ***** PROGRAM TO DEMONSTRATE
110 REM ***** NUMBER BASE CONVERSION
120 REM ***** AND BIT-WISE OPERATORS
130 REM
140 DIM N$(40):REM (MUST BE AT LEAST 32)
150 REM
200 ? :? :PRINT "IN BASE ";:INPUT BASE
210 PRINT "NUMBER ";:INPUT N$
220 GOSUB 9400:X=N
230 IF N<0 THEN PRINT "OOPS":GOTO 200
240 PRINT "BASE FOR INPUT";:INPUT BASE
250 PRINT "NUMBER":INPUT N$
260 GOSUB 9400:Y=N
270 IF N<0 THEN PRINT "OOPS":GOTO 200
280 PRINT "BASE FOR OUTPUT";:INPUT BASE
290 PRINT
300 PRINT RESULTS
310 N=X:GOSUB 9200:PRINT "FIRST NUMBER : ";N$
320 N=Y:GOSUB 9200:PRINT "SECOND NUMBER : ";N$
330 GOSUB 9000:GOSUB 9100
340 N=XANDY:GOSUB 9200:PRINT " BITWISE AND : ";N$
350 N=XORY:GOSUB 9200:PRINT " BITWISE OR : ";N$
360 GOTO 200
999 REM ***** END OF MAIN CODE *****
COMPUTE! ISSUE 82 / MARCH 1987 / PAGE 89

INSIGHT: Atari

Bill Wilkinson

Corrected File Conversions

Well, this month marks a historic occasion for those of us at Optimized Systems Software. March 1981 was the month we introduced our first Atari-oriented products: BASIC A+, EASMD, and OS/A + (called CP/A until a lawyer for DRI objected—maybe we could have fought them if we had had more than $2.98 in our checking account). We finished those products off in record time and presented them at the West Coast Computer Faire. We managed to sell 17 (yes, that is 3 less than 20) packages at about $120 each (that was cheap in those days), and we decided then and there we could stay in business for another month (maybe even two).

Well, the months kept passing like that, OSS has never been a wildly successful company—selling languages for a computer on which fewer than 10 percent of all owners actively program is not conducive to instant wealth—but we have always had some loyal followers. As I have mentioned here before, I started writing this column because I saw some questions in COMPUTE! about Atari software internals that I thought needed some answers. But I wouldn’t have even gotten interested in reading COMPUTE! if we hadn’t started OSS. See? All things are related when you look deep enough.

Unified We Stand

Speaking of software internals and answers…. In the recent issues of COMPUTE! there are a pair of programs which purport to convert standard Atari binary object files into either strings (“Stringing Atari Machine Language,” September 1986) or DATA statements (“ML Write for Atari” January 1987). Both of these programs have a common limitation which was not mentioned in the articles accompanying them: You must use them only with single-segment binary files. How do you know if a particular binary file consists of only a single segment? Glad you asked.

The program which accompanies this article is a simple little utility that analyzes any standard Atari binary file, printing the first and last address of each segment as it goes. When the program asks for the complete file name, you should enter the name of a binary file, including the disk drive specifier and extension (for example, D1:RAMDISK.COM). Watch the resultant screen display. If addresses for more than one file segment are displayed, then you may not use the programs described in those articles for this file.

Exception: If the addresses are all contiguous (that is, if the starting address of a segment is exactly one more than the ending address of the prior segment and if this holds true for all segments), you can use this file if you unify it first. I discussed segmented files in my April 1986 column and presented a unifying program there. Unfortunately, the program accompanying that article was misprinted, so you have to look in the article titled “Custom Characters for Atari SpeedScript” by Charles Brannon in the May 1986 issue (pages 88–90) for a corrected version of the file unifier.

If you are not comfortable with the hex addresses printed by the segment-checking program, you may view decimal addresses instead by replacing lines 110 through 150 below with just this one line:

110 PRINT "SEGMENT: ";START;"THROUGH ";QUIT

And one last caution: Though not mentioned in the article, machine language code placed in strings (as in the September 1986 article) must be intrinsically relocatable. Many of the routines floating around on BBSs and in user-group libraries are indeed relocatable, but don’t rely on this always being so. Test these routines in strings (or any machine language routines, for that matter) only after you have made sure you have saved your program and after you have put a junk diskette in the drive. (If you have an Indus drive or other drive that you can protect from the front panel, setting the protection is another adequate safeguard.)

Binary File Segment Checker
10 REM **** BINARY FILE SEGMENT CHECKER ***
20 DIM FILE$(20),HEX$(16):HEX$="0123456789ABCDEF"
30 GRAPHICS 0
40 PRINT "COMPLETE FILE NAME";:INPUT FILE$
50 OPEN #1,4,0,FILE$
60 TRAP 200:GET #1,LOW:GET #1,HI
70 IF HI=255 AND LOW=255 THEN GET #1,LOW:GET #1,HI
80 START=LOW+256*HI
90 TRAP 40000:GET #1,LOW:GET #1,HI
100 QUIT=LOW+256*HI
110 PRINT "FILE SEGMENT:";
120 HEX=START:GOSUB 230
130 PRINT " THROUGH ";
140 HEX=QUIT:GOSUB 230
150 PRINT
160 FOR ADDR=START TO QUIT
170 GET #1,JUNK
180 NEXT ADDR
190 GOTO 60
200 REM *** GET HERE ON END OF FILE ***
210 IF PEEK(195)<>136 THEN PRINT "UNEXPECTED ERROR ";PEEK(195)
220 END
230 REM *** HEXPRINT SUBROUTINE ***
240 DIV=4096
250 FOR DIGIT=1 TO 4:TEMP=INT(HEX/DIV)
260 PRINT HEX$(TEMP+1,TEMP+1);
270 HEX=HEX-DIV*TEMP:DIV=DIV/16
280 NEXT DIGIT
290 RETURN
COMPUTE! ISSUE 83 / APRIL 1987 / PAGE 78

INSIGHT: Atari

Bill Wilkinson

Retry, Retry, Retry Again

In the past, I have claimed in this column that I have never had an Atari disk “crash” on me if I wrote to it with verify turned on. Thanks to the little display panel on my Indus drives, I finally figured out why, A couple of times now, I have been writing programs or articles to disk (with verify turned on) and have noticed the Indus drive indicate that an error occurred. Yet neither DOS nor BASIC (nor the word processor) have reported any such error. Why?

Simple: Because of a safety feature in Atari DOS called a retry counter. Any time a disk operation fails, Atari DOS tries to perform the task again. In fact, it tries up to three more times. Now, if you write to the disk without verify, the drive may not know if something goes wrong (for example, if there is a defective spot on the disk). So you don’t find out that the write failed until the next day (or week or year), when you try to reread the data. Kablooey! Even the fact that DOS retries the read three times may not help.

On the other hand, if you write with verify turned on, the drive writes the information to the disk and then immediately reads it to be sure the write was successful. Assuming that there was an error, though, you would seem to be no better off than you were without verify mode turned on except that the drive tells DOS about the verify error and DOS tries again (and again and again if it needs to) to write that same sector.

Soft Errors

Usually, disk write errors are what are called soft errors. That is, if you try again, the write succeeds. And after three attempts, if the write still fails, that disk is in really bad shape (or you forgot to format the disk or remove the write-protect tab). Well, very few of my disks are in really bad shape, so DOS tries again—and I don’t even realize that I had a near miss with a blown file.

The point of all this is twofold: First, I would like to try to convince anyone doing work which would require a long time to reconstruct to use the “write with verify” mode. (If you are using DOS 2.5, this is easy, since the SETUP.COM program allows you to choose verify mode with just a few keystrokes. DOS 2.0 users can type POKE 1913,87 and then make the change permanent by using the DOS menu’s WRITE DOS option.) Second, although three retries seem like enough for any write operation, I have had a few disks where a file seemed “slightly” damaged— that is, one time it would seem to read okay and the next time it would give me an error (usually ERROR 164). Well, I really should move that file to another disk (and I do), but how can I increase my chances that I can read it well enough to do the transfer? Answer: Change the number of retries that DOS makes.

Time for another program: Program 1 is a short BASIC program that you can use to modify DOS 2.5’s retry count (and, incidentally, turn on verify mode for writes). Once you run it, all DOS disk operations will be tried as many times as you specify until you boot DOS again. (And, if you would like to make the changes permanent on a given disk, just answer the question in the program appropriately. An error message from this operation probably indicates that the disk was full, the disk was write-protected, or the DOS.SYS file was locked. In any case, try another disk.)

Finally, if you are still using DOS 2.0s (or DOS XL, most versions), just change three lines in the listing. I doubt seriously that either Atari version is compatible with any other DOS, so you may need to experiment and/or contact the publisher of your DOS to change the retry counts. Anyway, the three lines to change are

40 IF PEEK(1932)<>169 OR PEEK(1933)>10 THEN 230
100 POKE 1933,RETRY
230 REM *** APPARENTLY NOT DOS 2.0 ***

Random Ramblings

Unlike most personal computers, the Atari 8-bit machines use a hardware random number generator which is supposed to produce better random numbers than purely software schemes. Notice the words “supposed to.” In practice, the hardware random number generator operates in a fairly predictable manner, and, if you were to take two values from it in very close succession (“very close” means under a dozen microseconds or so), you would find that certain values tend to follow certain other values.

Atari BASIC itself pulls a pair of hardware random numbers in very close succession. Most of the time, the results of the bias this creates are unnoticeable, especially when the range of random numbers you want is under 200 or so (for reasons too complicated to go into here). But look at what happens when you run Program 2. If BASIC’s RND function produced truly random results, then you would expect the four counts that are printed to be approximately equal. They obviously aren’t.

My suggested solutions: If you are a BASIC programmer having problems with Atari’s randomness (and remember, most of you won’t), look at Program 3. We avoid the RND function and instead read two random bytes directly from the hardware register. Because BASIC takes so long (relatively speaking) to do this, there is plenty of time between the PEEKs, and the generator gives pretty good results. For machine language programmers who need to get two random numbers in quick succession, try a sequence like this:

LDA RNDLOC ; get first random byte
STA WSYNC ; wait for horizontal sync
LDX RNDLOC ; get second random byte
Program 1: DOS Reliability Enhancer
10 REM **** DOS RELIABILITY ENHANCER ****
20 DIM Q$(1)
30 IF PEEK(1913)<>80 AND PEEK(1913)<>87 THEN 230
40 IF PEEK(1947)<>169 OR PEEK(1948)>10 THEN 230
50 POKE 1913,87:REM TURN ON VERIFY MODE
60 PRINT "HOW MANY TIMES SHOULD DOS RETRY"
70 PRINT " A DISK OPERATION (0 TO 10)";
80 INPUT RETRY
90 IF RETRY<0 OR RETRY>10 OR RETRY<>INT(RETRY) THEN RUN
100 POKE 1948,RETRY
110 PRINT :PRINT
120 PRINT "DO YOU WANT TO MODIFY THE DOS ON"
130 PRINT " THE DISK IN DRIVE 1 (Y/N)"
140 INPUT Q$:IF Q$<>"Y" THEN STOP
150 TRAP 200
160 OPEN #1,8,0,"D1:DOS.SYS"
170 CLOSE #1
180 PRINT :PRINT "DOS MODIFIED"
190 END
200 PRINT "ERROR WHILE TRYING TO MODIFY DOS ON"
210 PRINT " DISK IN DRIVE 1. ERROR #";PEEK(195)
220 END
230 REM *** APPARENTLY NOT DOS 2.5 ***
240 PRINT "INCOMPATIBLE WITH THIS DOS"
250 END
Program 2: Random Number Generator Test
100 DIM TEST(3):FOR I=0 TO 3:TEST(I)=0:NEXT I
110 FOR I=1 TO 1000
120 R=INT(RND(0)*65535/128+0.5)
130 T=INT(R/4):R=R-T*4
140 TEST(R)=TEST(R)+1
150 NEXT I
160 PRINT
170 PRINT "VALUE","COUNT"
180 FOR I=0 TO 3
190 PRINT I,TEST(I)
200 NEXT I
Program 3: Improved Random Number Routine
100 DIM TEST(3):FOR I=0 TO 3:TEST(I)=0:NEXT I
110 FOR I=1 TO 1000
120 R=PEEK(53770)*256+PEEK(53770)
130 T=INT(R/4):R=R-T*4
140 TEST(R)=TEST(R)+1
150 NEXT I
160 PRINT
170 PRINT "VALUE","COUNT"
180 FOR I=0 TO 3
190 PRINT I,TEST(I)
200 NEXT I
COMPUTE! ISSUE 84 / MAY 1987 / PAGE 56

INSIGHT: Atari

Bill Wilkinson

RUN And INIT Vectors

This month’s discussion is something of a continuation of my column of a couple of months ago, where I presented a program that showed you the segments of a binary file. And that column, in turn, referred back to the April 1986 column. Both columns are required reading for a full understanding this month, but you’ll learn something even if you are reading this cold.

We begin by noting that when you ask Atari DOS (version 2,OS or 2.5) to save a chunk of memory as a binary file, it asks you to supply four numbers:

START,END,INIT,RUN

And, if you’ve looked through enough magazine articles or user group newsletters, you’ve probably come across places where an author instructed you to use the save binary file option, mentioned the beginning and ending addresses, and then told you to be sure to give the proper RUN (and/or INIT) address. The START and END numbers seem obvious: They are the first and last addresses of the range of memory to be written out. But what about INIT and RUN? What can those possibly mean?

A Feature Unmatched

The ability of any binary file, including the ever-important AUTORUN.SYS, to have a RUN or INIT address associated with it is, in my opinion, a feature unmatched by any small system DOS, up to and including MS-DOS (IBM PC and clones) and TOS (for the ST). Only with Atari DOS’s binary files and their format-compatible relatives can you tell the operating system to load part of your binary file (also called machine language file, object code, and so on—several names for the same thing), execute that part, and then continue loading more of the file. So let’s see how it all works.

When DOS loads a binary file, including the AUTORUN.SYS file at power-up time, it monitors two locations. The simpler of the two is the RUN vector. Before DOS begins the load of a binary file, it puts a known value into locations 736–737 (hex $2E0–$2E1). When the file is completely loaded—DOS encounters the end of the file—if the contents of location 736 have been changed, then DOS assumes the new contents specify the address of the beginning of the program just loaded. DOS calls the program (via a JSR) at that address.

The second monitored location is the INIT vector, at 738–739 (hex $2E2–$2E3). This vector works much the same as the RUN vector, but DOS initializes and checks it for each segment as the segments are loaded. If the INIT vector’s contents are altered, then DOS assumes the user program wants to stop the load process long enough to call a subroutine. So DOS calls (via a JSR) at the requested address, expecting that the subroutine will return so that the rest of the load can take place. This is a very handy feature. Most of you have probably seen it at work—for example, when a program first puts up an introductory screen (maybe just a title and a Please wait message) when you run (or boot), then continues to load.

Taking Full Control

The other important difference between the RUN and INIT vectors is that DOS leaves channel 1 open while the INIT routine is called. (DOS always opens and loads the binary file via this channel.) I suppose a really tricky program could close channel 1, open a different binary file, and then return to DOS. DOS would proceed to load the new file as if it were continuing the load of the original one. Most of the time, though, INIT routines should not touch channel 1.

As noted, when you SAVE a binary file from DOS 2.x (and many of its variants), you are allowed to specify both an INIT and a RUN address. But the INIT address is sort of useless, since it is added to the end of a file; so, for example, your opening screen display won’t occur until the entire file is loaded. To take full control, you must resort to assembly language (or to a compiled language, such as Kyan Pascal or OSS’s Action). For those of you familiar with assembly language, I present the skeleton listing below. This listing is compatible with the Atari Assembler Editor cartridge or the MAC/65 assembler. You will need to make a handful of minor substitutions if you are using some other assembler.

I’m not going to explain the program in great detail—the source code is fairly well documented. A couple of important points though: Notice that there is no special command to the assembler that will force it to put in an INIT vector (or RUN vector—unless you have the AMAC assembler). Instead, we simply create a binary file segment that is only two bytes (one word) long. And this segment is loaded by DOS’s loader at—where else—the appropriate vector. So the very act of loading the specified addresses modifies the contents of the vector. What could be neater?

As mentioned, this is strictly a program skeleton. It will do nothing as is. You must add some of your own assembly language to it to make it actually do something. So, if you thought INIT and RUN vectors were beyond you, try this skeleton and be ready to change your mind.

INIT Vector Example

; Skeleton of a program which puts
; a ’please wait’ type message on
; the screen before loading the ; main code.
;
    *= $3000 ; or someplace
DOINIT
;
; the code which follows is for
; demo purposes only! Use your
; own code...pretty display lists
; or dazzling colors or whatever
;
      LDX #0    ; channel zero
      LDA #9    ; Put Text command
      STA $342  ; command byte
      LDA #MSG&255
      STA $344  ; low byte, addr of msg
      LDA #MSG/256
      STA $345  ; high byte, ditto
      LDA #255  ; use a too-big length.*.
      STA $348  ; since RETURN terminates
                ; this call anyway
      JSR $E456 ; call CIO
      RTS       ; back to DOS

MSG   .BYTE 125 ; (clear screen)
      .BYTE 29,29,29,29 ; (cursor down)
      .BYTE 127 ; (tab once)
      .BYTE "--please wait--"
      .BYTE 155 ; (return...end of msg)
;
; now the INIT VECTOR forces DOS
; to call our DOINIT routine
;
      *= $2E2 ; init vector
      .WORD DOINIT ; gets pointed to us
;
; Your main program...
; you are on your own here!
;
      *= $3000 ; the same address if you like
;
; I can use the same address because
; my init code can disappear when
; its job is done. This may not
; work with your code. Be careful.
;
DORUN
      …
      …
;
; then we get DOS to run our program
; by using a RUN vector.
;
      *= $2E0 ; AMAC uses ORG, not *=
      .WORD DORUN; AMAC uses WORD, no dot
      .END ; AMAC uses END, no dot
COMPUTE! ISSUE 85 / JUNE 1987 / PAGE 72

INSIGHT: Atari

Bill Wilkinson

The Protection Racket

How many times have you written a program that is supposed to write to a disk file, only to have the drive make an ugly grinding noise and then have BASIC (or your program, if it is doing error trapping) tell you that the disk is write-protected? If you’re like me, the answer is very often.

I’m usually cautious when writing my programs. I leave the write-protect tab on all my disks until I am 99 percent certain that the program will work. So during the development of a program, I tend to get lots of error number 144 messages. (Error 144 can mean anything from a too-fast disk drive to a 1050 drive’s door being open, but most often it means that a disk is write-protected.)

Testing First

Wouldn’t it be neat to be able to test if a disk is write-protected before you open a file for a write? Actually, you can. The method is a fairly obscure one; and to find it, once again I had to consult the old Atari Technical Reference Manual.

A short sidetrack: Those of you who don’t mind searching through a couple hundred pages to find a small item buried in a lot of software (and hardware) engineering talk really should own a copy of this manual. It is now a six-year-old document, but it is still useful and accurate (aside from some of the RAM locations used by the newer XL/XE operating systems—for which you can consult COMPUTE!’s revised version of Mapping the Atari). This is a tribute to the design capabilities of the engineers from the old Atari and the astuteness of today’s Atari: Never has an entire line of personal computers stayed so compatible.

Where The Secret Lies

The secret to the write-protect is buried in the information about the disk drive’s status command. (Don’t confuse the drive status with a file’s status, as tested by BASIC’s STATUS command.) If you can recall my September 1985 column regarding SIO (Serial Input/Output) commands, it may be easier for you to understand the code which follows. I am not going into great detail, but, briefly, an SIO call must have certain information placed in page 3 (locations $300 to $30B, specifically) before the program jumps to the subroutine at location $E459. (You can use my handy-dandy number converter program of a few months back to convert those hex numbers to decimal.) Information placed in page 3 includes the drive number, type of request (S, for status, in this case), the address of a buffer, and the number of bytes to transfer.

It is this last set of information that is most important to us: The drive status returns four bytes of information that we need to receive somewhere so that we can analyze it. We’ll take a closer look at the accompanying listing later, but for now just notice that we dimension a buffer (BUF$) in line 30100. The four bytes of BUF$ will be used to receive the four bytes of drive status. We’ll be using only the first byte of that status, because that is where the write-protect flag is located. Some of the bits in that byte are related to various hardware error conditions, which we need not discuss, leaving the following bits as useful to us:

Bit 3   $08   write-protected disk
Bit 5   $20   double density disk
Bit 7   $80   1050 enhanced density disk

Well, well! So not only can we find out if the disk is write-protected, we can also find its density.

A Useful Subroutine

In this month’s listing, then, lines 30000 and beyond are a subroutine that you can include in your own programs. To use the subroutine, simply set the variable DRIVE to a valid drive number (1 to 4, usually) and GOSUB 30000. Upon return, the variable CHECK will contain one of the following values:

less than 0   an error occurred (invalid drive number, for example)
0             disk may be written to
1             disk is write-protected

If the value returned is less than zero, then the value will be the negative of the appropriate error code. For example, if you try to check a drive that isn’t turned on, the value returned should be —138, indicating that error 138—device timeout—has occurred.

Similarly, a second number is returned in a variable named DENSITY. Its meaning:

-1    density value was invalid
 1    drive is single density
 2    drive is double density
 3    drive is enhanced density

And all of this is demonstrated by the code in lines 100 through 220. These lines are provided just to give you a test bed to try out the subroutine of lines 30000 and up. Try the program, and then incorporate the subroutine in your own programs. And never again will you or your users see that dreaded error number 144 (unless your drive speed is off—but that’s another topic).

Just a couple of last comments: Notice line 30170. This demonstrates another programming trick. Worried about making sure that your RESTORE statements refer to the right DATA line numbers? For short DATA lines, why not simply combine RESTORE and DATA on the same line, as in 30170?

Also, notice the sneaky way that BUF$ is dimensioned in line 30100. If we come to line 30100 a second time, we get an error when we try to re-DIMension BUF$. But, because of the TRAP, the error is effectively ignored—a useful way of making sure a variable is dimensioned only once.

Write-Protect Checker

For instructions on entering this program, please refer to “COMPUTE!’s Guide to Typing In Programs” elsewhere in this issue.

100 REM SIMPLE PROGRAM TO DEMONSTRATE
110 REM THE WRITE PROTECT CHECKER ROUTINE
120 PRINT :PRINT :PRINT " DRIVE NUMBER ";
130 INPUT DRIVE
140 GOSUB 30100
150 IF CHECK<0 THEN PRINT "ERROR # ";-CHECK;" ACCESSING DRIVE ";DRIVE:GOTO 120
160 IF CHECK>0 THEN PRINT "DISK IS WRITE PROTECTED";
170 IF CHECK=0 THEN PRINT "DRIVE IS READY";
180 IF DENSITY=1 THEN PRINT ", SINGLE DENSITY."
190 IF DENSITY=2 THEN PRINT ", DOUBLE DENSITY."
200 IF DENSITY= 3 THEN PRINT ", ENHANCED DENSITY. "
210 IF DENSITY<0 THEN PRINT ", UNKNOWN DENSITY"
220 GOTO 100
30000 REM SUBROUTINE TO CHECK DISK FOR WRITE PROTECT
30010 REM
30020 REM ENTER WITH DRIVE TO CHECK IN DRIVE (1 TO 0)
30030 REM
30040 REM ON EXIT, CHECK WILL NORMALLY BE 0
30050 REM IF CHECK IS 1, DISK IS WRITE PROTECTED
30060 REM IF CHECK IS <0 THEN CHECK IS NEGATIVE OF SIO ERROR CODE
30070 REM FOR CHECK = 1 OR 0, DENSITY IS RETURNED:
30080 REM DENSITY IS 1,2,3 FOR SINGLE, DOUBLE, ENHANCED DENSITY
30090 REM DENSITY IS -1 IF UNRECOGNIZED
30100 TRAP 30110:DIM BUF$(4)
30110 POKE 768,49:POKE 769,DRIVE
30120 POKE 770,83:POKE 771,64
30130 POKE 773,INT(ADR(BUF$)/256)
30140 POKE 772,ADR(BUF$)-256*PEEK(773)
30150 POKE 774,2:POKE 775,0
30160 POKE 776,4:POKE 777,0
30170 RESTORE 30170:DATA 104,76,89,228
30180 FOR BYTE=1 TO 4:READ CHECK
30190 BUF$(BYTE)=CHR$(CHECK):NEXT BYTE
30200 BYTE=USR(ADR(BUF$))
30210 CHECK=PEEK(771):IF CHECK>127 THEN CHECK=-CHECK:RETURN
30220 CHECK=0:BYTE=INT(ASC(BUF$)/8)
30230 IF BYTE/2<>INT(BYTE/2) THEN CHECK=1
30240 BYTE=INT(BYTE/4):DENSITY=-1
30250 IF BYTE=0 THEN DENSITY=1
30260 IF BYTE=1 THEN DENSITY=2
30270 IF BYTE=4 THEN DENSITY=3
30280 RETURN
COMPUTE! ISSUE 86 / JULY 1987 / PAGE 52

INSIGHT: Atari

Bill Wilkinson

Three Questions

I have received a fair number of letters from 8-bit owners in recent months, and most people ask one of these three questions: “Where can I find a book that tells me …?” “Do you know of any program that will …?” “How do I convert my Atari BASIC program to assembly language so that I can …?”

Although all your questions are slightly different, I have a few answers that will work for most of you, regardless of the ending you would like to put on any of the questions.

First, it is an unfortunate fact that many of the best books for the 8-bit Atari computers (400, 800, and XL/XE series) are no longer in print. I would like to hope that some enterprising publisher might decide to reprint a few of the best of these in limited editions, but I am not going to hold my breath until that happens. In the meantime, your best bet is to try to track down a copy now, while there are still a few in dealers’ hands. What books am I referring to? There are so many books that would make my “nice to have” list that I can’t possibly list them here, so, instead, here (reluctantly) is a limited list of what I consider my own, personal “basic necessities” library.

For all programmers:

For BASIC neophytes:

For assembly language neophytes:

For those who are really serious:

Some of these are still pretty easy to find. Others have all but disappeared. Still, every so often I hear of dealers who have a nice stock of one or more of them. For example, you may have read in the May issue of COMPUTE! that B&C ComputerVisions of Santa Clara, CA, has a stock of De Re Atari (which just barely failed to make my essentials list). I have just learned that they also have a good stock of the Atari Technical Reference Manuals. Dealers rarely advertise that they have a certain book—by the time the ad appears, they may be sold out with no way to get more copies. So call around, ask around, check with your local bulletin board, and/or leave a message or two on some of the national time-share systems that have Atari interest areas (for example, CompuServe, Genie, and Delphi).

Ask

By now, you probably won’t be surprised to find that the answer to that second question is about the same: Ask. About the only kind of programs you can not find for your 8-bit Atari are what I call “heavy-duty” programs. For example, I have yet to see a good, complete civil engineering package. Or an off-the-shelf order-entry system. The primary limitations of these small machines have always been their slow disk I/O speed and limited disk space. (Historically, there has been a more important limitation that I’ll address in a future column. Ironically, I am writing this column on an 800 XL connected to a Supra 10-megabyte hard disk using ICD’s SpartaDOS, and I find that this system now does everything I need. But a large percentage of Atari owners have, only one floppy disk drive, which is simply not enough for most business purposes.)

One amazing aspect of Atari software is the amount of usable public-domain software available. But until you join a user group—or, perhaps, buy a modem and call some BBSs or one of the national time-sharing systems—you will be cut off from this free software.

Converting BASIC To ML

The final question listed above is actually the most interesting to me: “How do I convert my Atari BASIC program into machine language?” The first and most obvious answer: Buy a BASIC compiler. I don’t want to belabor this topic now, but you should know that Atari BASIC is an interpreted language. It is not fast. If you could compile your program into machine language, it would run much faster. (Of course, getting a better BASIC interpreter will also speed up your programs.) Remembering my advice above about finding Atari software, you might be able to find such a compiler. But even compiled BASIC doesn’t come close to what is possible in assembly language. (Did you notice my shift from machine to assembly language? There is a technical difference between the two, but it is one we can ignore.)

Many, many articles have been written that provide you with handy machine language subroutines that you can call with Atari BASIC’s USR function. For example, also in the May issue, Rhett Anderson presented a set of routines for doing bitwise operations via USR calls. The problem with most of these routines: They all tend to reside in the same hunk of Atari memory (the so-called Page 6, memory locations $600–$6FF, 1536–1791 decimal), so you can use only one or, perhaps, two at a time. What happens when you need about 20 or 30 machine language subroutines? I did a whole series of articles, once upon a time, on writing self-relocatable code, machine language routines that can be loaded anyplace in memory; but it seems I was fighting a losing battle. In truth, though, it may be just as well: If you are ready to use 20 or 30 major assembly language routines, why not write the entire program in assembly language?

To do so, you need to learn two things: First, how to program in assembly language. Second, how BASIC performs its various operations. The first of these needs is answered by the books I mentioned in the first part of this article. And some of those books also go far, far beyond what Atari BASIC is capable of. But nobody seems to have written a book that shows, in a simple direct manner, how to convert the most common and useful operations of Atari BASIC into assembly language.

In particular, the topic of Atari graphics is poorly covered. There have been volumes written on display-list interrupts, player-missile graphics, custom character sets, and so on. But how does one do a simple little PLOT in assembly language? Finding the answer to that is like looking for the proverbial needle in a haystack.

When I first saw how well-designed the Atari Operating System (OS) was, I was impressed. That was more than eight years ago, and I still think it is the best OS in the world of small machines. I think you’ll agree when I show you next month how little work Atari BASIC must do to perform such seemingly complex operations as GRAPHICS, PLOT, and DRAWTO.

COMPUTE! ISSUE 87 / AUGUST 1987 / PAGE 74

INSIGHT: Atari

Bill Wilkinson

Graphics: From BASIC To ML

As promised, this month I will introduce you to how Atari BASIC (and, indeed, virtually every other language available for the 8-bit Atari machines) “talks” to the Atari Operating System (OS). It is important first to note that Atari BASIC has no built-in graphics subroutines. All graphics support in these machines comes directly from the OS. Today, this is not much of a revelation: The Atari ST, Apple Macintosh, and Commodore Amiga machines all come complete with an OS that supports numerous sophisticated text and graphics functions. To use these features with any given language (BASIC, C, Pascal, or whatever), the language designer only needs to provide the user with a simple interface and then to let the OS do the real work.

When the Atari 8-bit machines first appeared, though, they were unique in providing this kind of interface in a family of low-priced machines. For example, the Commodore 64 has no graphics whatsoever built into its OS, and the Apple II (up until the IIGS) had only limited low-resolution capabilities. Generally, such machines are not very friendly things to write assembly language for, in marked contrast to the Atari 8-bit models. (Looking back, if we Atari loyalists have any regrets or complaints, they might be only that Atari never produced the software tools—for example, computer languages—to fully exploit the capabilities of the OS. Other companies produced those languages—such as the Pascal from Kyan Software and BASIC XL/XE from my employer, OSS—but they never achieved the success that a language from Atari could have.)

The above history lesson was not entirely an exercise in nostalgia. It leads us to a very important point: If Atari BASIC didn’t have any graphics-oriented statements built in, you could still perform graphics fairly easily. Let’s take a look at some Atari BASIC equivalents:

10 GRAPHICS mode

is actually the same as

10 TEMP=12:IF mode<16 THEN TEMP=TEMP+16
11 CLOSE #6:OPEN #6,TEMP,mode,"S:"

Do you see what we did? The GRAPHICS statement is really just a special kind of OPEN. (Actually, I have left out consideration of the “+32” modes and have simplified things a bit, but for 99 percent of all programs the above will work fine.) In other words, all of the graphics modes of the Atari 8-bit computers are built into the OS ROMs (Read Only Memory chips).

The simplest BASIC statement to emulate is COLOR—you can leave it out altogether. We’ll take a look at why when we get to PLOT in a moment. But POSITION isn’t much harder (if we ignore GRAPHICS modes 8 and 24—see below).

330 POSITION xpos,ypos

can be emulated via

330 POKE 85,xpos:POKE 84,ypos

And, once POSITION has been dispensed with, PLOT becomes easy, also. (As you study these conversions, see if you can figure out why and how they need changing for GRAPHICS modes 8 and 24.)

281 PLOT xpos,ypos

becomes

281 POSITION xpos,ypos:PUT #6,mycolor

which, in turn, can be changed to

281 POKE 85,xpos:POKE 84,ypos:PUT #6,mycolor

Incidentally, mycolor is the color value you would otherwise have specified in the COLOR statement. And I am purposely using lowercase names for my variables to show that the names don’t matter. Choose your own as you like. Similarly, then, you can make the following substitutions.

978 LOCATE xpos,ypos,what

can be written as

978 POSITION xpos,ypos:GET #6,what

or, by extension,

978 POKE 85,xpos:POKE 84,ypos:GET #6,what

The last one, for now, is just a bit more exotic:

330 DRAWTO xpos,ypos

is actually performed as if you had used

330 POSITION xpos,ypos
331 POKE 763,mycolor:XIO 17,#6,12,0,"S:"

or, in more detail,

330 POKE 85,xpos:POKE 84,ypos
331 POKE 763,mycolor:XIO 17,#6,12,0,"S:"

Why did I go to the trouble of breaking the BASIC statements above down into equivalent forms? Because these equivalent forms are much easier to translate into assembly language, and such translation is the main point of this article. For example, POKE and PEEK are the easiest BASIC statements to translate to assembly language. The reason? Much of assembly language consists of nothing more than fancy ways to do PEEKs and POKEs. In this short set of articles, I can’t hope to teach you all the addressing modes of the 6502, so let me restrict myself to the simplest form.

For a first example, to emulate the BASIC statement

POKE 85,xpos

in 6502 assembly language, we need only code

LDA xpos
STA 85

which can be read as “LoaD the A register with the contents of xpos and then STore the contents of the A register into location 85.” That sounds simple enough, but the thing that makes the 6502 one of the more difficult CPU’s to program for is the fact that the A register can hold, at most, one byte of information.

You don’t see why that is a problem? Well, suppose we are doing work with GRAPHICS 8 or 24, where the horizontal (x) position can range from 0 to 319. A single byte can hold only values in the range 0–255. Oops. Ah, well, the analogy with Atari BASIC isn’t so bad here, because the POKE command has the same restriction. It, too, can only affect a single byte. By extension, then: How do you change more than byte when using POKEs in BASIC? By using more than one POKE, right? So, in assembly language, you must use more than one STA instruction.

But if I even hope to be able to finish this set of articles, I will have to cut off this introduction to 6502 assembly language at this point. If you did not understand any of this article, I would suggest you learn more about BASIC before trying assembly language. If you are a good BASIC programmer and this left you looking for more, then I suggest that you look into some of the books I recommended in my July column.

COMPUTE! ISSUE 88 / SEPTEMBER 1987 / PAGE 72

INSIGHT: Atari

Bill Wilkinson

Machine Language Graphics

Last month we looked at how Atari BASIC translates its own graphics-oriented statements into simpler pieces for its calls to the Atari’s operating system (the OS). Or, more correctly, we showed how you could do such an expansion. When Atari BASIC executes a statement in your program, it actually interprets it as a request to do a series of machine language operations—the equivalents of the simplified pieces we discussed last month.

The only example we’ve taken a close look at so far is POKE. I showed you that

POKE 85,xpos

may be accomplished by the machine language instructions

LDA xpos
STA 85

(Remember, I’m using variable names with lowercase letters on purpose, to remind you that the names are arbitrary. Please pick your own.)

Again, if you go back to last month’s column, you’ll find that the only BASIC statements I used to simulate the graphics commands of BASIC were OPEN, CLOSE, PUT, GET, and XIO. You may also have noted that each of these statements was associated with a channel number (specifically, channel 6, because that’s where BASIC does all its graphics operations). You won’t be too surprised, then, when I tell you that each of these five is actually a fundamental OS operation. Specifically, each involves a direct call to Atari’s Central Input/Output (CIO) processor. You may, however, be a little startled when I tell you these five calls represent all but one of the fundamental OS operations. (The missing one is represented by BASIC’s STATUS statement, which is generally used only for modem operations because of a flaw in BASIC’s implementation of the OS call.)

The point of all this is both simple and important: If you master these five OS calls from machine language, you can use virtually any input/output (I/O) operations you might need or want. For example, you can read records from a disk file using only three of these operations (OPEN, GET, and CLOSE). True, there are some variations on GET and PUT that are useful with lines of text or with large files, but the concepts are all the same. So, without further delay, let’s translate the five BASIC I/O statements into five machine language routines.

All I/O on the Atari is controlled through eight Input/Output Control Blocks (IOCBs), one for each channel or file number. Each IOCB is 16 bytes long and is located adjacent to another, beginning at addresses 832, 848, 864, and so on. (In hexadecimal, the sequence is $340, $350, $360, and so on.)The channels are numbered 0–7 in BASIC, but in machine language, we use the offset from the start of the first IOCB as the IOCB number. Under this system, the first block is still IOCB 0, but the fourth, known as channel 3 in BASIC, is designated as IOCB number 48 ($30). The reason for this is because it begins at memory location 880 ($370), which is 48 bytes beyond the start of IOCBs at location 832.

Graphics I/O

To perform any I/O operation, you put information into certain places in the IOCB of your choice. Then you put the IOCB number into the processor’s X register and call the CIO routine at address $E456 in ROM. (I’m not going to put in the decimal equivalents from now on. You really should learn to use hexadecimal—it’s much more logical for machine language.) The only magic, then, is in learning just what to put into the IOCBs.

Each IOCB consists of 16 bytes, as shown in Table 1.

All of these labels and bytes have uses (I refer you to Mapping the Atari, or Atari Roots for more details), but for our purposes, we need to learn about only a few of them. Again, I have prepared a chart (Table 2) to summarize which labels are meaningful for which graphics-related commands. (Remember, see last month’s column for examples of the BASIC commands we are using.) If a labeled location has a number assigned to it, then use that number with the operation. Descriptions in italics (device, for example) will be explained in the text that follows. An X means that the value in the corresponding location has no effect for the operation, and = = = means that the contents of the corresponding location should not be disturbed. For our purposes, these two symbols are equivalent: We won’t change the contents of these locations.

Table 1
Label Name Size in bytes Offset in IOCB Mnemonic Description
ICHID 1 0 Identifier
ICDNO 1 1 Device number
ICCOM 1 2 Command
ICSTA 1 3 Status
ICBA 2 4 Buffer address
ICPT 2 6 Put vector
ICBL 2 8 Buffer length
ICAX1 1 10 Auxiliary byte 1
ICAX2 1 11 Auxiliary byte 2
ICAX3 1 12 Auxiliary byte 3
ICAX4 1 13 Auxiliary byte 4
ICAX5 1 14 Auxiliary byte 5
ICAX6 1 15 Auxiliary byte 6
Table 2
Command ICCOM ICBA ICBL ICAX1 ICAX2
OPEN 3 device X type mode
CLOSE 12 X X X X
GET 7 X $0000 = = = = = =
PUT 11 X $0000 = = = = = =
XIO xio device X = = = = = =

CLOSE is the simplest of the routines. To do a CLOSE, you simply place the command number in the appropriate location, load the X register properly, and call CIO. The complete routine looks like this:

LDX #$60  ;using channel 6—graphics
LDA #12 ;CLOSE command
STA ICCOM,X ;put command in place
JSR $E456 ;call CIO

Don’t understand all that? Don’t worry. A few sessions with an assembler and a good tutorial will help you get started.

For OPEN and XIO, the buffer address (ICBA) field should contain the address in memory of the beginning of a string, and that string should have the name of the device (and/or file) that you wish to work with. For graphics, the device name is always S:. The command value (ICCOM) is always 3, for OPEN. For XIO, you use the same number you would in BASIC. (For example, 17 for DRAWTO, as we saw last month.)

For OPEN, the first two auxiliary bytes (ICAX1 and ICAX2) correspond to the two auxiliary values in the BASIC version of the statement. Although ICAX2 is usually given a zero value, when opening a graphics screen, it gets the number of the appropriate graphics mode instead. Usually no command, except OPEN, should touch the auxiliary two bytes. (Atari BASIC actually errs in making them part of the normal XIO commands, and that’s why we had to stick in a value of 12 in our DRAWTO equivalent last month. The exceptions that prove the rule are various modem command XIOs, used with the R: device.)

Finally, for GET and PUT, as we will use them for graphics, you need only put a value of zero in both bytes of the buffer length (ICBL), put the appropriate command value (7 or 11) in its field (ICCOM), set up the X register, and use the A register to transfer the byte. That is, if you want to PUT a byte to the screen— which, as I hope you remember from last month, is how you implement PLOT—put the byte (for example, the color value) in the A register just before calling CIO. If you want to GET a byte from the screen to simulate the LOCATE command, do all of the above and the byte will be in the A register after your call to CIO.

Too complicated? Cheer up. This is the worst of it. Next month we’ll put together some bona fide examples to try out. Next month will also be the last part of this series on converting BASIC graphics commands to machine language. I intended all of this to be an introduction (or refresher, for you old-timers) to machine language. If you want to take this topic further, you really must get an assembler and a couple of books. Good luck.

COMPUTE! ISSUE 89 / OCTOBER 1987 / PAGE 61

INSIGHT: Atari

Bill Wilkinson

Machine Language Graphics: The Final Installment

This month I will finally show you the machine language equivalents of the most important BASIC screen I/O operations. We’ll begin with an example. Suppose we wanted to implement a GRAPHICS 7 statement. From two months ago, we know that the equivalent low-level statements are

CLOSE #6
OPEN #6,12+16,7,"S:"

A direct translation into machine language follows.

;GRAPHICS 7
;CLOSE IOCB 6
      LDX #$60     ; IOCB number
      LDA #12      ; the CLOSE command
      STA ICCOM,X  ; put in place
      JSR $E456    ; call CIO
;OPEN IOCB 6
      LDX #$60     ; IOCB number
      LDA #3       ; the OPEN command
      STA ICCOM,X  ; put in place
      LDA #12+16   ; give it the same
                   ; value
      STA ICAX1,X  ; as you would in
                   ; BASIC
      LDA #7
      STA ICAX2,X
      LDA #DEVICE&$FF ; don’t worry why
      STA ICBAL,X  ; this works
      LDA #DEVICE/$100; it just does.
      STA ICBAL+1,X
      JSR $E456    ; do the real work

Don’t bother assembling this code yet—it won’t work without some of the help given later in this article.

Now, if you all you ever wanted to do was emulate GRAPHICS 7, that would be an adequate method. But in BASIC, the general form of the command is GRAPHICS mode, where mode is any numeric variable or expression or your choice. It would be better if we could emulate that in machine language. And, to some degree, we can.

In BASIC’s GRAPHICS statement, the mode value is called a parameter to the operation. In machine language, we also use parameters. With the 6502 microprocessor that Atari machines use, we usually try to pass the parameters in one or more of the three registers that the chip possesses: the A register (also called the accumulator), the X register, and the Y register. Suppose you need to pass an IOCB number. Since it needs to be in the X register for the call to CIO anyway, why not pass it there?

The listing that follows is not a program in and of itself. Rather it is a set of subroutines that your program may call (via JSR) to implement the given operation. At the very end of the article you will find a sample program that calls these subroutines.

When you use the subroutines in your own programs, you must note carefully the description of the parameters that I have given. Be sure that the appropriate registers contain the proper values before you jump to a subroutine.

The listing is given without line numbers. Some assemblers use line numbers, but, when they do, it’s for editing purposes only—the numbers have no effect on the program. Comments are preceded by a semicolon. You may omit any of them that you like. When you have typed all this in (and have checked it carefully for errors—one mistake can cause a lockup), you should save it (or LIST it, depending upon your assembler) to disk or tape. You can then use it as the nucleus of your own graphics programs.

;
; Equates
;
; Without these, the program won’t assemble properly
;
ICCOM = $342  ; the COMMAND byte in the IOCB
ICBAL = $344  ; the low byte of the buffer address (filename)
ICBLL = $348  ; the low byte of the buffer length
ICAX1 = $34A  ; auxiliary byte 1: type
ICAX2 = $34B  ; auxiliary byte 2: mode
;
CIO = $E456  ; Central Input/Output routine
ROWCRS = 84  ; ROW CuRSor—y position
COLCRS = 85  ; COLumn CuRSor—x position
ATACHR = 763  ; where line color goes for DRAWTO
;
; Now the working routines
;
; REMEMBER: these are only subroutines
; You must call them via JSR from your own code
;
;
; CLOSE channel
;
; Parameter: X register holds IOCB number
; On exit: Y register holds error code
;
CLOSE
     LDA #12  ; close command
     STA ICCOM,X  ; in place
     JMP CIO   ; do the real work
;
;
; OPEN channel,type,mode,file
;
; Parameters: X register holds IOCB number
;   A register holds type
;   Y register holds mode
;   the address of the file/device
;   name must already be set up
;   in the IOCB-
; On exit:  Y register holds error code
;
OPEN
      STA ICAX1,X  ; the type value
      TYA STA ICAX2,X ; and the mode, if appropriate
      LDA #3  ; OPEN command
      STA ICCOM,X ; in place
      JMP CIO  ; the real work
;
;
;GRAPHICS mode
;
;Parameter: A register holds desired mode
;On exit: Y register holds error code
;
GRAPHICS
      PHA   ; save the mode for a moment
      LDX #$60  ; always use IOCB #6
      JSR CLOSE  ; be sure it is closed
      LDX #$60  ; the same IOCB again
      LDA #SNAME&$FF; the "S:" device name
      STA ICBAL,X  ; must be put in place
      LDA #SNAME/$100 ; before we go further
      STA ICBAL + 1,X ; (take this part on faith)
      PLA   ; recover the GRAPHICS mode
      TAY   ; put it where OPEN wants it
      AND #16 + 32 ; isolate the text window and no-clear bits
      EOR #16  ; flip state of the text window bit
      ORA #12  ; allow both input and output
      JMP OPEN  ; do this part of the work
;
;
;PUT channel,byte
;
;Parameters: A register holds byte to output
;   X register holds channel number
; On exit: Y register holds error code
;
PUT
      TAY   ; save the byte here for a moment
      LDA #0
      STA ICBLL,X  ; $0000 to length
      STA ICBLL + 1,X ; as noted last month
      LDA #11  ; the command value
      STA ICCOM,X
      TYA   ; data byte back where CIO wants it
      JMP CIO
;
;
; byte = GET( channel )
;
; Parameter: X register holds IOCB number
;On exit: A register holds byte from GET call
;
GET
      LDA #0
      STA ICBLL,X ; $0000 to length…
      STA ICBLL+1,X ; as noted last month
      LDA #7  ; the command value
      STA ICCOM,X ; where CIO wants it
      JMP CIO  ; believe it or else, that’s all
;
;
;PLOT x,y,color
;
; Parameters: A register holds color
;   X register holds x location
;   Y register holds y location
; NOTE: not for use with GR.8 or GR.24
;
PLOT
      STX COLCRS  ;see my August column
      STY ROWCRS  ;these are just POKEs
      LDX #$60 ;the S: graphics channel
      JMP PUT  ;color is already in A
;
;
;byte = LOCATE( x,y )
;
;Parameters: X register holds x location
;   Y register holds y location
;On exit: A register holds color of point at (x,y)
;
LOCATE
      STX COLCRS  ;again, see column
      STY ROWCRS  ;from two months ago
      LDX #$60 ;the S: graphics channel
      JMP GET  ;color returned in A
;
;
;DRAWTO x, y, color
;
;Parameters: A register holds color
;   X register holds x location
;   Y register holds y location
; NOTE: not for use with GR.8 or GR.24
;
DRAWTO
      STX COLCRS  ; once more: see the article
      STY ROWCRS  ; from two months ago
      STA ATACHR  ; location 763, also in that article
      LDX #$60 ; again, we use IOCB #6
      LDA #17  ; the XIO number for DRAWTO
      STA ICCOM,X  ; is actually the command number
      JMP CIO  ; and that’s all we really need to do

Now that we have these routines, how do we use them? A full explanation would need at least the beginnings of a tutorial book. But here’s a short example. First, a small program in BASIC:

100 GRAPHICS 3+16
110 COLOR 2:PLOT 10,10
120 COLOR 3:PLOT 20,20
130 COLOR 1:PLOT 0,15:DRAWTO 30,15
140 GOTO 140:REM (just wait for RESET)

Now the same thing in machine language, using the routines of the program above. The only decision you will have to make is where in memory to place the assembled code. My first line reflects what should be a safe choice for most assemblers used in most 48K byte (or 64K byte) machines. If your assembler has a SIZE or MEMORY command, use it to get an idea of what is safe. In any case, LIST or SAVE your code to disk or tape before assembling, just in case.

  *= $6000  ; my "usually safe" location
;
START LDA #3 + 16  ; first
  JSR GRAPHICS ; emulate GRAPHICS 19
;
  LDX #10  ; now do PLOT 10,10
  LDY #10  ;(x and y locations)
  LDA #2  ;with COLOR 2, a slight
  JSR PLOT  ;change from BASIC, but close
;
  LDX #20  ;similarly:
  LDY #20  ;we want PLOT 20,20
  LDA #3  ;with COLOR 3
  JSR PLOT  ;one call does it all
;
  LDX #0  ;last PLOT:
  LDY #15  ;PLOT 0,15
  LDA #1  ;with COLOR 1
  JSR PLOT  ;
;
  LDX #30  ;and now the DRAWTO:
  LDY #15  ;DRAWTO 30,15
  LDA #1  ;still with COLOR 1
  JSR DRAWTO ;the routine does the work
;
LOOP1 JMP LOOP1  ;(loop here until RESET is pressed)
;
;Append all of the code for all of; the subroutines here
;
;Your assembler may need .END or END as
; the very last line
;
COMPUTE! ISSUE 90 / NOVEMBER 1987 / PAGE 71

INSIGHT: Atari

Bill Wilkinson

Atari’s Newest Drive

After three months of pretty heavy stuff, it’s time for a slightly different tack. And since my time has recently been monopolized by a project near and dear to all eight-bit Atari owners, I’ve decided to share some “secrets” with you. We’re going to take a very close look at the new XF551 drive from Atari.

The XF551 is a sleek drive, lower and wider than a 1050, and in a style and color that matches the XE computers. Quite simply, it looks good. As you read about the internals of the drive, I hope I can convince you that Atari has really done something right.

The XF551 started out as the XF351—the 3 designated a 3½-inch drive. Some people are disappointed that Atari changed over to a 5¼-inch drive, but I view it as a very positive step. Current users can upgrade to this drive, yet still keep and use all their old disks. Software manufacturers don’t have to produce two different versions of their software, and there are other points of compatibility.

For starters, the drive is compatible with disks created by virtually all Atari-compatible drives—in single, enhanced, and double density. Not only that, several of the different DOS systems I’ve tried have also worked flawlessly. And I know Atari has tested the drive with many, many pieces of commercial software with many different protection schemes. Summary: The drive works, and works well.

At a suggested price of under $200, the very fact that a true double-density drive is now available from Atari would be welcome news. But the drive is also double sided. That means that each disk can hold up to 360K—nearly three times the capacity of a 1050 and four times that of an 810.

As I write this article, Atari does not have a DOS that will support this extra capacity. However, the reason this drive has monopolized my time recently is simple—I have been writing a new DOS for Atari. ADOS (as it will be known) is full-featured, with subdirectories, random access files, a combination menu/command structure, and much more. However, it is not releasable as I write this, so back to the drive.

Inside The Drive

As you may remember, I discussed SIO (Serial Input Output) as it applies to disk drives, in the September 1985 issue. I noted that the four basic SIO commands are R, W, P, and S, for Read, Write, Put, and Status, respectively. Besides these, the Atari 810 and 1050 only understand format commands.

Then, in the next issue, I explained the concept of a device configuration table, as implemented by all the makers of true double-density drives. Well, we can add Atari Corporation to that list: The XF551 supports the Percom standard configuration table. That means you can tell the drive that it’s an 810, a 1050, a double-density drive, or (best of all) a double-sided double-density drive. Or, perhaps just as important, the drive can tell you what kind of disk it holds. For these capabilities, we add N and O (which I think of as iN and Out) commands on the serial bus.

But there’s even more. If you send it a Read or Write or Put command with the upper bit set (the inverse video bit, in screen terms), then the XF551 transfers data in high-speed mode. To take advantage of this, you need a compatible DOS, but ADOS is nearly ready and I’m sure others will be modified to support high-speed transfers.

Last, but not least, the XF551 adds a special format command (hex $A1, an inverse-video exclamation point) that tells the drive to use a special high-speed interleave that enhances the high-speed read and write commands even more. (But note that ordinary reads and writes are even slower than usual on disks formatted in this special way, just as they are on Sparta DOS ultraskew disks used in non-US Doubler drives. I should warn you that each of these drives seems to use a slightly different high-speed scheme.)

So the drive gets my nod of approval from a software standpoint. But what about the hardware? Will the drive stand up to physical abuse, overheating, and the like? Truthfully, I have not had even the prototype long enough to make a definitive statement on this point. But I have had the cover off the drive, and I have looked at its construction. It looks great. The inside is as well built as the outside.

In fact, Atari has never produced a more solid piece of equipment. The drive frame is heavy-duty cast aluminum, the mechanical parts are finely polished and aligned, and the controller board appears to be adequately ventilated. Only one point of caution: Double-headed drives are more sensitive to shock and misalignment than their single-headed cousins. Treat the drive with care. Always use its cardboard protector when you move it. Make sure it has adequate ventilation. In other words, use common sense.

If this column sounds like an advertisement for the XF551, I won’t apologize—I’m not getting a penny in royalties on the drive or ADOS. This glowing report is for one reason and one reason only: I just had to tell you that Atari has not abandoned the eight-bit market. And they’ve proven that fact in grand style.

COMPUTE! ISSUE 91 / DECEMBER 1987 / PAGE 70

INSIGHT: Atari

Bill Wilkinson

Crosswords And Home Computers

I recently attended a meeting of the ESCAPE user’s group (of Santa Cruz, California) and had the pleasure of participating in a lively discussion of programming in general, and program design in particular. I must give John Pilge credit for starting the session off with an example program that I’m still thinking so hard about. I would like to share his tough nut with all of you.

Consider a simple acrostic square, such as this:

T   O   N

A   R   E

B   E   T

There are six interlocking words in the square, three vertically and three horizontally. The problem: Given a list of three-letter words, can you write a computer program that will create an acrostic square? Better yet, given a list of five-letter words, can your program use ten of those words to create a larger acrostic square?

John did, indeed, produce a program to perform that task, but when he gave it a list of several thousand five-letter words to work with, and then performed some speed measurements, he estimated that his Atari 800 computer would take 67 years to finish! What can be done to improve that time? The most obvious answer would seem to be to move up to a faster machine. Why not? An Atari ST might even be able to do the job in just a handful of years. Hmmm…not good enough yet? I didn’t think so either.

So now we are into the meat of this month’s column: How can we write programs to get the best possible performance out of our little beasts?

Speed Demons

For starters, John did write his program in BASIC. Now, as convenient as BASIC is, it is certainly not a language to use when you need speed, so let’s consider using another language. Typically, compiled languages will run programs from 10 to 200 times faster than interpreted BASIC (depending on what compiler you use and what kind of program you are testing).

If you are ready to resort to assembly language, you can improve those numbers by an additional factor ranging from 2 to 20. Still, that means that even at best, a change of languages will get us an improvement of no more than, say, 400 to 1 over BASIC. So, 67 years becomes about two months. Sigh, I’m not sure I could do without my machine for that long.

Besides changing languages or computers, there are two ways to attack the problem of a too-slow program. The first is to examine your code carefully, looking for the little things that slow down the system. For example, most of us have learned that with Atari BASIC (and indeed, with most BASICs), you can improve performance markedly if you put FOR…NEXT loops at the beginning of your program. Yet sometimes, even that is not enough. As I mentioned once upon a time in an article about card shuffling, the only real solution might be to find another method to solve the problem.

Try the listing accompanying this article, as is. Note how quickly the program finds the acrostic squares. Then, run it twice more, removing lines 1430 and 1450, in turn, to see the effect of increasing the word list even a little bit. Now, imagine the effect of having thousands of words. Worse, imagine thousands of five-letter words. Scares you a little, doesn’t it?

In the same vein, consider crossword puzzles. If five-square acrostics are a tough nut for computers, imagine how long it would take your Atari computer to generate 12 × 12 crossword. Even today, there is no real crossword-generating program for any personal computer. (Yes, I am aware of Crossword Magic, but that program only aids crossword makers. It doesn’t even come close to being able to generate puzzles by itself.) Yet, there are humans who can produce original crossword puzzles in a matter of hours or even minutes.

For you nonprogrammers, I hope I’ve shown you that computers can’t do everything as well as humans, There is a not-too-hidden message here as well: Program design is very important. Yes, careful implementation is important (no one wants a buggy program, of course), but sometimes a good design can make the real difference, hopefully producing a program that can finish its task before you nod off with boredom.

For you programmers, here is a challenge: Can you come up with a better method? I would hope so. My version is fairly simplistic (probably much more so than John’s program, which I have not seen) and not too hard to follow. I do have a sneaking hunch, though, that you won’t improve the program too much if you stick to BASIC—not because BASIC itself is slow (although that doesn’t help)—but because BASIC is so weak when it comes to data types and structures.

Foreign Languages

And where is this leading us? Into one of my favorite topics: computer languages. More specifically, I would like to explore the strengths and weaknesses of the various languages available for Atari computers (both eight-bit and ST). Beyond that, I would like to discuss some of the more fundamental programming topics. In particular, in next month’s column we will begin looking at the advantages of strucured data (and no, that’s not the same as structured programs).

In the meantime, I’m going to be giving the acrostic squares problem some thought. Certainly, if you get any brilliant ideas for solving the acrostic squares problems, please let me know. You can write to me at P.O. Box 710352, San Jose, CA, 95171-710352. Or, you can contact me in one of the Atari forums on CompuServe. (I am active in the Atari eight-bit forum, especially since they introduced the Kyan/OSS/ICD special topics areas. My PPN is 73177,2714.) And please, if your solution is a lengthy one, consider sending a disk or uploading the program.

10 REM COPYRIGHT 1987 COMPUTE! PUBLICATIONS, INC. ALL RIGHTS RESERVED.
20 PRINT "{CLEAR}COPYRIGHT 1987":PRINT "COMPUTE! PUBLICATIONS, INC.":PRINT "ALL RIGHTS RESERVED."
30 FOR TT=1 TO 1200:NEXT TT:PRINT "{CLEAR}"
1000 REM THE HORIZONTAL WORDS:
1001 DIM H1$(3),H2$(3),H3$(3)
1010 REM THE VERTICAL WORDS:
1011 DIM V1$(3),V2$(3),V3$(3)
1020 REM A TEMPORARY AND MASTER WORD LIST
1021 DIM T$(3),U$(31100)
1030 REM INITIALIZE THE MASTER WORD LIST
1040 FOR I=1 TO 1000000 STEP 3
1050 READ T$
1060 IF T$<>"*" THEN W$(I)=T$:NEXT I
1070 REM NOW BEGIN THE REAL WORK
1080 WCNT=I-3
1090 FOR H1=1 TO WCNT STEP 3
1100 H1$=W$(H1,H1+2)
1110 FOR H2=1 TO WCNT STEP 3
1120 IF H2=H1 THEN 1350
1130 H2$=W$(H2,H2+2)
1140 FOR H3=1 TO WCNT STEP 3
1150 IF H3=H2 OR H3=H1 THEN 1330
1160 H3$=W$(H3,H3+2)
1170 V1$=H1$(1):V1$(2)=H2$(1):V1$(3)=H3$(1)
1180 FOR V1=1 TO WCNT STEP 3
1190 IF V1$<>W$(V1,V1+2) THEN NEXT V1:GOTO 1320
1200 V2$=H1$(2):V2$(2)=H2$(2):V2$(3)=H3$(2)
1210 FOR V2=1 TO WCNT STEP 3
1220 IF V2$<>W$(V2,V2+2) THEN NEXT V2:GOTO 1320
1230 V3$=H1$(3):V3$(2)=H2$(3):V3$(3)=H3$(3)
1240 FOR V3=1 TO WCNT STEP 3
1250 IF V3$<>W$(V3,V3+2) THEN NEXT V3:GOTO 1320
1260 PRINT "FOUND ONE!"
1270 PRINT
1280 PRINT ,H1$
1290 PRINT ,H2$
1300 PRINT ,H3$
1310 PRINT
1320 REM (TO HERE IF VN$ NOT IN LIST)
1330 REM (END OF 'IF H3=H2 ...' )
1340 NEXT H3
1350 REM (END OF 'IF H2=H1 ...' )
1360 NEXT H2
1370 NEXT H1
1380 STOP
1390 REM
1400 REM THE WORDS!
1410 REM
1420 DATA ARE,BET,NET,ORE,TAB,TON
1430 DATA *
1440 DATA TOP,PET,TAP,POT,TAN,PEN
1450 DATA *
1460 DATA LAP,LOP,CAP,COT,CAT,CAN
1470 DATA *