Category Archives: Color BASIC

Let’s write Lights Out in BASIC – part 1

See also: part 1, part 2, part 3, part 4, part 5, part 6 and part 7.

Recently, Rick Adams let me see a CoCo 3 Lights Out game he was working on. I have heard of the Lights Out, but have never played it. I gave it a shot…

I was immediately frustrated by how difficult such a simple game could be to win. In fact, I have yet to purposely win it, but I did get lucky one time and accidentally won.

For those who, like I was, are unfamiliar with the game… There is a 5×5 grid of squares. Of the 25 squares, random squares will be “on”, represented as green in Rick’s version. The goal is to turn them all off, represented by black.

You select a square, and that square, and the squares above, below, left and right of it will switch their state. If an adjacent square is on, it will be turned off. If it is off, it will be turned on. If you are at the edge of the grid, it does not wrap around and affect any squares on the other side.

Simple… and incredibly frustrating. I can manage to get down to one or two squares, but still haven’t figured out a pattern to shut them all off. It reminds me of when I was trying to figure out a Rubik’s Cube when they were new (maybe around 1979 or 1980?). I never did. I gave up the first week and ordered the official solution manual.

I recall Lights Out being quite trendy in recent years — maybe as a phone app or something online. This made me wonder about the origin of the game. I decided to do some “intensive/extensive” research.

1990s

From checking the always accurate and reliable Wikipedia, I learned that a game called “Lights Out” first appeared in 1995 as a handheld electronic game from Tiger Electronics. That version used a 5×5 grid of push button lights. Here is a commercial for it:

Lights Out (1995) TV commercial.

From that TV ad, I learned that not only was there the 5×5 original, but also a “Deluxe Lights Out” that used a 6×6 grid.

In 1997 they also released Lights Out in cartridge form for their touchscreen Game.com game system. I owned one of those, and since I read this game was included with the system, perhaps I did play it back then. From looking at a gameplay video, this version appears to use a 6×6 grid of lights, which I suppose made it closer to Deluxe Lights Out.

Side note: I believe it was the late Steve Bjork that told me about Game.com. It had the potential to be a Gameboy Killer, but disappeared into obscurity instead. I had one and a number of the cartridges. I actually found my Game.com Monopoly cartridge a few weeks ago, but I sold the game unit years ago.

But I digress…

1980s

Tiger Electronics was not the first to sell this game — only the first (that I can find) to call it Lights Out. In 1983, there was a handheld game called XL25 from Vulcan Electronics . It appears to play the same style game — except the goal was to turn all the lights ON instead of off… Did Tiger rip them off, 12 years later? (And what does XL25 even mean?)

XL25 TV commercial.

I can find very little about this game, and even less about Vulcan Electronics. Please comment if you can find more information.

1970s

The Wiki also mentions a similar game existed in the 1970s for the Parker Brothers Merlin handheld system. I remember seeing TV ads for Merlin, and I recall wanting one. I don’t think I ever even got to play with one, though.

From this TV ad, the game in question appears to be Magic Square. The goal of it was not to get all of its 3×3 lights off or on, but to make just the outer lights on to form a square.

It does appear the game mechanics where the same as Lights Out.

Did Magic Square lead to XL25 which led to Lights Out? Or was there something even earlier? Maybe on some mainframe system? Leave a comment if you have more information on just what “Lights Out” patient zero is.

Present Day (not Christmas or Birthday)

Since the concept was known in the 1970s, someone could have used it for inspiration to write a CoCo version of the Merlin 3×3 “Magic Square” on a 1980 CoCo. In 1983, a CoCo owner could have created a 5×5 XL25 version. Heck, many of us were still using our CoCos in 1995 and 1997 when Tiger Electronics had their 5×5 and 6×6 versions.

Did you ever see this game on the CoCo back then?

Inspired by Rick Adams’ work, I thought it might be fun to see how much effort this game would be to write. I expect it will be much easier to write than it is to win.

In the next installment, we’ll look at some ways to implement this game in Color BASIC.

