Category Archives: Retro Computing

Porting a BBC Micro galaxy program to the CoCo

Over in the Facebook CoCo group, Carlos Comacho shared a link to a cool online emulator for the BBC Micro. The link contains a BASIC program listing that drew a very cool galaxy graphic.

Here is the page:

https://bbcmic.ro/?t=9db8C

Here is the BBC Micro program:

10 MODE1
20 VDU5
30 FORG=RND(-64)TO400
40 t=RND(99):q=RND(99)
50 u=RND(1280):v=RND(1024)
60 A=RND(1)*3
70 R=90/(1+RND(200))
80 Q=1+R*(.5+RND(1)/2)
90 a=1+3*RND(1)^2
100 M=1
110 IFRND(9)<4Q=R:t=0:q=0:A=0:M=PI/3:a=1
120 C=(1+3*RND(1)^2)*R*R
130 FORi=0TOC
140 S=-LNRND(1)
150 T=i*M
160 U=S*R*SINT
170 V=S*Q*COST
180 T=S*A
190 X=U*COST+V*SINT
200 Y=V*COST-U*SINT
210 D=(X*X+Y*Y)/(R*R+Q*Q)
220 Z=99*((2.7^-D)+.1)
230 Z=Z*(RND(1)-.5)^3
240 y=Y*COSt+Z*SINt
250 Z=Z*COSt-Y*SINt
260 x=u+X*COSq+y*SINq
270 y=v-X*SINq+y*COSq
280 P=POINT(x,y)+a
290 IFP>3P=3
300 GCOL0,P
310 PLOT69,x,y
320 NEXT,
330 REM

I decided to try converting it to the CoCo. This led me to looking up some BBC Micro Commands to understand what they did. I found a few significant differences:

  • All variables need to be uppercase to work on the CoCo.
  • PI is not a constant on the CoCo, so it needs to be defined.
  • Line 10 – MODE1 sets a 320×256 4 color screen. On the CoCo 1/2, the 4-color screen has a resolution of 128×191.
  • Line 20 – VDU5 – Not used on the CoCo. It outputs a screen character of 5 for some reason.
  • Line 30 – RND(-64) would seed the random number generator, but on the BBC Micro it returns the -64. On the CoCo, RND(-64) looks like it does the same as RND(0) on the CoCo, returning a value from 0.0 to .9999. The FOR/NEXT will just have to do -64 for the start number.
  • Line 80, 90, 120, 140 and 230 – Their RND(1) returns a number from 0.0 to .99999. This will be changed to RND(0) on the CoCo.
  • Line 110 and 290 – The CoCo needs a THEN between IF X>4 THEN DO SOMETHING.
  • Line 160,170, 190, 200, 240, 250, 260 and 270 – SIN and COS need parens on the CoCo.
  • Line 280 – POINT looks like PPOINT on the CoCo.
  • Line 290 – If the program was written for a 4-color “MODE1” display, I assume their colors are 0-3. On the CoCo, they are 1-4, so that is a minor change.
  • Line 300 – GCOL0,P sets a color, so probably COLOR P.
  • Line 310 – PLOT69,X,Y becomes PSET(X,Y)
  • Line 320 – Their BASIC has an interesting shortcut for NEXT. After a FOR A you need a NEXT A, but you can also just say “NEXT” and it uses the most recent FOR. If you have nested loops, like FOR A :FOR B … you can end with “NEXT:NEXT” or “NEXT B,A”. It looks like their BASIC allows leaving out the variables, but using the comma. I tried this on CoCo, and it didn’t work. That would have been a fun discovery. That just means “NEXT,” becomes “NEXT:NEXT”.

When I did a quick port, I saw it passing negative values to PPOINT and PSET, so I had to add an extra check that skips plotting anything that would be off the screen (X <0 or >255, and Y<0 or >191).

My attempt at a CoCo 1/2 version looks like this:

0 'BBC MICRO - 320X256	4 COLOUR DISPLAY
1 POKE 65395,0
5 PI=3.141592653589793238
10 'MODE1
11 PMODE 4,1:PCLS:SCREEN 1,1:PMODE 3,1
20 'VDU5
30 'FORG=RND(-64)TO400
31 FORG=-64TO400
40 T=RND(99):Q=RND(99)
50 U=RND(1280):V=RND(1024)
60 A=RND(1)*3
70 R=90/(1+RND(200))
80 'Q=1+R*(.5+RND(1)/2)
81 Q=1+R*(.5+RND(0)/2)
90 'A=1+3*RND(1)^2
91 A=1+3*RND(0)^2
100 M=1
110 'IFRND(9)<4Q=R:T=0:Q=0:A=0:M=PI/3:A=1
111 IFRND(9)<4THENQ=R:T=0:Q=0:A=0:M=PI/3:A=1
120 'C=(1+3*RND(1)^2)*R*R
121 C=(1+3*RND(0)^2)*R*R
130 FORI=0TOC
140 'S=-LNRND(1)
141 S=-(LOG(RND(0)) / 0.4342944819)
150 T=I*M
160 'U=S*R*SINT
161 U=S*R*SIN(T)
170 'V=S*Q*COST
171 V=S*Q*COS(T)
180 T=S*A
190 'X=U*COST+V*SINT
191 X=U*COS(T)+V*SIN(T)
200 'Y=V*COST-U*SINT
201 Y=V*COS(T)-U*SIN(T)
210 D=(X*X+Y*Y)/(R*R+Q*Q)
220 Z=99*((2.7^-D)+.1)
230 'Z=Z*(RND(1)-.5)^3
231 Z=Z*(RND(0)-.5)^3
240 'Y=Y*COST+Z*SINT
241 Y=Y*COS(T)+Z*SIN(T)
250 'Z=Z*COST-Y*SINT
251 Z=Z*COS(T)-Y*SIN(T)
260 'X=U+X*COSQ+Y*SINQ
261 X=U+X*COS(Q)+Y*SIN(Q)
270 'Y=V-X*SINQ+Y*COSQ
271 Y=V-X*SIN(Q)+Y*COS(Q)
275 IF X<0 OR X>255 THEN 321
276 IF Y<0 OR Y>191 THEN 321
280 'P=POINT(X,Y)+A
281 P=PPOINT(X,Y)+A
290 'IFP>3P=3
291 IFP>3THENP=3
300 'GCOL0,P
301 COLORP
310 'PLOT69,X,Y
311 PSET(X,Y)
320 'NEXT,
321 NEXT:NEXT
330 REM
340 GOTO 340

Unfortunately, these changes were not enough.

Can you help?

My old (and new) VIC-20 stuff is now on Github

Preservation…

https://github.com/allenhuffman/VIC-20

And so is my *ALL RAM* BBS for the CoCo:

https://github.com/allenhuffman/ALL-RAM-BBS

And various BASIC things from other blog posts:

https://github.com/allenhuffman/BASIC

And a Lites Out game from an upcoming blog series:

https://github.com/allenhuffman/Lites-Out

There is even a VIC-20 port of that one:

https://github.com/allenhuffman/VIC-20/tree/main/my%20new%20programs/lites%20out

Simple 8×8 and 16×16 Tank on 256×192 screen in Color BASIC

Just for fun…

Here is a program that creates four 8×8 “sprites” that can be PUT on the screen. Each sprite represents the tank aimed in a different direction (up, down, left or right). For speed, they are only PUT on a byte boundary.

Use the arrow keys to move the tank around the screen.

The POKEs in line 180 reset the BASIC keyboard rollover table, which lets you hold down an arrow and the tank keeps moving.

Just for fun…

0 ' 
1 ' TANKPUT.BAS
2 ' ALLEN C. HUFFMAN
3 ' SUBETHASOFTWARE.COM
4 ' 2023-01-16
5'

