Author Archives: Allen Huffman

About Allen Huffman

Co-founder of Sub-Etha Software.

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…

VIC-20: Sky-Ape-Er code dissection – part 4

See also: part 1, part 2, part 3, part 4 or part 5 (with more coming).

Hey hey! It’s another VIC-20 Tuesday!

I have completed my code walk-through of one of my earliest computer programs, a Donkey Kong-inspired VIC-20 game called Sky-Ape-Er. But, the version I presented was not the only version of the game I created. I have dozens of saved copies of this game in various stages of completion, but one in particular stood out. It used completely different graphics.

Sky-Ape-Er: The Prototype

As a reminder, here is what the graphics were later changed to in the release version:

The ape was completely different, and the platform graphics were meant to resemble the ones used in the arcade game Donkey Kong. But why were there pinwheels? I have so many questions for my junior high self.

When I first uncovered these tapes I was unaware of how many variations of my programs were on them. When I started this article on my Sky-Ape-Er game, I discovered that the earlier version with different graphics was also using some different code — most notably in how it read the keyboard input.

I thought it might be fun to look at the programming choices I originally made, and speculate on why I changed them.

Sky-Ape-Er: The Mystery

The first thing I want to discuss is a mystery I am currently trying to solve. My VIC-20 games that used custom character sets seem to come in three forms:

DATA STATEMENTS

The BASIC program reads the character set from DATA statements and POKEs it into memory. This is what the Sky-Ape-Er INSTRUCTIONS program does. I believe these character sets may have been designed by the Eight by Eight Create program I previously mentioned. If true, I don’t envy my junior high self having to manually copy down the numbers to paper and type them in to my own program later. From the documentation:

“Once you have created, designed and examined enough characters, you can copy their associated numbers on paper to be used in any programs you make.”

Eight by Eight Create instruction from January 1983 Creative Computing magazine (Volume 9, Number 1), page 270.

Yipes.

LOADABLE CHARACTER SET

A standalone binary “program” of the custom character set that can be loaded into memory, presumably at the address of where the character data goes. I have found several programs called things like CHARS and TNTCH that do not have any BASIC code in them. I suspected these were character set data (especially TNTCH which was on the tape after the main program TNT) but I had no idea how to use them. I was finally able to see what was inside by importing them into the CBM prg Studio‘s characters set editor. CBM prg Studio is a Windows integrated development environment (IDE) for making Commodore programs in BASIC or assembly. It has some great features and is worth checking out.

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

This let me see that these were indeed character sets, though my first attempt to import them had all the graphics off by a few lines. I needed to use an offset (bytes to skip in the file) of 2 for the characters to load properly. That told me that whatever type of file this was had some 2 byte header at the start (perhaps memory location where to load the data?).

ALL-IN-ONE PROGRAM WITH CHARACTER SET

And this is the mystery! My early prototype version of Sky-Ape-Er was just one program, and it loaded up with the custom character set. There was no font in DATA statements. There was no pre-loader that did it. It just loaded and “just worked.” I have no idea how I created this, nor do I know why, for the release version, I change it to use two programs and DATA statements.

But I have theories.

Dissecting the data

Thanks to suggestions from a VIC-20 group on Facebook and the Denial Commodore forum, I looked at the contents of the mystery SKY-APE-ER program file.

Using a free hex editor, I opened the file and looked for the end of the BASIC program. I could tell it ended around byte 2612 because the last line was a “SYS xxxxx” command that would reboot the VIC-20. The xxxxx numeric value was visible as plain text in the tokenized BASIC file, so it was easy to spot.

VIC-20 .prg file in a HEX editor.

After this was a bunch more data. Somewhere in there must be the character set. But where? I decided to try opening the entire program file in the character set editor and using the 2612 offset where the BASIC program ended.

VIC-20 CBM prg Studio importing a .prg to find the embedded charset data.

Doing this showed garbage between the BASIC program and character data, but scrolling down let me visibly see where the font data began.

VIC-20 CBM prg Studio trying to find where character set data is in a .prg file.

I now knew that approximately 58 characters (each character is 8×8, so 8 bytes per) into the file was the start of the font data. A little math (which was hard) and some trial and error (which was easy) and I came up with 3073 as the offset to use from the stat of the .prg to where my custom characters were. I imported using that value and got this:

VIC-20 character set data imported from a .prg file.

Tada!

If I knew what the font data was to begin wish (from DATA statements), I could have just scanned the HEX file looking for those values. But I didn’t, so I couldn’t.

Now I have a BASIC file for the game, as well as a character set file in the CBM prg Studio editor. But how did I combine them together in the first place?

Where does the data go from here?

The clue is in these POKEs found on the first line of the program:

POKE45,56:POKE46,26:POKE51,0:POKE52,28:POKE55,0:POKE56,28:CLR

They reminded me of similar POKEs in Color BASIC that track where the program starts in memory as well as where variables and strings go. I expected CBM BASIC would be similar, so I went searching for a VIC-20 memory map.

I found this one archived on Bo Zimmerman’s site. He’s the guy behind the incredible Zimodem firmware that lets you wire up a WiFi serial modem for under $10.

http://www.zimmers.net/cbmpics/cbm/vic/memorymap.txt

I want to do a deep dive into this later, but for now, here are what those POKEs are doing:

*002D-002E 45-46 Pointer: Start of Variables
*0033-0034 51-52 Pointer: String storage (moving down)
*0037-0038 55-56 Pointer: Limit of memory

The “*” notes “Useful memory locations” in the memory map. I agree. I seem to be changing where variables and strings start, as well as where the end of memory is on startup.

Why was I changing the start of variables, the end of string storage, and limiting the end of BASIC? I have a theory, which parallels something I’ve done on the CoCo.

In Color BASIC, we use the CLEAR command to allocate more string space (“CLEAR 500” for 500 bytes for strings). It looks like CBM BASIC doesn’t do that, and allows strings to use as much memory as is available (the memory between the end of the BASIC program + variable arrays, and the limit of memory).

CLEAR can also limit how much memory BASIC can use (“CLEAR 200,&H3F00”). That’s useful when you are wanting to use some of that memory for machine language and don’t want BASIC to overwrite it. I am betting POKE 51/52 is like CLEAR x,XXXX.

VIC-20 Memory Map

To better visualize this, let’s take a quick look at where the 5K of RAM in the VIC-20 is located.

   0 -> +------------------------------+
....    | 1K of System Memory          |
1024 -> +------------------------------+ <- 1023
        | 3K Expansion RAM (cartridge) |
4096 -> +------------------------------+ <- 4095
        | User BASIC Area (3583 bytes) |
7680 -> +------------------------------+ <- 7679
        | Screen Memory (512 bytes)    |
        +------------------------------+ <- 8191

Hey, look at that! The memory range used by BASIC (4096-7679) is the “3583 BYTES FREE” value shown on the startup screen:

VIC-20 startup screen showing 3583 bytes free.

Notice the 3K gap (1024-4095) which is where the 3K RAM expansion cartridge goes if you have one. I never did, though I did have the Super Expander cartridge which gave extra memory as well as enhanced graphics and sound commands.

Side Note: When memory expansion cartridges are plugged in, more memory becomes available and some things shift around. But for this discussion, we will talk only about the stock 5K VIC-20. It was only in recent years that I learned the VIC-20 was a 5K computer. I’d always thought it was 4K. That now makes the weird 3K memory expansion make more sense, since that would boost it to a nice even 8K. But I digress…

Now let’s zoom in on just the memory BASIC is using:

43/44 -> +---------------+
         | BASIC program |
45/46 -> +---------------+
         | Variables     |
47/48 -> +---------------+  
         | Arrays        |
         +---------------+ <- 49/50 End of Arrays
         |               |
         |               |
51/52 -> |---------------+  
         | Strings       |
         +---------------+ <- 55/56 Mem Limit (7679)

When a new numeric variable is added, it goes into the Variables section, which grows larger downward. When a new array is added, it goes into the Arrays area (and likely the entries there point to the Variable) and it grows larger downward. When a string is added, it gets an entry in the Variable section (“A$”) which has a pointer into the actual string content in the Strings section, which grows upwards.

So why was I changing the start of variables, the end of string storage, and limiting the end of BASIC? I believe I was making BASIC think the program was larger than it really was so it would SAVE out (and thus LOAD back later) the program PLUS some custom character data. When the program would run, it would need to reset the pointers to be at the actual end of the BASIC program, and limit memory so BASIC did not write over the character data.

In order to explain this, we need to look at how the VIC-20 custom characters worked.

How the VIC-20 custom characters worked

The VIC-20 character set was 4K of data stored in ROM starting at 0x8000:

8000-83FF 32768-33791 Upper case and graphics
8400-87FF 33792-33815 Reversed upper case and graphics
8800-8BFF 33816-35839 Upper and lower case
8C00-8FFF 35840-36863 Reversed upper and lower case

Each character was 8 pixels wide (one byte) by 8 pixels tall (8 bytes total). There is room for 512 characters in that 4K. Normal printable ASCII characters are 0-127, so it looks like the Commodore PETASCII was similar, with 128 special Commodore characters per bank (128 characters * 8 bytes per character = 1024 bytes).

On power up, the VIC’s video chip is programmed to use the first of those four 1K blocks of ROM for its character set. There is a register with four bits that can be changed to select which of those four ROM blocks it uses, or point it to four 1K RAM blocks in RAM. By loading a character set in to one of those RAM areas and setting the register, the VIC will now display the custom character set rather than the one built in to the ROM. Here are the important four bits:

9005 36869 bits 0-3 start of character memory (default = 0)
                     bits 4-7 is rest of video address (default= F)
                     BITS 3,2,1,0 CM starting address
                                  HEX   DEC
                     0000   ROM   8000  32768
                     0001         8400  33792
                     0010         8800  34816
                     0011         8C00  35840
                     1000   RAM   0000  0000
                     1100         1000  4096
                     1101         1400  5120
                     1110         1800  6144
                     1111         1C00  7168

Memory location 36869 can be one of these 8 values:

  • 0xF0 / 240 / 11110000 – Use 8000-83FF (Upper case and graphics)
  • 0xF1 / 241 / 11110001 – Use 8400-87FF (Reversed upper case and graphics)
  • 0xF2 / 242 / 11110010 – Use 8800-8BFF (Upper and lower case)
  • 0xF3 / 243 / 11110011 – Use 8C00-8FFF (Reversed upper and lower case)
  • 0xfc / 252 / 11111100 – Use 1000-13FF RAM area #1
  • 0xfd / 253 / 11111101 – Use 1400-17FF RAM area #2
  • 0xfe / 254 / 11111110 – Use 1800-1BFF RAM area #3
  • 0xff / 255 / 11111111 – Use 1C00-1FFFF RAM area #4

