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…

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

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

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

Welcome to my second VIC-20 Tuesday!

Previously, I began to dissect my old VIC-20 game Sky-Ape-Er. It was made up of two BASIC programs:

  1. INSTRUCTIONS – Display instructions, set up custom character set, then load and run the main game.
  2. SKY-APE-ER – The main game.

VIC-20 custom fonts

The VIC-20 did not have a true graphics screen. Instead, it used font characters that could be dynamically changed. Each character on the 22×20 screen was 8×8. You could create custom 8×8 characters to represent anything you wanted.

I believe I used a program called Eight by Eight Create by Robert Spahitz from the January 1983 issue of Creative Computing (Vol 9, Number 1). See page 270 (or 272 of this PDF scan):

https://archive.org/details/creativecomputing-1983-01/page/n271/mode/2up

Or this text version of just the article.

This also helps me confirm that I got my VIC-20 in 1982 since I initially did not have a Commodore Datasette tape deck for it and had no way to save programs until later. By the time this issue came out, I had the tape deck.

But I digress…

VIC-20 custom fonts on the CoCo

I have screen shots of what the custom characters looked like in the VIC-20 game, but thought it would be fun to take those DATA statements and display them on my CoCo. Using lines 100 to 125 from the INSTRUCTIONS program, I created this Color BASIC program:

0 REM skychars.bas
5 POKE 65495,0
10 CLS:XO=4:YO=2
15 DIM BM(7):FOR A=0 TO 7:BM(7-A)=(2^A):NEXT
20 FOR R=0 TO 7
25   READ V:IF V=-1 THEN 99
30   FOR C=0 TO 7
35     IF V AND BM(C) THEN SET(XO+C,YO+R,8) ELSE RESET(XO+C,YO+R)
40   NEXT
45 NEXT
50 XO=XO+10:IF XO>60 THEN XO=4:YO=YO+10
55 GOTO 20
99 GOTO 99
100 DATA223,223,223,0,253,253,253,0,0,0,0,0,1,3,3,7,0,60,126,219,129,165,165,129
105 DATA0,0,0,0,128,192,192,224,31,63,127,255,252,254,127,63,0,126,126,0,255,0,231,231
110 DATA248,252,254,255,63,127,254,252,15,7,8,30,31,31,31,31,231,219,60,255,126,126
115 DATA189,195,240,224,16,120,248,248,248,248,31,31,31,15,15,63,127,0,231,129,0,0,129
120 DATA129,129,0,248,248,248,240,240,252,254,0,12,12,24,47,8,15,82,36,48,48,24,244,16
125 DATA240,74,36,28,8,28,42,8,20,20,54,12,76,40,47,40,159,82,36
130 DATA -1

It now displays what those font characters look like:

VIC-20 Sky-Ape-Er font display on a CoCo.

These custom characters would replace “@ABCDEFGHIJKLMNOP”. The “@” symbol was the brick used to make the game playfield.

Letters A-L were used to make the ape, like this:

ABC
DEF
GHJ
JLK

Letters M-O were the player’s character, facing right, left and forward.

Letter P were the small apes that were running towards the player. They never turned back, so no additional characters we needed for them.

My program was written to just put those characters on the screen, which would look like this:

My VIC-20 Sky-Ape-Er game without the custom font.

But with the custom font in use, it looks like this:

In the next installment, I’ll walk through the actual SKY-APE-ER game code and see how it works.

Until next time…

IF AND/OR THEN versus IF THEN IF

Or, “I’ll NEVER compare multiple values the same way again”

Here is a quick Optimizing Color BASIC quickie.

Tonight, I saw this YouTube video from Robin at 8-Bit Show and Tell. He was responding to someone who noticed a bit of code in an old Commodore magazine that did:

IF Z>192 THEN IF Z<219 THEN Z=Z-128

…instead of the more common…

IF Z>192 AND Z<219 THEN Z=Z-128

He performed a benchmark very much like the ones I have been doing on the CoCo (I’m glad I’m not the only one doing this). I decided to try it on the CoCo and see what happened here.

I first tried the normal “IF AND THEN” way:

0 REM IFAND.BAS
5 DIM TE,TM,B,A,TT
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 IF Z>192 AND Z<219 THEN Z=Z-128
70 NEXT
80 TE=TIMER-TM:PRINT,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END

