Atari BASIC programming

This article by Dan Polansky intends to give an impression of Atari BASIC for 8-bit Atari computers, including exercises and examples. One may try out the examples in an Atari emulator such as Atari800 or Altirra (with BASIC ROM) and appreciate the speed of modern computers. One can have more fun by trying the exercises without reading the solutions. One purpose is to get an idea of what kinds of simple programs kids in the 1980ies could have been writing.

Exercises

edit

1) Print numbers from 1 to 10.

2) Calculate the factorial of n input by the user and print it.

3) Plot a large cross made from two diagonal lines.

4) Plot an ellipse using sine and cosine and plotting the individual pixels.

5) Cover the screen in random-color pixels in various graphical modes.

6) Plot the Mandelbrot set in a 2-color, 4-color or 16-color mode.

7) Other exercises can be inferred from section headings.

Getting started

edit

Programs have to be entered with line numbers. One can see the program listing by typing "LIST" or "L.". One can list a particular line number e.g. by typing "LIST 30". Once one is ready to run the program, one enters "RUN". To enter a line, one types e.g. "10 PRINT 3*7". To erase a line, one types just its line number, e.g. "10".

One can also enter BASIC commands interactively, e.g. by typing "PRINT 3*7".

To erase the current program, one can type NEW.

The Atari emulator may have a "Paste" option in its context menu that types text content of the clipboard.

A paused program can be resumed using CONT.

Variable names cannot not start with a builtin keyword name. Thus, entering LETTER=1 results in TER being set to 1 since the statement is interpreted a LET TER=1. Similarly, FORMULA=1 results in error: it is interpreted as FOR MULA=1. Underscores are disallowed in variable names.

Text output

edit
10 A=1:B=2:C=3:D=4
20 PRINT A,B,C,D:REM COLUMNS WITH LARGE SEPARATION
30 PRINT A;B;C;D:REM NO SEPARATION
40 PRINT A;" ";B;" ";C;" ";D
50 PRINT A;:PRINT B:REM NO NEWLINE AFTER A

Conditionals

edit

There is IF conditional. There is no block grouping (BEGIN, END or {, }); at least, multiple statements can be made on the same line of the IF conditional. To achieve arbitrarily large blocks guarded by IF, one uses GOTO.

10 IF 5>4 THEN PRINT 1:PRINT 2
20 INPUT A:IF A<=4 THEN GOTO 40
30 PRINT "A IS > 4"
40 INPUT A:IF A>=2 THEN 60:REM IMPLICIT GOTO
50 PRINT "A IS < 2"
60 END

There is no ELSE. To achieve if-else, one can either use GOTO or one can repeat the condition, e.g. like this:

10 INPUT X
20 IF X>=10 THEN PRINT "X>=10"
30 IF X<10 THEN PRINT "X<10"

There is no SWITCH command.

Loops

edit

A for loop:

10 FOR I=1 TO 10
20 PRINT I
30 NEXT I

An early exit from the loop ("break"):

10 FOR I=1 TO 10
20 PRINT I
30 IF I=5 THEN I=10
40 NEXT I

There is no while loop. One can use GOTO instead:

10 I=1
20 IF I>10 THEN GOTO 60
30 PRINT I
40 I=I+1
50 GOTO 20
60 END

One can also emulate a do-while (repeat until) loop using GOTO:

10 I=1
20 PRINT I
30 I=I+1
40 IF I<=10 THEN GOTO 20

Arrays

edit

One can dimension and use a one-dimensional array of Atari BASIC numbers.

10 DIM A(10)
20 FOR I=1 TO 10:A(I)=I:NEXT I
30 FOR I=1 TO 10:PRINT A(I):NEXT I

One can also have two-dimensional arrays, but not three-dimensional ones:

10 DIM A(10,10)
20 FOR I=1 TO 10
30 FOR J=1 TO 10:A(I,J)=I*J:NEXT J
40 NEXT I
50 FOR I=1 TO 10
60 FOR J=1 TO 10:PRINT A(I,J);";";:NEXT J
70 PRINT
80 NEXT I

One can emulate any-dimensional array by using a one-dimensional array and calculating the physical index from the logical indices.

String arrays are impossible except as being emulated by using a single string and interpreting it as sequence of blocks.

Strings, like arrays, need to be dimensioned, and can be thought of as 8-bit character arrays. They can be actually used as arrays of bytes/octets: to store a byte, one would use CHR$, and to retrieve a byte, one would use ASC. The benefit is that this way, one item take one bytes, whereas an Atari BASIC number takes 6 bytes.

Calculating factorial

edit
10 INPUT N
20 F=1
30 FOR I=1 TO N
40 F=F*I
50 NEXT I
60 PRINT F

Graphics modes

edit

In the plotting examples below, we refer to graphics modes via GRAPHICS command. A table of those is in the link. For a start, we need to know:

  • mode 0 is the default, hi-res text/character mode
  • mode 7 is a low-res four-color pixel bitmap mode with split text window; this is the resolution of Draconus although Draconus (from what I recall) uses character mode rather than pixel bitmap mode
  • mode 8 is a hi-res two-color pixel bitmap mode with split text window
  • mode 8+16 is a variant of mode 8 without a split window, 320x192 pixels

Links:

Plotting diagonal lines

edit
10 GRAPHICS 8
11 COLOR 1
20 FOR X=0 TO 150
30 PLOT X,X
40 PLOT 150-X, X
50 NEXT X

Depends on PLOT for plotting individual pixels. To switch back to the default graphics mode for comfortable source code listing, one may type "GRAPHICS 0".

Doing the same by drawing lines using DRAWTO:

10 GRAPHICS 8
20 COLOR 1
30 PLOT 0,0
40 DRAWTO 150,150
50 PLOT 150,0
60 DRAWTO 0,150