The four RAM locations all are within the 4K that is used by BASIC and screen memory:

  • 0x1000 – 0x1dff – 3583 bytes used by BASIC programs.
  • 0x1e00 – 0x1fff – 512 bytes used by screen memory.

We can’t use RAM area #1 for characters because that is where our BASIC program is. If we kept our BASIC program and all its variables very small (1K, 0x1000-0x13FF), we could use area #2. But, it makes more sense to use area #4 and give as much memory as possible to BASIC.

In my programs I see POKE 36869,255 and POKE 36869,240. The first POKE makes the video chip start using characters in RAM starting at 0x1c00 (bit pattern 1111). This means a BASIC program and all its variables can’t be any larger than 3072 bytes (0x1000-0x1bff). The second poke switches the characters back to using the standard ROM location for uppercase and graphics characters (bit pattern 0000).

My Sky-Ape-Er INSTRUCTIONS program would READ character data and then POKE it into memory starting at 0x1c00 (7168). It then did POKE 36869,255 to start using them. To display a normal text screen, or at the end of the program, it would POKE 36869,240 to get back to the ROM character set. (This is the part I actually mostly remembered.)

I do want to point out that the character RAM area #4 overlaps with the screen memory:

  • 0x1c00 – 0x1fff – Character RAM area #4.
  • 0x1e00 – 0x1fff – 512 bytes used by screen memory.

This tells me that you really only have 0x1c00 to 0x1dff (7168-7679) for custom characters. That’s 512 bytes, and at 8 bytes per letter, there is only room for 64 custom characters. Assembly language programs that did not need BASIC could move things around and use one of the other blocks in its entirety, but this last block shares memory with the screen so not all of it can be used for character data.

Character sets

The PETSCII character set starts at zero with an “@” symbol, followed by the alphabet characters “A-Z” (1-26), then various punctuation and symbols (27-47), then numbers (48-57), then more punctuation and symbols (59-63). This covers the basic characters and uppercase alphabet just like standard ASCII does for characters 32-96. This means being limited to just 64 characters is not bad at all.

And, as I was working through this, I discovered why my Factory TNT game is not displaying the score correctly! I did not realize it also contained updated number characters (48-57) that I was not properly loading during my “restoration” of the game:

VIC-20 Factory TNT character set also remaps the numbers.

I’m so glad I am writing this article and figured that out. It was driving me mad!

But I digress…

Stay on target… Stay on target…

This means that you could have a BASIC program from 0x1000 to 0x1bff followed by custom character data from 0x1c00 to 0x1dff. If you had the characters in memory for your BASIC program to use, and you SAVEd your BASIC program, those custom characters would NOT be saved with it since BASIC doesn’t know anything about them.

But … you could lie to BASIC and tell it your BASIC program actually ENDS at 0x1dff, then when you SAVE it should write out the entire range of memory (0x1c00 to 0x1dff) thinking it’s just one large BASIC program…

Then, when you loaded it back it, BASIC would start loading it into memory at 0x1c00 and keep going until it got to the end of the program. You now have loaded memory that is part BASIC, and part character set!

But, if you tried to RUN it, you wouldn’t get very far because BASIC would think there is no memory left for variables. You would need to un-lie to BASIC and tell it where the program really ends, and do so without using varaibles.

That is what my six POKEs were apparently doing.

In part 5, we’ll see if this theory works.

Until next time…

Can you initialize a static linked list in C?

Updates:

  • 2020-04-30 – Added missing semicolon in code and updated example.

NOTE: This article was originally written a few years ago, so some references may be out of date.

I have been enjoying working on the SirSound project the past month, as well as some fun challenges at my day job. Every now and then I run into something I’d like to do that is not doable in C, or doable but not proper to do in C, or … maybe doable. It’s sometimes difficult for me to find answers when I do not know how to ask the question.

With that said, I have a C question for anyone who might be able to answer it.

Using my multi-track music sequencer as an example, consider representing data like this:

[sequence]
  [track]
    [note /]
    [note /]
    [note /]
  [/track]
  [track]
    [note /]
    [note /]
    [note /]
  [/track]
[/sequence]

It’s easy to create a sequence like this in C. Here’s some pseudo code:

track1 = { C, C, C, C };
track2 = { E, E, E, E };
sequence1 = { track1, track 2};

I thought there might be a clever way to do all of this with one initializer.  If I treat the data like nested XML (like the first example), I thought it might be possible to do something like this:

typedef struct SentenceStruct SentenceStruct;

struct SentenceStruct
{
  char           *Word;
  SentenceStruct *Next;
};

Something like this allows me to represent that tree of data very easily, and I find many examples of building things like this in C code:

int main()
{
   SentenceStruct Word1;
   SentenceStruct Word2;
   SentenceStruct Word3;

   Word1.Word = "sequence";
   Word1.Next = &Word2;

   Word2.Word = "track";
   Word2.Next = &Word3;

   Word3.Word = "note1";
   Word3.Next = NULL;

   SentenceStruct *Word;

   Word = &Word1;

   while( Word != NULL )
   {
      printf("%s ", Word->Word);
      if (Word->Next == NULL) break;
      Word = Word->Next;
   }
   printf("\n");

   return EXIT_SUCCESS;
}

