Category Archives: Retro Computing

PUT defined by ASCII strings

NOTE: Can anyone tell me how to get the blank lines out of the code listings? I used to be able to copy/paste from MS Visual Studio Code with no problem, but something has changed in WordPress. For the past few weeks, I have to paste in (it looks fine), then when I look at it, it has the line feeds. I then EDIT, and copy/paste it back in to Code (where I see the blank lines) then search/replace those blank lines, then copy/paste it back in to WordPress. That has been working fine, but now that does not work. Maybe it’s a different between PC Code and Mac Code (PC using CR/LF or something?). Anyone know?

Revisiting the PUT from DATA concept… For those who don’t want to figure out the binary values for the characters, here is a routine that lets you define them using strings:

'TANK UP
DATA " XX "
DATA " XX "
DATA "XX XX XX"
DATA "XXXXXXXX"
DATA "XXXXXXXX"
DATA "XXXXXXXX"
DATA "XXXXXXXX"
DATA "XX XX"

The routine reads these 8-character strings, then goes through them to build a value based on where the X is found.

Here is some code you can play with…

0 'PUTDATA.BAS
1 '2024-01-22
2 'SHOWS HOW TO DO IT USING
3 'STRINGS RATHER THAN #S
4 '

10 'TO SPEED UP STR-TO-BYTE
20 DIM BT(7):FOR BT=0 TO 7:BT(BT)=2^BT:NEXT
30 '
40 'VARIABLES USED IN THE
50 'POKE-TO-ARRAY ROUTINE MUST
60 'BE PRE-ALLOCATED OR THEY
70 'WILL CAUSE ARRAY MEM TO
80 'SHIFT WHEN IT GET USED.
90 '
100 DIM L,V,Z,Z$
110 '
120 'EACH ARRAY ENTRY CAN STORE
130 '5 BYTES. AN 8X8 SINGLE
140 'COLOR CHARACTER IS 8, SO
150 'WE NEED TWO ARRAY ENTRIES
160 '(10 BYTES) TO FIT THE 8
170 'BYTE CHARACTER.
180 '
190 DIM TU(1),TD(1),TL(1),TR(1):GOSUB 320

200 '
210 ' TEST PROGRAM
220 '
230 PMODE 4,1:PCLS 1:SCREEN 1,1
240 PUT (0,0)-(7,7),TU
250 PUT (16,0)-(16+7,7),TD
260 PUT (32,0)-(32+7,7),TL
270 PUT (48,0)-(48+7,7),TR
280 GOTO 280

290 '
300 'LOAD SPRITE CHARACTERS
310 '
320 PRINT "LOADING DATA";
330 L=VARPTR(TU(0)):GOSUB 390
340 L=VARPTR(TD(0)):GOSUB 390
350 L=VARPTR(TL(0)):GOSUB 390
360 L=VARPTR(TR(0)):GOSUB 390
370 RETURN

380 '
390 'READ DATA AND POKE AT L
400 '
410 PRINT
420 'READ STRINGS AND CONVERT
430 'TO BYTES.
440 FOR Z=0 TO 7:V=0
450 READ Z$:PRINT Z$,;
460 FOR BT=1 TO 8
470 'FASTER WAY
480 IF MID$(Z$,BT,1)="X" THEN V=V+BT(BT-1)
490 'SLOW WAY
500 'IF MID$(Z$,BT,1)="X" THEN V=V+2^(BT-1)
510 NEXT:PRINT INT(V);HEX$(V)
520 POKE L+Z,(NOT V)+256:NEXT:RETURN
530 NEXT:PRINT V;HEX$(V):NEXT
540 RETURN

550 '
560 '8X8 SPRITE CHARACTERS
570 '
580 'TANK UP
590 DATA " XX "
600 DATA " XX "
610 DATA "XX XX XX"
620 DATA "XXXXXXXX"
630 DATA "XXXXXXXX"
640 DATA "XXXXXXXX"
650 DATA "XXXXXXXX"
660 DATA "XX XX"

670 'TANK DOWN
680 DATA "XX XX"
690 DATA "XXXXXXXX"
700 DATA "XXXXXXXX"
710 DATA "XXXXXXXX"
720 DATA "XXXXXXXX"
730 DATA "XX XX XX"
740 DATA " XX "
750 DATA " XX "

760 'TANK LEFT
770 DATA " XXXXXX"
780 DATA " XXXXXX"
790 DATA " XXXX "
800 DATA "XXXXXXX "
810 DATA "XXXXXXX "
820 DATA " XXXX "
830 DATA " XXXXXX"
840 DATA " XXXXXX"

850 'TANK RIGHT
860 DATA "XXXXXX "
870 DATA "XXXXXX "
880 DATA " XXXX "
890 DATA " XXXXXXX"
900 DATA " XXXXXXX"
910 DATA " XXXX "
920 DATA "XXXXXX "
930 DATA "XXXXXX "

You can take this and change the characters real easily to play with it. Here is a smiling face instead of the UP TANK:

580 'SMILING FACE
590 DATA " XXXXXX "
600 DATA "X X"
610 DATA "X X X X"
620 DATA "X X"
630 DATA "X X X X"
640 DATA "X XX X"
650 DATA " X X "
660 DATA " XXXX "

Have fun… When I get time, I’ll try to post versions of these routines on my Github that handle different size objects as well as 4-color objects for us on PMODE 1 and 4 screens.

Tank 8×8 with no flicker, tracks and sound.

When I posted a video of my CoCo Factory TNT test, Chet Simpson suggested I could eliminated flicker by making a double-high sprite, with the top half blank. When I put the falling bomb, it would erase the position after it. L. Curtis Boyle suggested something similar to my tank game.

So I did it. It took a bit to figure out the logic. I have four 8×8 characters of the tank facing each direction, then I have two 16×8 (for left, and right) and two 8×16 (for up and down) versions.

Ericomont mentioned that the tank could leave tracks (I guess, since it seems like my black on white screen looks like snow). I added those too.

And then some sound. When the tank is not moving, the sound is slow — as if the tank is idling. When the taking is moving, it makes a faster sound.

I am having fun playing.

This version does to have the firing. I need to optimize this a bit and then add some bullets to it ;-)

Here is a video:

And here is the current code. There are some commented out lines I was using for debugging – like making a box around where the tank’s current position is (TX/TY) and where the old position was (OX/OY). That was massively helpful in me being able to see the logic to how I should draw and erase the tanks.

Now that I understand it, maybe I can do it better.

And this code can certainly be optimized and be made much faster.

Enjoy…

0 ' 
1 ' TANKPUT8X8-SOUND.BAS
2 ' ALLEN C. HUFFMAN
3 ' SUBETHASOFTWARE.COM
4 ' 2023-01-19
5 '

10 ' LOAD SPRITES
20 DIM TU(1),TD(1),TL(1),TR(1),UE(4),DE(4),LE(4),RE(4),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=8:TY=8:TD=4:OX=8:OY=8:Z=0

90 ' ERASE PREV POSITION
100 LINE(OX,OY)-(OX+7,OY+7),PSET,BF
110 ' PUT TANK ON SCREEN
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:GOTO 180
165 ' PUT WITH BLANK NEXT TO IT
166 PUT(TX,TY)-(TX+7, TY+15),UE:GOTO 180
167 PUT(OX,OY)-(OX+7, OY+15),DE:GOTO 180
168 PUT(TX,TY)-(TX+15,TY+7),LE:GOTO 180
169 PUT(OX,OY)-(OX+15,OY+7),RE

170 ' READ KEYBOARD (WASD)
180 Z=0:POKE&H155,&HFF:POKE&H156,&HFF:POKE&H157,&HFF:POKE&H158,&HFF
181 'LINE(TX-1,TY-1)-(TX+8,TY+8),PRESET,B
182 'LINE(OX-1,OY-1)-(OX+8,OY+8),PRESET
185 Z=Z-1:IF Z<0 THEN Z=20:EXEC 43345
190 A$=INKEY$:IF A$="" THEN 185
191 'LINE(TX-1,TY-1)-(TX+8,TY+8),PSET,B
192 'LINE(OX-1,OY-1)-(OX+8,OY+8),PSET
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 IF TD=1 THEN IF TY>0 THEN TY=TY-8:GOTO 166
250 TD=1:GOTO 130
260 ' DOWN
270 IF TD=2 THEN IF TY<184 THEN TY=TY+8:GOTO 167
280 TD=2:GOTO 140
290 ' LEFT
300 IF TD=3 THEN IF TX>0 THEN TX=TX-8:GOTO 168
310 TD=3:GOTO 150
320 ' RIGHT
330 IF TD=4 THEN IF TX<248 THEN TX=TX+8:GOTO 169
340 TD=4:GOTO 160

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
2170 ' 8X16 AND 16X8
2180 L=VARPTR(UE(0)):GOSUB 3025
2190 L=VARPTR(DE(0)):GOSUB 3025
2200 L=VARPTR(LE(0)):GOSUB 3025
2210 L=VARPTR(RE(0)):GOSUB 3025
2220 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
3021 FOR Z=L TO L+7:READ V:POKE Z,(NOT V)+256:NEXT:RETURN
3025 PRINT "--";
3026 FOR Z=L TO L+15: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

3120 ' TANK UP+EMPTY
3130 DATA 24,24,219,255,255,255,255,195
3131 DATA 0,195,0,0,0,195,0,0
3140 ' TANK EMPTY+DOWN
3150 DATA 0,0,195,0,0,0,195,0
3151 DATA 195,255,255,255,255,219,24,24
3160 ' TANK LEFT+EMPTY
3170 DATA 63,68, 63,68, 30,0, 254,0, 254,0, 30,0, 63,68, 63,68
3180 ' TANK EMPTY+RIGHT
3190 DATA 34,252, 34,252, 0,120, 0,127, 0,127, 0,120, 34,252, 34,252

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…