Plotting an ellipse

edit
10 GRAPHICS 8
20 COLOR 1
30 RDS=75
40 FOR X=0 TO 2*3.141592 STEP 0.02
50 PLOT 2*RDS+2*RDS*SIN(X),RDS+RDS*COS(X)
60 NEXT X

The above is very slow.

Random numbers

edit

The function RND outputs floating-point numbers in the range of 0 to 1, excluding 1. We can output random integers using INT function (here we output random integers from 0 to 9 including):

10 FOR I=0 to 10
20 PRINT INT(10*RND(1))
30 NEXT I

We can obtain random integers from 0 to 255 from address 53770. The following plots the counts of generated values, allowing a partial verification of the generator by means of visual inspection.

10 REPEATCOUNT=1000000
20 DIM VALCOUNT(256)
30 GRAPHICS 8: COLOR 1
40 FOR I=1 TO 256: VALCOUNT(I)=0: NEXT I
50 FOR I=1 TO REPEATCOUNT
60 R=PEEK (53770): PRINT "RND: ";R
70 VALCOUNT(R+1)=VALCOUNT(R+1)+1
80 IF VALCOUNT(R+1)>160 THEN END:REM SCREEN LIMIT
90 PLOT R, VALCOUNT(R+1)-1
100 NEXT I

We can make the above frequency investigation for the Mandelbrot map-based pseudorandom generator (for more of which see section Plotting random pixels):

10 REPEATCOUNT=1000000
20 DIM VALCOUNT(256)
30 GRAPHICS 8: COLOR 1
35 X=0.1
40 FOR I=1 TO 256: VALCOUNT(I)=0: NEXT I
50 FOR I=1 TO REPEATCOUNT
55 GOSUB 200:REM RND
60 R=INT(256*X): PRINT "RND: ";R
70 VALCOUNT(R+1)=VALCOUNT(R+1)+1
80 IF VALCOUNT(R+1)>160 THEN END:REM SCREEN LIMIT
90 PLOT R, VALCOUNT(R+1)-1
100 NEXT I
110 END
200 REM NEXT RND
205 X=X*4-2.0
210 X=X*X-2.0:REM FROM [-2, 2] to [-2, 2]
215 X=X/4+0.5
216 IF X=1 THEN X=0.999999999
260 RETURN

Links:

Plotting random pixels

edit

Plotting random pixels in 2-color high-resolution mode (with text window):

10 GRAPHICS 8
20 COLOR 1
30 FOR X=0 TO 319
40 FOR Y=0 TO 159
50 C=INT(2*RND(1))
60 COLOR C
70 PLOT X,Y
80 NEXT Y
90 NEXT X

Plotting random pixels in 4-color low-resolution (2x2-sized pixel) mode (with text window):

10 GRAPHICS 7
20 COLOR 1
30 FOR X=0 TO 159
40 FOR Y=0 TO 79
50 C=INT(4*RND(1))
60 COLOR C
70 PLOT X,Y
80 NEXT Y
90 NEXT X

Plotting random pixels in 16-color low-resolution mode (4x1-sized pixel):

10 GRAPHICS 9
20 COLOR 1
30 FOR X=0 TO 79
40 FOR Y=0 TO 191
50 C=INT(16*RND(1))
60 COLOR C
70 PLOT X,Y
80 NEXT Y
90 NEXT X

Plotting random pixels by low-level access to video memory (not particularly fast either):

10 GRAPHICS 8
20 P=PEEK(560)+PEEK(561)*256
30 VB=PEEK(P+4)+PEEK(P+5)*256:REM VIDEOBASE
40 FOR I=0 TO 320/8*160-1
50 POKE VB+I, PEEK(53770)
60 NEXT I

Plotting random pixel by low-level access, using not the RND but rather custom low-quality pseudorandom generator based on the Mandelbrot map f(x) = x**2 - 2. The result looks sort of random, but with suspectly long streaks/blocks of pixels, which probably correspond to the attractive power of x = 2 for the f(x). We rescale [-2, 2] to [0, 1] to make this work; rescaled, the attracting point x=1 shows it power, e.g. in the following sequence:

  • 0.999753695
  • 0.999015022
  • 0.99606397
  • 0.984317847

The properties of the Mandelbrot map are investigated in the article Mandelbrot set along the real axis and the orbits.

10 GRAPHICS 8
20 P=PEEK(560)+PEEK(561)*256
30 VB=PEEK(P+4)+PEEK(P+5)*256:REM VIDEOBASE
35 X=0.1
40 FOR I=0 TO 320/8*160-1
45 BYTE=INT(256*X)
50 POKE VB+I,BYTE
55 GOSUB 100
60 NEXT I
70 END
100 REM NEXT RND
105 X=X*4-2.0
110 X=X*X-2.0:REM FROM [-2, 2] to [-2, 2]
115 X=X/4+0.5
116 IF X=1 THEN X=0.999999999
155 PRINT X;",";
160 RETURN

Clearing the screen

edit

There is no CLS. One can PRINT CHR$(125) instead.

Outputting Atari ASCII characters

edit

Outputting Atari ASCII sequence except for special CHR$ codes, which are replaced with underscore ("_"):

10 DIM S$(256)
20 FOR I=0 TO 255
30 S$(I+1)=CHR$(I)
40 NEXT I
60 S$(27+1)="_"
61 S$(28+1)="_"
62 S$(29+1)="_"
63 S$(30+1)="_"
64 S$(31+1)="_"
80 S$(125+1)="_"
90 S$(155+1)="_"
100 S$(253+1)="_"
101 S$(254+1)="_"
102 S$(255+1)="_"
110 PRINT S$

Outputting them one character per line, with the ASCII code:

10 DIM S$(256)
20 FOR I=0 TO 255
30 S$(I+1)=CHR$(I)
40 NEXT I
60 S$(27+1)="_"
61 S$(28+1)="_"
62 S$(29+1)="_"
63 S$(30+1)="_"
64 S$(31+1)="_"
80 S$(125+1)="_"
90 S$(155+1)="_"
100 S$(253+1)="_"
101 S$(254+1)="_"
102 S$(255+1)="_"
110 FOR I=0 TO 255
120 PRINT I, S$(I+1,I+1)
130 NEXT I

Links:

Plotting Mandelbrot set

edit

An adaptation of AWK code from rosettacode.org for the 2-color mode 8:

10 GRAPHICS 8
20 XSIZE=320:YSIZE=160
30 ITERMAX=10
40 MINIM=-1.0:MAXIM=1.0:MINRE=-2.0:MAXRE=1.0
50 STEPX=(MAXRE-MINRE)/XSIZE:STEPY=(MAXIM-MINIM)/YSIZE
60 FOR Y=0 TO YSIZE-1
70 CIM=MINIM+STEPY*Y
80 FOR X=0 TO XSIZE-1
90 CRE=MINRE+STEPX*X
100 ITCOUNT=0
110 RE=0:IM=0
120 RESQ=RE*RE:IMSQ=IM*IM
130 IF RESQ+IMSQ > 4 THEN GOTO 170
140 ITCOUNT=ITCOUNT+1
150 IM=2*RE*IM+CIM:RE=RESQ-IMSQ+CRE
160 IF ITCOUNT<ITERMAX THEN GOTO 120
170 ICMOD = ITCOUNT-INT(ITCOUNT/2)*2
180 COLOR ICMOD
190 PLOT X,Y
200 NEXT X
210 NEXT Y

A modification of the above for the 4-color mode 7:

10 GRAPHICS 7
15 SETCOLOR 4,0,0:SETCOLOR 0,0,5:SETCOLOR 1,0,10:SETCOLOR 2,0,15
20 XSIZE=160:YSIZE=80
30 ITERMAX=16
40 MINIM=-1:MAXIM=1:MINRE=-2:MAXRE=1
50 STEPX=(MAXRE-MINRE)/XSIZE:STEPY=(MAXIM-MINIM)/YSIZE
60 FOR Y=0 TO YSIZE-1
70 CIM=MINIM+STEPY*Y
80 FOR X=0 TO XSIZE-1
90 CRE=MINRE+STEPX*X
100 ITCOUNT=0
110 RE=0:IM=0
120 RESQ=RE*RE:IMSQ=IM*IM
130 IF RESQ+IMSQ>4 THEN GOTO 170
140 ITCOUNT=ITCOUNT+1
150 IM=2*RE*IM+CIM:RE=RESQ-IMSQ+CRE
160 IF ITCOUNT<ITERMAX THEN GOTO 120
170 ICMOD=ITCOUNT-INT(ITCOUNT/4)*4
180 COLOR ICMOD
190 PLOT X,Y
200 NEXT X
210 NEXT Y

A modification of the above for the 16-color mode 9:

10 GRAPHICS 9
20 XSIZE=80:YSIZE=192
30 ITERMAX=16
40 MINIM=-1.0:MAXIM=1.0:MINRE=-2.0:MAXRE=1.0
50 STEPX=(MAXRE-MINRE)/XSIZE:STEPY=(MAXIM-MINIM)/YSIZE
60 FOR Y=0 TO YSIZE-1
70 CIM=MINIM+STEPY*Y
80 FOR X=0 TO XSIZE-1
90 CRE=MINRE+STEPX*X
100 ITCOUNT=0
110 RE=0:IM=0
120 RESQ=RE*RE:IMSQ=IM*IM
130 IF RESQ+IMSQ > 4 THEN GOTO 170
140 ITCOUNT=ITCOUNT+1
150 IM=2*RE*IM+CIM:RE=RESQ-IMSQ+CRE
160 IF ITCOUNT<ITERMAX THEN GOTO 120
170 ICMOD = ITCOUNT-INT(ITCOUNT/16)*16
180 COLOR ICMOD
190 PLOT X,Y
200 NEXT X
210 NEXT Y

Calculating modulo

edit

Atari BASIC has no built-in modulo operator. Thus:

10 INPUT X, Y
20 MOD = X - Y*INT(X/Y)
30 PRINT X;" MOD ";Y;": ";MOD

Random walk

edit

Plot a random walk: let x start at 0 and in each successive step, let x either stay where it is, have 1 added to it or have 1 subtracted from it, where one of the 3 options is chosen at random.

10 GRAPHICS 8
20 COLOR 1
30 PLOT 0,0
40 DRAWTO 0,160
50 PLOT 0,80
60 DRAWTO 319,80
70 X=0
80 STEPNO=0
90 PLOT STEPNO,80-X
100 X=X+INT(3*RND(1))-1
110 STEPNO=STEPNO+1
120 IF STEPNO<320 THEN GOTO 90

A different kind of random walk in which the system starts at the middle of the screen, and then both of its X and Y coordinates are repeatedly subject to addition of one of -1, 0, or 1, chosen at random:

10 GRAPHICS 8
20 COLOR 1
30 X=160:Y=80
40 PLOT X,Y
50 X=X+INT(3*RND(1))-1
60 Y=Y+INT(3*RND(1))-1
70 GOTO 40

Plotting Mandelbrot map orbit diagram

edit

Let Mandelbrot map refer to x**2 + c. Plotting its orbit diagram/bifurcation diagram:

10 GRAPHICS 8
20 COLOR 1
30 FOR X=0 TO 320-1
40 C=X/320*2.25-2
50 Y=0
60 FOR IT=0 TO 20
70 YPL=80-Y*40
75 PLOT X,YPL
80 Y=Y*Y+C
90 NEXT IT
100 NEXT X