This creates a single linked list of words, then prints them out.

I thought it might be possible to initialize the whole thing, like this:

 SentenceStruct sentence =
 {
   { "kitchen", { "light", { "ceiling", { "on", NULL }}}};
 }

…though the address of “light” is quite different than the address of a structure which contains a pointer that should point to “light”.

I was going to make each one a pointer to an array of words, so I could have a tree of words like the earlier example (with kitchen/light and kitchen/fan):

typedef struct SentenceStruct SentenceStruct;

struct SentenceStruct
{
  char *Word;
  SentenceStruct *Next[];
}

Does anyone know how (or if) this can be done in C?

Thoughts?

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…

GEnie Nondisclosure Agreement from 1991

GEnie was an text-based online server owned by General Electric. I recently found the nondisclosure agreement I signed to become an assistant at the Tandy RoundTable. I was COCO-SYSOP there until the service shut down in 1999. Apparently GEnie (which became Genie by then) was not Y2K compliant… Enjoy!


                           Non-Disclosure Agreement


Between:

Allen C. Huffman           Effective Date: March 15th, 1991
xxxxxxxxxxxxxx
Lufkin, Texas 75901

(hereinafter referred to as "Assistant SysOp" or "Assistant"),

And: General Electric Company, a New York corporation, acting through its GE
Information Services Division, (hereinafter referred to as "GE"), 401 N.
Washington Street, Rockville, Maryland 20850.

WHEREAS, GE operates remote access computer systems through which it offers
various network-based information services, both directly and through
distributors;

WHEREAS, one of the services which GE offers by such means is a consumer
information service known as GEnie Service (hereinafter referred to as "the
Service");

NOW, THEREFORE, GE and Assistant SysOp hereby agree as follows:

A:  Either GE or Assistant SysOp may disclose to the other certain
information which the disclosing party deems to be confidential and
proprietary ("Information").  Such Information shall be clearly and
conspicuously marked as confidential and proprietary at the time of first
disclosure to the receiving party.  Such Information would include, but is
not limited to, documentation related to the  Product and the Service and
business information of GE or Assistant SysOp which is not generally
available to the public.

B:  The receiving party shall exercise reasonable care to prevent
disclosure of or use for any purpose unrelated  to use on the Service or to
the  evaluation of the Product for suitability on the Service, at any time
prior to the expiration of three (3) years following the termination of this
Non-Disclosure Agreement, of any Information which it receives from the
other party pursuant to and in accordance with the terms of this
Non-Disclosure Agreement.  The receiving party shall also require its
employees and agents to similarly restrict use and disclosure of such
Information.  The receiving party, however, shall not be required to keep
confidential any Information which is or may become publicly available
without fault on its part; is already in the receiving party's possession
prior to receipt from the disclosing party; is independently developed by
the receiving party; is disclosed by the disclosing party to third parties
without similar restrictions; or is rightfully obtained by the receiving
party from third parties without restriction.

GENERAL ELECTRIC COMPANY

   By: __________________________     By: __________________________
       Robert Chiappone                   Allen C. Huffman

Title: Manager, Product Marketing  Title: Tandy RT SysOp
       GEnie
 Date: __________________________   Date: __________________________

                                        GEnie User Number: _____________

                                        RT, Game or Product: ___________

BEAMSCAPE – A game of flashlight tag

NOTE: This document is from 1985. It was the “official” instructions for a nighttime game of tag we played when I lived in Broaddus, Texas. I recently found it on some old Deskmate disks and thought it was fun enough to share… If you decide to play, let me know how you like it! We had a blast.


BEAMSCAPE – Official Rules

You quickly dash around a corner in an effort to evade your pursuer. You press yourself against the wall, hoping you won’t be sighted. Your heart pounds as you hear the sound of running feet. Suddenly, you are engulfed in a bright light. You have been spotted. You start to run, but there is nowhere to hide. A loud voice yells out:

“One one-thousand! Two one-thousand! Three…”

Welcome to BeamScape! BeamScape is the classic game of hide-and-seek with a new twist. First, it is played at night with the aid of flashlights. Secondly, the light used by the Beamer (the person who is “it”) can be used to tag out other players.

BeamScape is more like a cross between tag and military wargames. The many strategies used in playing make it a fun and exciting “sport.”

GENERAL INFORMATION

In order to play BeamScape, you will need a high-powered flashlight such as the Mag-Light, which uses a Krypton bulb and can be adjusted to a small beam. The official number of players is five, although any number of three or more can play. The more, the better!

GAME RULES

Most of the rules of BeamScape are similar to those of hide-and-seek. The person who is it, called the “Beamer,” starts counting while the players, known as “Runners,” go hide. After counting to a preselected number, which will vary depending upon the playing area, the Beamer then begins his persuit.

In order to get someone, the Beamer must either touch them, or catch them in the light. If the Beamer gets the light on a Runner, he must keep it on them for five seconds while couting “One one-thousand, two one-thousand,” and so forth up to five. If the runner gets out of the light in that time, counting must begin again at one. Once a Runner has been “tagged,” he becomes a Helper and is alowed to aid others in getting to the base. A helper cannot, however, purposely get in the way of the Beamer’s light to help a Runner get away.

Any runner who safely reaches base must remain there until the next round. A round begins after everyone has either reached base, or gotten caught. If all the Runners safely reach base, the game starts again with the same Beamer. If one or more Runners get caught, the last one tagged becomes the new Beamer for the next round.

Because this game is played at night without light, optional rules may allow every Runner to carry a small penlight to help them see. A regular sized flashlight should not be used since it’s light could confuse other Runners.

PLAYING AREA

The game field for BeamScape may be anywhere. Good locations are places where there are lots of obstacles, such as storage buildings and trees. Before playing, though, be sure that the area is clear of any dangerous locations, and that it is not in an area that might cause trouble with non-players. (It is quite suspicious to see many people running around an area at night with flashligts, so if you are playing on someones private property, be sure to let the owner know what is going on!)

Also, be sure to have all the boundries decided upon before playing and make sure the rules are clear to all players to help prevent any arguments caused by misunderstanding.

FINAL NOTES

The version of BeamScape which is presented here is a simple form designed to be played by many. A higher and more high-tech game may have options like two-way communicators with all the Runners, but not the Beamer. In fact, the rules of BeamScape were meant to set the basis for many different variations depending on the abilities of the players, so feel free to incorporate “home rules” to enhance the fun on BeamScape.

End of File – Allen C. Huffman

VIC-20: Sky-Ape-Er code dissection – part 3

See also: part 1, part 2, part 3, part 4 or part 5 (with more coming).

Its time for another VIC-20 Tuesday!

In this installment, I will walk through the code for my VIC-20 game Sky-Ape-Er. Thanks to a wonderful utility I mentioned in part one, I have this ASCII program listing. The utility replaces the special PETASCII control characters with {words} so they can be viewed on non-Commodore systems.

Sky-Ape-Er program listing

Initialization

1 POKE 808,100
  • That poke would “disable the RUN/STOP key, the RESTORE key, and the LIST command” to keep someone from being able to save a copy of it.
5 REM  ************          *SKY-APE-ER*        *******BY*******
6 REM*ALLEN HUFFMAN* ****************{$cc}
  • Comments, but with no dates. Thanks, me. One of my tapes had the game in various stages of completion, named “SKY-APE-ER 1”, “SKY-APE-ER 2” and “SKY-APE-ER 3”, but all my other tapes just called it SKY-APE-ER, and I’ve found at least two distinct versions of the game so far.
  • I am not sure what the {$cc} is at the end. From looking at the c64list.exe command I used to make this listing, it seems to be an unknown token. This might have been something I placed in that line to prevent listing, even after a LOAD without the POKE being executed. But, you can LIST 10- and still see it ;-)
