08 August 2014

...And That's BASICally It....[]

Those of you who follow me on Twitter will know that I've recently been working on some programming on my Atari 800XL computer (well, both of them, since I have two). That means using BASIC, and specifically Atari BASIC.

While I learned some BASIC as a young ninja, it's been a bit of a journey so far on the Atari, for a couple of reasons. Firstly, it's been a while, and I had forgotten a lot. Secondly, I never really learned all that much to begin with, being only around 10 years old at the time. And thirdly, Atari BASIC is a distinctly different flavor of BASIC from what I learned.

In any case, I recently managed to spit out a short program, which I also shared on Twitter. What I wanted was something that would spit out a specified lines of random letters/symbols either to the screen or to a printer.

Why would I want such a thing? Because reasons...Reasons I say!

Okay, I just wanted something that would (a) be a good test for my Atari 1025 dot matrix printer and (b) look cryptic to an observer, as if it were some secret code. *

So anyway...I figured I'd talk a bit about this program, my thought processes while writing it, etc. It'll be an educational column for some, especially in the area of how little I know, and I hope it'll at least be somewhat entertaining.

Disclaimer: Please do not mistake me for an expert (or even a mildly proficient) in programming. This is mostly just me playing around with forces I cannot possibly hope to comprehend.

Here's the full program:
1 ? "}":POKE 752,1
2 DIM Q$(1)
3 POSITION 10,3:? "# ROWS":POSITION 16,3:INPUT R
4 POSITION 10,5:? "PRINT":POSITION 16,5:INPUT Q$
5 IF Q$="N" THEN POSITION 10,6:? "OKAY, NO PRINT.":GOTO 10
6 IF Q$<>"Y" THEN POSITION 10,6:? "ERROR: ENTER Y/N":POSITION 10,7:
  INPUT Q$:? "}":GOTO 5
8 POSITION 10,6:? "OKAY, WILL PRINT."
9 POSITION 10,8:? "MAKE SURE PRINTER IS ON."
10 FOR W=1 TO 500:NEXT W:PRINT "}"
11 DIM Y$(80)
20 DIM X$(1)
25 FOR S=1 TO R
26 IF Q$="N" THEN GOTO 30
27 FOR I=1 TO 80:GOTO 40
30 FOR I=1 TO 36
40 X$=CHR$(INT(59*RND(1))+64)
50 Y$(I)=X$
60 NEXT I
65 IF Q$="Y" THEN LPRINT Y$:GOTO 80
70 ? Y$
80 NEXT S
90 POKE 752,0
100 END

I'm positive that this program has been written before, and better, but this is what I done wrote, and I'm sort of proud of it. By the way, wherever you see a "}" character, replace it in your mind with the "clear screen" symbol from Atari BASIC, which looks a bit like "↰."

If you're not familiar with BASIC, each line begins with a number which determines its order. Common practice is to begin with line 10 and enter new lines in multiples of 10 (or sometimes start with 100 and go in multiples of 100) so that, as needed, lines can be inserted in between others without re-numbering. That should give you a bit of an idea of the order in which some lines were added here (actually, I did re-do line 10 at least once, but you get the picture).

For those to whom this still looks like ancient Sanskrit, here's the overview: the program asks for a number of lines, and whether or not the output should go to a printer. Then it generates a line of random characters, then another, and so on until it has output the number of lines specified.  

So let's just take it line by line to see how it works:

1 ? "}":POKE 752,1 

First thing to note about this line is that "?" is a shortcut for the PRINT command, which tells the computer to print a text string. So this line first tells the computer to print a "clear screen" character, which blanks out anything on-screen at the time. The second part of the line (two commands can be on the same line if separated by a colon) is a command that directly changes a memory location — specifically, it turns off the cursor, which makes things look a bit neater.

2 DIM Q$(1)

This second line is here because of a peculiarity of Atari BASIC. While in some versions of BASIC you can just use string variables (contrasted with number variables) at will, in Atari BASIC you must first DIMension them, telling the program how many characters to allot the variable. So this line declares a string variable called Q$ which has a maximum length of 1.

3 POSITION 10,3:? "# ROWS":POSITION 16,3:INPUT R
4 POSITION 10,5:? "PRINT":POSITION 16,5:INPUT Q$

These two lines are the two big questions asked at the start. First, the (invisible) cursor is moved to column 10, row 3. Then the prompt "# ROWS" is printed, followed by an input for a number variable called R (the number of rows to print).

Then it moves two lines down, to column 10, row 5 and asks the second question, whether or not to print the output to a printer. Here is where we use the string variable Q$ which we DIMensioned a couple lines ago.

Also note another peculiarity of Atari BASIC: PRINT commands must be separate from INPUT commands. On a Commodore 64, for example, I could input instead 3 POSITION 10,3 : INPUT "# ROWS";R, but not so on the Atari.

5 IF Q$="N" THEN POSITION 10,6:? "OKAY, NO PRINT.":GOTO 10
6 IF Q$<>"Y" THEN POSITION 10,6:? "ERROR: ENTER Y/N":
  POSITION 10,7:INPUT Q$:? "}":GOTO 5
8 POSITION 10,6:? "OKAY, WILL PRINT."
9 POSITION 10,8:? "MAKE SURE PRINTER IS ON."

These next few lines are partially feedback to the user and partially error handling in regards to the INPUT command on line 4. Since the user may have entered any character at the prompt, it needs to weed out anything but "Y" or "N."