10 ' LOAD SPRITES
20 DIM TU(1),TD(1),TL(1),TR(1),Z,L,V:GOSUB 2000

30 ' ARROW KEYS
40 KY$=CHR$(94)+CHR$(10)+CHR$(8)+CHR$(9)

50 ' 256X192 BLACK AND WHITE
60 PMODE 4,1:PCLS 1:SCREEN 1,1

70 ' TANK POSITION AND DIR.
80 TX=0:TY=0:TD=4:OX=0:OY=0

90 ' ERASE PREV POSITION
100 LINE(OX,OY)-(OX+7,OY+7),PSET,BF
110 ' PUT TANK ON SCREEN
120 ON TD GOTO 130,140,150,160
130 PUT(TX,TY)-(TX+7,TY+7),TU:GOTO 180
140 PUT(TX,TY)-(TX+7,TY+7),TD:GOTO 180
150 PUT(TX,TY)-(TX+7,TY+7),TL:GOTO 180
160 PUT(TX,TY)-(TX+7,TY+7),TR

170 ' READ KEYBOARD (WASD)
180 POKE&H155,&HFF:POKE&H156,&HFF:POKE&H157,&HFF:POKE&H158,&HFF
190 A$=INKEY$:IF A$="" THEN 190
200 LN=INSTR(KY$,A$):IF LN=0 THEN 180
210 OX=TX:OY=TY
220 ON LN GOTO 240,270,300,330

230 ' UP
240 TD=1:IF TY>0 THEN TY=TY-8:GOTO 100
250 GOTO 120
260 ' DOWN
270 TD=2:IF TY<184 THEN TY=TY+8:GOTO 100
280 GOTO 120
290 ' LEFT
300 TD=3:IF TX>0 THEN TX=TX-8:GOTO 100
310 GOTO 120
320 ' RIGHT
330 TD=4:IF TX<248 THEN TX=TX+8:GOTO 100
340 GOTO 120

999 GOTO 999

2000 ' LOAD SPRITE CHARACTERS
2010 PRINT "LOADING DATA";
2030 L=VARPTR(TU(0)):GOSUB 3000
2040 L=VARPTR(TD(0)):GOSUB 3000
2050 L=VARPTR(TL(0)):GOSUB 3000
2060 L=VARPTR(TR(0)):GOSUB 3000
2190 RETURN

3000 ' READ DATA AND POKE AT L
3010 PRINT ".";
3020 FOR Z=L TO L+7:READ V:POKE Z,(NOT V)+256:NEXT:RETURN
3030 ' 8X8 SPRITE CHARACTERS
3040 ' TANK UP
3050 DATA 24,24,219,255,255,255,255,195
3060 ' TANK DOWN
3070 DATA 195,255,255,255,255,219,24,24
3080 ' TANK LEFT
3090 DATA 63,63,30,254,254,30,63,63
3100 ' TANK RIGHT
3110 DATA 252,252,120,127,127,120,252,252

And here is one using 16×16 sprites:

10 ' 
20 ' TANKPUT16.BAS
30 ' ALLEN C. HUFFMAN
40 ' SUBETHASOFTWARE.COM
50 ' 2023-01-16
60'

70 ' LOAD SPRITES
80 DIM TU(6),TD(6),TL(6),TR(6),Z,L,V:GOSUB 2000

90 ' ARROW KEYS
100 KY$=CHR$(94)+CHR$(10)+CHR$(8)+CHR$(9)

110 ' 256X192 BLACK AND WHITE
120 PMODE 4,1:PCLS 1:SCREEN 1,1

130 ' TANK POSITION AND DIR.
140 TX=0:TY=0:TD=4:OX=0:OY=0

150 ' ERASE PREV POSITION
160 LINE(OX,OY)-(OX+15,OY+15),PSET,BF
170 ' PUT TANK ON SCREEN
180 ON TD GOTO 190,200,210,220
190 PUT(TX,TY)-(TX+15,TY+15),TU:GOTO 240
200 PUT(TX,TY)-(TX+15,TY+15),TD:GOTO 240
210 PUT(TX,TY)-(TX+15,TY+15),TL:GOTO 240
220 PUT(TX,TY)-(TX+15,TY+15),TR

230 ' READ KEYBOARD (WASD)
240 POKE&H155,&HFF:POKE&H156,&HFF:POKE&H157,&HFF:POKE&H158,&HFF
250 A$=INKEY$:IF A$="" THEN 250
260 LN=INSTR(KY$,A$):IF LN=0 THEN 240
270 OX=TX:OY=TY
280 ON LN GOTO 300,330,360,390

290 ' UP
300 TD=1:IF TY>0 THEN TY=TY-8:GOTO 160
310 GOTO 180
320 ' DOWN
330 TD=2:IF TY<176 THEN TY=TY+8:GOTO 160
340 GOTO 180
350 ' LEFT
360 TD=3:IF TX>0 THEN TX=TX-8:GOTO 160
370 GOTO 180
380 ' RIGHT
390 TD=4:IF TX<240 THEN TX=TX+8:GOTO 160
400 GOTO 180

2000 ' LOAD SPRITE CHARACTERS
2010 PRINT "LOADING DATA";
2020 L=VARPTR(TU(0)):GOSUB 3000
2030 L=VARPTR(TD(0)):GOSUB 3000
2040 L=VARPTR(TL(0)):GOSUB 3000
2050 L=VARPTR(TR(0)):GOSUB 3000
2060 RETURN
3000 ' READ DATA AND POKE AT L
3010 PRINT ".";
3020 FOR Z=L TO L+31:READ V:POKE Z,(NOT V)+256:NEXT:RETURN
3030 ' 8X8 SPRITE CHARACTERS
3040 ' TANK UP
3050 DATA 1,128
3060 DATA 1,128
3070 DATA 1,128
3080 DATA 1,128
3090 DATA 225,135
3100 DATA 255,255
3110 DATA 225,135
3120 DATA 227,199
3130 DATA 229,167
3140 DATA 229,167
3150 DATA 228,39
3160 DATA 228,39
3170 DATA 227,199
3180 DATA 224,7
3190 DATA 255,255
3200 DATA 224,7
3210 ' TANK DOWN
3220 DATA 224,7
3230 DATA 255,255
3240 DATA 224,7
3250 DATA 227,199
3260 DATA 228,39
3270 DATA 228,39
3280 DATA 229,167
3290 DATA 229,167
3300 DATA 227,199
3310 DATA 225,135
3320 DATA 255,255
3330 DATA 225,135
3340 DATA 1,128
3350 DATA 1,128
3360 DATA 1,128
3370 DATA 1,128
3380 ' TANK LEFT
3390 DATA 15,255
3400 DATA 15,255
3410 DATA 15,255
3420 DATA 4,2
3430 DATA 4,2
3440 DATA 4,242
3450 DATA 5,10
3460 DATA 255,202
3470 DATA 255,202
3480 DATA 5,10
3490 DATA 4,242
3500 DATA 4,2
3510 DATA 4,2
3520 DATA 15,255
3530 DATA 15,255
3540 DATA 15,255
3550 ' TANK RIGHT
3560 DATA 255,240
3570 DATA 255,240
3580 DATA 255,240
3590 DATA 64,32
3600 DATA 64,32
3610 DATA 79,32
3620 DATA 80,160
3630 DATA 83,255
3640 DATA 83,255
3650 DATA 80,160
3660 DATA 79,32
3670 DATA 64,32
3680 DATA 64,32
3690 DATA 255,240
3700 DATA 255,240
3710 DATA 255,240

The VAL overflow bug and scientific notation

A few months back, 8-bit Show and Tell tweeted this:

I was curious if the bug existed in Color BASIC, so I tried it and tweeted back confirming ours had the same issue.