Until then…

How much faster is HEX versus decimal on the CoCo?

This is a topic I have mentioned in earlier articles about speeding up BASIC. I recently found one of my test programs and thought I’d update it a bit and share it.

A quick recap…

In BASIC, numbers in the program are stored as the characters. If you have “A=123” that “123” appears in the BASIC code as those characters. When the program RUNs, basic has to convert that series of characters to a number.

It takes more work to parse a standard base-10 (decimal, digits 0-9) set of characters to a number than it does to parse a base-16 (HEX, digits 0-F). To demonstrate, I wrote a very simple parser. It takes a string and converts it to a number, manually. (BASIC already has a VAL keyword that does this, but I wanted to show how it works.) Note this does not look for negative (“-“) or decimals or scientific notation – just a basic number like “12345”.

Decimal String to Number

10 ' STRTONUM.BAS
20 LINE INPUT "TYPE A NUMBER: ";NM$
30 GOSUB 60
40 PRINT "THE VALUE IS :";NM
50 END
60 ' CONVERT NM$ TO NM NUMBER
70 NM=0:ZM=1
80 ' SCAN FROM RIGHT TO LEFT
90 FOR Z=LEN(NM$) TO 1 STEP -1
100 ' GET CHARACTER VALUE
110 ZC=ASC(MID$(NM$,Z,1))
120 ' EXIT IF NOT 0-9
130 IF ZC>=ASC("0") THEN IF ZC<=ASC("9") THEN 160
140 PRINT "NOT A NUMBER : ";NM$
150 RETURN
160 ' SUBTRACT ASCII "0" AND MATH
170 NM=NM+(ZC-ASC("0"))*ZM
175 PRINT ZC-ASC("0"); "*";ZM;"=";NM
180 ' 0, 10, 100, 1000, ETC.
190 ZM=ZM*10
200 NEXT
210 RETURN

If you have a number like “12345”, you start at the right-most ASCII character (“5”) and convert that to a number. You multiply that number by a multiplier that starts out as 1. So, “5 * 1 = 5”.

Then you multiply the multiplier by 10, so 1 becomes 1. You move to the next digit, “4”, and multiply it by the new multiplier. So, “4*10 = 40”. That is added to the previous value, so “5 + 40 = 45”.

The multiplier is multiplied by 10, so 10 becomes 100. The next digit is a “3”, so that becomes “3 * 100 = 300”. It is added to the sum – “300 + 45 = 345”.

Repeating, the multiplier is multiplied by 10 making it 1000. The next digit is a “2”, so that becomes “2 * 1000 = 2000”. That is added making “2000 + 345 = 2345”. I think we see the pattern.

After the multiplier is multiplied by 10 again, making it 10000, we go through this again with the final digit, the “1”. That makes “1 * 10000 = 10000” and that is added making “10000 + 2345 = 12345”.

Whew!

HEX String to Value

But, with a hexadecimal value, the math gets much simpler since each character of a hex string represents a set of four bits in a byte. A similar converter is much smaller.

10 ' STRTONUM.BAS
20 LINE INPUT "TYPE A HEXNUM: ";NM$
30 GOSUB 60
40 PRINT "THE VALUE IS :";NM
50 END
60 ' CONVERT NM$ TO NM NUMBER
70 NM=0
80 ' SCAN FROM LEFT TO RIGHT
90 FOR Z=1 TO LEN(NM$)
100 ' GET CHARACTER VALUE
110 ZC=ASC(MID$(NM$,Z,1))
120 ' EXIT IF NOT 0-9 OR A-F
130 IF ZC>=ASC("0") THEN IF ZC<=ASC("9") THEN 200
140 IF ZC>=ASC("A") THEN IF ZC<=ASC("F") THEN 180
150 PRINT "NOT A HEXNUM : ";NM$
160 RETURN
170 ' CONVERT A-F TO 10-15
180 ZC=ZC-(ASC("A")-(ASC("9")+1))
190 ' NOW MAKE 0-15
200 ZC=ZC-ASC("0")
210 PRINT NM;"+";ZC;"=";
220 NM=NM+ZC
230 PRINT NM
240 IF Z<LEN(NM$) THEN NM=NM*16
250 NEXT
260 RETURN

