Porting 10 PRINT RACER by 8-Bit Show And Tell to the CoCo

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

There is even a book about it (which you can also download free). See the official site:

https://10print.org

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:

CoCo version of the famous Commodore “10 PRINT” program.

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:

  1. Screen Size: The PET screen is 40×25, so I had to scale everything down to fit on the CoCo’s 32×16 screen.
  2. 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.
  3. Input: Commodore uses GET instead of INKEY$. I changed “GET A$” to “A$=INKEY$”.
  4. 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.
  5. 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().
  6. 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”.
  7. 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.
  8. 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:

CoCo port of 8-Bit Show And Tell’s “10 PRINT RACER” BASIC game.

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:

https://www.masswerk.at/pet/

You can use that to type in the original Commodore “10 PRINT” program and see it run it all its glory:

Commodore PET running the 10 PRINT program.

Until next time…

10 thoughts on “Porting 10 PRINT RACER by 8-Bit Show And Tell to the CoCo

  1. Pingback: 10 PRINT big maze in Color BASIC | Sub-Etha Software

  2. Pingback: Revisiting 10 PRINT RACER | Sub-Etha Software

  3. Jason Pittman

    Here’s a COCO3 example that fails miserably at being either faster or smaller (or working), but it did get my rusty mental gears turning a little. I was thinking you might be able to modify the COCO3’s high res “\” and “/” characters to fill the edges of the 8×8 square and then use HPRINT to display it in a way that more closely matches the PET, but I forgot one important detail…HPRINT is as dumb as a rock. It doesn’t automatically scroll, and also it just overlays the character on the existing graphics, so I added a blank HGET/HPUT to clear the lines. This resulted in it being grotesquely slow, but it might be a fun way to approach something different, like a maze game that only redraws a few characters at a time instead of the whole screen. There isn’t a way to modify the low-res characters in this way, is there?

    10 HSCREEN 2:PALETTE 0,0:PALETTE 1,21:HCOLOR 1,0
    20 HBUFF 1,2560:HCLS:HGET (0,0)-(320,8),1
    30 FOR X = 0 TO 15:READ A:READ V:POKE A,V:NEXT
    40 REM VARIABLES TO HOLD THE START POSITION AND LENGTH FOR EACH SCREEN LINE
    50 DIM ST(24),LE(24):LE(0)=0:ST(0)=10:W=10
    60 REM SCREEN FILL
    70 FORX=1TO23:ST(X)=RND(20):LE(X)=RND(3)-2:NEXT
    80 L$=””:FOR X = 1 TO 60:L$=L$+CHR$(47+45*(RND(2)-1)):NEXT
    90 FORY=0TO23:HPRINT(0,Y),MID$(L$,ST(Y), LE(Y)+W)+STRING$(W,” “)+MID$(L$,ST(Y), 40-ST(Y)-LE(Y)+W):NEXT
    100 REM END SCREEN FILL
    110 FORZ=1TO1000
    120 REM ADVANCE LINES & REDRAW
    130 FOR X=23 TO 1 STEP -1:ST(X)=ST(X-1):LE(X)=LE(X-1):NEXT
    140 ST(0)=RND(20):LE(0)=RND(3)-2
    150 FORY=0TO184 STEP 8:LN=Y/8:HPUT (0,Y)-(320,Y+8),1,PSET:HPRINT(0,LN),MID$(L$,ST(LN), LE(LN)+W)+STRING$(W,” “)+MID$(L$,ST(LN), 40-ST(LN)-LE(LN)+W):NEXT
    160 NEXT
    170 GOTO 170
    180 DATA &HF115,1,&HF116,2,&HF117,4,&HF118,8,&HF119,16,&HF11A,32,&HF11B,64,&HF11C,128,&HF27D,128,&HF27E,64,&HF27F,32,&HF280,16,&HF281,8,&HF282,4,&HF283,2,&HF284,1

    Reply
    1. Allen Huffman Post author

      I can’t wait to try this out. I don’t know if it’s posted yet, but I do have a PMODE 4 attempt doing DRAW that fills the screen, but does not scroll. In the video, I mention maybe using GET and PUT to scroll. I do nit recall how to use those but I will look in to it.

      Reply
    2. Allen Huffman Post author

      I forgot to reply — tried that on a CC3 emulator. Good job. My PMODE 4 used the DRAW command, since it remembers where it left off, and has a diagonal command. Instead of a PRINT “/”; I would have DRAW”whatever” and they’d just go right after each other, and I’d track the “column” and then reset on the next “line.” But no scrolling.

      This makes me want to revisit this program and see if there’s any way to get better performance out of GET/PUT or HGET/HPUT.

      Reply
  4. Sebastian Tepper


    1 REM 10PRINT FOR COCO
    5 DIM X,Y,CR(1),CL(1),SS(1177)
    10 PMODE 4,1:PCLS
    15 DRAW"BM0,0;F7U1H6G1F6"
    20 DRAW"BM15,0;G7U1E6F1G6"
    25 GET(0,0)-(7,7),CR
    30 GET(8,0)-(15,7),CL
    35 SCREEN 1,0
    40 FOR X=0 TO 255 STEP 8
    45 IF RND(0)>.5 THEN PUT(X,Y)-(X+7,Y+7),CR,PSET ELSE PUT(X,Y)-(X+7,Y+7),CL,PSET
    50 NEXT X
    55 IF Y<184 THEN Y=Y+8:GOTO 40
    60 GET(0,8)-(255,191),SS
    65 PUT(0,0)-(255,183),SS
    70 LINE(0,184)-(255,191),PRESET,BF
    75 GOTO 40

    Reply
  5. Sebastian Tepper

    I noticed I left two “PSET” keywords in line 45. If you remove them the characters are drawn much faster.

    Reply
  6. Sebastian Tepper

    I like this one better, as it fills each row alternating direction like a loom–more pleasing to watch, in my opinion.
    I also replaced the LINE…BF used to erase the bottom row with a PUT statement, as it is clearly faster.

    1 REM 10PRINT ALTERNATING
    5 DIM X,Y,CR(1),CL(1),SL(51),SF(1177),D
    10 PMODE 4,1:PCLS
    15 DRAW"BM0,0;F7U1H6G1F6"
    20 DRAW"BM15,0;G7U1E6F1G6"
    25 GET(0,0)-(7,7),CR
    30 GET(8,0)-(15,7),CL
    35 GET(0,184)-(255,191),SL
    40 SCREEN 1,1
    45 IF D THEN FOR X=248 TO 0 STEP -8 ELSE FOR X=0 TO 255 STEP 8
    50 IF RND(0)>.5 THEN PUT(X,Y)-(X+7,Y+7),CR ELSE PUT(X,Y)-(X+7,Y+7),CL
    55 NEXT X
    60 D=1-D:IF Y<184 THEN Y=Y+8:GOTO 45
    65 GET(0,8)-(255,191),SF
    70 PUT(0,0)-(255,183),SF
    75 PUT(0,184)-(255,191),SL
    80 GOTO 45

    Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.