Recently, he posted this deep dive video about the bug, mentioning my reply and others (including CoCoist Tim Lindner) that showed this bug on various other systems:

At the time of the original tweet, I wrote a blog post about the bug (that is the screen shot he shows in his video). I thought I’d add a follow-up to my post, with a bit more detail.

The bug explained…

William Astle commented on my original post explaining what was going on, similar to the explanation Robin gave in a tweet reply.

The issue is that VAL puts a NUL at the end of the string then calls the regular number parser, which can then bail out unceremoniously. While VAL itself does restore the original byte after the string, that code path does not execute when an error occurs parsing the number. The NUL is required so the parser knows where to stop interpreting bytes.

– William Astle, 8/17/2023

Let’s dive in a bit more from the CoCo side… First, just in case you aren’t a BASIC programmer, the VAL keyword is designed to convert a string to a numeric variable. For instance, you can do this:

A$="1234"
A=VAL(A$)

Above, A$ is a string variable containing the characters “1234” and A is a numeric variable of 1234. I see this often used in Extended Color BASIC when the LINE INPUT command is used with a string, and then converted to a number:

10 LINE INPUT "AGE: ";A$
20 A=VAL(A$)

But I digress..

LINE INPUT is a better form of INPUT, but it only works with string variables. If you were to type letters in to a LINE INPUT, then run those through VAL, they should evaluate as 0. So type in “42” and VAL(A$) gives you 42. Type in “BACON” and VAL(A$) gives you 0. If you had just used INPUT “AGE”;A and typed non-numbers, it would respond with “?REDO” and go back to the input prompt.

Second, let’s make the bug easier to see by clarifying this “1E39” scientific notation thing. The bug has nothing to do with using scientific notation. It has to do with having a number that is too big causing the Overflow Error and aborting the VAL conversion of a string to a number.

Scientific Notation

“1E39” is a number 1 followed by 39 zeros. It appears BASIC is happy to print out the full number if it is short enough, but at some point starts showing it in scientific notation. I found that 1 followed by 8 zeros (100000000) is fine, but 9 zeros switched over to scientific notation:

And it does that even if you just try to use a number like “1000000000” directly:

I guess I had never used numbers that large during my Color BASIC days. ;-)

You may notice it prints “1E9” back as “1E+09”. You can use “1E+09” or “1E+9” as well, and it does the same thing. If you leave out the “+”, it assumes it. The reason for the plus is because you can also use it to represent fractional numbers. In the case of +9, it is moving the decimal place nine places to the right. “1E5” is taking “1.0” and moving the decimal place five places to the right like “100000.0”

If you use a “-“, you are moving the decimal that many places left. “1E-1” takes “1.0” and moves the decimal one spot left, producing “.1”. It appears you cannot print as many values that way before it turns in to scientific notation:

And, printing those values directly shows something similar:

I guess I had never used numbers that small during my Color BASIC days. ;-)

This made me wonder if the VAL bug would happen if a value was too small, but it seems at some point the number just becomes zero, so no error occurs. (Maybe William will chime in with some more information on this. I was actually expecting a similar “Underflow” error, but I don’t think we have a ?UF ERROR in Color BASCIC ;-)

For fun, I wondered if this was truly considered zero. In C, using floating to compare against specific floating point values can cause issues. For example:

#include <stdio.h>
#include <stdlib.h>

int main()
{
float a = 902.1;

if (a == 902.1)
{
printf ("a is 902.1\n");
}
else
{
printf ("a is NOT 902.1\n");
}

return EXIT_SUCCESS;
}

I have discussed this here in the past, but if you run that, it will print “a is NOT 902.1″. This is because 902.1 is not a value that a 32-bit C floating point variable can exactly represent. I expect this could also be the case in Color BASIC, so I wanted to do a quick check and see if “1E-39” (which shows as zero) really was 0:

IF 0=1E-39 THEN PRINT "YES"

That printed “YES” so I will just assume at a certain point, BASIC floating point values just turn in to zero.

But I digress… Again.

The point is, it’s a bug with the number being too large, so even if you do this, you can cause the overflow:

10 A=VAL("1000000000000000000000000000000000000000"):REM SHOW BUG
20 PRINT A

Above, that 40 character number (1 with the decimal place 39 places to the right) is just too long and it will cause the ?OV ERROR.

Strings in String Memory

In my String Theory series, I dove in to how strings work on the CoCo. The important bit is there is reserved string memory for strings, for things like INPUT/LINE INPUT, and string manipulation like MID$, LEFT$, etc. There are also “constant” strings that exist in the program code itself.

If you assign a string directly from BASIC (not in a line number of a program), it will go in to string memory:

A$="THIS IS IN STRING MEMORY"
PRINT A$
THIS IS IN STRING MEMORY

But, if that is in a program, BASIC just makes an entry for “A$” and points it to the spot in the BASIC program where the quoted text exists:

10 A$="THIS IS IN PROGRAM MEMORY"
20 PRINT A$
RUN
THIS IS IN PROGRAM MEMORY

That is what causes the problem with VAL. BASIC attempts to modify the closing quote in the BASIC program itself and make it a 0, and never restores it. The BASIC “LIST” command starts showing the line up until it sees a 0, then stops. The rest of the line is still in memory, but is now invisible to LIST. If you try to run the program after it gets “corrupted”, it will error out on the VAL line since it is missing the closing quote:

However, the code is still there. If you know where the BASIC program starts in memory, and ends in memory, you can use PEEK/PRINT to see the contents. Memory locations 25/26 are the start of the BASIC program, and locations 27/28 are the start of variables which are stored directly after the program, so something like this would do it:

Much like what Robin showed in his video using a machine language monitor to dump memory, above we can look for the bytes that would be the “1E39” (quote is 34, “1” is 49, “E” is 69, “3” is 51 and “9” is 57), we can find that byte sequence of 34, 49, 69, 43, 51 and 57 in the second line followed by a zero where the final quote (34) used to be. After that zero is a 41 which is the “)” that used to be in VAL(“1E39”), then a 58 which is a “:” colon, and then a 130 which is the byte for the “REM” token, then a 32 which is a space, and 83, 72, 79 and 38 are “SHOW” followed by a 32 space then 66, 85, 71 which is “BUG” and a real 0 marking the end of the line.

If I knew the byte that is now a 0, I could just POKE it back to 34 and restore the program, just like Robin did on his Commodore 64.

FOR A=PEEK(25)*256+PEEK(26) TO PEEK(27)*256+PEEK(28):PRINT A,PEEK(A):NEXT

That would start printing memory locations and I could quickly BREAK the program when I see the 0 I am looking for show up.

I believe the zero at 9744 is the one after “1E39” and I can do this to restore the program:

Now, if only Color BASIC did that after an ?OV ERROR! Although we did get an updated BASIC in 1986 for the Color Computer 3, it was just patches on top of the old Microsoft BASIC to add new CoCo 3 features.

Avoiding the VAL bug

Which brings me to this… If the string to parse was in string memory, changing that final byte and not changing it back would be no problem because strings all end with a 0 in memory anyway! There is nothing to corrupt.

To force a variable to be in string memory, you can add +”” when you declare it, like this:

10 A$="THIS IS IN STRING MEMORY"+""

Since BASIC has to combine those two strings, it makes a new string in string memory to copy the “THIS IS IN STRING MEMORY” string and the “” string there. (It is not smart enough to check to know that “” is unneeded, which is good because it lets us do things like this.)

10 A=VAL("1E39"+""):REM NO BUG HERE
20 PRINT A

And that is a simple way to work around this bug. Since the bug only affects hard-coded strings in program memory, it should be easy to avoid just by not using values too large for variables :)

And if you are inputting them, the INPUT is going in to string memory and you will still get an ?OV ERROR (crashing the program) but at least the program would not get corrupted:

10 PRINT "TYPE 1 AND 39 ZEROS":
20 INPUT A

Have fun…

VIC-20 manual type-in games

(Insert, yet again, my “the VIC-20 was my first computer” story here.)

When I got my VIC-20, that is all I had — just the VIC-20 computer. I did not have the Commodore Datasette (tape recorder). I could not save anything that I typed in. Because of this, I recall leaving my VIC turned on just so I wouldn’t lose the program I had typed in (or was in the process of typing in).

I am not 100% sure which manual came with my early model VIC, but it probably was the “Personal Computing on the VIC-20: a friendly computer guide.” At least, things inside this PDF look familiar.

In the back of the book is a section called “Programs to Try.” It contains listings of three games. In the PDF above, these games where called:

  • Tank Versus UFO by D. Later
  • Killer Comet by Duane Later
  • Rocket Command by Duane Later

But, in the book that came with my VIC-20, I believe at least two of them were called something else: Meteor and Rocket Fighter! I am pretty sure I remember Tank versus UFO being called that in my book.

I recall typing these programs in and fixing either bugs in the listing or, more likely, bugs in my typing ability. Much to my surprise, I found my original VIC-20 tapes several years ago and was able to get “most” of what I had on those tapes loaded in to an emulator. Two of the programs I found were ones I had typed in from the VIC-20 book.

When I went to look up these games, I found many videos on YouTube showing different versions than the ones I had. I originally figured some VIC owners just customized the games and they ended up in a collection somewhere on the Internet.

But now I think there might have been multiple versions of these games released by Commodore. The PDF of the book on archive.org says it is the 5th edition. I am now trying to find out how many editions of this book were published. I would like to see if they contain different versions of these programs.

Here are some differences I found between the ones I typed in from my book, and the ones shown in that 5th edition copy I found online.

Killer Comet (PDF) versus Meteor (mine)

The differences between the two versions are mostly cosmetic:

  • The name: “Meteor” in my book, and “Killer Comet” in the PDF.
  • The “graphics”: A block for the cannon and ball for the bullet in the PDF, versus a letter “A” for the cannon and vertical bar for the bullet in mine.
  • Game messages: “Delta base destroyed” in my book, versus “Moon base destroyed” in the PDF. Strangely enough, the “Killer Comet” prints “Meteor destroyed.” This further makes me think I had an earlier version and Meteor was the original name.
  • Stars on the screen (I may have added those) done through some extra POKEs neat the top of the program:
4 POKE7910,46:POKE8140,46:POKE8170,46:POKE8000,46:POKE7809,46:POKE7715,46:POKE7998,46
5 POKE7812,46:POKE7949,46:POKE8020,46:POKE7777,46:POKE7944,46:POKE8005,46:POKE7793,46
8 POKE8179,1

Thanks to a VIC-20 memory map at zimmers.net, I can see that all of these locations are in screen memory:

7680-8191     Screen memory

The 46 is a period. These POKEs are poking the “stars” in the background. As the meteor goes across the screen, it will erase those stars, so I am not sure if this was part of the original program, or something I had added. I know I had a game I was writing that did a similar thing with stars in the background, but mine got restored so they were not erased by other objects:

If I added those stars, perhaps I also changed the name? But if so, it wouldn’t explain why the Killer “Comet” version still calls it a meteor…

Rocket Command (PDF) versus Rocket Fighter (mine)

Again, these versions are very similar except for a few minor differences:

  • The name: “Rocket Fighter” in my book, versus “Rocket Command” in the PDF.
  • The “graphics”: Different ASCII (er, PETSCII) characters are used for the game playfield. The PDF uses a letter “X”, and mine used a PETSCII graphics character. For the missile, the original used a text up arrow, and mine uses a card clubs character.

Tank versus UFO

If I saved out Tank versus UFO, I couldn’t find it. Or, it might have been damaged on the ancient cassette tapes I found a few years ago and I couldn’t get it to load. I have no idea what differences there might be between the 5th edition version and whatever I typed in.

Why so different?

When I look up these games on YouTube, I see videos several other variations. I expect some where enhancements done by VIC-20 owners, but others look like just slightly updated versions. Did I do the same thing, and modify these programs to change the names?

Doing some web searching, I found this reference:

Modified versions of Tank-v-Ufo (p. 153-156) appear to have been published as Tank-v-Ufo (VIC Games), Tank Attack (Commodore Computing International, 82-11), Ufo-Jagd (Happy Computer, 83-12), Invasion (Commander, 84-02) and Save The City! (Big K, 84-04).

A slightly different version of Rocket Command (p. 158-159) was published in VIC! (Vol 6).

Modified versions of Rocket Command (p. 158-159) appear to have been published as UFO Target (Commodore Computing International, 83-08) and Rocket! (Big K, 84-04).

– http://www.vic20listings.freeolamail.com/book_userguide.html

And none of those match the names of the versions I have! It does “confirm” that Commodore published different versions of at least two of these – Tank Attack (Tank vs. UFO) and Rocket Command (UFO Target).

Tank vs UFO versions

Tank Attack (Commodore Computing International)

Yet the title screen still reads “Tank versus UFO”…

Invasion (Commander … magazine?)

Save the City (Big K)

Yet the title still reads “Tank versus UFO”…

Rocket Command versions

UFO Target (Commodore Computing International)

Rocket Command

I find this version interesting because it has the PETSCII “X” character used for the base, like my version, but still uses the up arrow character like the PDF version.

Rocket (Big K)

Yet the title still reads “Rocket Command”.

It is clear that games based on the type-in programs were certainly appearing in may publications… Did the version I have actually come from a magazine, rather than the VIC-20 manual? (I remember having Family Computing and Compute, which spun off in to Compute’s Gazette, during my VIC-20 days.)

Can anyone shed some light on this? How many editions to this manual were there, and did the program listings change? I did find a 1981 manual that does not say what edition it is, so it might be the first edition — but the program listings have the same names as the 5th edition.

I’d really like to know where I got “Meteor” and “Rocket Fighter” from.

To be continued…

Rocket Fighter program listing