In BASIC, once it sees some characters starting with “&H”, it gets the value of the next character. If it is A-F, it subtracts from the value so it looks like the ASCII values after “0” to “9”.

Then it subtracts the ASCII of “0” so the value becomes 0-15.

If there is another character, it shifts this value four places to the left (multiply by 16 in the code above) and it moves on to the next digit and repeats.

Bit shifts (multiplying by power of 2) are much quicker than multiplying. It makes the whole parsing routine much faster.

But how much faster?

I wrote this sample program which sits in a loop and sets a variable 10,000 times — first setting using a normal base-10 number (“A=1”), and then again using the HEX version (“A=&H1”). After each one it prints the TIMER value, and at the end of the line it prints how much faster (or slower) the HEX version was over decimal.

And as a bonus, at the end, it compares “A=.” (that is a fast shortcut for “A=0”) to “A=&H0” to show one case where there is something faster than HEX.

10 'DECVSHEX2.BAS
20 DIM A,I,TD,TH:MX=10000

30 PRINT "DO NOT PRESS KEYS WHILE RUNNING"
40 FOR I=1 TO 1000:NEXT I

50 PRINT "DEC TIME HEX TIME DIFF"

60 TIMER=0:FOR I=1 TO MX
70 A=0
80 NEXT:TD=TIMER:GOSUB 920
90 TIMER=0:FOR I=1 TO MX
100 A=&H0
110 NEXT:TH=TIMER:GOSUB 930

120 TIMER=0:FOR I=1 TO MX
130 A=1
140 NEXT:TD=TIMER:GOSUB 920
150 TIMER=0:FOR I=1 TO MX
160 A=&H1
170 NEXT:TH=TIMER:GOSUB 930

180 TIMER=0:FOR I=1 TO MX
190 A=2
200 NEXT:TD=TIMER:GOSUB 920
210 TIMER=0:FOR I=1 TO MX
220 A=&H2
230 NEXT:TH=TIMER:GOSUB 930

240 TIMER=0:FOR I=1 TO MX
250 A=3
260 NEXT:TD=TIMER:GOSUB 920
270 TIMER=0:FOR I=1 TO MX
280 A=&H3
290 NEXT:TH=TIMER:GOSUB 930

300 TIMER=0:FOR I=1 TO MX
310 A=4
320 NEXT:TD=TIMER:GOSUB 920
330 TIMER=0:FOR I=1 TO MX
340 A=&H4
350 NEXT:TH=TIMER:GOSUB 930

360 TIMER=0:FOR I=1 TO MX
370 A=5
380 NEXT:TD=TIMER:GOSUB 920
390 TIMER=0:FOR I=1 TO MX
400 A=&H5
410 NEXT:TH=TIMER:GOSUB 930

420 TIMER=0:FOR I=1 TO MX
430 A=6
440 NEXT:TD=TIMER:GOSUB 920
450 TIMER=0:FOR I=1 TO MX
460 A=&H6
470 NEXT:TH=TIMER:GOSUB 930

480 TIMER=0:FOR I=1 TO MX
490 A=7
500 NEXT:TD=TIMER:GOSUB 920
510 TIMER=0:FOR I=1 TO MX
520 A=&H7
530 NEXT:TH=TIMER:GOSUB 930

540 TIMER=0:FOR I=1 TO MX
550 A=8
560 NEXT:TD=TIMER:GOSUB 920
570 TIMER=0:FOR I=1 TO MX
580 A=&H8
590 NEXT:TH=TIMER:GOSUB 930

600 TIMER=0:FOR I=1 TO MX
610 A=9
620 NEXT:TD=TIMER:GOSUB 920
630 TIMER=0:FOR I=1 TO MX
640 A=&H9
650 NEXT:TH=TIMER:GOSUB 930