A variant of the above with initial iterations not plotted:

10 GRAPHICS 8
20 COLOR 1
30 FOR X=0 TO 320-1
40 C=X/320*2.25-2
50 Y=0
60 FOR IT=0 TO 40
70 YPL=80-Y*40
72 IF IT <= 20 THEN GOTO 80
75 PLOT X,YPL
80 Y=Y*Y+C
90 NEXT IT
100 NEXT X

Color registers

edit

Color registers are affected via SETCOLOR. The SETCOLOR parameters are color register number, hue and luminance.

Color register test for two-color text mode 0:

10 GRAPHICS 0
20 PRINT "HELLO"
30 SETCOLOR 0,2,8:REM NO IMPACT
40 SETCOLOR 1,7,0:REM TEXT FOREGROUND; HUE HAS NO IMPACT
50 SETCOLOR 2,0,15:REM TEXT BACKGROUND; SETS HUE FOR TXT FRG
60 SETCOLOR 3,3,8:REM NO IMPACT
70 SETCOLOR 4,1,8:REM OUTER BACKGROUND

Color register test for two-color pixel bitmap mode 8:

10 GRAPHICS 8
20 COLOR 0:PLOT 0,0:DRAWTO 319,0
30 COLOR 1:PLOT 0,1:DRAWTO 319,1
35 SETCOLOR 0,2,8:REM NO IMPACT
40 SETCOLOR 1,7,0:REM COLOR 1; HUE HAS NO IMPACT
50 SETCOLOR 2,0,15:REM COLOR 0; SETS HUE FOR COLOR 1
60 SETCOLOR 3,3,8:REM NO IMPACT
70 SETCOLOR 4,1,8:REM OUTER BACKGROUND

Color register test for four-color pixel bitmap mode 7:

10 GRAPHICS 7
20 COLOR 0:PLOT 0,0:DRAWTO 80-1,0
25 COLOR 1:PLOT 80,0:DRAWTO 160-1,0
30 COLOR 0:PLOT 0,1:DRAWTO 80-1,1
35 COLOR 1:PLOT 80,1:DRAWTO 160-1,1
40 COLOR 2:PLOT 0,2:DRAWTO 80-1,2
45 COLOR 3:PLOT 80,2:DRAWTO 160-1,2
50 COLOR 2:PLOT 0,3:DRAWTO 80-1,3
55 COLOR 3:PLOT 80,3:DRAWTO 160-1,3
60 SETCOLOR 0,0,5:REM COLOR 1
70 SETCOLOR 1,0,10:REM COLOR 2
80 SETCOLOR 2,0,15:REM COLOR 3
90 SETCOLOR 3,2,8:REM NO IMPACT
95 SETCOLOR 4,0,0:REM COLOR 0 AND OUTER BACKGROUND

Color register test for four-color pixel bitmap mode 7 with low-level access (POKEs and bit patters):

10 GRAPHICS 7
20 P=PEEK(560)+PEEK(561)*256
30 VB=PEEK(P+4)+PEEK(P+5)*256: REM VIDEOBASE
35 LB=320/8:REM LINE BYTES
40 FOR I=0 TO LB-1: POKE VB+I, 0: NEXT I
50 FOR I=LB TO 2*LB-1: POKE VB+I, 85: NEXT I
60 FOR I=2*LB TO 3*LB-1: POKE VB+I, 170: NEXT I
70 FOR I=3*LB TO 4*LB-1: POKE VB+I, 255: NEXT I
80 SETCOLOR 0,0,5:REM COLOR 1
90 SETCOLOR 1,0,10:REM COLOR 2
100 SETCOLOR 2,0,15:REM COLOR 3
110 SETCOLOR 3,2,8:REM NO IMPACT
120 SETCOLOR 4,0,0:REM COLOR 0 AND OUTER BACKGROUND

The above confirms the correspondence of the color index used by COLOR to bit patterns 0b00, 0b01, 0b10 and 0b11 in the video memory.

Links:

Low-level access plotting

edit

Plotting diagonal lines using assembly-style low-level programming for plotting pixels in color 1 in mode 8:

10 GRAPHICS 8
20 FOR I=0 TO 159
30 X=I:Y=I:GOSUB 200
40 NEXT I
50 FOR I=0 TO 159
60 X=I+1:Y=I:GOSUB 200
70 NEXT I
80 FOR I=0 TO 159:REM REPEAT FOR TEST
90 X=I:Y=I:GOSUB 200
100 NEXT I
110 END
200 REM PLOT X,Y FOR MODE 8 WITH COLOR 1
205 REM --------------------------------
210 P=PEEK(560)+PEEK(561)*256
220 VB=PEEK(P+4)+PEEK(P+5)*256: REM VIDEOBASE
230 BYTEOFFSET=INT(X/8) + 320/8*Y
240 MOD=X-INT(X/8)*8
250 BITPAT=2^(7-MOD)
260 A=PEEK(VB+BYTEOFFSET):B=BITPAT
270 GOSUB 400
360 POKE VB+BYTEOFFSET, ORED
370 RETURN
400 REM BYTE-OR A, B
405 REM ------------------
410 ORED=0
420 FOR J=0 TO 7
430 ORED=2*ORED
440 IF A<128 AND B<128 THEN GOTO 480
450 ORED=ORED+1
460 IF A>=128 THEN A=A-128
470 IF B>=128 THEN B=B-128
480 A=A*2: B=B*2
490 NEXT J
500 RETURN

Above, we use a custom routine for byte-OR to compensate for its lack in Atari BASIC; an alternative would be to use an assembly routine embedded in BASIC.

Bitwise OR

edit

Byte-sized bitwise OR:

400 REM BYTE-OR A, B
405 INPUT A,B
410 ORED=0
420 FOR J=0 TO 7
430 ORED=2*ORED
440 IF A<128 AND B<128 THEN GOTO 480
450 ORED=ORED+1
460 IF A>=128 THEN A=A-128
470 IF B>=128 THEN B=B-128
480 A=A*2: B=B*2
490 NEXT J
500 PRINT ORED

The Stack Overflow link below contains C code for bitwise AND and XOR that can be translated into Atari BASIC in a rather straightforward manner.

Links:

Reporting joystick state/events

edit
10 PRINT STICK(0), STRIG(0)
20 GOTO 10

Plotting joystick-controlled vehicle

edit

The following plots a joystick-controlled vehicle. The vehicle is a single pixel that has a direction (not just orthogonal) and leaves behind a trace.

10 GRAPHICS 8
15 TWOPI=2*3.141592
20 ANGLERAD=0
30 XRE=160:YRE=80
35 COLOR 1
40 PLOT INT(XRE), INT(YRE)
50 XRE=XRE+COS(ANGLERAD)
60 YRE=YRE+SIN(ANGLERAD)
70 REM ANGLERAD=ANGLERAD+RND(1)*0.1-0.5*0.1
80 IF STICK(0)<>11 THEN GOTO 100:REM 11=RIGHT
90 ANGLERAD=ANGLERAD-0.1
100 IF STICK(0)<>7 THEN GOTO 120:REM 7=LEFT
110 ANGLERAD=ANGLERAD+0.1
120 IF ANGLERAD<TWOPI THEN GOTO 140
130 ANGLERAD=ANGLERAD-TWOPI
140 IF ANGLERAD>=0 THEN GOTO 160
150 ANGLERAD=ANGLERAD+TWOPI
160 GOTO 40

Plotting a spiral

edit

Plotting a spiral with a linearly growing radius:

10 GRAPHICS 8
20 COLOR 1
30 R=0
40 PLOT 160,80
50 FOR X=0 TO 8*2*3.141592 STEP 0.1
60 DRAWTO 160+INT(R*COS(X)),80+INT(R*SIN(X))
70 R=R+0.1
80 NEXT X

Plotting a spiral with an exponentially growing radius:

10 GRAPHICS 8
20 COLOR 1
30 R=2
40 PLOT 160,80
50 FOR X=0 TO 6*2*3.141592 STEP 0.1
60 DRAWTO 160+INT(R*COS(X)),80+INT(R*SIN(X))
70 R=R*1.01
80 NEXT X

Abbreviations

edit

Abbreviations include:

  • "?" for PRINT.
  • L. for LIST
  • GR. for GRAPHICS
  • SE. for SETCOLOR
  • Etc.

Links:

Floating point numbers

edit

The numbers Atari BASIC works with are floating point numbers. They are 6-byte floating point numbers, stored in variable memory as binary-coded decimal (BCD) numbers[1] (but then, why is E97 maximum if it is BCD and not e.g. E99?) The 6 bytes seem to be not always fully made use of. Of them, 5 bytes seem to be for mantissa, and 1 byte for exponent and sign. The 5 bytes should theoretically be able to represent 9999999999, but this gets rounded to 9999999990.

Test:

10 MAXNUM=9.99999999E97
20 PRINT MAXNUM
30 MINNUM=-9.99999999E97
40 PRINT MINNUM
50 PRINT MAXNUM+MINNUM
60 SMALLPOSNUM=1.0E-98:REM PERHAPS NOT SMALLEST
70 PRINT SMALLPOSNUM
80 THIRD=1/3
90 PRINT THIRD
100 IF THIRD=0.333333333 THEN GOTO 100
110 PRINT "THIRD DIFFERS WITH DELTA:", THIRD-0.333333333

The following tests suggests that 1000000000 (1E9) is the largest integer contiguous with other integers with units precisely represented:

10 A=999999999
20 PRINT A
30 A=A+1
40 IF A<1000000010 THEN GOTO 20

However, the above terminates after 10 iterations while outputting 10000000000, which is puzzling and suggests the output does not exactly match the representation.

One more test:

10 FOR I=1 TO 100
20 J=1E9+I
30 K=J-1E9
40 PRINT I,K,J
50 NEXT I

The above outputs K with the same precision as I, but the J output has the final digit replaced with zero, suggesting that the representation during calculation is different from the one for output, and also possibly different from the representation used for tokenization (for storage as a literal in the program).

The internal representation of floating point numbers can be investigated using the following:

10 INPUT A
20 VB=PEEK(134)+256*PEEK(135):REM VARIABLE BASE
30 PRINT PEEK(VB+2);" ";PEEK(VB+3);" ";PEEK(VB+4);" ";PEEK(VB+5);" ";PEEK(VB+6);" ";PEEK(VB+7)

Link:

Calling assembly

edit

One can call assembly/machine code using USR.

Two places where to store the machine code:

  • Page 6: starts at 1536, at most 256 bytes (a page)
  • String, declared via DIM, with more than 256 bytes possible

Two manners of machine code entry:

  • DATA statement
  • String literal

Interaction/interfacing between the calling BASIC code and the machine code:

  • Stack contains the number of arguments and then two bytes per argument containing the integer part of the argument value. It is to be found out how and whether a string argument can be passed.
  • The USR return value is at addresses 212 and 213.

To obtain the machine code from assembly, one can use e.g. the online assembler at masswerk.at. However, it outputs hexadecimal codes, whereas the literals used with DATA command are decimal.

As an example, we implement 8-bit bitwise OR, to fill the gap in the BASIC command/function set:

ASM
-----
PLA ;arg count; discard
PLA ;arg1 upper byte; discard
PLA ;arg1 lower byte
STA 212; lower byte of USR return value
PLA ;arg2 upper byte; discard
PLA ;arg2 lower byte
ORA 212
STA 212
LDA #0
STA 213 ;clear the upper byte of ret val
RTS

