YouTube decided to show me a video by 8-Bit Show And Tell. It is a BASIC driving game called “10 PRINT RACER” for the Commodore PET. For those that don’t know, “10 PRINT” is a one line Commodore BASIC program that generates a maze by randomly printing slash and backslash PETASCII characters.
10 PRINT CHR$(205.5+RND(1)); : GOTO 10
I actually ported this to the CoCo years ago:
10 PRINT CHR$(47+(RND(2)-1)*45); : GOTO 10
…but using the ASCII “/” and “\” characters just doesn’t have the same effect:
But I digress…
10 PRINT RACER
The game, 10 PRINT RACER, was based on this maze program. You drive a car through a passage in the middle of a random maze. The video demonstrates the game and then walks through the code, line by line.
I was impressed with his awareness of speeding up BASIC, such as using a FOR/NEXT loop with STEP 0 to create an infinite loop that is faster than using a GOTO. He also removes unnecessary spaces and combines lines together for faster execution. Heck, he even knows about using a period as a faster way to make zero!
My PET experience
I’ve actually written programs for the PET. My high school had some, and I wrote a banner printing program for my typing teacher. She’d already written PRINT statements for each large letter based on needlepoint patterns, but there wasn’t enough memory in the PET to load them at the same time.
I solved this problem by having a main program input the message to print, then POKEing it to screen memory along with a “which character are we printing” counter. If the character to print was not in the current program, it would load and run the one that contained it. Those programs would then PEEK that screen memory to see what character it was supposed to print next.
A klunky solution, but it worked. (And in case you wondered why I used screen memory… I did not have a PET memory map, so I didn’t know where I could store the message data. I knew I could find screen memory by printing something on the screen and then PEEKing through memory until I found it. Once I located where the screen was stored, I used that for temporary memory. Funny enough, I did the same thing years later for my *ALLRAM* BBS when I needed a safe spot to temporarily load and execute PCLEAR 0 code.)
But I digress… Again.
From Commodore PET to Radio Shack CoCo
Inspired by the video, I decided to port the game over to Color BASIC. This involved a few things:
- Screen Size: The PET screen is 40×25, so I had to scale everything down to fit on the CoCo’s 32×16 screen.
- PETASCII: The game uses two special characters which are not part of the standard ASCII that the CoCo has. I substituted with a forward slash (“/”) and a back slash (“\”). It doesn’t look as maze-like as the PET version, but it is the same general idea. There is also a PETASCII character that you print to clear the screen. I replaced that with CLS.
- Input: Commodore uses GET instead of INKEY$. I changed “GET A$” to “A$=INKEY$”.
- Memory Map: The game uses POKE and PEEK to put the racer on the screen, and check to see if it hit something. I had to change the PET memory location to be 1024, the start of the CoCo screen. I was surprised to hear him say the Commodore 64’s screen also starts at 1024.
- Spaces: Commodore BASIC has the SPC() keyword which prints that many spaces. Color BASIC has TAB() but it moves to columns, so it couldn’t be used. I created an S$ of 32 spaces using STRING$() and then used MID$() to get just the number of them I needed. This is likely much slower than if I had SPC().
- Controls: The PET has a numeric keyboard, so the PET version used 4 for left, and 6 for right. The CoCo has arrow keys, so I changed it to use the left and right arrow keys. It now looks for CHR$(8) for left and CHR$(9) for right. Parsing CHR$ is slower than the original which looked for “4” and “6”.
- Random Differences: I thought I was going to have to change the random numbers. The Commodore version of RND would return a value from 0 to 1. You would then multiply the result by a number (X) which gave you a number from 0 to X. On the CoCo, doing RND(X) would return 1 to X. But, I realized RND(0) on the CoCo would do the same thing, so I ended up not changing it.
- Key Repeat: The Commodore has key repeat and a small typeahead buffer. At the end of the PET code there was a POKE to clear out that buffer so it wouldn’t instantly restart the game if key presses were still in the buffer. I removed that POKE since the CoCo has no such buffer.
Here is what I came up with:
And here is the code:
0 ' 10 PRINT RACER 1 ' BY WWW.8BITSHOWANDTELL.COM 2 ' 3 ' PORTED FROM PET TO COCO 4 ' BY SUBETHASOFTWARE.COM 5 R$="":CLS:PRINT"INIT:";:FORX=1TO75:M$=CHR$(47+45*(RND(2)-1)):R$=R$+M$:PRINTM$;:NEXT 6 S$=STRING$(32," ") 10 CLS:C=16:R=10:W=12:D=0:S=1024 20 L=0:FORZ=0TO1STEP0:X=RND(.)*10 30 IFX<4THENR=R-1:IFR<1THENR=1 40 IFX>5THENR=R+1:IFR+W>29THENR=29-W 50 RN=RND(.)*28+1:PRINTMID$(R$,RN,R);MID$(S$,1,W);MID$(R$,RN,31-R-W) 60 D=D+1:L=L+1:IFL>49THENL=0:W=W-1:IFW<3THENW=3 70 IFD<16THENNEXT 75 A$=INKEY$:IFA$=CHR$(8)THENC=C-1 80 IFA$=CHR$(9)THENC=C+1 90 P=PEEK(S+C):IFP<>96THEN200 100 POKES+C,106:NEXT 200 PRINTTAB(13)"CRASH!":IFD>H THENH=D 205 PRINTTAB(6)"SCORE:"D" HIGH:"H 210 FORX=1TO2000:NEXT:A$=INKEY$ 220 A$=INKEY$:IFA$=""THEN220 230 GOTO10
I tried to keep the code as close to the original as I could, so there are some more optimizations that we could do on the CoCo. For instance:
- Instead of using “RND(0)*10” for a value of 0-9, it might be faster to do RND(10) and adjust the IF/THEN to look for 1-10 instead of 0-9. This would save the overhead of doing the multiplication.
- If Extended BASIC can be used, then most constants can be changed to HEX values and they will be slightly faster. (Thought after seeing some code from Jim Gerrie recently, it may still be faster to parse some single digits as decimals over their HEX version. More on this in a future article.)
- Line 30 and 40 could be combined using ELSE, saving time each time the value is less than 5. Currently, if X is 1, it processes line 40 and then goes to line 50 which checks X again. Using ELSE would at least omit that step in that condition.
- The screen position variable (S) gets the car position (C) added to it each time it is POKEd or PEEKed. To save that math, the position of the car could be initialized as S+C and the two “+C”s in the code could be removed.
- There is a special IF in line 70 that is used to initially draw the screen before letting the car drive on it. Once the screen is drawn, it still checks this IF every time through. Time could be saved by drawing the initial screen outside of the main loop and then not needing to check that again.
What else do you see that might help speed things up? Please leave your comments, or take this code and see what you can do with it.
PET emulator online
If you never got the pleasure of using one of these early Commodore Business Machines, I found a PET emulator that runs from a web browser:
You can use that to type in the original Commodore “10 PRINT” program and see it run it all its glory:
Until next time…