!--------------------------------------------------
!- April 8, 2020 10:01:03 PM
!- Import of :
!- w:\vicconversions\vicrocket\rocket fighter.prg
!- Unexpanded VIC20 / C16 / Plus4
!--------------------------------------------------
2 REM ROCKET FIGHTER
10 VI=9*16^3:OF=38400-7680:PRINT"{clear}"
11 FORA=38400+22TO38400+22*23:POKEA,0:NEXT
15 C=7680+22*20+15
20 POKEVI+15,6+128+64+32+8
30 PRINT"{home}{red}*** rocket fighter ***";
35 PRINT"{space*5}hit any key"
40 PRINT"{down*14}{black}V{left}{down}V{left}{down}V{left}{down}V{left}{down}V{left}{down}V{left}{down}VVVVVVVVVVVVVVVV";
50 PRINT"{up}{left*14}XXXXXXXXXXXXXX"
60 A=7680+22*22+15
70 GETA$:IFA$<>""ANDB=0THENB=1:POKEC+22,32:D=C:C=C-1:K=1:POKE9*16^3+13,128+125:L=16
71 IFK=1THENL=L-1:POKE9*16^3+14,L
72 IFL=0THENK=0:POKE9*16^3+13,0
75 IFC=8121THENPRINT"{home}{down*6} no more rockets !!!":POKE9*16^3+14,0:FORAA=1TO3000:NEXT:RUN
80 IFB=0THEN110
85 POKED,32:D=D-22
90 IFPEEK(D)=60ORPEEK(D)=62THENGOSUB500:GOTO70
100 IFD<7680+88THENB=0:GOTO110
105 POKED,88
110 IFH=0THEN200
115 IFH=0THEN70
120 POKEF,32:F=F+E
125 IFF=ITHENH=0:GOTO70
130 IFPEEK(F)=30THENGOSUB500:GOTO70
140 POKEF,G:GOTO70
200 H=1
205 IFINT(RND(1)*2)=1THENE=-1:F=7702+(INT(RND(1)*10)+6)*22:I=F-22:G=60:GOTO115
210 E=1:F=7680+(INT(RND(1)*10)+6)*22:I=F+22:G=62:GOTO115
500 B=0:H=0
501 SC=SC+10:PRINT"{home}{down*3}{purple}score="SC
502 POKEF+OF,4:POKEF+1+OF,4:POKEF-1+OF,4
503 POKEF+OF+22,4:POKEF-22+OF,4
510 POKEF,102:POKEF+1,42:POKEF-1,42:POKEF+22,42:POKEF-22,42
521 POKE9*16^3+13,128+35
522 FORY=16TO0STEP-1
523 POKE9*16^3+14,Y
524 FORP=1TO10:NEXT:NEXT
530 POKEF,32:POKEF+1,32:POKEF-1,32:POKEF+22,32:POKEF-22,32
531 POKEF+OF,0:POKEF+1+OF,0:POKEF-1+OF,0
532 POKEF+OF+22,0:POKEF-22+OF,0
533 POKE9*16^3+13,0
540 FORGH=FTOF+22*16STEP22
544 II=PEEK(GH):POKEGH,G:FOROO=1TO50:NEXT
546 POKEGH,II:NEXT
800 RETURN
999 GOTO70
1000 POKE9*16^3+13,128+125
1001 FORY=16TO0STEP-1
1005 POKE9*16^3+14,Y
1010 NEXT:POKE9*16^3+13,0
1020 RETURN

Meteor program listing

!--------------------------------------------------
!- April 8, 2020 10:00:20 PM
!- Import of :
!- w:\vicconversions\vicrocket\meteor.prg
!- Unexpanded VIC20 / C16 / Plus4
!--------------------------------------------------
1 C=7680+22*6:DIMB(12):LL=C:POKE9*16^3+15,8
2 PRINT"{yellow}{clear}******* meteor *******":T=0
3 PRINT"{space*5}hit any key"
4 POKE7910,46:POKE8140,46:POKE8170,46:POKE8000,46:POKE7809,46:POKE7715,46:POKE7998,46
5 POKE7812,46:POKE7949,46:POKE8020,46:POKE7777,46:POKE7944,46:POKE8005,46:POKE7793,46
8 POKE8179,1
9 FORA=38444TO38400+505:POKEA,1:NEXT
10 :
20 FORA=1TO12:B(A)=160:NEXT
21 IFW=0THEN25
22 POKE9*16^3+14,U:U=U-2:IFU<=0THENW=0:POKE9*16^3+13,0
25 F=1
26 FORE=0TO44STEP22
27 FORD=C+ETOC+3+E:POKED,32:F=F+1:NEXT:NEXT
28 C=C+1
29 F=1
30 POKE8179,1:FORE=0TO44STEP22
35 FORD=C+ETOC+3+E:POKED,B(F):F=F+1:NEXT:NEXT
36 IFPEEK(8178)=1THENPRINT"{clear}{down*5}delta base destroyed!":GOTO500
40 GETA$:IFA$<>""ANDG=0THENG=1:S=7680+15+22*21
50 IFG=0THEN80
55 POKES,32:S=S-22
60 IFS<7746THENG=0:GOTO21
70 IFPEEK(S)=160THENPOKES,32:G=0:T=T+1:W=1:POKE9*16^3+13,128+000:U=15:GOTO80
71 IFPEEK(S-1)=160THENG=0:POKES-1,32:T=T+1:W=1:POKE9*16^3+13,128+000:U=15:GOTO80
75 POKES,93
80 F=1
81 IFT=12THENPRINT"{clear}{down*5}***meteor destroyed***":FORR=1TO2500:NEXT:LL=LL+44:C=LL:GOTO2
82 FORE=0TO44STEP22
84 FORD=C+ETOC+3+E:IFPEEK(D)=32THENB(F)=32
86 F=F+1:NEXT:NEXT
90 GOTO21
500 POKE9*16^3+13,128+5
505 POKE9*16^3+14,5:FORR=1TO300:NEXT
510 FORA=15TO0STEP-1
511 POKE9*16^3+14,A
520 FORRR=1TO500:NEXT
530 NEXT
540 FORRR=1TO2000:NEXT:RUN

A=B=C=D in BASIC and C

Chet Simpson posted this in the Color Computer Facebook group recently:

Some folks understand it, some folks are confused by it, and I decided to be inspired by it to write this article.

In the C programming language, you can do that assignment:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
int a,b,c,d;

a = 0;
b = 1;
c = 2;
d = 3;

a = b = c = d = 4;

printf ("a=%d b=%d c=%d d=%d\n", a, b, c, d);

return EXIT_SUCCESS;
}

That program will produce the following output:

a=4 b=4 c=4 d=4

You can try this program online, if you want.

In BASIC, the “=” works a bit differently because it can be both an assignment (A=5) or a test (IF A=5 THEN). For example:

REM ASSIGNMENT
A=5

REM TEST
IF A=5 THEN PRINT "A IS 5"

Perhaps this is why there was a keyword LET in early BASIC. When you use LET (it is supported on the CoCo), it tells BASIC you are assigning a variable:

LET A=5

Perhaps if LET was required for an assignment, those BASICs could treat the “=” when seen not after a LET as a test. Anyone know the history of LET?

In C, this is solved by having “=” be an assignment, and “==” being a test:

a = 5; // assignment

if (a == 5) { ... } // test

BASIC could have used “LET” to me “=”, and no LET to mean “==”, but it doesn’t. Saying “the BASIC parser doesn’t work like that” is an accurate answer, but it did bring to mind some things I never knew back when I was learning BASIC.

What is A=B?

You cannot answer that, without seeing more context. “IF A=B THEN” is a test. “LET A=B” or just “A=B” is an assignment.

When is it a test? After IF.

IF A=5 THEN PRINT "A IS 5!"

It is also a test after a PRINT:

PRINT A=5

Or after an assignment:

Z=A=5

To make this cleared, the “test” part (after the initial assignment) could be put in parentheses is like this:

Z=(A=5)

But, the parents are optional in this case, but can matter when doing certain things, such as math.

I am sure I have discussed this in an earlier article, but to recap, when things are a test, they return either -1 if TRUE, or 0 if FALSE. For example:

A=1
B=1
PRINT A=B
-1

A=0
B=1
PRINT A=B
0

The result of any test in BASIC is either 0 or -1. BASIC treats 0 as FALSE, and anything else as TRUE:

IF 0 THEN PRINT "FALSE"
IF -1 THEN PRINT "TRUE"
IF 1 THEN PRINT "TRUE"
IF 42 THEN PRINT "TRUE"

Here’s a simple program that shows this:

10 FOR A=-2 TO 2
20 PRINT A,;:IF A THEN PRINT "TRUE" ELSE PRINT "FALSE"
30 NEXT

BASIC returns either -1 (TRUE) or 0 (FALSE) from a comparison, but IF only cares about “not zero” as true, and treats only 0 as false. That logic makes sense. “IF condition is true THEN do something”.

If you were trying to specifically test for 0 or -1, you would have a different result:

IF A=0 THEN PRINT "FALSE" ELSE IF A=-1 THEN PRINT "TRUE"

That would not catch anything that wasn’t a 0 or -1, so you’d really want to add a bit more:

IF A=0 THEN PRINT "FALSE" ELSE IF A=-1 THEN PRINT "TRUE" ELSE PRINT "UNKNOWN"