This produced 875 on my Xroar emulator.

Then I tried the “odd” IF AND THEN way…

0 REM THENIF.BAS
5 DIM TE,TM,B,A,TT
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 IF Z>192 THEN IF Z<219 THEN Z=Z-128
70 NEXT
80 TE=TIMER-TM:PRINT,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END

This produced 472! A significant time savings just by changing the way you compare multiple things.

Thinking this through, I have some theories, based on some very inefficient C code I found in a work project once.

See A C tale of two ampersands for that story…

Could BASIC be doing the same thing?

IF THEN BASIC

“IF this AND that THEN” surely isn’t doing a logical AND for values, is it? It might be, since you can print a comparison like this:

PRINTing the result of a comparison in Color BASIC.

In Color BASIC, true is -1 and false is 0. So perhaps doing this:

IF A=1 AND B=2 THEN PRINT "BOTH ARE TRUE"

…is actually turning into…

IF -1 AND 0 THEN PRINT "BOTH ARE TRUE"

…with -1 or 0 being used based on the conditions. Let’s try:

Color BASIC must not be doing bitwise math for compares.

Oh my. Maybe it IS doing this, thus requiring the values to be saved and compared, making it slower just like C generating more code to do the same thing.

I know I could just look at the disassembly in Color BASIC Unravelled and know exactly what is going on, but it’s more fun to experiment. How can we test this?

Well, things that return true/false return -1 or 0. You can AND two numbers together and get mathematical results. Even a compare like:

IF A$="ALLEN" THEN

…will return a -1 if it is true, or a 0 if it is false:

Even string compares turn into -1 or 0.

It sure seems likely that BASIC is just taking the result of a compare (true or false, -1 or 0) and AND-ing or OR-ing those values. Thus, my theory is this:

Multiple AND/OR comparisons turn into multiple results of -1 or 0 (true or false) which are then mathematically processed:

IF A=1 AND B=2 AND C=3 THEN ...

…so all of it has to be processed. But, using this IF THEN IF approach:

IF A=1 THEN IF B=2 THEN IF C=3 THEN ...

…will be much faster since it does a compare, then if good, it does another compare, then if good, it does the final compare… rather than having to do three compares then do math on the three results of that compare then check that final result to know what to do.

What says you?

I suppose it gets much slower the more conditions you want to compare:

5 DIM TE,TM,B,A,TT
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 IF X=1 AND Y=1 AND Z=1 THEN Z=42
70 NEXT
80 TE=TIMER-TM:PRINT,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END

763 for that one. Versus:

5 DIM TE,TM,B,A,TT
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 IF X=1 THEN IF Y=1 THEN IF Z=1 THEN Z=42
70 NEXT
80 TE=TIMER-TM:PRINT,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END

335!

So obviously I have to do this:

5 DIM TE,TM,B,A,TT
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 IF G=1 AND H=1 AND I=1 AND J=1 AND K=1 AND L=1 AND M=1 AND N=1 AND O=1 AND P=1 AND Q=1 AND R=1 AND S=1 AND T=1 AND U=1 AND V=1 AND W=1 AND X=1 AND Y=1 AND Z=1 THEN Z=42
70 NEXT
80 TE=TIMER-TM:PRINT,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END

If those 10 conditions are true, do something… Testing this produces: 4782

And doing it the weird way:

5 DIM TE,TM,B,A,TT
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 IF G=1 THEN IF H=1 THEN IF I=1 THEN IF J=1 THEN IF K=1 THEN IF L=1 THEN IF M=1 THEN IF N=1 THEN IF O=1 THEN IF P=1 THEN IF Q=1 THEN IF R=1 THEN IF S=1 THEN IF T=1 THEN IF U=1 THEN IF V=1 THEN IF W=1 THEN IF X=1 THEN IF Y=1 THEN IF Z=1 THEN Z=42
70 NEXT
80 TE=TIMER-TM:PRINT,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END

…speeds up to 732!

Well I’ll be. It looks like, even though its longer code, it’s much faster. I never knew that. Or if I did, I forgot it long ago.

Thanks, Robin, for posting that video a few years ago. Very cool! Here’s his original video:

Until next time…

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

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

NOTE: This article was originally started on February 29, 2016, shortly after I discovered my cigar box of VIC-20 cassette. When I say “recently,” that now means “four years ago…”