ASM HEX
-----
0000: 68 68 68 85 D4 68 68 05
0008: D4 85 D4 A9 00 85 D5 60

ASM DEC
-----
0000: 104 104 104 133 212 104 104 5
0008: 212 133 212 169 00 133 213 96

BASIC WITH ASM AT PAGE 6
------------------------
10 X=0:RESTORE 40
20 READ D:IF D=-1 THEN GOTO 100
30 POKE 1536+X,D:X=X+1:GOTO 20
40 DATA 104, 104, 104, 133, 212, 104, 104, 5
50 DATA 212, 133, 212, 169, 00, 133, 213, 96, -1
100 INPUT A, B
110 RES=USR(1536, A, B)
120 PRINT RES

BASIC WITH ASM AT A STRING
--------------------------
5 DIM ORASM$(20)
10 X=1:RESTORE 40
20 READ D:IF D=-1 THEN GOTO 100
30 ORASM$(X)=CHR$(D):X=X+1:GOTO 20
40 DATA 104, 104, 104, 133, 212, 104, 104, 5
50 DATA 212, 133, 212, 169, 00, 133, 213, 96, -1
100 INPUT A, B
110 RES=USR(ADR(ORASM$), A, B)
120 PRINT RES

Link:

Monte Carlo area calculation

edit

We can calculate unit circle area using Monte Carlo integration, which will give us the value of pi. We will consider a circle with the radius of 128. We will visualize the random points that landed in the circle. There are more efficient methods of pi calculation; this is to illustrate Monte Carlo area calculation, which can be adapted for a broad range of shapes.

10 GRAPHICS 8: COLOR 1
20 I128SQ=128*128
30 FOR I=1 TO 10000
40 X=PEEK(53770)-128
50 Y=PEEK(53770)-128
60 RSQ=X*X+Y*Y
70 IF RSQ <= I128SQ THEN INSIDECNT=INSIDECNT+1
80 IF RSQ <= I128SQ THEN PLOT INT((X+128)/2), INT((Y+128)/2)
90 TOTALCNT=TOTALCNT+1
100 NEXT I
110 AREARATIO=INSIDECNT/TOTALCNT
120 PIAPPROX=AREARATIO*4
130 PRINT "PI ESTIMATE: ";PIAPPROX
140 REM CIRCLE AREA=PI*R^2; SQUARE AREA=4*R^2

Calculating median

edit

To calculate the median, we need to sort the sequence.

10 DIM V(101)
20 FOR I=1 TO 101
30 V(I) = PEEK(53770):REM RANDOM in 0-255
40 NEXT I
50 FOR I=1 TO 101: ? V(I);", ";:NEXT I:PRINT
60 GOSUB 200:REM BUBBLE SORT
70 PRINT "SORTED: "
80 FOR I=1 TO 101: ? V(I);", ";:NEXT I:PRINT
90 PRINT "MEDIAN: ";V(51)
100 END
200 REM BUBBLE SORT ON V
210 SW=0:REM SWAPPED
220 FOR I=1 TO 100
230 IF V(I+1)<V(I) THEN S=V(I):V(I)=V(I+1):V(I+1)=S:SW=1
240 NEXT I
250 IF SW=1 THEN GOTO 210
260 RETURN

Plotting trigonometric functions

edit

Plotting trigonometric functions (sine, cosine, arcus tangent):

10 GRAPHICS 8
20 FOR I=0 TO 319
30 X=I/320*8*2
40 PLOT I,80-40*SIN(X)
50 PLOT I,80-40*COS(X)
55 PLOT I,80-40*ATN(X)
60 NEXT I

Strings

edit

Strings have to be dimensioned for size before use. Their use is as follows:

10 DIM S$(5)
20 S$="HEY":PRINT S$
30 S$="HELLO":PRINT S$
40 S$(2,2)="A":PRINT S$
50 FOR I=1 TO LEN(S$):PRINT S$(I,I);:NEXT I:?
60 S$="HELLO1":REM TRUNCATE 1
70 PRINT CHR$(65):REM A
80 PRINT ASC("A")
90 DIM H$(11)
100 H$="HELLO"
110 H$(LEN(H$)+1)=" WORLD":REM CONCATENATE
120 PRINT H$

Strings can be used as byte arrays, via ASC and CHR$. The syntax is cumbersome but if one uses a numerical array to store byte values, one value occupies 6 bytes. Setting the byte value to 65 and then incrementing it:

10 DIM S$(5)
20 FOR I=1 TO 5: S$(I,I)=CHR$(65): NEXT I
30 FOR I=1 TO 5: S$(I,I)=CHR$(ASC(S$(I,I))+1): NEXT I
40 PRINT S$

We can also use strings as byte arrays via ADR, PEEK and POKE:

10 DIM S$(5)
15 S$(5)="A":SA=ADR(S$)
20 FOR I=1 TO 5: POKE SA+I-1, 65: NEXT I
30 FOR I=1 TO 5: POKE SA+I-1, PEEK(SA+I-1)+1: NEXT I
40 PRINT S$

ADR and other techniques from above are also used to store machine code in a string, as per Calling assembly section.

Calculating prime numbers

edit
10 FOR I=2 TO 100
20 PR=1
30 FOR D=2 TO INT(SQR(I))
40 IF D<>I AND I/D=INT(I/D) THEN PR=0
50 NEXT D
60 IF PR=1 THEN PRINT I
70 NEXT I

Calculating pi

edit

One can get an acceptable approximation of pi via PI=4*ATN(1) or PI=2*ATN(1E97), the latter turning out to be more accurate. An algorithm for calculation of many digits coded in Atari BASIC is in the link.

Further reading:

  • Pi, rosettacode.org

Player-missile graphics

edit