10 S=1:POKE36879,26:POKE36878,15:S1=36875:POKE775,200:POKE36869,255
  • S is the screen (level) to display. Sky-Ape-Er contained three screens, and a “game won” screen.
  • 36879 is the border color, with 26 being red.
  • 36878 is something to do with the music chip.
  • S1 is being set to the sound note location 36875.
  • 775,200 disables LIST. Or maybe not. There was conflicting information in the magazine I looked that up in.
  • 36869 enables the custom font characters.

Set up game screen

15 PRINT"{clear}":L=8118:B=7772:M=15
  • L is the starting memory location for the player.
  • B is the starting memory location of the chimp (B for barrel, I suppose, as this was inspired by Donkey Kong).
  • M is the character to POKE to the screen for the player (man). 15 is the letter”O”.
20 FORA=38400TO38905:POKEA,0:NEXTA
  • This clears the color for each character on the screen. The characters for the screen are stored in one block of memory, and the color of each character is stored in a different block of memory.

Display ape

25 PRINT"{home}{down}{right}{black}ABC{down}{left:3}DEF{down}{left:3}GHI{down}{left:3}JKL"
  • This clears the screen, then draws the letters that make up the ape character. The VIC-20 has no PRINT@ or LOCATE function, but you could embed cursor movement commands in the PRINT statements. Thus, it would “home” to the top fo the screen, move the cursor {down} one, then {right} one, set the color to {black}, print ABC, then go {down} one and {left} three times (cursor is now back under the A) and print DEF, and repeat for the other lines. The ape was 3×4 characters in size!

Display game screen

30 ONSGOTO35,45,55,500
  • This goes to the appropriate routine to display the current screen (level) based on the value of S. There were three screens. If S is 4, it goes to a “win” screen.

Screen 1

35 PRINT"{red}@@@@@@@@@@@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@@@@@@@@@@@@@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@@@@@@@@@@@@@@@{down}{left}";
40 PRINT"@@@{down}{left}@@@{down}{left}@@@@@@@@@@@@@@@@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@@@@@@@@@{up}{left}@{up}{left}@{up}{left}@":GOTO70

That would draw this:

Sky-Ape-Er screen 1.

Screen 2

45 PRINT"{blue}@@@@@@@@@@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@@@@@@@@@@@@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}";
50 PRINT"@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@@@@@@@@@@@@@@@@{up}{left}@{up}{left}@{up}{left}@":GOTO70

That would draw this:

VIC-20 Sky-Ape Er, screen 2.

Screen 3

55 PRINT"{purple}@@@@@@@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{up:3}@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}";
60 PRINT"@@@{up:4}{left}@@@{down}{left}@@@@@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@@@@@@@{up}{left}@{up}{left}@{up}{left}@{up}{left}@{up}{left}@"