Welcome to VIC-20 Tuesday!

I have slowly been importing my old VIC-20 programs in to the VICE emulator. This has been a tricky process. The tapes are over 30 years old and I no longer posses any real hardware to read them on. (I let my dad take my VIC-20 in exchange for getting me a Radio Shack TRS-80 Color Computer.) I played them on a higher end late 1990s Radio Shack dual cassette deck, and record them at 96khz stereo in Audacity on my Mac. The original tapes were mono, but some are stronger in one channel so I have been preserving both tracks as stereo audio files. I then use wav-prg to convert them to image files I can load in the VICE Commodore emulator.

My Sky-Ape-Er game has caught my attention. On the tape was a program called INSRTUCTIONS (with that typo I apparently never noticed) that will display instructions, wait for a keypress, then load the game as a second program called SKY-APE-ER. There were actually three copies of the main program, and at least two were different versions (oh, if only I had added version numbers to the file name). I also found several other copies of the main game program on other tapes in various forms of completion. I am hoping that this one is the “final” complete version.

The loading process looks like this:

vicCode-skyapeerload1
VIC-20 loading a program from tape.

…then you would type “RUN” (assuming you didn’t use the shortcut “RUN” key, which would do the “LOAD” and “RUN” automatically):

vicSkyApeEr-instructions
VIC-20 Sky-Ape-Er loader (instructions).

Then you press any key…

vicCode-skyapeerload2
VIC-20 Sky-Ape-Er loader, loading second part.

…and the program will have you “PLEASE WAIT” for a moment, then will erase itself with “NEW” and the “LOAD” the actual game.

I scanned the code to see how it did this, but I saw nothing obvious. No “NEW” or “LOAD” commands or anything. How did it work? Thanks to the Internet, I believe I figured it out.

First, let’s look at the source code of the program:

Sky-Ape-Er loader, lines -25.
Sky-Ape-Er loader, lines -25.
  • Lines 5-6 – REMarks (comments).
  • Line 10 – POKE stores a value at a memory location. But what is 36879, 808 and 775? A quick search lead me to some answers. 36879 ($900f) is “Screen color / Reverse mode / Border color”. In the VIC-20 manual, Appendix E shows that 26 would be red border, and white screen. Another search led me to a Compute magazine article showing that  808 ($328) is used to disable STOP, RESTORE and LIST (to prevent the user from BREAKing out of the program). 775  ($307) showed up in a correction article from the same magazine, stating that it is what disabled LIST. (Not sure about 808, then. Maybe there were two POKEs required and the first article had a typo or left the second one out by mistake.)
  • Line 15-25 – The Commodores allowed embedded special control characters, and the inverted heart is clear screen. The others are likely color codes and cursor movements, to position the text where I wanted it to be.
Sky-Ape-Er loader, lines 25-55.
Sky-Ape-Er loader, lines 25-55.
  • Line 25-50 – More things that print the text on the screen.
  • Line 55 – Gets a keypress then, if the keypress is nothing (“”), go back and get another keypress. This causes the program to spin in a loop until a key is pressed on the keyboard.
Sky-Ape-Er loader, lines 60-105.
Sky-Ape-Er loader, lines 60-105.
  • Line 60 – Please wait … for what? It looks like what happens next may take a few seconds.
  • Line 65 – This is clearing out memory locations 7424 to 7432. But why? All I can tell so far is this is some memory location in RAM with other places saying it’s in the User BASIC area. A helpful forum post gave me a hint. This is actually clearing out the 8 bytes that will make up the “space” character so once I customize the character set with game graphics, that character will still be a space. More on this next…
  • Line 70 – This walks through memory locations 7168 to 7303 and POKEs them with the values read from DATA statements below. This is storing values in that range of memory. But what is 7168? According to this page, that is the start of “half RAM, half ROM of upper case normal characters since the 14-bit address space of the VIC wraps around”. I believe this is the font/character set, and the DATA statements are the custom graphics for the Sky-Ape-Er program (such as the bricks for the levels, the pieces that make up the ape, the chimps, and the player).
  • Line 80 – This is the magic! According to this page, memory location 198 is the number of characters in the keyboard buffer. The VIC-20 has a keyboard buffer from 631-640 (9 bytes?). I am stuffing the buffer with 78 (“N”), 69 (“E”), 87 (“W”), 13 (“ENTER”) and then 131 (the “RUN” key). Thus, it’s like I typed “NEW” and pressed ENTER (clearing out the BASIC memory), followed by hitting the “RUN” key, which does a “LOAD/RUN” sequence, loading the next file from tape and running it. Huzzah! The autostart with done by stuffing the keyboard buffer as if the user typed commands. This tells me just putting in “LOAD:RUN” in a line of BASIC wouldn’t have worked (probably because it would overwrite the program, and you could never get to the RUN part). Cool