I’ll mention this again in a moment…

Let’s see what the BASIC parser is trying to do when it sees something like this:

A=B=4

What does that mean? A= tells us there is an assignment, then after that, then B=4 is what we’d like for it to be, and B=4 is a test. In Chet’s case…

A=B=C=D=4

We might feel all variables would come back as 4, but the variables appear to be completely left alone:

10 A=0:B=1:C=2:D=3
20 A=B=C=D=4
30 PRINT A;B;C:D

Running that will print…

 0  1  2  3

The key part is “appear to be completely left alone”. There is something happening that we are not seeing. If you change the code to set the variables to 1, 2, 3 and 4, and then try to set all of them to 5, you now see more of what is going on:

10 A=1:B=2:C=3:D=4
20 A=B=C=D=5
30 PRINT A;B;C:D

Running THAT version will print:

 0  2  3  4

Now we can see that A, which starts out as 1, is getting set to 0. Something is being assigned after all. If you break it down, here it what you see:

A=1:B=2:C=3:D=4
PRINT A=B
0
PRINT B=C
0
PRINT C=D
0

Individually, each of those tests returns 0 for FALSE. Since we see that A is getting set to 0, it is getting assigned the result of the first test after it: B=C. If you try this, you would get the same result in A:

A=1:B=2:C=3
A=(B=C)
PRINT A
0

That makes sense. And if the parser is just going through the line, automatically grouping the tests, perhaps something like this:

A=1:B=2:C=3:D=4
A=(B=(C=D))

And THAT looks exactly like what we are getting.

  • C=D returns 0, false.
  • B=0 (the result of C=D) returns 0, false.
  • Then A is assigned 0.

Could it be as simple as that? Somehow BASIC has to know if something is an assignment or a test, and perhaps as it scans forward, finding an additional “=” is enough to start it looking for tests rather than assignments. (I bet William Astle already knows how this works, and maybe he will see this and comment.)

Chet’s code shows BASIC does not work like we might expect, especially if we are used to how things work in languages like C. In C, it would look like this:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
int a,b,c,d;

a=1;
b=2;
c=3;
d=4;

a=b==c==d;

printf ("a=%d b=%d c=%d d=%d\n", a, b, c, d);

return EXIT_SUCCESS;
}

Notice the use of double equals “==” in the assignment line. If you run that in C, you get the same results as BASIC:

a=0 b=2 c=3 d=4

The thing C does differently is that when you have an assignment, that code block returns the value of the assignment, since it knows it is an assignment:

printf ("a=1 returns: %d\n", a=1);
printf ("b=2 returns: %d\n", b=2);
printf ("c=3 returns: %d\n", c=3);
printf ("d=4 returns: %d\n", d=4);

That would print:

a=1 returns: 1
b=2 returns: 2
c=3 returns: 3
d=4 returns: 4

And in BASIC, the “a=1” if being seen as a test will return either -1 (if false) or 0 (true).

In C, we have “==” to change the behavior from assignment to test:

a=1;
b=2;
c=3;
d=4;

printf ("a==1 returns: %d\n", a==1);
printf ("b==2 returns: %d\n", b==2);
printf ("c==3 returns: %d\n", c==3);
printf ("d==4 returns: %d\n", d==4);

Above, that prints:

a==1 returns: 1
b==2 returns: 1
c==3 returns: 1
d==4 returns: 1

In C, 1 is true, and 0 is false. Had the variables been initialized to something that made the tests for 1, 2, 3 and 4 invalid…

a=42;
b=42;
c=42;
d=42;

printf ("a==1 returns: %d\n", a==1);
printf ("b==2 returns: %d\n", b==2);
printf ("c==3 returns: %d\n", c==3);
printf ("d==4 returns: %d\n", d==4);

…you would see that the value returned from the “a==1” test would be 0 for false.

a==1 returns: 0
b==2 returns: 0
c==3 returns: 0
d==4 returns: 0

If BASIC forced the use of LET for an assignment, or used “==” for test instead of “=”, then it could figure out what we mean when we try “A=B=C=D=5″” or whatever. BASIC would know that “D=4” is an assignment, and to return 4, versus “D==4” being a test, and returning true 0 or false -1.

But it doesn’t.

Happy new year!

VIC-20 “smooth move” revisited.

This is a quick follow-up to a post I did back in 2020. I had written this at the time, but never completed my experiments to post the results. Here is a video demo of using eight sets of two programmable characters to represent the 8×8 object at each position. This allows smooth horizontal positioning.

5 GOSUB 500:rem poke 36869,255
10 print"{clear}{reverse on}generating frames:"
20 for c=0 to 7:print "{reverse on}";chr$(65+c);chr$(73+c);" -> {reverse off}";chr$(65+c);chr$(73+c):print:next
25 get a$:if a$="" then 25
28 poke 36869,255
30 rem char row
40 for r=0 to 7
50 read v:v1=v
60 l1 = 7168+8+r
70 l2 = 7168+8+(8*8)+r
80 rem character
90 for c=0 to 7
100 poke l1+(c*8),v1
110 v1=v1/2
120 v2=(v and bt(c)-1) * bt(8-c)
130 poke l2+(c*8),v2
140 next
150 next
200 rem smooth move
210 print "{reverse on}move: a/s quit: q"
220 c=0:f=0
230 print chr$(65+f);chr$(73+f);"{left*2}";
240 k=peek(197):if k=64 then 240
250 if k=48 then 380
260 if k=17 then 300
270 if k=41 then 340
280 goto 240
290 rem left
300 if f>0 then f=f-1:goto 230
310 if c>0 then c=c-1:f=7:print" {left*3}";
320 goto 230
330 rem right
340 if f<7 then f=f+1:goto 230
350 if c<20 then c=c+1:f=0:print" ";
360 goto 230
370 rem quit
380 poke 36869,240:poke 198,0
390 end
500 rem initialize
510 for z=0 to 7:bt(z)=2^z:next
520 for z=7168+8 to 7168+8+16*8:poke z,0:next
525 for z=7126+(32*8) to 7168+(32*8)+8:poke z,0:next
530 return
600 rem print bits
610 for bt=0 to 7
620 if v and bt(bt) then print"1";: goto 640
630 print "0";
640 next:return
5000 rem 8x8 object
5010 rem data 60,126,255,255,255,255,126,60
5020 DATA 24,60,126,60,255,126,255,255

Extended Color BASIC PUT from DATA

Updates:

  • 2024-01-09 – Corrected a PMODE 1 size typo from 8×18 to the correct 8×16, and a 16×17 typo.

Here is a quickie…

I did not know this was possible “back in the day,” and do not think I have ever tried it until now.

In Extended Color BASIC you can use “GET” to get a block of memory from the screen and store it in array memory. You can then “PUT” it back on to the screen.

But, if you know where the array memory is (using the VARPTR function) you can POKE bytes directly in to the array and then just PUT it without ever needing to GET.

10 ' GET/PUT ARRAY (2 ENTRIES)
20 DIM B(1)
30 ' PRE-ALLOCATE VARIABLES
40 DIM A,D,L,V
50 ' 256x192 (32X24 OF 8X8 CHARS)
60 PMODE 4,1:PCLS:SCREEN 1,1
70 ' GET ADDRESS OF ARRAY DATA
80 V=VARPTR(B(0))
90 ' POKE 8X8 DATA IN TO IT
100 FOR L=V TO V+7:READ D:POKE L,D:NEXT
110 ' PUT THE DATA ON THE SCREEN
120 PUT(0,0)-(7,7),B
130 GOTO 130
140 ' BOMB DATA
150 DATA 24,24,60,118,122,126,126,60

This demo puts an 8×8 bomb on the top left of the screen. The bomb was one of the characters from my VIC-20 game Factory TNT.

