Category Archives: CoCo

Tandy/Radio Shack TRS-80 Color Computer (CoCo)

BASIC and ELSE and GOTO and Work – part 1

My recent return to exploring my old Commodore VIC-20 code has reminded me about the main reason I jumped ship to a Radio Shack TRS-80 Color Computer: Extended Color BASIC. The older CBM BASIC V2 used by the VIC was missing keywords like ELSE, and had no functions for graphics or sounds. While I am having a blast re-learning how to program VIC-20 games, I sure do miss things like ELSE.

But should I?

IF/THEN/ELSE versus IF/THEN versus ON/GOTO

Pop quiz time! Suppose you were trying to determine if you needed to move a game character up, down, left or right. Which is the faster way to handle four choices?

30 IF Z=1 THEN 100 ELSE IF Z=2 THEN 200 ELSE IF Z=3 THEN 400 ELSE IF Z=4 THEN 500

…or…

30 IF Z=1 THEN 100
31 IF Z=2 THEN 200
32 IF Z=3 THEN 400
33 IF Z=4 THEN 500

Of course, if the values were only 1, 2, 3 and 4, you wouldn’t do either. Instead, you might just do:

ON Z GOTO 100,200,300,400

…but for the sake of this question, assume the values are not in any kind of order that allows you to do that.

IF/THEN/Work/ELSE versus IF/THEN/WORK

Suppose you were a junior high kid learning to program and you wanted to update some player X/Y positions based on keyboard input. Which one of these would be faster?

30 IF Z=1 THEN X=X+1 ELSE IF Z=2 THEN X=X-1 IF Z=3 THEN Y=Y+1 IF Z=4 THEN Y=Y-1

…versus…

30 IF Z=1 THEN X=X+1
31 IF Z=2 THEN X=X-1
32 IF Z=3 THEN Y=Y+1
33 IF Z=4 THEN Y=Y-1

All is not fair

I should point out that the speed it takes to run these snippets depends on the value of Z. For the sake of this article, let’s assume no key is pressed, so Z is set to something that is not 1, 2, 3 or 4.

Obviously, when there are four IFs in a row (either in a single line with ELSE, or on separate lines), the order of the checks determines how fast you get to each one. If Z is 1, and that is the first IF check, that happens faster than if Z is 4 and the code has to check against 1, 2 and 3 before finally checking against 4.

The same thing applies in languages that use switch/case type logic, so the things that need to be the fastest or happen most often should be at the top of the list and checked before things that happen less often.

Because of this, to be fair, we should also check best case (Z=1) and worst case (Z=4) and see what that does.

Benchmarking going to a line

In my benchmark program, this one counted to 954:

0 REM elsetst1.bas '954
5 DIM TE,TM,B,A,TT
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 IF Z=1 THEN 100 ELSE IF Z=2 THEN 200 ELSE IF Z=3 THEN 400 ELSE IF Z=4 THEN 500
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END

And this one was a tiny bit faster. It counted to 942:

0 REM elsetst1.bas '942
5 DIM TE,TM,B,A,TT
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 IF Z=1 THEN 100
31 IF Z=2 THEN 200
32 IF Z=3 THEN 300
33 IF Z=4 THEN 400
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END

Thus, using ELSE was a bit worse if none of the conditions were true.

IF we could have used ON/GOTO, that would be blazing at 253!

0 REM elsetst3.bas '253
5 DIM TE,TM,B,A,TT
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 ON Z GOTO 100,200,300,400
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END

But I said we couldn’t, because I changed the rules to “do work” rather than “go to a line.”

Benchmarking doing work

Doing work with ELSE clocked in at 601:

0 REM elsetst4.bas '601
5 DIM TE,TM,B,A,TT
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 IF Z=1 THEN X=X+1 ELSE IF Z=2 THEN X=X-1 IF Z=3 THEN Y=Y+1 IF Z=4 THEN Y=Y-1
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END

Since ELSE was slower to go to a line, I thought maybe it would be here, too, but instead, splitting the statements was slower. This one reports 963:

0 REM elsetst5.bas '963
5 DIM TE,TM,B,A,TT
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 IF Z=1 THEN X=X+1
31 IF Z=2 THEN X=X-1
32 IF Z=3 THEN Y=Y+1
33 IF Z=4 THEN Y=Y-1
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END

It seems like ELSE has its place, but not for just going to a line.

Best versus Worst: FIGHT!

Let’s try some best and worst cases now. For this test, I’ll resolve the jumps to lines 100, 200, 300 and 400 by adding this:

100 GOTO 70
200 GOTO 70
300 GOTO 70
400 GOTO 70

That will greatly slow things down since we have to search forward to the new line, then it has to start back at the top of the program and search forward to find line 70. BUT, it will be consistent from test to test. I’ll add a “6 Z=1” or “6 Z=4” line.

  • elsetst1.bas (else): Z=1 produces 507. Z=4 produces 1058.
  • elsetst2.bas (separate): Z=1 produces 390. Z=4 produces 1053.
  • elsetst3.bas (on/goto): Z=1 produces 317. Z=4 produces 357.

Wow. ON/GOTO is really good at going places, best or worst case.

And what about the “doing work” stuff?

  • elsetst4.bas (else): Z=1 produces 632. Z=4 produces 633.
  • elsetst5.bas (separate): Z=1 produces 1171. Z=4 produces 1172.

In conclusion…

If you are using IF to go to some code, ON/GOTO is the fastest, following by separate lines. Even in the worst case, separate lines are still a tiny bit faster, which surprised me. I suspect it’s the time it takes to parse the ELSE versus a new line number. Retesting with all the spaces removed might change the results and make them closer.

But it does look like we need to stop doing “IF X=Y THEN ZZZ ELSE IF X=Y THEN ZZZ ELSE” unless we really need the extra bytes ELSE saves over a new line number.

And if you are trying to do work, ELSE seems substantially faster than separate line numbers. But, in both cases, best and worst case are very close. I believe this is a benchmark issue, since the time to scan a few lines is tiny compared to the time it takes to do something like “X=X+1”, and both best and worst case do the same amount of work. A better test would need to be performed.