660 TIMER=0:FOR I=1 TO MX
670 A=10
680 NEXT:TD=TIMER:GOSUB 920
690 TIMER=0:FOR I=1 TO MX
700 A=&HA
710 NEXT:TH=TIMER:GOSUB 930

720 TIMER=0:FOR I=1 TO MX
730 A=11
740 NEXT:TD=TIMER:GOSUB 920
750 TIMER=0:FOR I=1 TO MX
760 A=&HB
770 NEXT:TH=TIMER:GOSUB 930

780 GOTO 850

790 TIMER=0:FOR I=1 TO MX
800 A=12
810 NEXT:TD=TIMER:GOSUB 920
820 TIMER=0:FOR I=1 TO MX
830 A=&HC
840 NEXT:TH=TIMER:GOSUB 930

850 TIMER=0:FOR I=1 TO MX
860 A=.
870 NEXT:TD=TIMER:PRINT " . ";:PRINT USING"#####";TD;:PRINT" ";
880 TIMER=0:FOR I=1 TO MX
890 A=&H0
900 NEXT:TH=TIMER:GOSUB 930

910 GOTO 910

920 PRINT USING"### ##### ";A;TD;:PRINT" ";:RETURN

930 PRINT "&H";HEX$(A);" ";:PRINT USING"##### ";TH;:PRINT USING "###.##%";((TD-TH)/TD)*100:RETURN

Running this program (eventually) displays:

So the answer is — for zero, 7% faster. For other single digit values, only slightly faster. For multiple digit values, about 30% faster. BUT, if using zero, using the period (“A=.”) will be about 16% faster than using HEX.

Thank you for coming to my talk.

Until next time…

1986 Doctor Who demo source found.

As I wrote about earlier, in 1986 I created a demo program that displays a Doctor Who logo (based on the one from the Tom Baker era of the show) and played a version of the theme song I created in Musica 2.

Doctor Who music demo.

You can download a disk image of this demo from the CoCo archive site:

https://colorcomputerarchive.com/repo/Disks/Demos/Dr.%20Who%20Demo%20%28Allen%20Huffman%29.zip

I created the music by ear (listening to a cassette tape recording I made from TV) in Musica 2:

Here is a video of the demo playing:

I remembered using a BASIC program to draw most of the logo, then using some graphics program to add the text. I have recently located the original BASIC program:

10 PMODE4,1:COLOR0,1:PCLS:SCREEN1,1
15 GOTO30
20 DRAW"BM128,20;M35,90;M128,160;M220,90;M128,20;BM128,30;M47,90;M128,150;M208,90;M128,30"
25 GOTO25
30 CIRCLE(128,135),100,,1,.66,.85:CIRCLE(128,135),80,,1,.66,.85:LINE(73,53)-(83,68),PSET:LINE(182,53)-(172,68),PSET:PAINT(128,36)
40 DRAW"BM65,70;M70,110;M80,110;M85,95;M90,110;M100,110;M105,70;M95,70;M93,95;M85,80;M78,95;M78,95;M75,70;M65,70":PAINT(66,71)
50 DRAW"BM110,70;M110,110;M120,110;M120,95;M140,95;M140,110;M150,110;M150,70;M140,70;M140,85;M120,85;M120,70;M110,70":PAINT(111,71)
60 CIRCLE(175,90),21:CIRCLE(175,90),12:PAINT(175,71)
70 GOTO20

It draws this:

Sometime, I’d like to try to create a more accurate version of that logo, and update the music to be more accurate as well.

Sometime.

Getting LN in BASIC on a machine that only has LOG

I need to know the real way to do this. BING AI and ChatGPT keep giving me different solutions, and only this last one works. Or seems to work.

I was amused that the BING AI suggested using DEF FN to create this function on a BASIC without LN.

DEF FNLN(x)
=LOG(x) / LOG(2.71828)

Changing the X to uppercase allows this to work on the CoCo. The results are close — are they close enough? Would more decimal places in the LOG value help?

Where are the math people?

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?

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…

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!