VIC-20 Factory TNT character set in CBM prg Studio.

It does not look like you can use a multi-dimensional array got GET/PUT (does anyone know if this is possible?). Pity. If that were possible, I had something I wanted to try.

How much array memory do I need?

To know how many DIM array elements you need for an object, run this program:

0 REM pmodes.bas
5 CLS:INPUT "W,H";MW,MH:PRINT
10 FOR M=0 TO 4:READ M$
20 PRINT "PMODE";M;"(";M$;")"
30 NEXT
40 PRINT
50 PRINT "M W X H W X H PXLS BYT E"
60 PRINT "- -- -- -- -- ---- --- --"
70 FOR M=0 TO 4
80 W=MW:H=MH
90 PRINT USING "# ##X##";M;W;H;
100 PRINT" -> ";
110 IF M<4 THEN W=W/2
120 IF M<2 THEN H=H/2
130 P=W*H
140 B=P/(8-4*(M AND 1))
150 E=INT(B/5+.5)
160 PRINT USING "##X## #### ### ##";W;H;P;B;E
170 NEXT
180 IF INKEY$="" THEN 180
190 GOTO 5
999 GOTO 999
1000 DATA "128 X 96 X 2"
1010 DATA "128 X 96 X 4"
1020 DATA "128 X 192 X 2"
1030 DATA "128 X 192 X 4"
1040 DATA "256 X 192 X 2"

Type in the width/height and it shows you the memory requirements for that object on each PMODE screen. If it says it needs two elements, that is a DIM X(1) because DIM starts at 0. DIM X(3) would give you four — X(0), X(1), X(2) and X(3).

PMODE width and heights

Be aware that all PMODE screens use coordinates of 256×192, and scale to whatever resolution is being used (128×96, 128×192 or 256×192).

On a PMODE 4 screen (256×192), doing LINE(0,0)-(7,7),PSET,B draws a box that is truly 8 pixels wide by 8 pixels tall.

On a PMODE 2 or 3 screen (128×192), the same command would draw a box that was 4 pixels wide and 8 pixels tall.

On a PMODE 0 or 1 screen (128×96) would be drawing a box that was 4 pixels wide by 4 pixels tall.

If you truly wanted an 8×8 object on a PMODE 0 or 1 screen (128×96) screen, you would have to draw it as 16×16. An 8×8 object on a PMODE 2 or 3 screen (128×192) would be drawn as if it was 8×16.

Here is a short program that draws a 16×16 pixel box on each of the five PMODE screen types. It adjusts the width and height based on the mode to know how big of a box it has to draw which would appear as 16×16:

10 FOR M=0 TO 4
20 W=16:H=16
30 PMODE M,1:PCLS:SCREEN 1,1
40 IF M<4 THEN W=W*2
50 IF M<2 THEN H=H*2
60 LINE(0,0)-(W,H),PSET,B
70 LINE(0,0)-(W,H),PSET
80 LINE(W,0)-(0,H),PSET
90 IF INKEY$="" THEN 90
100 NEXT
999 GOTO 999

And that is not confusing at all ;-) But it did mean that you could draw anything as if it were a PMODE 4 screen and then draw it on any lower resolution screen without changes — you could just lose detail.

But I digress. It looks like I should write a new article series…

Tackling the Logiker 2023 Vintage Computing Christmas Challenge – part 4

See also: part 1, part 2, part 3 and part 4.

If I would have told my teenage-self that one day I would be corresponding with Radio Shack ROM-Pak game programmer, Rick Adams, I would have not believed myself. Rick became a very well known name in the CoCo community, and I remember seeing his name in the title screen of Radio Shack ROM-Paks. Temple of ROM was one I played at Radio Shack, but never owned. I just recall thinking it was like a fancy version of my all-time favorite Atari VCS game, Adventure.

But I digress…

It started with a message containing a screen shot:

Rick had simplified the program by breaking up the pattern in to sections he could GOSUB to and print a few lines. It was simple, and smaller than printing the whole thing.

“Why line 2046?” I asked. Apparently, the PDP-8 BASIC he was using had that as the highest line number. This was one of many restrictions this version of BASIC had. Through our chat, I learned it had a limit to the length of a string, and could not do things like MID$(A$,1,1)=”A” which were methods I had been toying with.

The next thing he sent me was optimizing this pattern further, breaking it up in to just one part of the diamond shape:

1 DIM A$(4)
10 FOR I = 1 TO 4
20 READ A$(I)
30 NEXT I
40 FOR I = 1 TO 4
50 PRINT A$(I);A$(I);A$(I)
60 NEXT I
70 FOR I = 3 TO 1 STEP -1
80 PRINT A$(I);A$(I);A$(I)
90 NEXT I
300 DATA " * "
301 DATA " * * "
302 DATA " * *"
303 DATA "* "
2046 END

This proof-of-concept code worked by loading this pattern in to an array. It can print the top array string three times across the screen, then continue through the pattern. Then, for the reverse diamond, print the array backwards. This version was clever, though it left off the right-most asterisk of the bottom/middle row, which could be handled by adding an extra IF/PRINT for those lines. Neat!

But, even when packing everything together (the PDP-8 BASIC has its own way of combining lines using a backslash), it did not look like it would be the smallest approach to this challenge.

A bit later, I received this screen shot:

This, my friends, is what a programmer comes up with when they are tired of typing in Microsoft BASIC. Rick has a front-end (pre-processor?) that allows him to write nicely readable (for BASIC) code and then have it converted to Microsoft BASIC for loading on a CoCo.

I typed that in (well, not literally) to a CoCo and saw that it worked. But what is it doing? Let’s look at a packed CoCo version of this code he provided (adjusted to work on the 40 column screen):

1 WIDTH40:GOSUB2:FORK=1TO3:FORI=1TO3:GOSUB2:NEXTI:FORI=2TO0STEP-1:GOSUB2:NEXTI:NEXTK:END
2 A$=STRING$(20,32):FORJ=4TO16STEP6:MID$(A$,J-I)="*":MID$(A$,J+I)="*":NEXTJ:PRINTA$:RETURN

This looks like a similar approach that Jason Pittman had mentioned he was working on — drawing the pattern as if it was two asterisks that just get further apart as they move down the line (then closer together as it continues).

Rick’s version appears to work by starting with an empty string – generated using the STRING$(20,32) for a string of 20 CHR$(32) spaces. As it goes forward through the loop, it changes the character in the string at position J+I to be an asterisk, then the character at position J-I to be an asterisk . At the top/bottom of each diamond, the offset is 0, so it is just putting the asterisk on top of itself.

Let me break this apart, closer to his original pre-preprocessed source. I will put some extra spaces in to space out the loops:

1 GOSUB 100
2 FOR K=1 TO 3
3 FOR I=1 TO 3
4 GOSUB 100
5 NEXT I
6 FOR I=2 TO 0 STEP -1
7 GOSUB 100
8 NEXT I
9 NEXT K
10 END
100 A$=STRING$(20,32)
101 FOR J=4 TO 16 STEP 6
102 MID$(A$,J-I)="*"
103 MID$(A$,J+I)="*"
104 NEXT J
105 PRINT A$
106 RETURN

Line 100 – The subroutine creates an A$ of 20 space characters.

Line 101 – The FOR loop of 4 TO 16 STEP 6 will produce values of 4, 10 and 16. That matches the top spacing of the peak of the diamond shapes in the pattern:

.........11111111112
12345678901234567890
* * *
* * * * * *
* * * * * *
* * * *

Line 102 – at position J-I (I is set by a FOR loop before it GOSUBs to this routine) will be places an asterisk at that location inside the A$. The outer FOR I loop is 1 TO 3, calling this routine each time, so it would look like this:

I=1, J=4, 10, 16 (producing 4-1, 10-1 and 16-1 ... 3, 9 and 15).
"..*.....*.....*....."

I=2, J=4, 10, 16 (producing 4-2, 10-2 and 16-2 ... 2, 8 and 14).
".*.....*.....*......"

I=3, J=4, 10, 16 (producing 4-3, 10-3 and 16-3 ... 1, 7 and 13).
"*.....*.....*......."

Line 103 – this line does the same thing, but uses the position of J+I so the asterisk moves to the right each time:

I=1, J=4, 10, 16 (producing 4+1, 10+1 and 16+1 ... 5, 11 and 17)
"....*.....*.....*..."

I=2, J=4, 10, 16 (producing 4+2, 10+2 and 16+2 ... 6, 12 and 18)
".....*.....*.....*.."

I=3, J=4, 10, 16 (producing 4+3, 10+3 and 16+3 ... 7, 13 and 19)
"......*.....*.....*."

Since these two lines are adding asterisks to the same A$, the results actually look like this:

"..*.*...*.*...*.*..."
".*...*.*...*.*...*.."
"*.....*.....*.....*."

Line 104 – is the NEXT for the FOR/J, so it goes through all three entries (4, 10 and 16).

Line 105 – prints the modified string.

Line 106 – returns back to the main code.

You will notice that calling this routine like this misses the top line. Let’s look at the start of the program to see how it gets there:

Line 1 – This initial GOSUB to 100 is what draws that top line. When the program starts, all variables are zero. So the routine enteres with I set to 0, which would make J+I and J-I just be the J value, putting an asterisk at the 4, 10, and 16 positions:

"...*.....*.....*...."

Well. That’s clever.

Line 2 – This is just a FOR loop to draw the pattern three times.

Line 3 – This is the FOR loop that draws the top part of the diamond, but since I always starts at 0, it doesn’t draw the “top” row — that was done by LINE 1, and then when it draws the reverse/bottom of the diamond it will finish that pattern, which is the last row and start of the next diamond. (Confused yet?)

Line 4 – Draw the line for the top part of the diamond (inside the I loop of 1 to 3).

Line 5 – This is the NEXT for the top-to-bottom I loop.

Line 6 – This is a second I loop, that goes from 2 to 0.

Line 7 – Calling the GOSUB routine, so now it will be drawing like this:

I=2, J=4, 10, 16 (producing 4+2, 10+2 and 16+2 ... 6, 12 and 18)
".....*.....*.....*.."

I=1, J=4, 10, 16 (producing 4+1, 10+1 and 16+1 ... 5, 11 and 17)
"....*.....*.....*..."

I=0, J=4, 10, 16 (producing 4+0, 10+0 and 16+0 ... 4, 10 and 16)
"...*.....*.....*...."

And the second part of the GOSUB doing the right side of the pyramid shape:

I=2, J=4, 10, 16 (producing 4-2, 10-2 and 16-2 ... 2, 8 and 14)
".*.....*.....*......"

I=1, J=4, 10, 16 (producing 4-1, 10-1 and 16-1 ... 3, 9 and 15)
"..*.....*.....*....."

I=0, J=4, 10, 16 (producing 4-0, 10-0 and 16-0 ... 4, 10 and 16)
"...*.....*.....*...."

Together, the strings end up as:

".*...*.*...*.*...*.."
"..*.*...*.*...*.*..."
"...*.....*.....*...."

And that draws the bottom of the diamond!

Line 8 – The NEXT for the I (bottom of diamond) loop.

Line 9 – The NEXT for the “do it three times” loop.

Line 10 – END, so it won’t try to run the subroutine again.

Wow, that’s cool.

What do you think?

More to come…?

Tackling the Logiker 2023 Vintage Computing Christmas Challenge – part 3

See also: part 1, part 2, part 3 and part 4.

I have to admit, this year’s Logiker challenge stumped me. I had a few “clever” ideas on how to reproduce the pattern, but the code was larger than just printing out the pattern from arrays of strings.

Meanwhile, Jason Pittman kept posting revisions of his concept in the comments. In a response to part 2, he shared this:

This is the last attempt I came up with. It uses the same “-3 to 2 … ABS()” from the other example I sent on part 1. I think one potential trick here is to treat it as if you are holding a rubber stamp that stamps out three asterisks that are six spaces apart. You want to make this pattern by stamping it twice on each line. You’re keeping track of a starting position on each row (1027 + 32 for each row) and an offset to add and subtract to the starting position for each row. On the first line, the offset is zero, so you stamp it twice on top of itself at the starting position. On the next line, the offset is 1, so you stamp it twice, but one time you add -1 and the other time you add +1 to the starting position. Does this make any sense?

1 CLS:P=1027:FORC=1TO3:FORX=-3TO2:FORS=0TO12STEP6:POKEP-3+ABS(X)+S,106:POKEP+3-ABS(X)+S,106:NEXT:P=P+32:NEXT:NEXT:GOTO1

– Jason Pittman

This was an optimization of his original approach, but it had one limitation that might prevent it from solving the challenge: The CoCo’s 32×16 screen is too small to display the entire image, and POKEing characters on the screen would be limited to just 16 lines of text. He was aware of this, and his program does POKE the full image, but it is POKEing past the end of the visible screen. Would this count? RUNning the program displays the pattern over and over again (which was done to avoid having a new line with an endless loop GOTO):

POKEing past the visible screen works here because I am emulating a Disk Extended BASIC CoCo, and the memory after the text screen is reserved for four pages of PMODE high resolution graphics. But, I suspect, if I ran this on a cassette based CoCo, it might be POKEing in to memory used for the BASIC program itself.

Perhaps the CoCo 3 could help, since it has 40×25 and 80×25 text modes? Jason tried that:

I may play around with LPOKE. This should get it on the 40 column screen. I bet there is a crafty way to (a) not do the last line manually outside of the loops (b) remove one of the FOR loops (c) Shoot, there’s probably some crafty wizard way to do it in one FOR loop with logical operators, but I wouldn’t ever find it.

1 WIDTH40:FORZ=0TO12STEP6:FORX=-3TO2:FORS=0TO12STEP6:LOCATEABS(X)+S,Z+X+4:PRINT”*”;:LOCATE6-ABS(X)+S,Z+X+4:PRINT”*”;:NEXT:NEXT:NEXT:FORX=3TO15STEP6:LOCATE X,19:PRINT”*”;:NEXT

– Jason Pittman

In this version, Jason uses LOCATE(x,y) to position the cursor. That is what the CoCo 3 used instead of PRINT@ for text positioning. And it works!

It also feels better to use built-in BASIC text commands versus POKEing in to memory.

But he wasn’t done! He added this version:

10 WIDTH 40
20 FOR S = -15 TO 33 STEP 6
30 FOR X = 0 TO 18
40 IF S+X < 19 AND S+X >= 0 THEN LOCATE S+X,X:PRINT "*";
41 IF S-X >= 0 AND S-X < 19 THEN LOCATE S-X,X:PRINT "*";
50 NEXT X
60 NEXT S
70 GOTO 70

This one draws the same pattern, but in a very different way. It draws diagonal lines going down from the points at the top. Try it! It’s cool!

And, then this odd one, which creates the pattern by drawing the asterisks RANDOMLY, eventually getting them all on the screen.

0 WIDTH40
1 X=RND(3)*6:Y=RND(3)*6:RX=RND(7)-4:RY=ABS(RX)-3:D=RND(2)*2-3:LOCATE X+RX,Y+RY*D:PRINT"*";:GOTO 1

Nice job, Jason!

But it is going to make my brain hurt to understand how this works…

Meanwhile, I received a message from Rick Adams on Facebook with an implementation he was working on for a (much more limited) PDP-8 BASIC.

To be continued…