Bonus

There is a way to speed up the separate line statements when doing work, especially for better case. Consider this:

0 REM elsetst6.bas '1034
5 DIM TE,TM,B,A,TT
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 IF Z=1 THEN X=X+1:GOTO 70
31 IF Z=2 THEN X=X-1:GOTO 70
32 IF Z=3 THEN Y=Y+1:GOTO 70
33 IF Z=4 THEN Y=Y-1
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END

By adding the GOTO, if line 30 is satisfied (Z=1), the parser can start searching for line 70 without having to do the check against Z three more times. But, when a case is not satisfied, it now has to parse through the GOTO token and a line number to find the end of the line, meaning that for worst case (Z=4) it should be a bit slower.

Let’s see if this works.

  • elsetst6.bas (separate/goto): Z=1 produces 544. Z=4 produces 1241.

Compare that to the previous version without the end line GOTOs:

  • elsetst5.bas (separate): Z=1 produces 1171. Z=4 produces 1172.

It looks like there’s a significant improvement for best case, and a slight decrease in performance for worst case (the overhead of skipping more characters to find the end of the line for the false conditions).

The more you know…

I guess I am learning quite a bit by revisiting the VIC-20 and having to do things without ELSE.

What do you think? Did I miss anything?

Until next time…

More Crazy ON/GOTO/GOSUB and IF/THENs

More comments from the first ELSE article… First, MiaM chimes in:

MiaM:

You could also split that to two separate statements. One handling K=17 case, and then do ON K-38 GOTO 50,x,30 where x is just the line following the ON GOTO line.

don’t know about speed but you could also try ON K-16 GOTO 40,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,50,x,x,30 (also where x is the following line)

MiaM

In my example, I was getting keypress values back that represented left (17), right (39) and jump (41). By filling the ON/GOTO/GOSUB with bogus line values where the gaps are, you can now use ON/GOTO for non-sequential values. But, if the first number expected was a 17, that would be 17 dummy values at the start. Mia’s approach is to subtract that starting value, eliminating the need for 16 dummy values. Clever!

Clever, sure. But can it be benchmarked?

So how bad is this with speed? Let’s find out.

First, for the dummy lines we will just put nothing between the commas. That will be parsed as a zero, which is bad if any of those values are hit since going to 0 would restart the program, but since we are just testing and can control the value, it will give us the fastest way to parse a long ON/GOTO/GOSUB. Using real lines numbers will only be slower.

0 REM ONMIAM.BAS
5 DIM TE,TM,B,A,TT
6 K=17 'BEST CASE
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 ON K-16 GOTO 100,,,,,,,,,,,,,,,,,,,,,,200,,300
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END
100 REM LEFT
110 GOTO70
200 REM RIGHT
210 GOTO70
300 REM JUMP
310 GOTO70

Best case for the first target gives me 590. Not bad!

Trying again with “K=41” for worst case gives us 664. Still not terrible.

How does this rank against manual IF/THENs like this?

0 REM ONMIAM2.BAS
5 DIM TE,TM,B,A,TT
6 K=17 'BEST CASE
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 IF K=17 THEN 100:GOTO 70
40 IF K=39 THEN 200:GOTO 70
50 IF K=41 THEN 300
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END
100 REM LEFT
110 GOTO70
200 REM RIGHT
210 GOTO70
300 REM JUMP
310 GOTO70

Best case (17) reports 504 and worst case (41) reports 1128. Can there really be that much more overhead to skip two extra IF/THENs? It seems so. In this example, the long ON/GOTO is faster in worst case. Interesting. If worst case is a button not used that often (“smart bomb”), IF/THEN may be the best option, but if all buttons are used equally, there’s probably a point where a long ON/GOTO makes more sense.

But wait … there’s more!

Rob provided a suggestion about using an array:

Yep, could also do something like
Dim C(256)
C(17)=1:C(39)=2:C(41)=3

ON C(K) GOSUB 20,30,40
etc.
But that’s probably a bit memory hungry.

Rob

Rob’s idea of using an array to translate the non-sequential values into sequential numbers is a fun one. It uses more memory, and trades the time it takes to do an array lookup for the time it takes to parse a long ON/GOTO/GOSUB line.

Let’s try:

0 REM ONMIAM2.BAS
5 DIM TE,TM,B,A,TT
6 K=17 'BEST CASE
7 DIMK(41):K(17)=1:K(39)=2:K(41)=41
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 ON K(K) GOTO 100,200,300
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END
100 REM LEFT
110 GOTO70
200 REM RIGHT
210 GOTO70
300 REM JUMP
310 GOTO70

Since the largest value we need to check for is 41, I did a DIM K(41). That will allow for values from 0 to 41.

Best case (17) gives us 432! Faster than the manual IF/THEN check!

Worse case (41) gives us 432 … Really? ON/GOTO is really fast with just a few choices. It would be slower if there were dozens and you wanted the one at the end.

The downside of this approach is the memory it took for an array of 42 (0-41) variables. Doing something like this:

NEW:CLEAR
PRINT MEM
DIM K(41)
PRINT MEM

…shows me 22823 and 22606. That’s 217 bytes being taken by the 42 K array entries. (There is an entry reserved for the array itself, then each array element takes 5 bytes, I believe. It’s been awhile since I wrote my String Theory articles which I think looked into how variables are stored.)

This may be the fastest approach if you have a few hundred bytes available to use for this. On a VIC-20 with 3583 bytes free on startup, if I had memory left when I was done with my normal IF/THEN version, I could retrofit it with this approach and use that extra available RAM to speed up my program a tad.

Very cool.

Thanks to MiaM and Rob for these interesting ideas.

Until next time…

ON/GOTO/GOSUB with arbitrary values!?!

Someone named Rob posted a comment to my recent ELSE article that simply contained one line of weird BASIC code:

Arbitrary ON/GOTO/GOSUB!?!

It looked like Rob was presenting a way to do an ON/GOTO with arbitrary values. Will that actually work?

ON GOTO/GOSUB

ON/GOTO (and ON/GOSUB) normally expects values from 1 to X, and a corresponding line number for each consecutive value:

10 INPUT "ENTER 1-5";A
20 ON A GOTO 100,200,300,400,500
30 GOTO 10
100 PRINT "YOU CHOSE 1":GOTO 10
200 PRINT "YOU CHOSE 2":GOTO 10
300 PRINT "YOU CHOSE 3":GOTO 10
400 PRINT "YOU CHOSE 4":GOTO 10
500 PRINT "YOU CHOSE 5":GOTO 10

If A is 1, it will go to 100. If A is 2, it will go to 200. And so on.

Mind the gaps

If you had wanted gaps in the choices, like 1, 3 and 5, you’d have to fill out the ON/GOTO with numbers for the missing choices:

10 INPUT "ENTER 1, 3 OR 5";A
20 ON A GOTO 100,10,300,10,500
30 GOTO 10
100 PRINT "YOU CHOSE 1":GOTO 10
300 PRINT "YOU CHOSE 3":GOTO 10
500 PRINT "YOU CHOSE 5":GOTO 10

That might be a nice approach if the numbers were relatively close to each other, but at some point, adding a bunch of dummy numbers to the ON/GOTO line would take more time to parse than just using separate IF/THEN statements.

Arbitrary GOTO

My example was based on some VIC-20 code I wrote back in 1983. I was reading which key was currently being held down, and would get back three different values for the keys I was reading:

  • 17 – ‘A’ key is pressed (LEFT)
  • 42 – ‘S’ key is pressed (RIGHT)
  • 39 – ‘F1’ key is pressed (JUMP)

I couldn’t use ON/GOTO for values 17, 42 and 39.

But Rob’s code does just that!

20 ON -(K=41)-2*(K=17)-3*(K=39) GOTO 30,40,50

In BASIC, any comparison returns a -1 if it is TRUE, or a 0 if it is FALSE:

PRINTing the result of a comparison in Color BASIC.

…so in Rob’s example, the checks in parenthesis will be turned in to either a -1 or a 0 based on the value of K.

  • If K is 41, then (K=42) will be (-1) and (K=17) and (K=39) will both be (0).
  • If K is 17, then (K=17) will be (-1) and (K=41) and (K=3) will both be (0).
  • If K is 39, then (K-39) will be (-1) and (K=42) and (K=17) will both be (0).

Let’s see what that does:

20 ON -(K=41)-2*(K=17)-3*(K=39) GOTO 30,40,50

K = 41 produces:
ON -(-1) - 2*(0) - 3*(0) GOTO 30,40,50
ON    1  -    0  -    0  GOTO 30,40,50
ON    1                  GOTO 30,40,50

K-17 produces:
ON -(0) - 2*(-1) - 3*(0) GOTO 30,40,50
ON   0  -    -2  -    0  GOTO 30,40,50
ON            2          GOTO 30,40,50

K-39 produces:
ON -(0) - 2*(0) - 3*(-1) GOTO 30,40,50
ON   0  -    0  -    -3  GOTO 30,40,50
ON                    3  GOTO 30,40,50

Fantastic! Subtracting a negative makes it a positive, and multiplying by zero makes zero.

Math rules! And it actually works:

0 REM robgoto.bas
10 INPUT "41, 17 OR 39";K
20 ON -(K=41)-2*(K=17)-3*(K=39) GOSUB 30,40,50
25 GOTO 10
30 PRINT "30":RETURN
40 PRINT "40":RETURN
50 PRINT "50":RETURN
Arbitrary ON/GOTO (tip by Rob).

Fantastic! What a great tip. Thanks, Rob!

Arbitrary benchmark

So of course, I now have to see how this compares to separate IF/THEN’s speed-wise. Let’s pull out the trusty benchmark test code and do a version for best case (first choice) and worst case (last choice) for each approach (Rob’s, and IF/THENs).

Arbitrary ON/GOSUB, best case:

0 REM robgoto1.bas
5 DIM TE,TM,B,A,TT
6 K=41
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 ON -(K=41)-2*(K=17)-3*(K=39) GOSUB 100,200,300
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END
100 RETURN
200 RETURN
300 RETURN

This produces 1368.

Arbitrary ON/GOSUB, worse case:

0 REM robgoto2.bas
5 DIM TE,TM,B,A,TT
6 K=39
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 ON -(K=41)-2*(K=17)-3*(K=39) GOSUB 100,200,300
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END
100 RETURN
200 RETURN
300 RETURN

This produces 1434

Separate IF/THEN/GOSUB, best case:

0 REM ongoto1.bas
5 DIM TE,TM,B,A,TT
6 K=41
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 IF K=41 THEN GOSUB 100:GOTO 70
40 IF K=17 THEN GOSUB 200:GOTO 70
50 IF K=39 THEN GOSUB 300
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END
100 RETURN
200 RETURN
300 RETURN

This produces 518 – almost three times faster!

Separate IF/THEN/GOSUB, worse case:

0 REM ongoto2.bas
5 DIM TE,TM,B,A,TT
6 K=39
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 IF K=41 THEN GOSUB 100:GOTO 70
40 IF K=17 THEN GOSUB 200:GOTO 70
50 IF K=39 THEN GOSUB 300
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END
100 RETURN
200 RETURN
300 RETURN

This produces 1098, meaning even worst case is still faster.

BUT, we are doing a bunch of number parsing and math here. We can’t do anything about the math, but on Color BASIC, we can change those decimal values to HEX and speed up that part. Let’s try that:

0 REM robgoto3.bas
5 DIM TE,TM,B,A,TT
6 K=41
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 ON -(K=&H29)-&H2*(K=&H11)-&H3*(K=&H27) GOSUB 100,200,300
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END
100 RETURN
200 RETURN
300 RETURN

By switching the five integer values in line 30 one to HEX, the speed of best case goes from 1368 to 1150! That’s faster, but it still doesn’t beat 518 using separate IF/THEN/GOSUB.

We might be able to make this a bit faster by using variables, so lets try that:

0 REM robgoto4.bas
5 DIM TE,TM,B,A,TT
6 K=41
7 L=&H29:M=&H2:N=&H11:O=&H3:P=&H27
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 ON -(K=L)-M*(K=N)-O*(K=P) GOSUB 100,200,300
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END
100 RETURN
200 RETURN
300 RETURN

This brings the time down slightly to 1092. Still not enough to beat the separate IF/THEN/GOSUB (and that could also be sped up slightly using HEX or variables).

Conclusion

This trick is very cool. From my calculations, it looks like it save code space, which could be very important on a low-memory system like a 4K CoCo or the 5K VIC-20. That alone might make this trick worth doing.

But for speed, such as a BASIC game, it looks like brute force IF/THEN may be a better approach.

It’s really nice to have options. I can’t wait for an opportunity to use this technique in something.

Thanks, Rob, for leaving such a cool comment!

Until next time…

Faster and smaller Color BASIC with Carl England’s CRUNCH

Making BASIC run faster is hard to automate, but there have been some attempts to do this over the years.

Carl England CRUNCH

No, it’s not that cereal you remember from Saturday morning TV ads. It’s one of the coolest utilities ever created for the CoCo. Carl England wrote quite a few of those, actually.

Carl England was the creator of my all-time favorite Disk Extended BASIC program – Super Boot. It was a superb “type DOS and auto run your program” utility that added many neat features. I have it on many of my RS-DOS disks.

Carl also created THE DEFEATER, a copy utility that could duplicate any copy protected disk. It did not crack the software – it just cloned it, making a duplicate copy protected disk.

Carl also showed off a scanner attachment at the 1990 Atlanta CoCoFest that turned a Tandy DMP printer into scanner! But that’s a story for another time…

Make BASIC small again

Today I want do discuss one of Carl’s programs called CRUNCH. It is a machine language program that will pack a BASIC program to be as small as possible. It does this by removing REMs and unnecessary spaces. It also removes other unnecessary things like “IF X THEN GOTO 100” which could be written just as “IF X THEN 100”. It will even remove trailing quotes at the end of a line (which looked weird, but saved a byte).

But the most important thing it does is pack (er, crunch?) a BASIC program into as few lines as possible. It will take a program like this:

0 REM
1 REM GUESS.BAS
2 REM
3 REM BY ALLEN HUFFMAN
4 REM
5 REM GENERATE RANDOM NUMBER
10 N=RND(100)
15 REM DISPLAY INSTRUCTIONS
20 PRINT "I AM THINKING OF A NUMBER"
30 PRINT "BETWEEN 1 AND 100."
35 REM ASK FOR A GUESS
40 PRINT "WHAT IS YOUR GUESS";
50 INPUT G
55 REM IS GUESS TOO HIGH?
60 IF G > N THEN 100
65 REM IS GUESS TOO LOW?
70 IF G < N THEN 200
75 REM IS GUESS CORRECT?
80 IF G = N THEN 300
85 REM REPEAT
90 GOTO 35
100 REM TOO HIGH
110 PRINT "TOO HIGH!"
120 GOTO 35
200 REM TOO LOW
210 PRINT "TOO LOW!"
220 GOTO 35
300 REM CORRECT
310 PRINT "CORRECT!"
320 END

…and turn it into something like this:

10 N=RND(100):PRINT"I AM THINKING OF A NUMBER":PRINT "BETWEEN 1 AND 100.
40 PRINT"WHAT IS YOUR GUESS";:INPUTG:IFG>N THEN100
70 IFG<NTHEN200
80 IFG=NTHEN300:GOTO 40
110 PRINT"TOO HIGH!":GOTO35
210 PRINT"TOO LOW!":GOTO35
310 PRINT"CORRECT!":END

…except you generally can not list the program afterwards because CRUNCH will combine lines up to the maximum allowed (250 bytes or so?) and create lines too long to be EDITed or LISTed manually.

And, look at line 70 and 80. If you type them, you have to have a space after the variable in “IFG<N THEN…” because the tokenizer needs to know where the variable ends and the next keyword starts. CRUNCH can remove that space, which is impossible to do when typing that in by hand.

Here is what it looks like in operation on the GUESS.BAS program above:

Carl England’s CRUCCH program, before.

You can select a specific operation to do, or choose 7 and do them all:

Carl England’s CRUCCH program, after.

In this example, it changed a 519 byte BASIC program into 214. And, if you renumber the results by 1s starting at line zero (RENUM 0,0,1), that will save an addition 9 bytes because “GOTO30” (two digit line numbers) takes up more bytes than “GOTO9” (single digit line numbers).

CRUNCH is pretty amazing. And, you can download it today for free! It is one of the extra utilities in Carl’s DEFEATER disk copy utility package:

https://colorcomputerarchive.com/repo/Disks/Utilities/Defeater%2C%20The%20%28Carl%20England%29%20%28Coco%203%29.zip

While that archive says CoCo 3, CRUNCH itself will work on a CoCo 1/2. At least, it seems to in my testing using the Xroar emulator.

Try CRUNCH out on some of your programs and share the results in the comments. Just keep in mind that it will destroy your ability to edit the program! Save your crunched program under a different name! You can then distribute that crunched copy, or be nice and give folks both the original (hopefully easier to read) and crunched (smaller and faster to run).

Let me know what you think!

Until next time…

Color BASIC challenge: ball bouncing WITHOUT any IFs?

It seems like all I’m doing lately is regurgitating things that Robin of 8-Bit Show and Tell has already done.

So let’s do that again.

Don’t blame me. Blame YouTube!

YouTube did what YouTube does and it showed me another of Robin’s well-done videos. This one caught my attention because it dealt with the Commodore VIC-20 and its Super Expander cartridge.

The main thing that pulled me away from Commodore was seeing the TRS-80 Color Computer’s Extended Color BASIC. The CoCo had simple commands to play music and draw lines, boxes and circles. It also had this wondrous ELSE command I’d only heard rumors about.

On the VIC-20, it seemed you needed to use POKE and PEEK for just about anything graphics or sound related. Thus I gave up a computer with programmable characters and a hardware sound chip for a machine that had neither. On my new CoCo, at least I could draw a circle and play music without needing pages of DATA statements and cryptic PEEKS and POKEs.