First, it handles the case where the user entered "N." It moves down to row 6, tells the user that the output will not be printed, and then moves to line 10 for further instructions.

The next line is there because ATARI basic only follows an IF/THEN model for conditionals, not an IF/THEN/ELSE model. If the IF condition is true, the THEN is executed, and if not it just goes to the next line. So now the program weeds out anything that isn't a response of "Y," since any "N" responses were caught by the previous line. For all of these responses, it prints an error message, allows the user to re-input for Q$, then moves back to the first conditional statement.

If anything made it through lines 5 and 6 without triggering one of the THEN clauses, it's because the user entered "Y." So the next two lines (there was a line 7 at one point) just offer feedback and remind the user that the printer needs to be turned on.

10 FOR W=1 TO 500:NEXT W:PRINT "}"
11 DIM Y$(80)
20 DIM X$(1)

Okay, now we really get into the operations of the program. First, line 10 uses a FOR/NEXT loop, which will be talked about in a couple lines. Basically what it does is tell the computer to execute a set of commands a given number of times. In this case, though, the loop is empty, so the computer does nothing 500 times (the 1 to 500 part). This is a sort of messy way (which depends on running in Atari BASIC on an 800XL) of causing a delay, which in this case is long enough for the user to read feedback messages before the second half of the line clears the screen again. A professional would use a more precise clock, but I'm not a professional.

Lines 11 and 20 DIMension two more string variables, which are the ones that are really going to be used in the program. Y$ has a maximum length of 80, and X$ has a maximum length of 1.

25 FOR S=1 TO R
26 IF Q$="N" THEN GOTO 30
27 FOR I=1 TO 80:GOTO 40
30 FOR I=1 TO 36

Line 25 starts another FOR/NEXT loop, using the variabl R that was entered in line 3. So for R times, the program will repeat whatever comes between this statement and a line that says "NEXT S."

The next two lines toggle between two different line lengths. Atari BASIC in the standard text mode can only display 40 characters across, while a printer can do 80 characters to a line. So if the user entered "N" for Q$, the program goes to line 30, which starts a FOR/NEXT loop that repeats 36 times (because the line starts a couple of characters in) and otherwise goes to the next line down, which starts a different loop that goes to 80. The second part of line 27 skips it past line 30.

So the loop started by either line 27 or line 30 will do the same thing (for R times, because it's nested within the S loop from line 25).


40 X$=CHR$(INT(59*RND(1))+64)
50 Y$(I)=X$
60 NEXT I
 

Here's the real mechanism. These two lines are the repeated steps that churn out the text.

Line 40 sets the X$ string variable equal to a random character. It does this by making use of the CHR$() function, which outputs the Atari ASCII (also known as ATASCII) character with a given numeric value. It also uses the built in RND() function, which outputs a random number between 0 and 1.

Between 0 and 1? Yes, that's right. The RND() function will give you some decimal value between 0 and 1 (and never 0 or 1). So to get it to give you a range of values, you have to get tricky. I want it to give me the range starting with character 64 ("A") and character 122 ("z"). This will also include a number of symbol characters (punctuation, etc.), which was an unintended side-effect. **

So at the heart of the expression in line 40, it takes a random number (the 1 in parentheses doesn't really have any effect) and multiplies it by 59, which is 123-64 (the random number never equals 1, so I have to go one beyond my target range). Then it uses the INT() function to strip out decimals and just use the closest integer. Then it adds 64. So now it will output random numbers from 64-122.

Line 50 is where I had to get creative. There's unfortunately no string concatenation function in Atari BASIC, so I couldn't just build up a line by going X$+X$+X$, and so on. However, it is possible to specify a character position within a string variable. Y$(1) means the first character of the Y$ string variable, and so on. So this line uses the step number of I (which is incremented by 1 each time the FOR/NEXT loop churns) as the position indicator on Y$ and copies over the randomly generated character of X$ onto it.

Line 60 completes the loop begun in line 27/30, which eventually builds up a line of the required length.


65 IF Q$="Y" THEN LPRINT Y$:GOTO 80
70 ? Y$
80 NEXT S


The next couple lines are the output end of the program. Depending on the value of Q$, the program will either use the LPRINT command to print the value of Y$ to the printer (and then go to line 80) or print it to the screen. Line 80 completes the loop begun in line 25 and causes the next line to be generated, until the required number of lines are generated.


90 POKE 752,0
100 END


Okay, here we are at the wrap-up. After everything's printed out, the cursor is turned back on and the program terminates (until the users types "RUN" again).

So yeah. Took me a bit of noodling to get this one to work, but there it is. Actually, there are a number of improvements I should make to it. For one thing, I should make the user hit a key at line 9 before it moves on, to be sure it doesn't execute before the user has a chance to turn on the printer. For another thing, it might be a good idea to add a line counter, especially when the user's printing a large number of lines to a printed page, as a bit of a progress meter. I have an idea of how I'd do it, but since I currently have no way of saving programs (gotta get an external floppy drive at some point), that's just gonna wait for now.

As I said, I am 100% positive this has been done before and better. But this is what I wrote...BASICally....


Peace,
JT


* Fun thing to do if you're using laptop running Linux in a public space: either open a new terminal emulator window or go to a virtual console and run either (a) sudo apt-get update or (b) cmatrix. People walking by will think you're either a hacker or a spy. Totally. 

** It wouldn't be straightforward to filter out the range of values for the punctuation characters the way I did it here. I could have just generated the text based on a string variable containing all the alphanumeric characters, but hey, that's hindsight for you.

No comments:

Post a Comment