Player-missing graphics (called sprites on some computers) can be controlled only via low-level access, peeks and pokes. One can use string tricks, by which one cleverly makes sure a string to contain the PMG buffer is aligned at a boundary, which is required of the buffer. Moreover, copying strings is rather fast, unlike copying bytes one by one in a for loop; and copying blocks of bytes is required to change the vertical position of the PMG objects.

Links:

Plotting a rectangular spiral

edit
10 GRAPHICS 8: COLOR 1
20 FOR I=0 TO 39
30 PLOT 0+2*I,0+2*I
40 IF I>0 THEN PLOT 0+2*I-2,0+2*I
50 DRAWTO 319-2*I,0+2*I
60 DRAWTO 319-2*I,159-2*I
70 DRAWTO 0+2*I,159-2*I
80 DRAWTO 0+2*I,0+2*I+2
90 NEXT I

A LCG pseudorandom number generator

edit

There is the built-in RND pseudorandom generator function, which probably uses the 8-bit pseudorandom generator provided by the POKEY chip's 17-bit linear feedback shift register available at address 53770. This generator does not provide seeding and reproducibility; sources indicate that POKEY updates the value of its generator each CPU cycle, regardless of whether the value is being read. If one wants to have seeding and reproducibility, one can use e.g. linear congruential generator, in which the next integral value is calculated from the previous one using the formula x := a*x + c mod m for well chosen values of a, c and m. Since Atari BASIC numbers are floating point numbers with guaranteed 9 decimal places (rather than integers), one has to use m much smaller than 2^32 or 2^31. In the following, from Wikipedia's parameter combinations we select the one indicated for ZX81 (it traces to no source!), for which both the m and a*m fit with full integer precision into the Atari BASIC float. We convert the generated numbers to 8-bit integer values and plot the counts of value visits for a crude visual verification of the uniformity of the distribution. An alternative with a much longer period would be Wichmann–Hill, which combines three LCGs with the m of ca. 30000 and the a of ca. 170, well fitting into Atari BASIC float.

10 REPEATCOUNT=1000000
20 DIM VALCOUNT(256)
30 GRAPHICS 8: COLOR 1
35 RX=0:REM OR A DIFFERENT SEED
40 FOR I=1 TO 256: VALCOUNT(I)=0: NEXT I
50 FOR I=1 TO REPEATCOUNT
55 GOSUB 200:REM RX:=RND
60 R=RX-256*INT(RX/256):REM MOD
70 VALCOUNT(R+1)=VALCOUNT(R+1)+1
80 IF VALCOUNT(R+1)>160 THEN END:REM SCREEN LIMIT
90 PLOT R, VALCOUNT(R+1)-1
100 NEXT I
110 END
200 REM NEXT RND
210 RX=75*RX+74
220 RX=RX-65537*INT(RX/65537):REM MOD
260 RETURN

Further reading:

Hangman

edit

The game of hangman is beautifully simple to implement, making it a good fit as an exerise for novice kid programmers. In the following, we omit any graphics and only count lives lost/left. Note that the words in the DATA statement are entered, perhaps surprisingly, without quotation marks.

10 WORDMAXLEN=10:LIVES=5:WRDCOUNT=10
20 DIM WORD$(WORDMAXLEN), GUESS$(1)
30 DATA GROUND,SNEEZE,MOTHER,SISTER,PILLAR,SMILED,POOLED,TOTALS,FROZEN,RABBIT
40 IDX=INT(RND(1)*WRDCOUNT)
50 I=0
60 READ WORD$
70 IF I<IDX THEN I=I+1: GOTO 60
80 DIM LTRUNKN(WORDMAXLEN)
90 FOR I=1 TO LEN(WORD$):LTRUNKN(I)=1:NEXT I
100 LTGC=LEN(WORD$):REM LETTER TO GUESS COUNT
110 FOR I=1 TO LEN(WORD$)
120 IF LTRUNKN(I)=1 THEN PRINT "*";
130 IF LTRUNKN(I)=0 THEN PRINT WORD$(I,I);
140 NEXT I
150 PRINT
160 IF LTGC=0 THEN PRINT "COMPLETED!":END
170 INPUT GUESS$
180 LTGCO=LTGC
190 FOR I=1 TO LEN(WORD$)
200 IF WORD$(I,I) = GUESS$ THEN LTRUNKN(I) = 0:LTGC=LTGC-1
210 NEXT I
220 IF LTGCO=LTGC THEN LIVES=LIVES-1: PRINT "LIVES LEFT: ";LIVES
230 IF LIVES = 0 THEN PRINT "NO MORE LIVES.":END
240 GOTO 110

Binary expansion

edit

We can calculate the binary expansion of a number between 0 and 1, an analogue of decimal expansion. The bits after decimal points represent 1/2, 1/4, 1/8, etc. The task seems simple enough for kids to figure it out. Tests values: 0.5: 0.1; 0.25: 0.01; 0.125: 0.001; 0.625: 101; 0.7: 0.10110011001100110011....

There is an ambiguity: e.g. 0.5 can be rendered as 0.1 or as 0.01111... The requirement is to produce the former, arguably more natural, output, in part since we cannot really output an infinite sequence, so the output sequence would be slightly imprecise in the latter option but not in the former option.

10 PRINT "BINARY EXPANSION"
20 INPUT X
30 IF X<0 OR X>=1 THEN PRINT "BAD X":END
40 BV=0.5
50 PRINT "0.";
60 FOR I=1 TO 30
70 IF X >= BV THEN GOTO 90
80 PRINT "0";:GOTO 100
90 X=X-BV:PRINT "1";
100 BV=BV/2
110 IF X=0 THEN I=30
120 NEXT I
130 PRINT
140 GOTO 20

An alternative implementation by repeated multiplying the X by 2.