Commodore was aware of this shortcoming, and they sold the Super Expander as a way to make up for it. Not only did it provide an extra 3K of memory (giving a whopping 6.5K for BASIC), it also added new commands to do “high resolution” graphics including drawing lines and circles, as well as ways to PRINT music using simple notation.

I used the Super Expander to do TV titles for fishing videos my father shot and edited. It was a thrill to see my VIC-20 graphics on TV screens at the Houston Boat Show.

But no one else could run my programs unless they had purchased the Super Expander as well.

But I digress.

(And besides, the Commodore 64 was $600 when it came out, and I was able to get a 64K CoCo 1 for $300 at the time.)

Don’t blame YouTube. Blame Twitter.

Robin’s video was making use of the Super Expander to let the VIC-20 solve a challenge initiated by Twitter user Dataram_57. On June 8th, 2019, they wrote:

This was, more or less, a classic bouncing ball program very much like the ones I have been writing about lately. But, all mine certainly made use of IF. Here’s how mine started:

0 REM bounce.bas
10 CLS:X=0:Y=0:XM=1:YM=1
20 PRINT@Y*32+X,"O";
30 X=X+XM:IF X<1 OR X>30 THEN XM=-XM
40 Y=Y+YM:IF Y<1 OR Y>13 THEN YM=-YM
50 GOTO 20

That’s not a very good example. It doesn’t erase itself, nor does it use the bottom line to avoid screen scrolling when the ball hits the bottom right position. It does show how I would use X and Y coordinates then an XM (X movement) and YM (Y movement) variable to increment or decrement them based on if they hit an edge.

The parameters of Dataram_57’s challenge were as follows:

  • Width: 90
  • Height: 80
  • Starting Position: 0,0
  • Time: ???

I wrote a quick graphical program do do this using my X/Y/XM/YM method:

0 REM dataram_57 twitter challenge
1 POKE 65495,0
10 W=89:H=79:X=0:Y=0:T=0:XM=1:YM=1
20 PMODE0,1:PCLS0:SCREEN1,1
30 LINE(0,0)-(89,79),PSET,BF
40 PSET(X,Y,0)
50 X=X+XM:IF X<0 THEN XM=1:GOTO 50 ELSE IF X>=W THEN XM=-1:GOTO 50
60 Y=Y+YM:IF Y<0 THEN YM=1:GOTO 60 ELSE IF Y>=H THEN YM=-1:GOTO 60
70 T=T+1:IF T=7031 THEN END
80 GOTO 40

The first thing to notice is that I draw a filled box from 0,0 to 89,79 and then set black pixels in it. This lets me visually verify my line is going all the way to the edge of the 90×80 target area. Also, I am using the CoCo 1/2 double speed poke since this is time consuming. If you do this on a CoCo 3, feel free to use POKE 65497,0 instead.

Twitter user Dataram_57’s challenge running on a CoCo.

Eventually the area should be entirely black when every dot has been erased.

How long has this been going on?

I did some tests and figured out that it takes 7032 iterations (0-7031) for the dot to cycle through the entire 90×80 area before it has erased all the other dots.

With that in mind, I propose we turn this into both a logic and optimization challenge. On the CoCo, let’s see if we can use the PMODE 0 screen (128×96 resolution with 1 color). We can put this in a modified version of benchmark framework for 7032 cycles and see how fast we can do it. (By modified, I am removing the outer “try this three times and average the results” loop.)

My example, using IFs, looks like this:

0 REM dataram_57 twitter challenge 2
1 POKE 65495,0
5 DIM TM,A
10 TIMER=0:TM=TIMER

15 W=89:H=79:X=0:Y=0:XM=1:YM=1
16 PMODE0,1:PCLS0:SCREEN1,1
17 LINE(0,0)-(89,79),PSET,BF

20 FORA=0TO7030

30 PSET(X,Y,0)
40 X=X+XM:IF X<0 THEN XM=1:GOTO 40 ELSE IF X>=W THEN XM=-1:GOTO 40
50 Y=Y+YM:IF Y<0 THEN YM=1:GOTO 50 ELSE IF Y>=H THEN YM=-1:GOTO 50

80 NEXT
90 PRINTTIMER:END

Mine, running in Xroar, displays 9808 at the end. And it’s not the correct way to meet the requirements of the challenge, so … the real versions may be faster, or slower.

Your challenge, should you decide to accept it…

Our challenge is to:

  1. Rewrite this to work WITHOUT using any “IFs”.
  2. Try to make it as fast as possible while keeping the benchmark code (lines 5, 10, 20, 80 and 90) intact. You can add variables to the DIM, but otherwise leave those lines alone.

What says you?

Credit where credit is due…

And lastly, for those who want to cheat, here is the solution that Robin came up with using the VIC-20 Super Expander cartridge…

Is his the only way? The best way? The fastest way?

Let the regurgitated challenge begin!

Until next time…

Understanding and Using the CoCo 3 MMU

This document was originally written in 1987 and was available on my BBS. It was originally in some kind of text editor format that I do not remember, so I have reformatted it for WordPress and made it available here for archival purposes. If you want the original file, here it is:


Understanding and Using the MMU

A “Hopefully Helpful” Text file by
Allen C. Huffman