That would draw this:

VIC-20 Sky-Ape-Er, screen 3.

Begin game

70 T=0
  • T is used for time. It starts at zero and increments, so the goal is to get to the top as fast as possible.

Main game loop

Display player and check to see if we made it to the top, or were hit by a chimp.

80 POKEL,M:POKEB,32:POKES1,0
83 IFL=7772ORL=7749THEN400
84 IFL=BTHEN250
  • Line 80 pokes the Man on the screen at its Location. The Barrel is poked to a space, so erased. The S1 sound is stopped. Nothing would be playing the first time this code is ran.
  • If the Location is 7772 or 7749, we go to 400.

Move the chimp

85 B=B+23:IFPEEK(B)=0THENB=B-22:POKEB,16
  • This controls the movement of the enemy (chimp). B is the location. The VIC-20 screen is 22 characters per line, so incrementing B by 23 would move the position down and to the right.
  • The new location of B is checked to see if it is 0 (the brick). If it is, it moves the location back 22 (up one). Thus, the chimp always tries to go down and to the right, and if it can’t it just goes right.
  • The POKE is what puts the chimp character on the screen at that position.
  • From looking at this, it seems the chimp is not displayed if it goes down and to the right successfully. I wonder why? Bug? It would get displayed the next time. Or inteitonal? I’ll look into this.

Handle player falling

87 IFPEEK(L+22)=32THENPOKEL,32:L=L+22:POKEL,M
  • This looks at the block under the player. If it is 32 (empty), it erases the player and moves it down, then displays it at the new location.
  • This code is what makes the player “fall” when it jumps or, on screen 3, when it falls off the edge. Cool.

Make “tick” sound

90 POKES1,240:POKES1,0
  • S1 is the address of one of the sound channels (the VIC-20 has three musical channels, plus noise). This turns one on and off, making the “tick” sound.
  • I did not even consider being able to do background music for a game back then. I bet I could today, memory permitting, even in BASIC.

Collision detection (player and chimp)

95 IFB=LTHEN250
  • This is the collision detection. If the chimps location (B) is the same as the players Location, we go to 250 (death).

Reset chimp back to top

100 IFB=8118THENPOKEB,32:B=7772:POKEB,12
  • This checks to see if the chimp is at the bottom right of the screen.
  • If it is, it gets erased, and the position is reset back to the top left starting position and it is displayed there.

Update and check timer

105 PRINT"{home}{blue}{rvrs on}{right}TIME:";T:T=T+1
110 IFT>200THEN300
  • This homes the cursor, changes the color to blue, turns on reverse video, then displays “TIME:” and the current time value. The reason it goes reverse is because we are using a special character set that replaces the lower alphabet characters. In this mode, there is some memory thing that happens that wraps around the inverse video characters to the standard characters. When using special characters, you have access to the basic letters by using reverse. Apparently.

Keyboard input

115 K=PEEK(197):IFK=64THEN80
  • Memory location 197 is the current key being pressed. If no key is pressed, 64 is returned and the program goes back to the top and continues. (Move the chimp, update the time, etc.)
120 POKEL,32:POKES1,230
  • If there was input, then this line will erase the player, and turn on the sound chip to start making a tone.

Move player or jump

125 IFK=17THENL=L-1:M=14
130 IFK=41THENL=L+1:M=13
135 IFK=39THEN180
140 IFPEEK(L)=0THEN150
145 GOTO80
  • K is the value of the key being pressed.
  • 17 is “A”, so if that is pressed, we move the player’s location to the left and change the player’s character to the “facing left” letter.
  • 41 is “S”, so if that is pressed, we move the player’s location to the right and change the player’s character to the “facing right” letter.
  • 39 is the F1 function key. If that is seen, we go to the jump routine at line 180.
  • If the player’s position (which may or may not have been updated) is 0, it is in a wall, so go to line 150 to fix that location.
  • If still here, we go back to line 80 and continue with the main loop.

Handle running into wall collision

150 IFK=17THENL=L+1
155 IFK=41THENL=L-1
160 M=15:GOTO80
  • This is kind of silly, but if we are here, we hit a wall, so we look at what the last key was pressed and reverse the direction that it did.
  • We set the player’s character to the “facing forward” character.

Jump routine

180 POKES1,235:IFM=14THENL=L-23
183 IFM=15THENL=L-22
185 IFM=13THENL=L-21
190 IFPEEK(L)=0THEN200
195 M=15:GOTO80
  • First we start to make a tone.
  • Then we check to see if the player’s character is 14 (facing left). If it is, we move the player’s position up and to the left (22 characters per row, so -23).
  • If the player’s character is 15 (facing forward), we move the location up,
  • If the player’s character is 13 (facing right), we move the location up and to the right.
  • If the new location is in a wall, we go to 200 to fix that.
  • We set the player’s character to 15 (facing forward) so the jump will land facing that way.
  • We go back to 80 to continue the main game loop.

Handle jumping into wall collision

200 IFM=13THENL=L+21
210 M=15:GOTO80
  • This code takes care of jumping into walls.
  • If the character is facing right, then we move the position down and to the left back where it came from.
  • Else, we know the only other place it can hit a wall is to the right (wow, I was clever here!) so we just make the character face forward and go back to the main loop.

Death screen