Sky-Ape-Er loader, lines 110-END.
Sky-Ape-Er loader, lines 110-END.
  • Line 110-END – The rest of the code are the DATA statements that make up the custom character set. I will have to dissect them so I can see what the bitmaps look like.

I have no recollection of how this works, so I assume these POKE values were things I found in the Commodore VIC-20 manual or read in magazines such as Compute’s Gazette. There was very little information around back then. In fact, when I got this computer, I knew of only one store in all of Houston that sold software for it (other than Commodore cartridges at some places that sold the computer). I had my grandmother drive me across town to go there once, and that is the store where I bought the Krazy Kong game that inspired me to write Sky-Ape-Er.

There was a Commodore Houston’s User’s Group (CHUG) that I attended a meeting of, but that was the only time I ever met other VIC-20 users (other than going to meet the publishers of the FOX-20 cassette magazine at their house). It was a whole different world back then, and it’s amazing that I can go from from “what does this do?” to writing this article in just a few web searches.

Up next: A look at the actual Sky-Ape-Er game itself, including how it uses this custom character set for game graphics.

Converted Source Code

Here is the INSTRUCTIONS program converted from PETASCII to ASCII. I found a utility that does this thanks to this blog post. The utility is found here:

https://www.commodoreserver.com/Downloads.asp

It does a nice job of translating the PETASCII characters to {words} so I can better understand what was supposed to be there.

5 REM  ************          *SKY-APE-ER*        *******BY*******
6 REM*ALLEN  HUFFMAN*      ****************{$cc}
10 POKE36879,26:POKE808,100:POKE775,200
15 PRINT"{clear}{black}{right:2}-=<<SKY-{red}APE{black}-ER>>=-{right:5}BY:{blue}ALLEN HUFFMAN"
20 PRINT"{purple}{$a4:22}{$a5}    INSTRUCTIONS    {$a7}{$a3:22}"
25 PRINT"{black}{down}CLIMB THE BUILDINGS TO CAPTURE THE MAD APE!"
30 PRINT" JUMP OVER CHIMPS AND UP STEPS TO GET TO THETOP. YOU MUST FACE THE";
35 PRINT"  DIRECTION TO JUMP."
40 PRINT"{down}{blue}      CONTROLS :{down}"
45 PRINT"{red}<LEFT>='A'{right}<RIGHT>='S'{right:4}<JUMP>='F1 KEY'"
50 PRINT"{down}{black}{rvrs on}    PRESS ANY KEY.    "
55 GETA$:IFA$=""THEN55
60 PRINT"{clear}{right:4}PLEASE WAIT..."
65 FORA=7424TO7432:POKEA,0:NEXTA
70 FORA=7168TO7303:READB:POKEA,B:NEXTA
80 POKE198,6:POKE631,78:POKE632,69:POKE633,87:POKE634,13:POKE635,131
100 DATA223,223,223,0,253,253,253,0,0,0,0,0,1,3,3,7,0,60,126,219,129,165,165,129
105 DATA0,0,0,0,128,192,192,224,31,63,127,255,252,254,127,63,0,126,126,0,255,0,231,231
110 DATA248,252,254,255,63,127,254,252,15,7,8,30,31,31,31,31,231,219,60,255,126,126
115 DATA189,195,240,224,16,120,248,248,248,248,31,31,31,15,15,63,127,0,231,129,0,0,129
120 DATA129,129,0,248,248,248,240,240,252,254,0,12,12,24,47,8,15,82,36,48,48,24,244,16
125 DATA240,74,36,28,8,28,42,8,20,20,54,12,76,40,47,40,159,82,36

I normally just use the WordPress “preformatted text” box, but it did not like this listing. Hopefully this shows up.

Until next time…