10 PRINT "BINARY EXPANSION"
20 INPUT X
30 IF X<0 OR X>=1 THEN PRINT "BAD X":END
35 PRINT "0.";
40 FOR I=1 TO 30
50 X=X*2
60 IF X<1 THEN PRINT "0";
70 IF X>=1 THEN PRINT "1";:X=X-1
80 IF X=0 THEN I=30
90 NEXT I
100 PRINT
110 GOTO 20

Going in the other direction, from binary to decimal, is also very simple and is left as an exercise.

Ternary expansion

edit

Using a method similar to binary expasion, we can calculate the ternary expansion. We can also add a verification part, which converts the ternary expansion back to the decimal form.

5 DIM S$(37)
10 PRINT "TERNARY EXPANSION"
20 INPUT X
30 IF X<0 OR X>=1 THEN PRINT "BAD X":END
40 S$="0."
50 FOR I=1 TO 35
60 X=X*3
70 IF X<1 THEN S$(I+2,I+2)="0"
80 IF X>=2 THEN S$(I+2,I+2)="2":X=X-2
90 IF X>=1 THEN S$(I+2,I+2)="1":X=X-1
100 IF X=0 THEN I=35
110 NEXT I
120 PRINT S$
130 REM ----Verification----
140 W=1/3:X=0
150 FOR I=1 TO 35
160 IF S$(I+2,I+2)="1" THEN X=X+W
165 IF S$(I+2,I+2)="2" THEN X=X+2*W
170 W=W/3
180 NEXT I
190 PRINT "RECONSTRUCTED X: ";X

Base n expansion

edit

Using the exercise of ternary expansion above as a warm up, we can implement general base n expansion. For base greater than 16, we output the digits in decimal separated by semicolon. For base greater than 10 and smaller than or equal to 16, we output A-F as digits in addition to 0-9. We include verification by calculating the inverse, where the inverse is simpler.

10 DIM S(30), SEP$(1)
20 PRINT "BASE N EXPANSION"
30 PRINT "X:";:INPUT X:OX=X
40 IF X<0 OR X>=1 THEN PRINT "BAD X":END
50 PRINT "BASE:";:INPUT B
60 B=INT(B)
70 IF B<2 THEN PRINT "BAD BASE":END
80 L=30:LF=0:REM LENGTH and LENGTH FOUND
90 FOR I=1 TO 30
100 X=X*B
110 IF X<1 THEN S(I)=0
120 FOR J=B-1 TO 1 STEP -1
130 IF X>=J THEN S(I)=J:X=X-J
140 NEXT J
150 IF LF=0 AND X=0 THEN LF=1:L=I
160 NEXT I
170 PRINT "0.";
180 IF B>10 AND B<=16 THEN GOTO 230
190 SEP$="":IF B>10 THEN SEP$=";"
200 FOR I=1 TO L:PRINT S(I);SEP$;:NEXT I
210 PRINT
220 GOTO 300
230 REM BASE 11 TO 16
240 FOR I=1 TO L
250 IF S(I)<10 THEN PRINT S(I);
260 IF S(I)>=10 THEN PRINT CHR$(65+S(I)-10);
270 NEXT I
280 PRINT
290 REM -------- Verification --------
300 RX=0
310 W=1/B
320 FOR I=1 TO 30
330 RX=RX+S(I)*W
340 W=W/B
350 NEXT I
360 PRINT "RECON.X & DELTA: ";RX;"; ";ABS(OX-RX)

Number system conversion

edit

We can implement number system conversion for positive integers: the user enters a decimal number and the target base; the number converted to the targer base is output. The idea used is rather similar to the base n expansion above. The reader can write a more general program as an exercise: the input is not in decimal and is a string, and there are both input base and output base specified.

10 DIM S(100)
20 PRINT "NUMBER SYSTEM CONVERSION"
30 PRINT "X: ";:INPUT X
40 PRINT "BASE: ";:INPUT B
50 TH=1:REM THRESHOLD
60 TH=TH*B
70 IF TH<=X THEN GOTO 60
80 I=1
90 TH=TH/B
100 IF X<TH THEN S(I)=0
110 FOR J=B-1 TO 1 STEP -1
120 IF X>=TH*J THEN S(I)=J:X=X-TH*J:J=1
130 NEXT J
140 IF TH>1 THEN I=I+1:GOTO 90
150 IF B>10 AND B<=16 THEN GOTO 190
160 DIM SEP$(1):SEP$="":IF B>10 THEN SEP$=";"
170 FOR K=1 TO I: PRINT S(K);SEP$;:NEXT K:PRINT
180 GOTO 250
190 REM BASE 11-16
200 FOR K=1 TO I
210 IF S(K)<10 THEN PRINT S(K); 
220 IF S(K)>=10 THEN PRINT CHR$(65+S(K)-10);
230 NEXT K
240 PRINT
250 REM ---- Verification ----
260 X=0:W=1
270 FOR K=I TO 1 STEP -1:X=X+S(K)*W:W=W*B:NEXT K
280 PRINT "RECON.X: ";X

Limitations

edit

Limitations of Atari BASIC include the following:

  • Very limited procedural programming (with GOSUB and RETURN): no named procedures, no argument declaration and no automatic storage of local variables on the stack.
  • No separation of integer arithmetic from floating-point arithmetic. The consequence is a major slowdown of what could otherwise be integer operations.
  • No while loop. One can use GOTO instead.
  • No blocks (like BEGIN and END). One can use GOTO instead.
  • No bitwise operators.
  • No taking of address of a variable except for a string variable.
  • No hexadecimal literals.
  • No commands for player-missile graphics.
  • Almost no plotting commands: only PLOT and DRAWTO.
  • No CLS (very minor limitation if one at all).

References

edit
  1. Why did 8-bit Basic use 40-bit floating point?, retrocomputing.stackexchange.com

Further reading

edit