@L}5 _$% l0$)$$Hȱ$ UhL" `e$$%`$%`  R@P!( L(1   Y I`  d  Ld M * @  $ % CC$$)%1 Udߥ$9%: !0 S$% DD˙`  }J)Lr d M * @  $ % CC$$)%1 Udߥ$9%: !0 S$%} DD˙`  }J)Lr J  ((  p L ()   J}L= ( L 0q A    IB JC;? D W } LL  ` W )LA!  ߰")-݆ p" } $G@LL 08`Q")<2Q0 -G$Ș݆ UL# ; p8(()(0ʥ)NQ` }$GȘ݆LU )L ݆ L GȘ ݆LL )W>Z   HH)H }p h  hyhy D L> L JJ    ! LA*` BF }7'8  M HN H` 8 Z  \LdJJ!"! GFE@F (!L }EE !E^ ^ E E7EȩEdE/EȩE  D } .L }  ;F d  ;?F7F? ( .   Z D LL d } . D  L    p  E` , d)  D L) 0BM݊L݉} ML  N݆ L NLML [ TEqEHȱEqEh 0Gȹ G} HLL GɛL  LFREE SECTORS G) *Gȩ GȽG GȌ*jj >G} C8jJ3j2CD( C202C ԠBX` N 1? l LlD:RAMDISK}.COMLu L1 L ;LHL  T`  `8  ɐ     `TU  } L ? .  t`GBJ ~DEHI B V0dV!}QDEHI VF9 ,0 ,0 s0hhL  L` H hDHEh"}DEL8HI4 0 HI,0 0  9 .G VLO#},0 L4*IJ`llD1:AUTORUN.SYSNEED MEM.SAV TO LOAD THIS FILE.D8:MEM.SAV J y08 B|DEHI$} V0 0`B;DEL`?<0LV`@ʆ v s? F0Ξ05: [ BDEHI%} VY8 B V  @  /DE `E:D8:DUP.SYSERROR-SAVING USER MEMORY ON DISKTYPE Y TO &}STILL RUN DOS B;DE J  (` 9 V⪍ ઍ  -'}LLu ÝDEHILV 9 .l 9 .l  `` s$B BH(}I|DE V BLV nB,DE JLV B V BLVDEIʩ BꭝLu }05  15 2 2151^116115ύ011$r2ԭ5 3)5)5ԭx G*}@@L00)+& 2 L0=ɛ -L!1LV1L1 /3 3ȹ441L-4 v3 25 2H 2h͔5+}L3L3 2 k3U hh`DOC`QL3P1Lk0S1H /3h0B k3@ VL0LU!#$53 1181118,}1111Ε5 1Lk0552 1Ȍ1i111i11115Lk0pppB4f5&&&&&&&&^6f^6&&&&&-}&&A1@BJ D2ELVK@BHILV^Щ6 2BD2EJ VBD5EHI0 V0%5.}5 2iХiL[2 25 25`D:*.*` i@`8 ``@i `8@`0 BLV525<4/}5`HH i ԍΗ552 ԍhh@ Lح1Э1Ѣ 24.4 240}4`D4E` 2BJ k3LVRH` 2BD4EhK)I JLV333 " " THEN LOCATION = X:GOTO 90 60 NEXT X 70 PRINT "All Blanks":END 80 REM ** Substring ** 90 LA i}ST$ = STRING$(11,X) 100 LENGTH = X-10 110 PRINT LAST$; ", "; 120 PRINT LENGTH;" characters long" 130 END 200 DATA J j}ACK BEEBE 12345This will print: BEEBE, 5 characters longThis program searches from position 20, to position k}11 backwards, onecharacter at a time, searching for non-blanks. When it finds a non-blank (line50) it assumes it's found th l}e end of a name, and jumps out of the loop. Thelocation in the string is equal to X, the loop variable. We can specify asu m}bstring beginning at position 11, and continuing to X.------------------Substring Summary:------------------When writi n}ng programs dealing with string inputs, which are the majority ofprograms, you will often find it handy to have string handl o}ing ability. Onecommon application is in error checking strings brought in from the keyboard.BASIC comes through once agai p}n, with a built-in function!Note that the actual substring function STRING$(B,E) may appear on either sideof the equals si q}gn, depending on the application.An oddity about Atari BASIC is that we cannot create arrays of strings. Thatis to say, we r} cannot create a string array that holds 25 different firstnames. Other BASIC's do allow that. A prime difference is that o s}ther BASIC'suse: DIM NAME$(25)To declare an array that holds up to 25 names. We in Atari BASIC need to usethat state t}ment to declare a string of 25 characters in length. You DON'T needto dimension individual strings in other BASIC's.This i u}s a big quirk for a BASIC, but...that's why our systems cost so fewdollars.-----------------Numerical Arrays:----------- v}------On to a new topic, arrays. Arrays are an extremely useful concept, oftendifficult to visualize at first. Eventually w}the light bulb goes on, and you'rethere! Persevere until you grasp these. It's totally worth it.Arrays are like....a Ferri x}s wheel. Imagine a Ferris wheel with a total of 10seats around it. Each seat has a number painted on it's side, 1 through 10 y},visible to us on the ground. As it turns around, we stop it at each seat, andput a test paper with a grade written on it, z}on each seat. When we're done wehave: SEAT # GRADE 1 85 2 88 3 {} 90 4 80 5 92 6 87 7 79 8 95 |} 9 93 10 90We could refer to the test grades individually, as 85, 88, etc, or we couldref }}er to them by their seat number, as SEAT 1, SEAT 2, etc. Note there are two90's. It's easy to tell them apart by referring t ~}o them as SEAT 3 and SEAT 10.Now, let's say the Ferris wheel is painted yellow. We could even use ashorthand kind of notat }ion for the seats and number them like this: YELLOW(3) = 90 YELLOW(6) = 87 etc.This is in fact how arrays }work.--------------DIM Statement:--------------This statement allocates space for a numeric array. We specify a numeric }variable name, like YELLOW, and specify the number of elements it canhold,using a DIM or dimension statement as: 10 D }IM YELLOW(10)This declares (allocates storage for) a numeric array of ten elements namedYELLOW. We can load it, then refe }r to it's elements (contents) by the arrayname and numbers in parenthesis. Additional arrays can be visualized asdifferent }color Ferris wheels, even with different numbers of seats as: 10 DIM YELLOW(10), BLUE(5), RED(25)More than one array may } be dimensioned in the same DIM statement. Array namesand sizes must be separated by commas.Note that strings are dimensio }ned with the same DIM statement. How do we tellwhether a string or array is being DIMensioned? Easy! Strings always end in }adollar sign, so a numeric variable name indicates an array.Let's declare a numeric array called JOHN to hold his eight gr }ades for thesemester. We can put John's grades in a data statement, read them into ourarray, and perform various operations } on them like taking the average, etc.Here's how to declare and load the array: 5 REM ** John's 8 grades ** 10 DIM J }OHN(8) 20 FOR X = 1 to 8 30 READ GR 35 REM ** Load GR into JOHN(X) ** 40 JOHN(X) = GR 50 NEXT X 60 REM ** P }rint it out ** 70 FOR Z = 1 TO 8 80 PRINT JOHN(Z) 90 NEXT Z 100 END 200 DATA 90,80,70,75,88,92,79,83This will }print: 90 80 70 75 88 92 79 83This is not a highly practical example, but does demonstrate the principle o }floading arrays. Note we read our data into a variable GR, then assign thatvalue to an element of the array. We use both l }oop counters X and Z toincrement the array elements, as we load or print them. Keep reading thisexample until it makes sens }e. This is a classic way of reading data (from DATAstatements) into an array. Usually you load arrays with data before you u }sethem. READ statements read in the next piece of data and load it into thevariable specified.A common use of arrays, is }to hold a set of numbers to be sorted. Elements arethen switched into proper order. It is easy to keep track of, reference, }andaccess numbers as elements of an array. I used a six element array to holdLOTTO number guesses. I generated a random num }ber, checked to see if it hadbeen guessed already, then loaded it into an array. When the array was full, Isorted it, then }used a FOR-NEXT loop to print my six number guess out on thescreen, or printer. The combination of the FOR-NEXT loop and the } array is anatural for reading or writing an array.-------------------------Multi-Dimensional Arrays:------------------- }------So far, the arrays we have used here are one dimensional arrays. We may usetwo dimensional arrays also in Atari BASI }C. Two dimensional arrays may bepictured as having rows and columns, and are dimensioned that way as: 10 DIM DBLARRAY(5 },10)This is an array having 5 rows, and ten columns. Uses for these include usingthe rows for individual students, and col }umns to hold grades, or rows to holdsuits, and columns to hold cards, rows to hold teams, and columns to holdscores etc.E }lements of the array are referred to by row and column as: 10 REM ** Double Array ** 20 DIM DBLARRAY(5,10) e }tc. to load array etc. 140 NUM1 = DBLARRAY(3,5) 150 NUM2 = DBLARRAY(4,9) etc.This program would assig }n elements to the NUM variables. Two dimensionalarrays are slick, and can often be used in place of two or more separatearr }ays. Start with one dimensional arrays, and work up to being able to usetwo dimensions.--------------CLR Statement:----- }---------Atari BASIC has a CLR statement, an abbreviation for CLEAR. Rather than clearthe screen, like the CLS command in }MS-DOS, this unDIMensions all strings andarrays. It's use is: 40 CLRThis can be very handy for the program that tries } to DIMension a string orarray inside the program, that is already DIMensioned. You can sometimes use aCLR before your redi }mension, and achieve your result. This may however causeworse problems by unDIMensioning other arrays or valuable strings wi }thin theprogram.-------------Card Dealing:-------------Okay, let's put it all together and write something real (or se }mi-real.)Dealing cards will give us a chance to demonstrate a great use of arrays,namely to avoid duplication in guessing p }rograms. For example to insure yourLOTTO program guesses six or seven different numbers, and your card programdoesn't deal }out multiple Aces of Spades!We can simulate a card deck with two variables, one called CARD and one calledSUIT. We will us }e the built-in Random Number generator (Lesson 5) to generatea number from 1 to 13 for CARD, and 1 to 4 for SUIT. Each combi }nation of twonumbers will represent a CARD and a SUIT.I want my program to print the card out on the screen as: Three o }f Diamonds Four of Hearts Jack of Diamonds Ace of Spades etc.The way I'm going to get from numbers in the varia }bles CARD and SUIT, to wordson the screen, is to use a pile of IF-THEN statements to print the number outin words as Ace, T }wo, Three, etc.We can number the SUITs from 1 to 4, Clubs, Diamonds, Hearts, Spades, and alsouse IF-THEN statements to pri }nt these words. This part is pretty straightforward so far.Now, how can we insure we haven't dealt a card already? I guar }antee you thatyour Random Number generator WILL repeat itself many, many times before itdeals all 52 cards !The Handy Hac }k of the Day, involves DIMensioning a numerical array calledARRAY, with 52 elements. When the program starts up, we will loa }d a zero ineach of the 52 array elements, one for each card of the deck. Once we deal acard, we will change that card's ele }ment from zero to 9 to indicate we've usedthat card already. Now, each time we deal a card, we check for a zero (virgin)or }a 9 (used already) in that element of ARRAY. Isn't that clever? This haswide application in programming, to keep track of w }hether or not we have useda card, LOTTO number, etc.We use an algorithm that takes the card number and suit number, and co }nvertsit to a number from one to 52. This algorithm is: NUM = CARD + 13*(SUIT-1)For example if CARD = 1 and SUIT = 1 t }hen NUM = 1 + 0 or 1, the first card inthe deck. If CARD = 13 and SUIT = 4, then NUM = 13 + 13*3 or 13 + 39 or 52,the last }card in the deck. If CARD = 7 and SUIT = 3, then NUM = 7 + 13*2 or 7+ 26 or 33. This is a classic for your notebook.Each t }ime we deal a card, we check to see if that card's place in ARRAY has azero or a 9 in it. If a zero is present we have not u }sed this number before,so we will use it, then put a 9 in it's place in ARRAY(NUM). If we deal acard, and find ARRAY(NUM) i }s NOT zero (it's a 9), we simply redeal, knowingwe've already used that card.Keep struggling until you understand using ar }rays to prevent duplication innumber choosing programs. It's so much simpler than a pile of 51 IF-THENstatement like: 53 }0 IF NUM(46) = NUM(45) or NUM(46) = NUM(44) or NUM(46) = NUM(43) or NUM(46) = NUM(42) etc. etc.You get the idea.Okay }, here we go! Here's a nice card dealing program for you. Type this one inand try it. It's actually a short one, but I inclu }ded plenty of REM's toclarify what's going on.10 REM ** CARDS.BAS **20 REM 30 REM ** CARD DEALER **40 REM ** by Jackso }n Beebe **50 REM Ver 1.3 2/13/8860 REM 70 REM ** Zero card counter **80 COUNT=090 REM 100 REM ** Clear screen & sk }ip 2 **110 ? CHR$(125):? :? 120 REM 130 REM ** OPEN Keyboard for 'more?'140 OPEN #1,4,0,"K:"150 REM 160 REM ** Dimensi }on ARRAY **170 DIM ARRAY(52)180 REM 190 REM ** Fill ARRAY with Zeros **200 FOR X=1 TO 52210 ARRAY(X)=0220 NEXT X230 RE }M 240 REM ** Loop to deal 5 cards **250 FOR DEAL=1 TO 5260 REM 270 REM ** Deal CARD & SUIT **280 CARD=INT(RND(0)*13)+12 }90 SUIT=INT(RND(0)*4)+1300 REM 310 REM ** Algorithm to turn CARD &320 REM ** SUIT into a number between330 REM ** 1 and 5 }2 **340 N=CARD+13*(SUIT-1)350 REM 360 REM ** Check for a Zero in370 REM ** ARRAY (virgin) or a 9 in380 REM ** ARRAY (use }d already).390 REM ** If used, deal again **400 IF ARRAY(N)<>0 THEN GOTO 280410 REM 420 REM ** Good card, so put a 9430 }REM ** in ARRAY(N) **440 ARRAY(N)=9450 REM 460 REM ** Print out CARD & count **470 COUNT=COUNT+1480 IF CARD=1 THEN ? " } ACE";490 IF CARD=2 THEN ? " TWO";500 IF CARD=3 THEN ? " THREE";510 IF CARD=4 THEN ? " FOUR";520 IF CARD=5 THEN ? " } FIVE";530 IF CARD=6 THEN ? " SIX";540 IF CARD=7 THEN ? " SEVEN";550 IF CARD=8 THEN ? " EIGHT";560 IF CARD=9 THEN } ? " NINE";570 IF CARD=10 THEN ? " TEN";580 IF CARD=11 THEN ? " JACK";590 IF CARD=12 THEN ? " QUEEN";600 IF CARD= }13 THEN ? " KING";610 REM 620 REM ** Now print SUIT **630 IF SUIT=1 THEN ? " of CLUBS"640 IF SUIT=2 THEN ? " of DIAMOND }S"650 IF SUIT=3 THEN ? " of HEARTS"660 IF SUIT=4 THEN ? " of SPADES"670 REM 680 IF COUNT=52 THEN ? :? "* * * That's ALL f }olks * * *":? :CLOSE #1:END 690 NEXT DEAL:REM ** Deal 5 **700 ? 710 ? COUNT;" cards. Want to deal more ?":? 720 GET #1, }A730 IF A=89 OR A=121 THEN 250:REM 'Y'740 END This has a few "goodies" in it. For instance line 140 opens a channel to t }hekeyboard, so that in lines 710-720, when you are prompted for "Want to dealmore ?", you don't need to hit RETURN after yo }ur choice. The program "GETs" acharacter and checks for 89 and 121, the ASCII numbers for the upper and lowercase letter Y' }s. Any other character ENDs the program. This is an example ofletting a program "fall through" a test to quit, that shoots c }ontrol back upthe program if you choose. A counter called COUNT is initialized to 0 in line80, and when it reaches 52 (line } 680), the program ENDs also.This program deals cards in groups of five, offering you the chance tocontinue or quit. Note }that this program DOES NOT deal the same card twice.This is a demo of the ARRAY method described above to eliminate duplicat }ions.Note that the more cards it deals, the slower it goes. The actual dealingdoesn't change speed, but as it randomly rep }eats itself many, many times, ithas to deal quite a few cards at the end, in order to "hit" the last fewremaining cards. No }te how long it takes to deal the first two, versus the lasttwo cards in the deck.Play around with this one, and hack a few } additions. For example, add code todetect PAIRS dealt in a "hand". Try rewriting this program to use a twodimensional arra }y, and eliminate the algorithm to convert SUIT and CARD to anumber from 1 - 52.--------Summary:--------Well, there you } have it. Arrays are a very handy, very valuable concept. Playwith them until they are clear. They show up in almost all pro }gramminglanguages. You should try writing a LOTTO guessing program to see if youunderstand how this all works. Arrays are }handy for storing any kinds ofnumeric data in a program.------------------------------------This concludes Lesson 8 of L }earning to Program in Atari BASIC. Be sure tocatch Lesson 9 which includes: Storing and Moving Data IOCB's }Devices OPEN # Statement CLOSE # Statement GET # Statement PUT # Statement Programing Tips: V }ersions Start in the middle Using Macros An 'advanced' DOS------------------------------------Contac }t me at: Jackson Beebe Prairie Data Fields 807 West Hill Street Urbana, IL 61801or 72550,317 on CompuServe } byeairie Data Fields 807 West Hill Street Urbana, IL 61801or 72550,317 on CompuServe LEARNING TO PROGRAM IN ATARI BASIC------------------------------------LESSON 9 Version 1.11INPUT AND OUTPU}T I/O------------------------------------(C) COPYRIGHT 1988 by Jackson BeebeThis lesson is placed in the Public Domain. }Individuals, user groups and BBS's may reprint, copy or distribute it, as long as this notice remains intact with the lesson.}------------------------------------CONTENTS:------------------------------------ Storing and Moving Data IOC}B's Devices OPEN # Statement GET # Statement PUT # Statement INPUT # Statement Editing Ve}rsions CLOSE # Statement ENTER-IN FEATURE * RWP.BAS (an ENTER-IN program) Renumbering SectionsThis is Le}sson 9 of LEARNING TO PROGRAM IN ATARI BASIC, brought to you by Jackson Beebe. Contact me at the address at the end of the le}sson.Greetings. Welcome back to old users, and 'Hello' to new users. This is the ninth in a series of programming lessons,} designed to help you get the most from your 8 bit Atari computer, a very powerful, and flexible machine. Previous Lessons ar}e available on CompuServe's ATARI8 Forum, and other BBS.------------------------------------Feedback:------------------}------------------THANKS to John McGowan of Columbia, Missouri for pointing out the correct location of the 'runtime stack'} as Locations 142,143, and not page one as I mistakenly said in Lesson 6 (the POP statement.) Sorry, I had the wrong stack.}And a HELLO and warm thank you to Dave Brehm of Mishawaka, Indiana who writes: "I do some programming, but...I am finding bet}ter ways, different ways, and other things I either didn't know about or had forgotten about."It's reassuring to know someo}ne IS OUT THERE ! Thanks.------------------------------------Introduction:------------------------------------Most new }users don't know much about computers, but buy one to perform a set task, like word processing or game playing. Atari compute}rs have many excellent games written for them, with outstanding graphics. Many users initially regard their Atari as a slick }toy, and well-built plaything, and gradually become aware, that it is a Real Computer, and even can be programmed (whatever t}hat is?) I want you to realize that you have (or can buy for $17.50 post paid) a programming language BASIC, available to yo}u as part of this package, that can unleash the enormous power and speed of the modern day computer, in whatever ways you can} imagine, think up, and write down.BASIC is a very "learnable" language, that can help you get started in literally one day}. Many languages cannot make that claim.------------------------------------Storing and Moving Data:--------------------}----------------Up to this point, we have been learning the nuts-and-bolts commands and statements of BASIC that allow us t}o perform basic computing functions. We can now write and store programs on disk, load them back in and use them.Programs m}ay be thought of as large "streams of data" entered from the keyboard, sent to or from a disk drive, to a printer, etc. Compu}ters move this data around a lot during use. For instance in printing out a BASIC listing while programming, the computer act}ually goes through a process of checking to see if a printer is present and ready, then sends a stream of data, one byte at a} time from RAM, to the printer. The computer starts and stops sending data from time to time, so it doesn't exceed the print}er's maximum speed, and quits sending when finished. Printers print out this stream of data, and disk drives write it to disk}, storing it as a string of magnetic impulses.BASIC includes a set of built-in commands that allow us to send or receive da}ta between devices, with very little effort. Again, BASIC is a HIGH level language, meaning it operates high up, away from t}he machine, eliminating a great deal of administrative housework for us. This is very evident here, where the use of a few co}mmands, can transfer data flawlessly between complex peripherals without the user even knowing how the machine accomplishes t}he task. The true task of a programming language.------------------------------------IOCB's:----------------------------}--------IOCB stands for Input/Output Control Block. Atari has 8 of these, numbered 0-7. IOCB's are sixteen bytes long, and }are used for communication and housekeeping between devices. For example they point to the device that is open; specify what }kind of I/O we are doing; status of the last operation; address of the data buffer; filename; amount of data yet to be transf}erred; disk sector number, etc.The computer itself always uses IOCB #0 for the screen editor, so we should get in the habit} of starting with #1, when specifying an IOCB. IOCB #7 is used by BASIC to communicate with the printer, disk drive and casse}tte.An example of their use will clear this up a little, after we get to the OPEN # statement. Bear with me a moment.---}---------------------------------Devices:------------------------------------We reference devices by using names already }assigned to Atari peripherals, and other data devices. They are: NAME DEVICE C: Cassette E: Screen Edito}r (kybd + scrn) K: Keyboard P: Printer R: RS-232 Handler (850) S: ScreenFor example, from the  }DOS menu, we can COPY a filename from D1:FILENAME to P: to get a printed copy. We can COPY from D1:FILENAME to E: to see the  }file listed to the screen. Use CONTROL+1 to start and stop the screen scrolling. Try this. Copy a few files to the screen an }d/or printer. Copying to the screen is the most common way to tell what any file on your disk is. Note what BASIC, text and b }inary files look like.------------------------------------OPEN # Statement:------------------------------------Now we  }put it all together with a statement to open an IOCB to a specific device. Devices must be opened before use with an OPEN # s}tatement. One additional piece of info is needed to use the OPEN # statement, and that's the code for the type of I/O we're p}erforming. I/O modes are coded as follows: I/O Code Mode Operations 4 Input Read Only 6 Direc}tory Disk Drive DIR 8 Output Write Only 9 Append Add to End of File 12 Update Read and Writ}eThese are used in an OPEN # statement as follows: OPEN #(IOCB),I/O Code,0,"Device"For example:10 OPEN #1,8,0,"P:"} opens printer for output.15 OPEN #1,4,0,"K:" opens keyboard for typed input.}13 OPEN #2,6,0,"D1:*.*" for a disk directory of D1:10 OPEN #3,12,0."D:FILE.TXT" opens } FILE.TXT for reading and writing (update)12 OPEN #1,9,0,"D:TEXT.DOC" opens t}he file TEXT.DOC for appending data to the end.The first arg}ument to OPEN # is the IOCB. We are free to choose any number we like. One through six is a good idea to start. Just use #1 }to begin, and if you find you need to open another, for instance to read and write separately, use #2, etc.The second argum}ent is the I/O code, for the type operation being performed. See chart above.Note that the third argument to OPEN # is alwa}ys a zero. That's just the way it is. It has to be there to work. The fourth argument is the device name, always enclosed }in quotes, with a colon. Don't forget the quotes.Don't despair, or panic, these aren't really hard. You keep a chart of exa}mples of devices, and I/O codes in front of you on a "cheat sheet", as you may not use them often enough for memorization. S}o much of programming involves having a good, ready, reference library, rather than memorization as you may think at first. S}ee RWP.BAS at the end of this lesson for reference.At first, it's hard to tell whether you should code Input, or Output mod}e for a given transfer, but keep in mind that it's input or output with respect to RAM, or from the point of view of 'the pro}gram'.Okay, now that we can open an IOCB to a device, what next?------------------------------------GET # Statement:-- }----------------------------------We use the GET # statement to get a byte (character) of data from an open device.This s!}tatement takes the form of: 10 OPEN #1,4,0,"K:" 20 GET #1,AThe number following the # sign (no space) is a number "}of a valid IOCB already opened with an OPEN # statement previously. The variable A, is the name of a numeric variable, to ho#}ld the ATASCII code of the character you are "getting" from the disk drive, keyboard, etc.Okay, time for a little code here$}: 10 REM ** OPEN101.BAS ** 20 REM ** GET Demo ** 30 REM 40 REM ** Open an IOCB ** 50 OPEN #1,4,0,"K:" 60 REM %} 70 REM ** Prompt and GET a byte ** 80 ? "Hit a key "; 90 GET #1,A 100 REM 110 REM ** Print it ** 120 PRINT CHR$(A):?&} 130 REM 140 REM ** Loop back ** 150 GOTO 80Try this. It's only a program fragment, but demonstrates the point. It promp'}ts us for input, and prints our keypress on the screen as soon as we hit the key. The program GETs a character at line 90 as (}soon as we press the key, unlike the INPUT statement that requires a first. Type in this program, and save it under )}OPEN101.BAS which tells us it's Version 1.01 and it's a BASIC program.Now, let's add a test to Quit the program if desired.*} We'll update the REM at line 10 with this program's new version number, 1.02. Do this by LOADing OPEN101.BAS into RAM, and t+}yping in the new lines with the correct line numbers. Remember new lines replace old lines with the same number. 10 REM **,} OPEN102.BAS ** 85 ? :? "(Q) to Quit "; 95 IF A=81 OR A=113 THEN ENDWhat we have now is a program that prompts us for -}input, and prints out the keypress on the screen. If we type an upper or lower case Q, the program ends. This is the usual wa.}y of writing menus, eliminating the need to push the key. We usually arrange a pile of IF-THEN statements, with one /}that tests for Q to quit.------------------------------------Editing:------------------------------------Actually the 0}way I work is I LOAD in the 101 version, run the cursor over the last 1 in 101, and type a 2. When you change a line using th1}e full screen editor (which you are doing), be sure to hit WHILE the cursor is in the line you are changing. If you 2}'cursor' out, without hitting , your new changes will be lost, and the line will be unchanged.I've gotten in the ha3}bit of working as 'full screen' as possible. When I want to add a line such as line 85, I LIST the program, run my cursor to 4}line 90, and push SHIFT+INSERT, inserting a blank line in the correct location for 85. I then type line 85 in this space. It 5}always gives me a full screen view of the program, and somehow aids in the way I visualize the program logic.-------------6}-----------------------Versions:------------------------------------SAVE this program under the name OPEN102.BAS. This al7}lows us to still save, examine and use the earlier program, and also save this, the latest version. It's always easy to see w8}hich version is the latest on disk, as it has the highest number. It's easy to see which program listing goes with which prog9}ram, as we have the program's correct version number in the initial REM statement in the program. We can add the date in REM':}s also, but it's pretty inconvenient to have to update more than one item when working on a program. I work on long programs ;}in perhaps 50-100+ sessions, sometimes for only 10 minutes at a time, and eventually end up with copies on multiple disks af<}ter a long while. Getting your latest version mixed up, can cost a lot of time, and losing your only copy of the entire progr=}am is really a disaster.I always number my program versions one number higher, each time I modify or work on them. One adva>}ntage to saving versions of programs, is recovery after accidentally pressing the wrong key when finishing up a long session ?}at 2 AM, only to realize you've wiped out your only copy of the program. What a frustration. It's not a question of WHETHER o@}r not this happens to you, but WHEN this happens to you. If you save frequently, the worst you can do is go back to the previA}ous version, and add the latest changes. It's also possible to SAVE your program routinely as: OPEN106.BAS, and save again aB}s: OPEN106.BAK to create a clone of your current program every time you save. Believe me, you WILL lose your completed proC}grams, and having a clone on disk, makes recoveryquicker than going after the disk with an UNERASE program. (You DO have one D}don't you?)Here's my all-time disaster: I worked for weeks on a long program, that I had only one copy of. I MEANT to makeE} a back up, but...what could happen? At the end of a late session, when I thought I was finished, I saved the program with: SF}AVE "D:FILENAME and shut off the computer. About 10 minutes later, I decided to make a printout to look over later. I turned G}on the computer, put in the disk, and in BASIC typed: LIST "D:FILENAME". Disaster. It took the "nothing" that was in RAM, anH}d dutifully LISTed the empty file to disk, under the same name. My old file was GONE. I of course meant to say: LOAD "D:FILENI}AME, and then: LIST "P:" but that's not what I did! Perhaps I could have hunted my file down with a sector editor and salvageJ}d it, but as it was an old fragmented disk, I just started over, a wiser man!I even use two disks now, a working disk, and K}a backup disk, and store copies of versions on EACH disk at the end of a session. I know it sounds paranoid and stupid, but..L}.extra disks are only 50 cents, and I certainly have had to go to my backups more than once, or even twice.---------------M}---------------------PUT # Statement:------------------------------------In the same way that the GET # statement gets orN} reads a byte from a device, the PUT # statement puts or writes a byte to an open device. For example, let's open one IOCB toO} the keyboard, and another IOCB to the printer, and send data from the keyboard to the printer.The PUT # statement puts jusP}t one byte (character), each time it is called(used), to a valid device as specified in an OPEN # statement. Got that? It's Q}commonly used with a FOR-NEXT loop around it, to write strings, sentences, entire files, etc one byte at a time from beginninR}g to end. BASIC knows enough to start you at the beginning of a file when the file is opened, and we can GET and PUT, one byS}te at a time. BASIC gives an EOF (End Of File) message, sending error 136 -End of File. We can set a TRAP statement for a liT}ne that tests for error 136 in an IF-THEN. If its TRUE, we're at the end of the file. 10 REM ** PUT Demo ** 20 OPEN U}#1,4,0,"K:":REM Open kybd 30 OPEN #2,8,0,"P:":REM Open prtr 40 ? :? :? "Begin typing" 50 REM 60 GET #1,A:REM V}Get keypress 70 PUT #2,A:? CHR$(A);:REM Put to printer and screen 80 GOTO 60:REM LoopThis prints a prompt "W}Begin typing" on the screen, then gets a character from the keyboard, prints it to the screen (not done automatically for youX}) and sends it to the printer.The PUT # statement writes data to disks, to create disk files; sends data to a printer; to tY}he screen; cassette; etc.------------------------------------INPUT # Statement------------------------------------In tZ}he way that the GET # statement brings in bytes, the INPUT # statement brings in strings from a device. Each time the command[} is issued, it brings in a string of the length specified in the DIM statement associated with the variable. The syntax is;\} 10 DIM STRING$(15) 20 CLOSE #1:OPEN #1,4,0,"K:" 30 ? "Type some - then " 40 INPUT #1,STRING$ 50]} ? STRING$ 60 GOTO 30Note it doesn't write to the screen as we type, as BASIC or DOS.It's handy to bring strings in f^}rom data files on disk. We will use it to bring in entries in a disk directory, in RWP.BAS.-------------------------------_}-----CLOSE # Statement:------------------------------------The CLOSE # statement, closes and releases the IOCB referenced`}. Closing an IOCB, 'turns it loose' meaning it is free to be called for a different purpose, or device. It's syntax is simplya}: 95 CLOSE #1 100 CLOSE #2:CLOSE #3 etc.Once an IOCB has been opened, it cannot be reopened again. It b}needs to be closed before reopening. Type in this code and RUN it: 10 OPEN #1,4,0,"K:" 20 OPEN #1,8,0,"P:"It generc}ates: ERROR- 129 AT LINE 20This means the IOCB is already open. We can sometimes solve this problem by using differentd} IOCB's for different purposes. Still, there will be times when program logic dictates looping back into an OPEN statement ine} a wild hack. No problem. We simply issue a CLOSE # statement, THEN issue the new OPEN # statement. Actually, it is common prf}actice to use an OPEN # statement preceded by a CLOSE # statement. This is routinely done. Check the listings in published BAg}SIC programs. It insures you don't issue an OPEN # statement to an open device.You should always close IOCB's at the end ofq}b%DOS SYSb )AUTORUN SYSb 5HELP DOCb>BASIC8 DOCb0BASIC9 DOCbBASIC10 DOC a program. This avoids potential problems. I usually precede my END statement with a CLOSE. A strange and terrible phenomenr}on of some BASIC's, involves Opening a data file (financial, mailing list, etc.) for appending, or updating for instance, ands} crashing out of the program without closing it. The file can hang out there, inaccessible, not able to be opened again, as it}t's already open. Bad News. It's sensible to be conscious of this, and add all needed CLOSE statements to our programs, coveru}ing all the different ways we can exit without closing.Okay, let's try a revision of the previous code: 10 CLOSE #1:OPv}EN #1,4,0,"K:" 20 CLOSE #1:OPEN #1,8,0,"P:"Type in and RUN this code. Note it runs correctly, without error 129, as itw} was closed before opening.------------------------------------Writing RWP.BAS:------------------------------------Welx}l, that's the building blocks for data transfer. Now let's add a few touches, and a little flash, and write a program that moy}ves data between devices, in Atari BASIC. Let's say it should...oh....Read a file - Write a file - Print a file - and give a z}disk Directory. This will give us understandable examples of how to do input/output in BASIC that we can refer back to when p{}rogramming.Here's the way I would go about writing such a program. I think it's important for beginners to see that program|}s are written from the middle out, or started at just any old place, and written until everything is finally finished. We don}}'t have to start at the beginning.Okay, I think, hmmm...let's use a menu for Read /Write /Print /Directory choice, and one ~}section for each part, that's four parts plus menu is five. Maybe subroutines? Yes, definitely subroutines. We'll leave the i}nitial lines from 10 and up for initialization, description and housekeeping. Maybe put the menu at 1000, (R)ead at 2000, (W})rite at 3000, (P)rint at 4000, and (D)irectory at 5000. This way,we're sure to have room to number new lines by 10's as we g}o along, and we will always intuitively know where we are in the program by what number lines begin with. We will number the }first line in the subroutines 1000, 2000, 3000, 4000 etc for clarity, and ease of remembering where the first line is, when l}ooping back to it, or going-sub to it. We'll hack this program out, then maybe renumber the sections for neatness in incremen}ts of 10 when we're finished. How do we renumber separate sections of a program without affecting the whole numbering system?} Stick around and I'll show you (with LIST and ENTER).Okay, let's think over some of the things we have to do. Let's put a} menu up at the "top" of the program, that reads input and sends control down to one of four subroutines below it. Think of t}he program's operation in terms of "falling down through" the program from the top, until diverted, then falling once again. }Kind of like "gravity."Let's have control revert to this main menu prompt, on return from any subroutine. The menu will cho}ose which subroutine to send control to by letting the program fall through a stack if IF-THEN statements. Down at the bottom}, we will use a GOTO to send control back up to the top of the menu if the user DOESN'T choose one of our options. We will p}rint our prompt, then bring in a character from the keyboard. We could use an INPUT statement, but...the user would have to p}ush , and that seems inelegant in a one letter menu. If we OPEN an IOCB, and GET a character from the keyboard, we wo}n't have to push .We forgot to add a way to end the program, so let's exit the program with a (Q)uit option at the }menu. We'll CLOSE IOCB's before quitting.Now the Read subroutine. Hmmm...Open an IOCB to the disk, by filename. We need to }allow for different disk drives other than the default D: and we might want to get a little fancy, and write a hack to recogn}ize a legitimate filename in any of the three formats: FILENAME D:FILENAME D1:FILENAMEOkay, after we OPEN an I}OCB, we will GET a byte and PUT that byte to the screen. Let's include error trapping to catch errors that would crash the p}rogram (also very inelegant), by using a TRAP statement, and letting control return to a subroutine's beginning prompt in cas}e of trouble. Generally, if programs don't bomb on mistakes, but keep prompting us until we get the input correct, most of us} will learn a program fairly quickly. It will be "user friendly". The TRAP function keeps a program running, when an error oc}curs, and gives us many recovery options by sensing the type of error that has occurred.Okay, now for the WRITE subroutine.}..we will need to open one IOCB to the keyboard, and another to the disk drive, to write the file to. We'll need a way to qui}t writing, and cause the file to be written to the disk. (CLOSE the IOCB) I think we'll have control stay at the WRITE prompt} when finished, to allow us to write multiple files if we wish. If we DON'T want to write another file, or if we got into the} WRITE subroutine by accident, we'll use an empty at the WRITE (or any other) prompt, to go back to the main MENU. A}gain, that's real "user friendly", not asking us to hunt for any one key, but to push just to go back to the MENU.}I think we'll use CONTROL+Z to finish writing a file. My reasoning is that we won't need that key combination for anything el}se in a file, and IBM-PC's (MS-DOS) use that sequence to end files. We'll print a prompt each time so a user won't have to me}morize this! And while we're at it, we may add our "smart filename" option to the Write function too.For the PRINT subrouti}ne...we can use the READ subroutine nearly "as is", sending output to the printer in addition to the screen. No sweat, that o}ne requires almost no new code. We will write the READ subroutine, and when ready, put our cursor on top of the first 2 in li}ne 2000 of the READ subroutine, type a 4, and hit . This will "clone" line 2000 exactly, with the new number, 4000. W}e can edit the new line and the old line will remain unaffected. We can clone lines from the READ subroutine into the PRINT s}ubroutine and edit them, saving some typing.One touch that's always nice in a program that accesses a printer, is checking }to see if the printer is off, then printing a message and prompting for a without crashing the program or locking up} if the printer is off. We will do this with a TRAP statement that PEEKS location 195 that holds the number of the error enc}ountered (because the printer is off.) Isn't that handy? Our handy dandy error guide says the code for "Device did not respon}d" is # 138. We will use an IF-THEN to look for that in case of error, then tell the user that the printer's off, POKE a zero} in 195, and wait for a . (You should look up location 195 that holds the number of the last error encountered, in yo}ur Memory Map, and follow this. You DO HAVE your Memory Map, don't you ?For a DIRECTORY, we will open an IOCB, and bring in} a string, using an INPUT # statement dimensioned to be 17 bytes long. Let's use a FOR-NEXT loop around it. We will allow for} 128 files. That should do it. When it has read the last file, it "errors" and returns to the prompt.Let's not use any "cle}ar-the-screen" commands in this program, so we can always see the previous commands and files on the screen. It can help to s}ee what we typed previously, especially when it doesn't work the way we thought it would.---------------------------------}---ENTER-IN FEATURE:------------------------------------As a new service to readers, I am importing actual BASIC program }code listings into these lessons, that may be ENTERed into your computer from this file, without typing, if you downloaded th}is from a BBS. These longer programs are identified as ENTER-IN programs, and are intended for BBS users with modems. The lin}es may break oddly in a word processor with narrow margins, but...edit them if you like.To use:Load LESSON 9 in your wor}d processor (AtariWriter Plus in a 130XE, or upgraded 800XL, etc.) t Place the cursor directly over the first character in th}e BASIC listing, and press OPTION+B to mark the beginning of text to be saved in the buffer. Move the cursor over the last ch}aracter in the BASIC listing, and press OPTION+DELETE. This saves the text (source code) in the buffer.Now erase the lesson} in main memory, retaining the BASIC listing in the buffer. With AtariWriter Plus, once you've blocked the BASIC listing in t}he buffer, you can use ESCAPE to move to main menu, and select Option (C)reate. Answer Y to "ERASE FILE IN MEMORY ?". The mai}n memory is now empty. Now copy the contents of the buffer into RAM, in AtariWriter Plus by pressing OPTION+X to write buffer}. This should be a perfect copy of the BASIC source code listing by itself, in memory as a document. Examine it in the word }processor for accuracy. Edit as required.Now do an ASCII SAVE, (in AtariWriter Plus use CONTROL+S from the main menu) under} the filename RWP.BAS. This will save only the source code, without word processor commands. This is exactly the same kind of} file the ENTER command produces using: LIST "Dn:RWP.BAS"To summarize, you load the entire lesson in the word processo}r, and either 'clip out' and save the BASIC listing, or delete out everything but the BASIC listing, depending on your word p}rocessor, and do an ASCII save of the listing.Now boot up BASIC, and load the file back into BASIC using the command: }ENTER "Dn:RWP.BAS"LIST your code to see if it contains any errors. You should have the complete BASIC listing. Many errors }may be fixed with the editing feature. Lines that begin with ERROR - are usually fixed by deleting the ERROR - message, and f}ixing the problem. Hitting while in the line will save it.Now SAVE your program in BASIC, using: SAVE "D:RWP.BAS}". then RUN your program. It should work fine. I've tried it, and it's worked well for me. For BBS users, it seems like a tim}e-saving idea. (I have also uploaded RWP.BAS to CompuServe ATARI8 Forum for CompuServe users.)----------------------------}--------RWP.BAS ** AN ENTER-IN PROGRAM **------------------------------------10 REM +++++++ RWP.BAS +++++++20 REM }+ +30 REM + A Demo for LESSON 9 of +40 REM + LEARNING TO PROGRAM +50 REM + IN ATARI BASIC } +60 REM + by Jackson Beebe +70 REM + Ver. 1.07 4/88 +80 REM +------------------------+90 REM 100 REM ***}** Initialize *****110 REM 120 DIM DN$(15),FILENAME$(15),DIRSTR$(17)130 OFF=40000:REM Turn off TRAP140 REM 150 REM ****}* MENU *****160 REM 170 REM ** Print menu, get choice **1000 ? :? :? "R/W/P/D/Q a file >";1010 CLOSE #1:OPEN #1,4,0,}"K:"1020 GET #1,CHOICE:CLOSE #11030 REM 1040 IF CHOICE=82 OR CHOICE=114 THEN GOSUB 2000:REM Read1050 IF CHOICE=87 OR CHOI}CE=119 THEN GOSUB 3000:REM Write1060 IF CHOICE=80 OR CHOICE=112 THEN GOSUB 4000:REM Print1070 IF CHOICE=68 OR CHOICE=100 TH}EN GOSUB 5000:REM Directory1080 IF CHOICE=81 OR CHOICE=113 THEN ? :? :CLOSE #1:CLOSE #2:CLOSE #3:TRAP OFF:END :REM Quit1090} REM 1100 REM ** Must be input error **1110 ? :? :? "(R)ead-(W)rite-(P)rint-(D)ir-(Q)uit"1120 GOTO 1000:REM Loop Back to M}ENU1130 REM 1140 REM ***** READ Subroutine *****1150 REM 2000 ? :? :? "READ FILENAME.EXT >";2010 TRAP 2000:REM Crash} proofing2020 GOSUB 6000:REM Input and check2030 ? :? 2040 CLOSE #2:OPEN #2,4,0,FILENAME$2050 GET #2,BYTE2060 PRINT CHR$}(BYTE);2070 GOTO 2050:REM Loop until error2080 REM 2090 REM ***** WRITE Subroutine *****2100 REM 3000 ? :? :? "WRITE FI}LENAME.EXT >";3010 TRAP 3000:REM Crash proofing3020 GOSUB 6000:REM Input and check3030 ? " (CONTROL+Z to end write)":? 3}040 CLOSE #2:OPEN #2,4,0,"K:"3050 CLOSE #3:OPEN #3,8,0,FILENAME$3060 GET #2,BYTE3070 IF BYTE=26 THEN CLOSE #2:CLOSE #3:GOT}O 30003080 PUT #3,BYTE:PRINT CHR$(BYTE);3090 GOTO 3060:REM Loop until error3100 REM 3110 REM ***** PRINT Subroutine *****}3120 REM 4000 ? :? :? "PRINT FILENAME.EXT >";4010 TRAP 4060:REM Crash proofing4020 GOSUB 6000:REM Input and check4030 ?} :? 4040 CLOSE #2:OPEN #2,4,0,FILENAME$4050 CLOSE #3:OPEN #3,8,0,"P:"4060 IF PEEK(195)=138 THEN ? :? "* Turn on Printer - }push ";:INPUT DN$:POKE 195,0:GOTO 40004070 TRAP 40004080 GET #2,BYTE4090 PUT #3,BYTE:? CHR$(BYTE);4100 GOTO 4080}:REM Loop until error4110 REM 4120 REM ***** DIR Subroutine *****4130 REM 5000 ? :? :? "Directory of Drive # >";:CLOSE }#1:OPEN #1,4,0,"K:"5010 TRAP 5000:REM Crash proofing5020 GET #1,DRIVE5030 IF DRIVE=155 THEN CLOSE #1:TRAP OFF:RETURN 5040} IF DRIVE<49 OR DRIVE>57 THEN GOTO 5000:REM Wrong Dn5050 DN$=CHR$(DRIVE)5060 FILENAME$="D"5070 FILENAME$(LEN(FILENAME$)+1)}=DN$5080 FILENAME$(LEN(FILENAME$)+1)=":*.*"5090 ? FILENAME$:? 5100 CLOSE #1:OPEN #1,6,0,FILENAME$5110 FOR FILE=1 TO 1285}120 INPUT #1,DIRSTR$:? DIRSTR$5130 NEXT FILE:REM Loop until error5140 REM 5150 REM *** NORMALIZE Subroutine ***5160 REM }6000 INPUT FILENAME$6010 IF FILENAME$="" THEN CLOSE #1:CLOSE #2:CLOSE #3:TRAP OFF:POP :RETURN 6020 IF LEN(FILENAME$)>2 THEN} IF FILENAME$(2,2)=":" OR FILENAME$(3,3)=":" THEN RETURN 6030 DN$="D:"6040 DN$(LEN(DN$)+1)=FILENAME$6050 FILENAME$=DN$606}0 RETURN ===== RWP.BAS Description ====Okay, let's look at the program. Line 120 dimensions string variables for di}sk number, filename, and directory listing. Line 130 sets a variable named OFF equal to 40000, used to turn TRAP off by setti}ng it to a line number higher than 32767. See line 1080.In Lines 1040-1080 note testing for the ASC value of a character. }That's how you test a GET. Note lines 1110-1120 prompt then shoot control back to 1000 for wrong input. This also takes care }of returns from subroutines, which fall down into 1120. It's a handy "catch-all" at the bottom of a menu.As mentioned earli}er, I like the idea of the program being 'smart' about filenames. I want the program to operate correctly if I enter the comp}lete drive number and filename, or D: and filename, or just the plain filename. Realizing this would be used in each subrout}ine, the way to go is to code this routine once, and put it in a subroutine, hence the NORMALIZE filename subroutine at line }6000.Note that we set a TRAP statement at the beginning of each function, then GOSUB 6000 to normalize and input filename.}Line 2040 opens an IOCB, 2050 GETs a byte and 2060 prints it to the screen. Line 2070 causes looping until the end of file, }when TRAP returns control to the (R)ead prompt.The (W)rite subroutine works the same way. Lines 3040 and 3050 open two IOCB}'s, used to read from the keyboard and write to the disk drive. Note line 3070 looks for a CONTROL+Z to quit writing. How did} I know that CONTROL+Z was a 26? I didn't. I wrote the following little dittie and ran it.10 CLOSE #1:OPEN #1,4,0,"K:"20 G}ET #1,A30 PRINT A40 GOTO 20I ran this for various inputs. It printed a 65 for uppercase A which I know is correct, and a }26 for CONTROL+Z. (Actually I have an ATASCII chart that said 26 too.)The (P)rint subroutine is similar, but note line 4010} sets TRAP to 4060 to prompt if the printer is off. When we don't use 4060 (printer is on), TRAP is set to 4000 in 4070. This} allows automatic return to the (W)rite prompt at the end of printing.In the (D)irectory subroutine, line 5030 checks for n}ull input, and 5040 checks for drive # 1-9. In lines 5060-5080 I "built-up" the filename to include the drive number, by brin}ging the drive number in as a character with a GET, testing it, then converting it to a string, so I could concatenate it wit}h a "D" and ":*.*" for a Dn:*.* filename. Got that? Look again. You just start from the left, and add things to the right wit}h the concatenation format, until you've got the string you want. There are other ways, but this is the one that came up this} time.The NORMALIZE subroutine checks for null input, then checks to see if the filename contains a drive portion. ====} Interesting Quirk ====I encountered an interesting wrinkle in writing the normalize subroutine. I initially thought that }any filename less than 3 characters must not have a drive specified, as D:A for example, would be the shortest possible name }that includes a drive number. I thought that I'd test for a filename length under three, and if TRUE, would concatenate the f}ilename to D: to make it legal. I thought I'd test names over 2 characters in length for a colon. My first code was:6010 IF} LEN(FILENAME$)<3 OR FILENAME$(2,2)<>":" OR FILENAME$(3,3)<>":" THEN GOTO 60306020 RETURN6030 DN$="D:"6040 DN$(LEN(DN$)+1)}=FILENAME$6050 FILENAME$=DN$6060 RETURNThis would graft on a default drive number to any plain filename. Looking this ove}r, it seemed awkward to have to jump over line 6020. If you have this situation, you can fix it by switching your logic aroun}d, from greater than to less than etc. as:6010 IF LEN(FILENAME$)>2 ...etc.This looked good, so I coded:6010 IF LEN(FILE}NAME$)>2 AND (FILENAME$(2,2)=":" OR FILENAME#(3,3)=":") THEN RETURN6020 DN$="D:"6030 DN$(LEN(DN$)+1)=FILENAME$6040 FILENAM}E$=DN$6050 RETURNThis looked good. Parenthesis and all. I reasoned that if the filename was longer than 2, then it would c}heck both places for the colon, and RETURN if it found one, and fall down into the concatenate section if not. Note lines 60}20-6040 concatenate FILENAME$ to D:, then assign the completed name back to FILENAME$. Looks good doesn't it?It didn't work}! It bombed with error 5 - "a string exceeded it's dimensioned length." If FILENAME$ was only one character long, the test at} line 6010 bombed trying to check the 2nd and 3rd character of a one letter string. Having put the AND in there, made it alw}ays check the 2nd and 3rd places! What's the solution? Simple. FIRST test for a length greater than 2, and then test the 2}nd and 3rd places like this:6020 IF LEN(FILENAME$)>2 THEN IF FILENAME$(2,2)=":" OR FILENAME$(3,3)=":" THEN RETURNGot that}? If has two IF-THEN tests in the same line. This is legal, and often needed. Study on this example if it doesn't make sense.} Try typing in these code lines, and experimenting. (Dimension first.)Try this. It's a fun program, and instructive. When }you are floundering in the future, coding I/O, just pull out this program listing and refer to it for examples of syntax, or} hacks. Modify it, and play with it to see what you can do. Note that you need quotes around device:filename in an OPEN # st}atement, except when you use the name of a string variable in the statement. (Lines 1010 and 2040 for example.)-----------}-------------------------SUMMARY:------------------------------------Well that's about it for INPUT AND OUTPUT I/O. Very }handy concept, and heavily used in programming. Don't be afraid to hack up fancy I/O routines, as they make all the differenc}e in the look and feel of a finished program.------------------------------------Renumbering Sections:------------------ }------------------So, how do we renumber programs in sections? We LIST lines of the program to disk, as: LIST "D:RWP.1 }",10,170 LIST "D:RWP.2",1000,1150 LIST "D:RWP.3",2000,2100 etc.Then we type NEW to erase code in memo }ry, and ENTER the first piece: ENTER "RWP.1"Now we renumber with a renumber program, or by putting the cursor over a l }ine number, editing it and pushing . We can do this to each line number, then LIST the program and delete the old lin }es, and once again LIST it back to disk, in it's new renumbered form, under the same name.Type NEW, and ENTER RWP.2. Renumb}er it, and LIST it back to disk. Type NEW, and ENTER RWP.3, etc. Continue this until the program sections are all renumbered }and LISTed to disk.Type NEW, then ENTER "D:RWP.1". Now ENTER "D:RWP.2", then ENTER "D:RWP.3" etc until we have entered all }the sections in together. The ENTER command doesn't erase what's in memory when it brings in new code. We end up with all the} lines, all renumbered, and SAVE "D:RWP.BAS" to finish. Clever eh?This turned out to be a long lesson, but does have a lot }of info in. Have you hugged your computer today?------------------------------------------------------------------------}This concludes Lesson 9 of LEARNING TO PROGRAM IN ATARI BASIC. Be sure to catch Lesson 10 which includes: SOUND Stateme}nt SETCOLOR Statement Joystick and Trigger * Demo Program (an ENTER-IN prog) Table of Contents - Lesson 1-10}------------------------------------Contact me at: Jackson Beebe Prairie Data Fields 807 West Hill Street }Urbana, IL 61801or 72550,317 on CompuServe byeirie Data Fields 807 West Hill Street O Learning To Program In Atari Basic LESSON 10 Version 1.06 (C)1988 by Jackson Beebe DIRECTORY from BASIC:} Have you ever been working in BASIC, and wished you could see a directory of files on your disk without exiting BASIC }to DOS, getting a directory, and re-entering BASIC? (Did I really need to ask?) Often I work in Turbo BASIC (PD), just fo}r this feature. Well, today I want to share a great "one line hack" from Kenny Ksajikian of Van Nuys, CA. I saw this in }ANTIC magazine Volume 3 Number 7, November 1984, in the letters section, and hope that by giving credit to author and sou}rce, I may pass it along your way. This is a little program you put on disk under the name DIR. The program has no lin}e numbers, and is used in Immediate mode in BASIC by ENTERing it into RAM with the command ENTER "D:DIR". It'll give you }a directory, but will not stick around, nor graft onto your program source code already in RAM. When it's done listing} files, it gives an Error 136, End-of-file (ignore this error) and returns you to BASIC. You may write this file onto di }sk in two ways. You may copy it using DOS, from the screen editor to disk, by: 1) Issuing a command to copy from the s!}creen editor E:, to D:DIR and . This opens a file. 2) Type in the program. 3) Press CONTROL+3 which closes th"}e file, and writes it to disk. A 2nd method is to write the file in a text editor/word processor and use an ASCII save u#}nder the name D:DIR. The editor TEDIT normally does an ASCII save, but with AtariWriter Plus, you must go to the main men$}u and push CONTROL+S for an ASCII save. (An ASCII save eliminates the header of format and punctuation codes in a word p%}rocessor file, and writes only the text.) Here's the program: 10 CLR 20 DIM N$(15) 30 CLOSE #5 40 OPEN #5, 6, 0, "Dn&}:*.*" 50 FOR I=1 TO 128 60 INPUT #5, N$ 70 ?N$ 80 NEXT I 90 CLOSE #5 I added the CLR command at the beginning of Ken'}ny's program, to avoid an Error 9 when using it over and over, caused by re-dimensioning N$. The CLR command undimensions(} all strings. Using DOS, put a copy of this on all your BASIC disks. Use this by issuing the command: ENTER )}"D:DIR" ...in Immediate mode in BASIC. Very handy. A big THANK YOU to Kenny for this tip! SOUND Statement: Okay,*} on to sound from your Atari. Atari 8 bit machines are rich in sound, having 4 built-in voices, numbered 0-3. Each of the+}se may be turned on or off independently, or in combination with any others. Once a voice is turned on, it remains on un,}til you turn it off manually, or the program encounters an END statement. The syntax of the SOUND statement is as follows-}: 10 SOUND N, F, D, V N = voice Number (0-3) F = Frequency (0-255) D = Distortion (0-14) even #'s V .}= Volume (0-15) Voice Number may be an integer from 0 to 3. Frequency may be an integer between 0 and 255. Zero/} sounds a high note and 255, a low note. The formula is: Actual Frequency = 31960 divided by(our number (0-255) plus on0}e.) Distortion works with even numbers only. 10 and 14 sound 'pure' tones. Experiment until you get a sound you like.1} Volume may be 0 to 15. 15 = maximum volume, and 0 = Off. You should keep the total volume of all voices added together,2} under 33, to avoid speaker distortion. If you wish to turn on two voices, it requires two SOUND statements (or a multi-s3}tatement line) as in: 10 REM ** SOUND DEMO ** 20 REM 30 SOUND 0, 126, 10, 10 40 SOUND 1, 81, 14, 10 50 FOR X = 1 4}to 150:NEXT X 60 END This program turns on two voices, counts from 1 to 150, then turns both voices off. Here's what 5}happens without an END statement in your program: Type this and RUN it. 10 SOUND 1, 100, 4, 10 Note this continues6} to sound. How do you turn off a voice within a program? Well, like this: 20 SOUND 1, 0, 0, 0 Issuing the SOUND stat7}ement with the correct voice #, and a zero for Volume, turns that voice off. It's standard practice to put zeros for all8} parameters, freq, distortion and volume, for quick recognition of an OFF statement. RUN, NEW and END will also shut SOUN9}D off. Okay, let's do a few things. Let's play Yankee Doodle. We'll use one voice, and mess around a bit. We will ne:}ed a little delay loop to slow BASIC down a little, and we may as well put the loop in a subroutine, and call it throughou;}t the program. To facilitate hacking around with this without having to type each line over and over again, let's put th<}e Distortion, Volume, and even the Frequency into variables in the SOUND statements, and initialize them up front in the =}program. We can easily alter them when experimenting, by changing these initialization statements. 10 REM *** SOUND D>}EMO *** 20 REM 30 REM ** Initialize ** 40 DIST=14:VOL=14:DELAY=100 50 FREQ1=63:FREQ2=57:FREQ3=50 60 REM 70 ?}REM ** SOUND ** 80 SOUND 1, FREQ1, DIST, VOL 90 GOSUB 1000 100 SOUND 1, FREQ1, DIST, VOL 110 GOSUB 1000 120 SOUND@} 1, FREQ2, DIST, VOL 130 GOSUB 1000 140 SOUND 1, FREQ3, DIST, VOL 150 GOSUB 1000 160 SOUND 1, FREQ1, DIST, VOL 170 A}GOSUB 1000 180 SOUND 1, FREQ3, DIST, VOL 190 GOSUB 1000 200 SOUND 1, FREQ2, DIST, VOL 210 GOSUB 1000 220 END 230 B}REM 990 REM *** DELAY SUB *** 1000 FOR X=1 TO DELAY:NEXT X 1010 SOUND 1, 0, 0, 0 1020 RETURN Experiment by changing tC}he values of Distortion, Volume, Delay and Frequency in lines 40 and 50. Take out line 1010, and see the effect of NOT sD}hutting off the sound between steps. ======== Sound Effects ======== We can make some neat sounds, by turning on a voiE}ce, then changing it's pitch, distortion, and/or frequency, up or down etc. FOR-NEXT loops are dandy for this. Here's aF} short demo of a lazer gun type sound: 10 REM ** EFFECT1.BAS ** 20 REM ** LAZER ** 30 FOR X=20 TO 200 STEP 4 40 SOUG}ND 1, X, 10, 12 50 NEXT X 60 END Here's a demo of how the Distortions sound: 10 REM ** EFFECT2.BAS ** 20 REM ** H}Distortion Demo ** 30 REM 40 FOR DIST=0 TO 14 STEP 2 50 SOUND 1, 100, DIST, 10 60 ? "DISTORTION ";DIST 70 FOR X=1 TI}O 300:NEXT X 80 NEXT DIST 90 END Here's one I call a Plink: 10 FOR PLINK =16 TO 0 STEP -1 20 SOUND 0, 30, 10, PLIJ}NK 30 NEXT PLINK 40 END Or a tone: 10 FOR TONE = 20 TO 0 STEP -1 20 SOUND 0, 53, 14, TONE 30 NEXT TONE 40 ENDK} Finally here's a rising and falling siren: 10 REM ** EFFECT3.BAS ** 20 REM ** SIREN ** 30 FOR REPEAT=1 TO 2 40 L}REM ** RISE ** 60 FOR FREQ=255 TO 5 STEP -1 70 SOUND 1, FREQ, 14, 10 80 NEXT FREQ 90 REM ** FALL ** 100 FOR FREQ=5 M}TO 255 STEP 1 110 SOUND 1, FREQ, 14, 10 120 NEXT FREQ 130 NEXT REPEAT 140 END Poking Sounds: If you have been uN}sing your Memory Map very much, you have probably wondered by now, if you can POKE sound values directly into memory. YesO} you can. Here are the locations (for you to read up on.) 53760 = Voice 0 Frequency 53761 " Dist / VolumeP} 53762 = Voice 1 Frequency 53763 " Dist /Volume 53764 = Voice 2 Frequency 53766 = Voice 3 Frequency 5Q}3767 " Dist / Volume The formula for the value to POKE in the odd numbered location (Distortion and Volume) iR}s: (Distortion * 16) + Volume For example: SOUND 2,121,10,10 is equal to: POKE 53762,121:POKE 53763,170 WellS}, this is the general idea. Try turning on more than one voice at once. Try one voice rising while one falls. Try diffeT}rent effects at different times with different voices going different directions. Save your favorite sounds for use in pU}rograms. There are many good sound demo programs available on Public Domain disks, from magazines by Type-In, or availablV}e for D/L on BBS's. Study these to see what makes them tick. Unfortunately, SOUND statements will slow down a prograW}m quite a bit. POKEs are faster, but will still have an effect on the speed of a program. SETCOLOR Statement: BASX}IC has good color control built right in. Options and colors vary with different graphics modes, but we will just cover GY}raphics Mode 0, BASIC's default mode. The familiar blue screen with white letters. The syntax for the SETCOLOR statementZ} is: 10 SETCOLOR R,H,I R = Register (1,2,4) H = Hue (0-15) L = Intensity (0-14) even #'s These[} three parameters (in Graphics 0) control the color and intensity of the Letters, Background and Border. The Letters are a\}lways the same color as the Background (screen), but may be varied in intensity from black to white. When they are the sa]}me intensity as the Background, they disappear ====== REGISTER ====== This parameter of the SETCOLOR statement d^}etermines whether we alter: LETTERS 1 BACKGROUND 2 BORDER 4 ======== HUE ======== This sets th_}e color of the Background or Border. Colors possible are: 0 = Gray 8 = Blue 1 = Gold 9 = Light Bl`}ue 2 = Orange 10 = Turquoise 3 = Red-Orange 11 = Green-Blue 4 = Pink 12 = Green 5 = Pink-Purple a}13 = Yellow-Green 6 = Purple-Blue 14 = Orange-Green 7 = Dark Blue 15 = Light Orange Note that you can't change tb}he hue in a SETCOLOR 1 statement. Letter color follows Background color. I usually put a zero in the HUE location of a c}SETCOLOR 1 statement. ====== INTENSITY ======= Intensities are roughly: Dark (0-2) Medium (4-8) Bright d} (10-14) Only the even numbers have an effect. Let's try some examples. Here's code to produce bright white letters one} a black screen with a dark blue border. 10 REM ** White on Blk/Blu ** 20 SETCOLOR 1,0,14 30 SETCOLOR 2,0,0 40 SETCf}OLOR 4,7,4 50 END Here's white letters on a red-orange background, with a dark blue border. Isn't this fun? 10 REM g}** Wht on Red-Or/Dk Bl ** 20 SETCOLOR 1,0,14 30 SETCOLOR 2,3,4 40 SETCOLOR 4,7,2 50 END Poking Color: Once agaih}n, you should realize that many of the functions you can accomplish with language statements, as SOUND or SETCOLOR, can bei} done by directly POKEing memory locations. There are three memory locations that correspond to the SETCOLOR statements. j} These are: POKE 709 LETTERS (0-14) POKE 710 BACKGROUND (0-238) POKE 712 BORDER (0-238) The relatiok}nship to the setcolor statements are: SETCOLOR's POKE 1 709 2 710 4 l} 712 The value to poke into RAM is determined by the formula: (HUE * 16) + INTENSITY For Example: SETm}COLOR 1,0,0 = POKE 709,0 SETCOLOR 2,5,6 = POKE 710,86 SETCOLOR 4,12,8 = POKE 712,200 In the last example, 4 stn}ands for Border, so we use POKE 712. Color times 16 plus intensity is (12 * 16) + 8 or 200. Hence POKE 712,200. I preo}fer using the POKE statements to control color in BASIC. You can always use SETCOLOR to develop a screen, then use a POKEp} statement in the finished program. SETCOLOR and POKE produce the same result. Of course, you can vary, flash and alterq} these colors by using a FOR-NEXT loop to alter the values of the SETCOLOR statement during program execution. This is er}specially nice on title screens. It's easy to change colors in different parts of your program, or from title screen to ps}rogram, etc. You only need to put a few statements in, and it's done. It allows you to use colors you enjoy. As alwat}ys, experiment with this, and see what you like. Keep a notebook, as you occasionally find good screen "recipes" by accidu}ent. Write them down for future use. JOYSTICK and TRIGGER: We can directly read the joysticks in BASIC, by checking v}STICK(0), STICK(1), 2, or 3, to determine the direction the stick is being pushed. See chart below. If the stick is restw}ing in the middle, that's a 15. If it's pushed UP for example, it's now a 14. We can have our program fall through a pilex} of IF-THEN statements, testing for all these possible positions. 14 10 6 \ | / y} \ | / \ | / \ | / \ / 11 ------- 15 ------z}- 7 / \ / | \ / | \ / | \ / | {} \ 9 5 13 The trigger is read by the STRIG(0), or STRIG(1), 2, or 3 etc. If STRIG() is ZERO, the|} trigger is pressed. If it's 1, it isn't. (That always seemed a bit backwards to me but that's how it works.) * KIDSD}}RAW.BAS (an ENTER-IN Program) Okay, let's put some of this to work in a BASIC program that demonstrates these concepts.~} KIDSDRAW.BAS is a goodie I wrote for my own education, as well as to orient my 3 year old daughter to eye-hand coordinat}ion on the computer. Let's take a look. Lines 60 and 230, clear the screen, and set screen colors. The next section d}rew screen boxes using CONT+Q, CONT+E, Z,C,W, in my ORIGINAL program, but here,for ASCII printer purposes, I used dashes. } Those control characters won't print out right, and may feed endless sheets of paper,or any number of odd things. You }should by all means, be making up screens using these CONTROL characters. Look them up, and try them. One screen will ad}dict you forever! You print BASIC listings then, with G:. All the control characters will print out correctly, and you g}et condensed print with 137 characters per line, making BASIC listings much more readable. We dimension a string variable}, two characters in length at line 70, and bring in a string in line 160. Line 190 tests for a null input, which has a l}ength of zero (less than 1.). If TRUE, it must have been a plain RETURN, so we "default" to a speed of 75, with the SPEE}D$="75" statement. Note, 75 is in quotes, as it's still a two character string at this point. Line 200 converts any string} in SPEED$ to a number. Note that no error checking is done in this program. You could add error checking for the pract}ice. The cursor is positioned in the middle of the screen, and the program falls through a pile of IF-THEN statements, }and a SOUND statement, then prints on the screen (that's supposed to be an INVERSE SPACE between the quotes in line 500.)} It loops back up to 290 to fall through the stack again. I used line 290 to save some typing at lines 310-380 by only }having to type the letters ST for a variable. Try putting STICK(0) in lines 310-380 and remove line 290. Change line 600} to read GOTO 310. Does it run faster? If the stick is not pushed (15), the program loops, awaiting a stick deflection. } When that occurs, X or Y are incremented or decremented, depending on which way the stick was pushed. Note that we s}pecified at line 310, that when STICK(0) = 7 (stick pushed to the right) it adds 1 to X. If it's pushed up and to the lef}t (10), it adds 1 to Y, and subtracts 1 from X. These are directions referenced from the middle of the screen, as specif}ied in line 260. UP (14), adds 1 to Y. DOWN (13), subtracts 1 from Y. LEFT (11), subtracts 1 from X, RIGHT (7), adds 1} to X etc. That's the plain vanilla joystick control. Pushing the trigger is checked for at line 500. If it is not be}ing pressed (1), an inverse blank is printed (black on a white screen), and there's a jump around the "regular blank" pri}nt statement of line 510. If the trigger is pushed (0), line 500 is not executed, and the "regular blank" overprints wha}tever may have been on the screen. Line 540 creates a delay for low numbers, while a 75 gives maximum speed. Try removi}ng line 570 (insert a REM at it's beginning) and see if it runs faster without the zero FOR-NEXT loop. Line 570 reads t}he memory location 53279 for a 6, which would mean that START is being pressed. (Got that location scoped out in your Mem}ory Map?). Line 600 zooms it back up to the top. 10 REM ** KIDSDRAW. BAS ** 20 REM Ver 1. 07 6/88 30 REM Ja}ckson Beebe 40 REM 50 REM * Clear screen /Set color * 60 ? CHR$(125):POKE 709,14:POKE 710,52:POKE 712,114 70 D}IM SPEED$(2) 80 REM 90 REM ** Draw Opening Screen ** 100 ? :? "-------------------------" 110 ? "KIDSDRAW by Jackso}n Beebe" 120 ? "----------------------------" 130 ? " 1 FIRE button erases " 140 ? "----------------------------" 1}50 POSITION 4,20:? "Press START to erase screen" 160 POSITION 11,12:? "Set SPEED (1-75)":POSITION 11,13:? "or push RE}TURN ";:INPUT SPEED$ 170 REM 180 REM ** Default to 75 ** 190 IF LEN(SPEED$)<1 THEN SPEED$="75" 200 SPEED=VAL(SPE}ED$) 210 REM 220 REM ** Clear screen/Set color ** 230 ? CHR$(125):POKE 709,0:POKE 710,14:POKE 712,116 240 REM 250} REM ** Middle of Screen ** 260 X=19:Y=11 270 REM 280 REM READ STICK(0) assign to ST 290 ST=STICK(0) 300 REM * Fall} thru pile of tests * 310 IF ST=7 THEN X=X+1 320 IF ST=14 THEN Y=Y-1 330 IF ST=13 THEN Y=Y+1 340 IF ST=6 THEN X=X+1:Y}=Y-1 350 IF ST=10 THEN X=X-1:Y=Y-1 360 IF ST=11 THEN X=X-1 370 IF ST=9 THEN X=X-1:Y=Y+1 380 IF ST=5 THEN X=X+1:Y=Y+1} 390 IF X>38 THEN X=38 400 IF Y>22 THEN Y=22 410 REM 420 REM ** Handle negative number ** 430 IF X<1 THEN X=1 440 }IF Y<1 THEN Y=1 450 REM 460 REM * Calc SOUND (Trigger-Off) * 470 SOUND 0,(ABS(X-Y)+2)*5,10,12:IF STRIG(0)=0 THEN SOUN}D 0,0,0,0 480 REM 490 REM ** Draw or Erase ** 500 POKE 752,1:POSITION X,Y:IF STRIG(0)=1 THEN ? " ":GOTO 540 510 ? "} " 520 REM 530 REM ** Speed (Delay) Loop ** 540 FOR ZZ=1 TO 75-SPEED:NEXT ZZ 550 REM SLOWS 560 REM ** Check for 'ST}ART' ** 570 IF PEEK(53279)=6 THEN 230 580 REM 590 REM ** Loop Back 600 GOTO 290 ** NOTE ** If you D/L'ed this fil}e from CompuServe, it will be modified to contain CR/LF at the end of each line, rather than the Atari EOL character. (C}opy the file to the screen using DOS, and see if it breaks correctly by line, or if it has inverse rectangles and triangle}s at the end of each line.) Before using a word processor as described below, convert the CR/LF back to Atari EOL charact}ers using FILEFIX.COM by Glenn K. Smith, or a similar program.) Conclusion: I hope you have enjoyed LEARNING TO PROG}RAM IN ATARI BASIC. This series was intended to "turn you on" to BASIC programming, get you over any program phobia you m}ay have had, and hook your curiosity. There are many opinions about BASIC, programming, and learning. Some folks say you} will be ruined forever if you learn BASIC as your initial language. They feel you will learn a mish-mash approach, and }never again be able to train your brain to think in "structured" language approaches. I believe you may teach yourself t}o write structured BASIC code if you choose. I am aware that most do-it- yourself self-taught programmers will quit befo}re they struggle months with a complex language, before producing meaningful output. I'm not kidding about struggling a l}ong time. It takes dedication for instance, to teach yourself "C" at home from a book. BASIC will get you up and runnin}g with immediate feedback, and meaningful error messages right away. I believe immediate feedback while learning is of p}rime importance. BASIC will allow you to write and use a program you need, right away. It will make you feel like a progr}ammer. It will build your confidence. It is important to note that you can find help with BASIC problems almost anywhere} around you. Try asking those around you for help with ASSEMBLER, or C language problems, and note the blank stares on th}eir faces. If a true hacker begins with BASIC, I believe that person will find their way to other languages as needed.} When learning a second or third language, you will know enough to ask "How do I bring in a string?", or "How do I test f}or 'null' input?" or similar questions. You're not starting from scratch. Many beginners feel that it is a hardship to }have to use more than one kind of computer at once, or to learn more than one language. Their brain fixates on the DIFF}ERENCE between machines or languages, and they tell themselves it is too confusing and too hard. I believe this is a fata}l thinking trap that you can easily overcome. If you stop and think for a moment, you will note that a person may encount}er as many as 3 or 4 types of computers between home, school, work and friends. It is futile to demand the world standar}dize. This is never going to happen, and why be left behind in the dust? I have used a mainframe while teaching colleg}e, a mini at work, and an Atari 8 bit and a PC-AT clone at home. I lived to tell about it! You can too. It is importan}t to focus on the SAMENESS of all the computers or software you use. This is the secret. Realize the importance of know}ing you may BLOCK text in a word processor, and asking "How do you BLOCK and MOVE in WordPerfect, or PC-Write, or Atariwri}ter Plus" etc. Apply this thinking to programming. Believe that more rather than fewer machines and programs will crop u}p in the future. Let your mind catch on to the basic feel and style of "how computers think." Develop a hacker approach}. Be brave enough to learn, but also be smart enough to develop a feel for what will damage or delete your files. Back u}p frequently! This series of ten lessons does not cover all of BASIC's possibilities. It will get you up and running, an}d expose you to a core of programming ideas and techniques. It's a firm foundation to build on. Read magazines. Borrow} books. Join a User Group. Subscribe to ANALOG and ANTIC magazine. Buy a modem and call Bulletin Board Systems. Downlo}ad programs. Write programs and Upload them to a BBS. Keep trying. Drink cokes (beer), eat cold french fries, and sta}y up until all hours hacking on your computer. Tell your wife "I'll be done here in just a minute honey." Upgrade your m}emory. Invent a clever hack. Become a computer nerd and write articles, or even a series of lessons. Above all, enjoy y}ourself, and give your computer a hug today! Hang in there. Thanks for listening. This concludes LEARNING TO PROGRAM I}N ATARI BASIC. I hope you have enjoyed this series, and have found some useful information here. Write me. Contact me a}t: Jackson Beebe Prairie Data Fields 807 West Hill Street Urbana, I}L 61801 CompuServe 72550,317eebe Prairie Data Fields 807 West Hill Street Urbana, I*