250 FORA=1TO11:READN,L:POKES1,N:FORP=1TOL:NEXTP:POKES1,0:NEXTA
255 PRINT"{clear}{black}{rvrs on}{down} GUESS WHAT! YOU HAVE {down}JUST BEEN STRUCK DOWN {down} BY A LUCKY CHIMP!"
260 PRINT"{rvrs on}{down}({blue}THAT MEANS {red}GAME OVER{black})"
265 GOTO450
  • The first line reads musical note and duration values from DATA statements, then makes each note sound for that amount of time.
  • The screen is then cleared, and a game over message is displays.
  • It then goes to 450 to see if the player wants to play again.
270 DATA202,250,202,250,202,150,202,350,210,250,208,150,208,200,202,200,202,250,200,200
275 DATA 202,400
  • Those DATA statements contain the note value and length of the tune that plays when you die (funeral march). I did not know anything about music at the time, so I figured it out by ear. The timing is awful.

Time exceeded

285 PRINT"{down:2}{rvrs on}PRESS ANY KEY TO START"
300 PRINT"{clear}{black}{rvrs on}{down:3}WELL WHAT DO YOU KNOW?"
305 PRINT"{rvrs on}{down:2}WHILE YOU WERE DOWN ONTHE GROUND JUMPING A- ROUND,YOUR TIME CLOCK"
310 PRINT"{rvrs on} JUST RAN OUT!"
315 PRINT"{rvrs on}{down:2} NEXT TIME {red}WATCH{black} THE CLOCK!"
320 GOTO450
  • This message is displayed when time runs out. It then goes to the “play again?” screen.

Screen completed

400 S=S+1:GOTO15
  • This increments the screen, and then goes back to display it.

Play again prompt

450 PRINT"{black}{rvrs on}{down:2}{right}WILL YOU TRY AGAIN?"
455 GETA$:IFA$=""THEN455
460 IFA$="N"THEN550
465 IFA$="Y"THEN480
470 GOTO455
  • This prompts the user to ask if they want to play again. It waits for a key press, then if it is “Y”es, it goes to 480 (start a new game). If it is “N”o, it goes to 550 (end the game).

Press any key to play again prompt

480 PRINT"{clear}{black}{rvrs on}{down:2}AT LAST! SOMEONE BRAVE ENOUGH TO {red}TRY{black} TO STOP  KING KING'S COUSIN!"
490 GETA$:IFA$=""THEN490
495 RUN

If the player chooses “Y”es to play again, this messages is displayed. It waits for any key to be pressed, then re-runs the program.

Game won – Falling ape animation

500 PRINT"{clear}{black}"
505 FORZ=0TO16:PRINT"{home}":FORR=0TOZ:PRINT:NEXTR
510 PRINT" {down}{left:3}ABC{down}{left:3}DEF{down}{left:3}GHI{down}{left:3}JKL"
515 NEXTZ
  • This cleans the screen, draws the ape, and then makes it “fall” to the bottom of the screen. Remember how Donkey Kong would fall at the end of the rivets level? Kinda like that. Just not as good. I had forgotten about this!
525 POKE36874,128
530 FORX=15TO0STEP-.1:POKE36878,X:NEXTX:POKE36874,0
  • That plays a sound effect when the ape hits the bottom of the screen.

Game won message

535 PRINT"{clear}{black}{rvrs on}{down:3}CONGRATULATIONS!!!!!!!{down} YOU NOW KNOW WHAT IT  FEELS LIKE TO BE ON"
540 PRINT"{rvrs on}TOP!{$a0}YOU PLAYED WELL!"
545 PRINT"{rvrs on}{down:2}{blue}BET YOU DON'T WIN NEXT TIME!"
548 PRINT:GOTO450
  • This displays the “game won” message, then goes to 450 to ask the player if they wish to play again.

Quit game message

550 PRINT"{clear}{black}{rvrs on}I DIDN'T THINK YOU HAD ENOUGH COURAGE TO TRY   TO STOP A MAD APE!"
560 PRINT"{down}{rvrs on}{red}I HOPE YOU ENJOYED NOT SAVING THE BUILDING!"
565 FORG=1TO10000:NEXTG
570 SYS64802
  • This would display a message, wait, and then reboot the VIC-20.

Programmer’s thoughts

I actually expected this code to be far worse than it actually is. If I were writing it today, I see a number of things I could do to make it more efficient. I also have many ideas of ways I could use the VIC-20 features to make the game fancier (animated characters, better music and sounds).

But, overall, I am fairly pleased with how my junior high self programmed this. I was completely self-taught from reading the VIC-20 manual and articles in magazines.

But since I know I could do it better today, I think I might just give that a shot.

Here is the full listing of this version of the program. If you want to run it, I would do two things:

  • Delete line 1 entirely.
  • In line 10, remove the POKE772,200.
  • And I’d get rid of the same POKEs in the INSTRUCTIONS loader.

And if you don’t want to type it in, here is a virtual tape for the VICE Commodore emulator. It has the above changes made, but otherwise is exactly as I last touched it in 1983. Enjoy!

Until next time…

Full SKY-APE-ER listing:

1 POKE 808,100
5 REM  ************          *SKY-APE-ER*        *******BY*******
6 REM*ALLEN  HUFFMAN*      ****************L
10 S=1:POKE36879,26:POKE36878,15:S1=36875:POKE775,200:POKE36869,255
15 PRINT"{clear}":L=8118:B=7772:M=15
20 FORA=38400TO38905:POKEA,0:NEXTA
25 PRINT"{home}{down}{right}{black}ABC{down}{left:3}DEF{down}{left:3}GHI{down}{left:3}JKL"
30 ONSGOTO35,45,55,500
35 PRINT"{red}@@@@@@@@@@@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@@@@@@@@@@@@@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@@@@@@@@@@@@@@@{down}{left}";
40 PRINT"@@@{down}{left}@@@{down}{left}@@@@@@@@@@@@@@@@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@@@@@@@@@{up}{left}@{up}{left}@{up}{left}@":GOTO70
45 PRINT"{blue}@@@@@@@@@@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@@@@@@@@@@@@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}";
50 PRINT"@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@@@@@@@@@@@@@@@@{up}{left}@{up}{left}@{up}{left}@":GOTO70
55 PRINT"{purple}@@@@@@@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{up:3}@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}";
60 PRINT"@@@{up:4}{left}@@@{down}{left}@@@@@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@{down}{left}@@@@@@@@@{up}{left}@{up}{left}@{up}{left}@{up}{left}@{up}{left}@"
70 T=0
80 POKEL,M:POKEB,32:POKES1,0
83 IFL=7772ORL=7749THEN400
84 IFL=BTHEN250
85 B=B+23:IFPEEK(B)=0THENB=B-22:POKEB,16
87 IFPEEK(L+22)=32THENPOKEL,32:L=L+22:POKEL,M
90 POKES1,240:POKES1,0
95 IFB=LTHEN250
100 IFB=8118THENPOKEB,32:B=7772:POKEB,12
105 PRINT"{home}{blue}{rvrs on}{right}TIME:";T:T=T+1
110 IFT>200THEN300
115 K=PEEK(197):IFK=64THEN80
120 POKEL,32:POKES1,230
125 IFK=17THENL=L-1:M=14
130 IFK=41THENL=L+1:M=13
135 IFK=39THEN180
140 IFPEEK(L)=0THEN150
145 GOTO80
150 IFK=17THENL=L+1
155 IFK=41THENL=L-1
160 M=15:GOTO80
180 POKES1,235:IFM=14THENL=L-23
183 IFM=15THENL=L-22
185 IFM=13THENL=L-21
190 IFPEEK(L)=0THEN200
195 M=15:GOTO80
200 IFM=13THENL=L+21
210 M=15:GOTO80
250 FORA=1TO11:READN,L:POKES1,N:FORP=1TOL:NEXTP:POKES1,0:NEXTA
255 PRINT"{clear}{black}{rvrs on}{down} GUESS WHAT! YOU HAVE {down}JUST BEEN STRUCK DOWN {down}  BY A LUCKY CHIMP!"
260 PRINT"{rvrs on}{down}({blue}THAT MEANS {red}GAME OVER{black})"
265 GOTO450
270 DATA202,250,202,250,202,150,202,350,210,250,208,150,208,200,202,200,202,250,200,200
275 DATA202,400
285 PRINT"{down:2}{rvrs on}PRESS ANY KEY TO START"
300 PRINT"{clear}{black}{rvrs on}{down:3}WELL WHAT DO YOU KNOW?"
305 PRINT"{rvrs on}{down:2}WHILE YOU WERE DOWN ONTHE GROUND JUMPING A- ROUND,YOUR TIME CLOCK"
310 PRINT"{rvrs on}    JUST RAN OUT!"
315 PRINT"{rvrs on}{down:2}  NEXT TIME {red}WATCH{black} THE CLOCK!"
320 GOTO450
400 S=S+1:GOTO15
450 PRINT"{black}{rvrs on}{down:2}{right}WILL YOU TRY AGAIN?"
455 GETA$:IFA$=""THEN455
460 IFA$="N"THEN550
465 IFA$="Y"THEN480
470 GOTO455
480 PRINT"{clear}{black}{rvrs on}{down:2}AT LAST! SOMEONE BRAVE ENOUGH TO {red}TRY{black} TO STOP  KING KING'S COUSIN!"
490 GETA$:IFA$=""THEN490
495 RUN
500 PRINT"{clear}{black}"
505 FORZ=0TO16:PRINT"{home}":FORR=0TOZ:PRINT:NEXTR
510 PRINT"   {down}{left:3}ABC{down}{left:3}DEF{down}{left:3}GHI{down}{left:3}JKL"
515 NEXTZ
525 POKE36874,128
530 FORX=15TO0STEP-.1:POKE36878,X:NEXTX:POKE36874,0
535 PRINT"{clear}{black}{rvrs on}{down:3}CONGRATULATIONS!!!!!!!{down} YOU NOW KNOW WHAT IT  FEELS LIKE TO BE ON"
540 PRINT"{rvrs on}TOP!{shft space}YOU PLAYED WELL!"
545 PRINT"{rvrs on}{down:2}{blue}BET YOU DON'T WIN NEXT       TIME!"
548 PRINT:GOTO450
550 PRINT"{clear}{black}{rvrs on}I DIDN'T THINK YOU HAD ENOUGH COURAGE TO TRY   TO STOP A MAD APE!"
560 PRINT"{down}{rvrs on}{red}I HOPE YOU ENJOYED NOT SAVING THE BUILDING!"
565 FORG=1TO10000:NEXTG
570 SYS64802

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…