The 6809 CPU can only access 64K at a time. In order for an 8-bit CPU to access memory beyond that range, some sort of Memory Management Unit must be used. The CoCo 3’s MMU does the job nicely by breaking all of the memory into 8K chunks. The CoCo 3 is set up to handle up to 512K of memory which is addressed from &H0000 to &H7FFFF (0 – 524287). The 6809 will recognize the last 64K in this map (&H70000 to &H7FFFF) as “normal” memory for it. (In Basic, when you do a POKE 1024,42 it is actually putting a 42 in physical memory &H70000 + 1024. (If you do an LPOKE 1024,42 you will actually be putting the 42 in physical memory location 1024.) This may seem strange at this time, but hopefully it will soon be clear.

In a 128K Color Computer, the RAM is located from &H60000 to &H7FFFF. (All memory from &H00000 to &H5FFFF is unusable unless you have 512K in which case your extra memory resides there.) The 128K is broken into two parts. One is the normal 64K workspace that the CoCo uses. The other 64K is used for Hi-Res graphics, screens, etc. (For more detail on where everything is, refer to your CoCo 3 owners manual Memory Map on Page 311.)

Meanwhile, back to the MMU, all of the CoCo’s memory is handled internally as 8K blocks. Thus, a 64K workspace is actually 8 MMU blocks. (8 x 8K is 64K, right?) The MMU can access any of these 8K blocks by using the corresponding block number. The blocks are numbered as follows:

##    Memory Range      ##   Memory Range      ##   Memory Range
00 00000 - 01FFF 10 20000 - 21FFF 20 40000 - 41FFF
01 02000 - 03FFF 11 22000 - 23FFF 21 42000 - 43FFF
02 04000 - 05FFF 12 24000 - 25FFF 22 44000 - 45FFF
03 06000 - 07FFF 13 26000 - 27FFF 23 46000 - 47FFF
04 08000 - 09FFF 14 28000 - 29FFF 24 48000 - 49FFF
05 0A000 - 0BFFF 15 2A000 - 2BFFF 25 4A000 - 4BFFF
06 0C000 - 0DFFF 16 2C000 - 2DFFF 26 4C000 - 4DFFF
07 0E000 - 0FFFF 17 2E000 - 2FFFF 27 4E000 - 4FFFF
08 10000 - 11FFF 18 30000 - 31FFF 28 50000 - 51FFF
09 12000 - 13FFF 19 32000 - 33FFF 29 52000 - 53FFF
0A 14000 - 15FFF 1A 34000 - 35FFF 2A 54000 - 55FFF
0B 16000 - 17FFF 1B 36000 - 37FFF 2B 56000 - 57FFF
0C 18000 - 19FFF 1C 38000 - 39FFF 2C 58000 - 59FFF
0D 1A000 - 1BFFF 1D 3A000 - 3BFFF 2D 5A000 - 5BFFF
0E 1C000 - 1DFFF 1E 3C000 - 3DFFF 2E 5C000 - 5DFFF
0F 1E000 - 1FFFF 1F 3E000 - 3FFFF 2F 5E000 - 5FFFF

That covers the 512K area. On a 128K CoCo, you can’t even use blocks 00 to 2F, so let’s look at what you can use:

30    60000 - 61FFF
31 62000 - 63FFF The first 4 blocks (32K) is where Basic puts the
32 64000 - 65FFF HSCREEN graphics.
33 66000 - 67FFF

34 68000 - 69FFF This is where the HGET/HPUT buffer is. (8K)

35 6A000 - 6BFFF This is the secondary stack area. (8K)

36 6C000 - 6DFFF This is where the 40/80 column screen goes. (8K)

37 6E000 - 6FFFF And this one is unused by Basic. (8K)

That takes care of the 64K you don’t have direct access to on startup. Here is the 64K you do have access to:

38    70000 - 71FFF     The first 32K is normal system RAM.  (System use,
39 72000 - 73FFF 32 column text screen. PMODE graphics, Basic program
3A 74000 - 75FFF and variable storage)
3B 76000 - 77FFF

3C 78000 - 79FFF The last 32K is where system ROM goes. (Note that
3D 7A000 - 7BFFF on the CoCo 3, all ROM is copied into RAM so these
3E 7C000 - 7DFFF locations are indeed RAM.)
3F 7E000 - 7FFFF

(Notice that all you have to do to convert physical locations to normal 8 bit locations on the older CoCo’s is to remove the 7.)

So now you should have some idea of how memory is looked at, but how do you actually use it? Eight memory locations control what physical block of memory goes where. Let’s have another chart…

FFA0    0000 - 1FFF      Remember those block numbers given earlier?
FFA1 2000 - 3FFF On startup, FFA0 - FFA7 contain:
FFA2 4000 - 5FFF
FFA3 6000 - 7FFF 78, 79, 7A, 7B, 7C, 7D, 7E, & 7F.
FFA4 8000 - 9FFF
FFA5 A000 - BFFF WHY? Shouldn't they be 38, 39, 3A, etc, as the
FFA6 C000 - DFFF block numbers would indicate? Read on...
FFA7 E000 - FFFF

The MMU locations have bit 6 set high which adds &H20 (64) to their actual value. Therefore, to the MMU 38 is the same as 78, 3E is the same as 7E, etc. The CoCo 3 Service Manual indicates that programmers may use any blocks in the range of 00-3F so we will go by that. Please try not to let this confuse you! Just accept it.

In order to use the MMU from Basic, you simply use the POKE command to tell the MMU what “block” goes where. For instance, location &HFFA2 contains 7A on startup. Anytime you peek memory between 4000-5FFF it is actually PEEKing from block 3A which is physical locations 74000-75FFF. You can change this by POKEing to it. If you POKE &HFFA2,&H30 then anytime you PEEK or use memory in the range of 4000-5FFF it will actually be coming from physical memory 60000-61FFF. Whatever was in block 3A is still there, but when you try to use that memory, the MMU points the CPU to somewhere else. The CPU never knows the difference! To get it back as before, POKE &HFFA2,&H3A.

You may now have a slight understanding of how those Basic HSCREEN save/load routines work. Take a look:

10 INPUT "Save picture as";N$
20 FOR A = &H30 TO &H33
30 POKE &HFFA2,A
40 SAVEM N$+"/HR"+CHR$(A-&H2F),&H4000,&H5FFF,0
50 NEXT A
60 POKE &HFFA2,&H3A

Line 10 simply asks for what to save the picture as. Line 20 does a loop. If you notice 30-33 is the physical location of the hi-res HSCREENs. The poke in line 30 switches the physical location pointed to by “A” into CPU location 4000-5FFF. (The first time through, block 30 is there, then 31, then 32, etc.) After this, line 30 does a SAVEM of whatever is now at that location. (It tags the extension of HR at the end followed by a number 1-4). Line 50 continues the loop and line 60 restores the memory like it was before this routine.

Programmers out there might think this is strange seeing a program save the same contents of memory (4000-5FFF) four times, but that is where the magic of the MMU comes in. Thanks to the POKE, each time the program gets to that line the MMU points to a different area. To load a file back in, a similar routine must be used that flips the MMU to that area then, after loading, flips it back where it goes. It might look something like this:

10 INPUT "Load picture";N$
20 INPUT "HSCREEN #";H
30 HSCREEN H
40 FOR A = &H30 TO &H33
50 POKE &HFFA2,A
60 LOADM N$+"/HR"+CHR$(A-&H2F)
70 NEXT A
80 POKE &HFFA2,&H3A

If you can understand why this program works, you are in good shape. By theory, someone could save the entire memory of the Color Computer to disk in 8K chunks using the above method. (Why? I don’t know, but they could…)

Now if you still don’t see how this works, try this experiment. Enter the following line which will fill up physical block &H37 (6E000-6FFFF) with the value of 42.

FOR A=&H6E000 TO &H6FFFF:LPOKE A,42:NEXT A

Okay. So all the memory from 6E000 to 6FFFF should have 42s in it, right? Right. (To verify, you could do FOR A=&H6E000 TO &H6FFFF:PRINT LPEEK(A):NEXT A and you would get 42s back.)

Somewhere in memory we have a block of 42s. We can make that block appear to be somewhere else. Try a POKE &HFF2A,&H30. You have just set it so whenever the CPU looks at locations 4000-5FFF it will really be looking at 60000-61FFF. Confirm this and try:

FOR A=&H4000 TO &H5FFF:PRINT PEEK(A);:NEXT A

You will see a bunch of 42s. Magic, right? No, just amazing. Still not convinced? Type POKE&HFF2A,&H3A and try that previous line again. What happened to the 42s? They are still where they were originally but, the CPU is being directed back to where it was supposed to be.

This is just the start of what the MMU can do. There is another bank of MMU registers from FFA8 to FFAF. These are being used by the CoCo itself while in basic, but any machine language programmer could define each set of MMU registers and then by toggling a single bit (bit 0 of FF91) he could select between which MMU map would be used.

If you still haven’t got it, leave a message for me and I will do my best to answer questions or to create another file that further explains the MMU.

CoCoFest Chronicles book by Allen Huffman

In honor of the 40th anniversary of the Radio Shack Color Computer, I am making my book, CoCoFest Chronicles, available as a free download. This book was first published in 1998 and has been out-of-print for many years.

The book contains updated versions of my CoCoFest reports covering events from 1990 to 1997. It also contains behind-the-scenes tidbits that explain various inside jokes and things that went on that were not included in the original reports I uploaded to online services back then.

Download it for free here:

Happy 40th birthday, CoCo!

CoCoWiFi in the UK

Thanks to Rob Inman for sharing this link over on Discord. This was supposed to have been posted in July 2019, but I just found it in my drafts folder. I think I was going to write an article about it, but forgot.

Someone in the UK is selling an all-in-one RS-232 to WiFi adapter. They use Bo Zimmerman’s excellent Zimodem firmware, though the version they use is based on my fork of the project with the defaults set to standard RS-232 rather than Commodore’s inverted RS-232.

https://www.simulant.uk/shop/retro-vintage-computer-wifi-modem-rs232-serial-hayes-compatible

What ELSE can you do when you don’t have ELSE?

NOTE: I have a follow-up to this ELSE thing already written which “changes everything” for me. There’s stuff about ELSE I never knew, which makes me want to never use it. More on that in the next installment. And now back to your regularly scheduled blog post…

First, a quick BASIC memory saving tidbit.

Shaun Bebbington left a comment about my VIC-20 dissection article asking if I knew you could omit zeros from DATA statements like this:

100 DATA 128,0,0,50,239,0,123,42,0,4

If you leave them out (comma comma), READ will still get a zero! It saves a byte each time you do that, which could be important on a machine with 3584 bytes free on startup like my VIC-20.

100 DATA 128,,,50,239,,123,42,,4

Good tip, Shaun! Thanks!

Is there anything ELSE I can help you with?

And while I had his attention, I asked him a few Commodore BASIC questions. I was wondering how you did IF/THEN/ELSE without ELSE! One of the many things I liked about the BASIC in the CoCo was it had ELSE and commands like PLAY, DRAW, CIRICLE, etc. My VIC-20 had no ELSE. In my Sky-Ape-Er game, I’d see my young self doing things like this:

125 IF K=17 THEN L=L-1:M=14
130 IF K=41 THEN L=L+1:M=13
135 IF K=39 THEN 180

In that code snippet, K represented the key value that was pressed. If it was 17 (left), I’d move the player left one and change its graphic character. If it was 41 (right), I’d move the player right one and change its graphics character. If it was 39 (F1, jump), I’d go to a routine that would handle the jump.

Without ELSE, if K was 17, it would do that, then check K two more times, even though it didn’t need to. Maybe I did not “see” it back then, but If I had the bytes to spare, I probably should have done something like this:

125 IF K=17 THEN L=L-1:M=14:GOTO 140
130 IF K=41 THEN L=L+1:M=13:GOTO 140
135 IF K=39 THEN 180

That way, if K=17 was true, it would do it’s thing, then GOTO would skip over the next two lines. This could be a huge time saver if there were a bunch of conditions (up, down, left, right, diagonals, jump, fire, punch, windshield wipers, etc.)

Someone has already suggested that I may have done it my original way to get consistent timing in the game loop. Somehow I doubt my junior high self was that clever. But I digress…

With ELSE, it could have been written like this:

125 IF K=17 THEN L=L-1:M=14 ELSE IF K=41 THEN L=L+1:M=13 ELSE IF K=39 THEN 180

Less code (the ELSE token takes up less memory than an addition line number and/or the extra GOTO to skip lines), and faster execution, maybe.

Side Note: Maybe? There is still the issue of, when completing a successful IF, BASIC’s parser still having to run through the rest of the line characters to find the end and the next line. Adding a “GOTO” wouldn’t help, either, since it would have to parse that and then STILL have to scan to the end of the line. (At least on Color BASIC, which does not remember line pointers once it is parsing a line.) It may actually be faster to break up a long set of IF/ELSE into small lines with a GOTO.

But Shuan mentioned a way of using ON to simulate an ELSE. A quick search led me to a forum post discussing this very thing.

It supposedly works like this… since a compare (K=42, A$=”RED”, G>100) will return either -1 (false) or 0 (true), you can do an ON GOTO based on the result of that compare. Since it’s a -1 or 0, you can just make the result negative (thus, -1 becomes 1, and 0 becomes -0 which is still 0):

10 ON -(A=42) GOTO 20:PRINT "NOT 42":END
20 PRINT "42!"

Er… what? I thought BASIC did not execute anything after a GOTO. It doesn’t, does it?

Nothing on a line after GOTO will be executed. WIll it?

But… but… How can ON/GOTO with something after it work, then? It turns out, ON/GOTO is special since the conditions of the “ON” may not be met, and thus the GOTO part may not get executed and BASIC will continue.

ON A GOTO 100,200,300:PRINT "A WAS NOT 1, 2 or 3"

Looking at it like that makes perfect sense to me. If A is not 1, 2 or 3, it won’t GOTO anywhere and the line continues to be processed.

Thus, this odd code serves as a simple “ELSE” when you don’t have ELSE:

10 INPUT "VALUE";A
20 ON -(A=42) GOTO 30:GOTO 10
30 PRINT "YOU KNOW THE ANSWER!"

…would be like…

10 INPUT "VALUE";A
20 IF A=42 THEN 30 ELSE 10
30 PRINT "YOU KNOW THE ANSWER!"
If no ELSE, use ON GOTO?

Interesting! I have not benchmarked this to see if it’s faster than using GOTO to skip lines, but it might be smaller due to not needing another few bytes for each line number.

Would this have helped my “left, right, jump” code? Maybe not. You can string these together like this:

124 ON -(K=17) GOTO 125:ON -(K=42) GOTO 130:ON -(K=39) GOTO 180
125 L=L-1:M=14:GOTO 140
130 L=L+1:M=13:GOTO 140

Since I was doing code in response to the IF, the ON/GOTO approach would just let me GOTO a line that does that code, which then still needs a GOTO to skip the lines after it that shouldn’t be executed. Not great for that use.

But, if I were dispatching to routines based on direction (like I do with the “jump” check to line 180), it would have worked just fine. Instead of this:

125 IF K=17 THEN 200
130 IF K=41 THEN 300
135 IF K=39 THEN 400
...
200 REM Handle LEFT
...
300 REM Handle RIGHT
...
400 REM Handle JUMP
...

Those three lines could be combined into one like this:

124 ON -(K=17) GOTO 200:ON -(K=42) GOTO 300:ON -(K=39) GOTO 400

But, doing a quick test typing in JUST those lines on a VIC-20 emulator, I got 3530 bytes free after each approach. No penalty, but no savings, and all the parsing with parens and the negatives is probably slower.

Interesting, for sure. Useful? I guess I’ll find out if I get around to updating my VIC-20 code.

Bonus offer

I also saw this example in the Commodore forum:

10 rem if then else demo
20 input a
30 if a = 1 then goto 60
40 print "this is the else part"
50 goto 70
60 print "this is the if (true) part"
70 end

This is how I’d write that with ELSE:

20 INPUT A
30 IF A=1 THEN PRINT "THIS IS THE IF (TRUE) PART" ELSE PRINT "THIS IS THE ELSE PART"

So we could change the Commodore example to match this a bit closer:

20 input a
30 if a = 1 then print "this is the if (true) part":goto 70
40 print "this is the else part"
70 end

…which leads us back to just adding a GOTO at the end of each separate IF:

10 GET A$:IF A$="" THEN 10
20 IF K$="U" THEN Y=Y-1:GOTO 70
30 IF K$="D" THEN Y=Y+1:GOTO 70
40 IF K$="L" THEN X=X-1:GOTO 70
50 IF K$="R" THEN X=X+1:GOTO 70
60 GOTO 10
70 REM DRAW X,Y...
80 GOTO 10

…which on the CoCo’s Extended Color BASIC might look like:

10 GET A$:IF A$="" THEN 10
20 IF K$="U" THEN Y=Y-1 ELSE IF K$="D" THEN Y=Y+1 ELSE IF K$="L" THEN X=X-1 ELSE IF K$="R" THEN X=X+1 ELSE 10
70 REM DRAW X,Y...
80 GOTO 10

Ultimately, we are just trading “ELSE” with “GOTO xx” and a new line, with ELSE being smaller due it it just being a token, verses the overhead of a GOTO token and the line number characters after it, AND a new line for each additional “else” condition.

Until next time, ELSE maybe sooner…

Virtual CoCoFest this Saturday, April 18, 2020.

News release:


CoCoTALK! is hosting a live, virtual CoCoFEST!

April 18th, 2020 would have been the 29th annual “last” Chicago CoCoFEST! celebrating the 40th anniversary of the Tandy RadioShack TRS-80 Color Computer. While the event itself has been postponed, the live retro podcast CoCoTALK! is hosting a live, virtual CoCoFEST! Saturday April 18th @ 2:00 PM EDT.You can view and chat live on YouTube, the friendly link is http://live.cocotalk.live which points to  https://www.youtube.com/c/Ogsteviestrow/live

CoCoFEST! is an annual event hosted by the Glenside Color Computer Club and has been running since the last Rainbow Fest ended.  This event is the K-FEST/VCF event of the Color Computer world. The virtual event is not an official Glenside Color Computer event, but is  being held, with their approval.  The goal is to get as many of the attendees, exhibitors, vendors, speakers, and presenters to join the live call present virtually, and turn the event into one that can be experience globally.

CoCoTALK! is a weekly live and interactive video talk show which multicast to YouTube and Facebook live, so there will be plenty of ways to join the celebration with us.  To learn more about CoCoTALK! go to http://cocotalk.live To learn more about the Glenside Color Computer club, visit http:///glensideccc.com