Category Archives: Color BASIC

Revisiting 10 PRINT RACER

Awhile back I ported 8-Bit Show and Tell‘s “10 PRINT RACER” from Commodore PET to CoCo. I tried to make it a literal port, keeping the code as close as I could to the original. I did, however, mention a few things that could make it faster, taking advantage of things like Extended Color BASIC’s hex values (&H2 is faster to parse than 2, for instance).

The other day, MiaM left a comment on the original article:

It might be faster to use A=ASC(INKEY$) and IF A=4 instead of IF A$=CHR$(4)

– MiaM

Intriguing. The original Commodore version, the direction was read by using GET A$, and I simply converted that over to A$=INKEY$ for Color BASIC. Here is a look at Robin’s Commodore PET original:

1 REM 10 PRINT RACER: 8-BIT SHOW & TELL
5 R$="":PRINT"{CLR}INIT:";:FORX=1TO75:M$=CHR$(205.5+RND(.)):R$=R$+M$:PRINTM$;:NEXT
10 PRINT"{CLR}":C=20:R=13:W=15:D=0:S=32768
20 L=0:FORZ=0TO1STEP0:X=RND(.)*10
30 IFX<4THENR=R-1:IFR<1THENR=1
40 IFX>6THENR=R+1:IFR+W>37THENR=37-W
50 RN=RND(.)*35+1:PRINTMID$(R$,RN,R);SPC(W);MID$(R$,RN,39-R-W)
60 D=D+1:L=L+1:IFL>49THENL=0:W=W-1:IFW<3THENW=3
70 IFD<25THENNEXT
75 GETA$:IFA$="4"THENC=C-1
80 IFA$="6"THENC=C+1
90 P=PEEK(S+C):IFP<>32THEN200
100 POKES+C,42:NEXT
200 PRINTSPC(17)"CRASH!":IFD>HTHENH=D
205 PRINT,"SCORE:"D"  HIGH:"H
210 FORX=1TO2000:NEXT:POKE158,0
220 GETA$:IFA$=""THEN220
230 GOTO10

And here is my Color BASIC conversion:

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

The block of code MiaM refers to is this:

75 GETA$:IFA$="4"THENC=C-1
80 IFA$="6"THENC=C+1

75 A$=INKEY$:IFA$=CHR$(8)THENC=C-1
80 IFA$=CHR$(9)THENC=C+1

On the Commodore PET, without arrow keys, it used “4” and “6” on the numeric keypad for Left and Right. On the CoCo, I changed that to the Left Arrow key and the Right Arrow key.

The Commodore PET has much less work to do looking for A$=”4″ versus A$=CHR$(8) not he CoCo (due to all the parsing). I could have made the CoCo use letter keys like “A” for left and “S” for right to get similar performance.

But what MiaM suggests may be faster. Instead of comparing strings like A$=CHR$(8), the suggestion is to use BASIC’s ASC() keyword to return the numeric value of the character, then compare a numeric value rather than a string compare.

Which is faster? A one character string compare, or ASC() and a number compare?

Let’s find out.

Comparing a String to a String

For this, I dug out my old BENCH.BAS benchmarking code and inserted the first method I wanted to test — the way the Commodore PET did it:

5 DIM TE,TM,B,A,TT
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 A$=INKEY$:IF A$="4" THEN REM

70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END

Comparing A$ to a quoted value in this loop produces 515.

Comparing a String to a CHR$

My conversion changed this to comparing to a CHR$(8) value, like this:

0 REM ascvsstringcompare.BAS
5 DIM TE,TM,B,A,TT
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 A$=INKEY$:IF A$="4" THEN REM
30 A$=INKEY$:IF A$=CHR$(8) THEN REM

70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END

This produces a slower 628. No surprise, due to having to parse CHR$() and the number. I could easily speed up the CoCo port by using quoted characters like “A” for Left and “S” for Right.

But I really wanted to use the arrow keys.

ASC and you shall receive…

The new suggestion is to use ASC. ASC will convert a character to its ASCII value (or PETASCII on a Commodore, I would suppose). For example:

PRINT ASC("A")
65

The cool suggestion was to try using INKEY$ as the parameter inside of ASC(), and skipping the use of a variable entirely. Unfortunately, when I tried it, I received:

?FC ERROR

Function Call error. Because, if no key is pressed, INKEY$ returns nothing, which I suppose would be like trying to do:

PRINT ASC("")

We have been able to use INKEY$ directly in other functions, such as INSTR (looking up a character inside a string), and that works even when passing in “”:

PRINT INSTR("","ABCDE")
0

But ASC() won’t work without a character, at least not in Color BASIC. And, even if we used A$=INKEY$, we can’t pass A$ in to ASC() if it is empty (no key pressed) which means we’d need an extra check like:

30 A$=INKEY$:IF A$<>"" THEN IF ASC(A$)=4 THEN ..

The more parsing, the slower. This produced 539, which isn’t as slow as I expected. It’s slower than doing IF A$=”4″ but faster than IF A$=CHR$(8). Thus, it would be faster in my CoCo port than my original.

This did give me another thing to try. ASC() allows you to pass in a string that contains more than one character, but it only acts upon the first letter. You can do this:

PRINT ASC("ALLEN TRIED THIS")
65

This means I could always pad the return of INKEY$ with another character so it would either be whatever keys he user pressed, or my other character if nothing was pressed. Like this:

30 IF ASC(INKEY$+".")=8 THEN REM

If no key has been pressed, this would try to parse “”+”.”, and give me the ASCII of “.”.

If a key had been pressed, this would parse that character (like “4.” if I pressed a 4).

As I learned when I first stated my benchmarking BASIC series, string manipulation is slow. Very slow. So I expect this to be very slow.

To my surprise, it returns 520! Just a smidge slower than the original IF A$=”4″ string compare! I’m actually quite surprised.

Now, in the actual 10 PRINT RACER game, which is doing lots of string manipulations to generate the game maze, this could end up being much slower if it had to move around other larger strings. But, still worth a shot.

Thank you, MiaM! Neat idea, even if Color BASIC wouldn’t let me do it the cool way you suggested.

Until next time…

Bonus

Numbers verses string compares:

30 IF Z=4 THEN REM

That gives me 350. Even though decimal values are much slower to parse than HEX values, they are still faster than strings.

But, in pure Color BASIC, there is no way to get input from a keypress to a number other than ASC. BUT, you could PEEK some BASIC RAM value that is the key being held down, and do it that way (which is something I have discussed earlier).

Any more ideas?

10 PRINT big maze in Color BASIC – part 4

See also: part 1, part 2, part 3 and part 4.

My “big maze” program printed 2×2 character blocks along the bottom of the screen until it got to the bottom right of the screen, then the screen will scroll (and an extra PRINT is added to add a second line) and the process resets and repeats.

After William Astle provided some optimizations, it dawns on me that there was another thing we could try. Here is the code in question (removing unneeded lines and adjusting the GOTO as appropriate):

70 P=448
100 P=P+2:IF P>479 THEN PRINT:GOTO 70
110 GOTO 100

Then William suggested changing the logic it as follows:

70 PRINT:P=448
...
100 P=P+2:IF P>479 THEN 70
110 GOTO 100

That was a very subtle change that could double (or more, or less) the speed just by not needing to parse over “PRINT:GOTO 70” every time P was NOT greater than 479 (which is most of the time in that loop).

This made me think that perhaps instead of checking for greater than 479 we could adjust the logic and check for less than 480. Something like this, perhaps:

70 PRINT:P=448
...
100 P=P+2:IF P<480 THEN 100
110 GOTO 70

There’s really no reason for this to be any different speed, is there? GOTO (“THEN”) 100 still has to start at the top and move forward, the same as GOTO (“THEN”) 70 would.

But, in the first case, it quickly skips “THEN 70” to hit the “GOTO 100” below, every time the value is not greater than 479. That

In the second, every time the value is LESS than 480 it returns to 100 (go to top of program and search forward).

Should it matter?

Here is the logic isolated.

5 TIMER=0
10 PRINT:P=0
20 P=P+1:IF P<1001 THEN 20
30 GOTO 50
50 PRINT TIMER

And here is the other version:

5 TIMER=0
10 PRINT:P=0
20 P=P+1:IF P>1000 THEN 50
30 GOTO 20
50 PRINT TIMER

I have adjusted it to reset the TIMER at the start, and count from 0 to 1000. In each case, instead of repeating forever, it ends, printing the TIMER.

The first version looks odd because in the real version, line 30 would be “GOTO 10” to reset P and continue.

The second version would have line 20 end with “THEN 10” to reset and continue.

I just wanted to make them as close as possible.

Which one is faster?

Until next time…

10 PRINT big maze in Color BASIC – part 3

See also: part 1, part 2, part 3 and part 4.

When we last left off, it had been so long since I did any BASIC programming that I found myself wondering why these two sections of BASIC did not perform as I expected:

0 'bigmazebench.bas
100 P=0:TIMER=0:A=0
110 P=0
120 P=P+2:IF P>479 THEN PRINT:GOTO 110
120 A=A+1:IF A >1000 THEN 150
140 GOTO 120
150 PRINT TIMER

200 P=0:TIMER=0:A=0
210 PRINT:P=0
220 P=P+2:IF P>479 THEN 210
230 A=A+1:IF A>1000 THEN 250
240 GOTO 220
250 PRINT TIMER

William Astle once again saw the obvious (just not obvious to me at the time)…

If you have both versions in the same program, the “backwards” jumps will be slower the later in the program they are because they have to do a sequential scan of the program from the beginning to find the correct line number. If you have been running them in the same program, try separating them and running them independently.

– William Astle

Well, duh. Of course. When the block of code starting at line 200 runs, the GOTO 220 has to start at the top of the program and seek past every line to find 220. Much slower compared to how few lines the GOTO 120 has to. Normally my benchmark program is inside a FOR/NEXT loop so there is no line seeking and it behaves the same speed regardless of line number location…

So let’s try them one at a time. I loaded the program and deleted the line 0 comment, and lines 200 and up (DEL 0 and DEL 200-):

100 P=0:TIMER=0:A=0
110 P=0
120 P=P+2:IF P>479 THEN PRINT:GOTO 110
120 A=A+1:IF A >1000 THEN 150
140 GOTO 120
150 PRINT TIMER

This gives me 762.

Then, loading it again, and deleting everything up to 200 (“DEL -199”):

200 P=0:TIMER=0:A=0
210 PRINT:P=0
220 P=P+2:IF P>479 THEN 210
230 A=A+1:IF A>1000 THEN 250
240 GOTO 220
250 PRINT TIMER

That gives me 1394!

Yep, William’s suggestion of moving the PRINT to the destination line, instead of using “THEN PRINT:GOTO xxx” almost doubled the speed it takes to run through that code.

Nicely done, William.

Until next time…

10 PRINT big maze in Color BASIC – part 2

See also: part 1, part 2, part 3 and part 4.

Previously, I presented this Color BASIC program:

0 ' BIGMAZE.BAS
10 C=2
20 B$=CHR$(128)
30 L$=CHR$(128+16*C+9)
40 R$=CHR$(128+16*C+6)
50 M$(0,0)=B$+R$:M$(0,1)=R$+B$
60 M$(1,0)=L$+B$:M$(1,1)=B$+L$
70 P=512-32*2
80 M=RND(2)-1
90 PRINT@P,M$(M,0);:PRINT@P+32,M$(M,1);
100 P=P+2:IF P>479 THEN PRINT:GOTO 70
110 GOTO 80

Running it produces this:

4×4 Maze

And I ended with “Make it smaller. Make it faster.”

William Astle commented:

Welp, everything up to line 60 can be mashed into a single line. Since it’s all setup and none of it is performance critical, you can dispense with the variables and just set the M$ array directly. More typing, but it keeps the variable table smaller. Or define P right at the start so it’s the first variable in the table which would give you a speedup all on its own since the lookups to find P will be faster.

I suspect that if you put the PRINT statement from line 100 at, say, the start of line 70 and have “THEN 70” instead of “THEN PRINT:GOTO 70”, you might get a bit of a performance gain there, especially in the false case where that gives a handful fewer bytes to skip over.

There might be some sort of trick involving FOR/NEXT that can be used to improve the main loop but I think the overhead of setting up a FOR loop will be more than the saving in this case, especially if the setup lines are combined into a single program line.

On a side note, and this won’t improve the speed any, you could put a DIM M(1,1) at the start to avoid the implied DIM M(10,10). That saves a bit of memory, though I don’t think that’s even an issue for this program even on a 4K machine. But it is 585 bytes nevertheless.

– William Astle

Let’s start with the “everything up to line 60” part, which gives us this:

10 M$(0,0)=CHR$(128)+CHR$(166):M$(0,1)=CHR$(166)+CHR$(128):M$(1,0)=CHR$(169)+CHR$(128):M$(1,1)=CHR$(128)+CHR$(169)

If I compare that to the original, it’s about a few less characters to type:

10 C=2:B$=CHR$(128):L$=CHR$(128+16*C+9):R$=CHR$(128+16*C+6):M$(0,0)=B$+R$:M$(0,1)=R$+B$:M$(1,0)=L$+B$:M$(1,1)=B$+L$

It loses the ability to change the color of the maze (easily), but it saves three string variables (B$ for blank block, R$ for right block, and L$ for left block) and one numeric variable (C for color). Definitely lower RAM use, and I am sure it is code-space too since you can’t tokenize “128+16*C+6” (10 bytes) which is replaced by “166”.

Combining the rest of the lines, where possible, and moving the PRINT (so line 100 has a bit less to parse through to get to the end of that line when P>479 is not true) results in:

0 ' BIGMAZE2.BAS - William Astle
10 M$(0,0)=CHR$(128)+CHR$(166):M$(0,1)=CHR$(166)+CHR$(128):M$(1,0)=CHR$(169)+CHR$(128):M$(1,1)=CHR$(128)+CHR$(169)
70 PRINT:P=448
80 M=RND(2)-1:PRINT@P,M$(M,0);:PRINT@P+32,M$(M,1);:P=P+2:IFP>479THEN70
110 GOTO80

On my simulated CoCo, removing the REM statements, then loading the original version and doing “? MEM” showed 8256. Doing the same to the second version shows 8307 — saving 51 bytes of program space. I did not measure what the saving in string and variable memory would be, but that would be even more. Great win.

Since the difference was mostly in the setup of the variables, they should run at the same speed — or will they? Let’s quickly test William’s suggestion of moving the PRINT so the IF statement doesn’t have to parse the end of the line:

0 'bigmazebench.bas
100 P=0:TIMER=0:A=0
110 P=0
120 P=P+2:IF P>479 THEN PRINT:GOTO 110
120 A=A+1:IF A >1000 THEN 150
140 GOTO 120
150 PRINT TIMER

200 P=0:TIMER=0:A=0
210 PRINT:P=0
220 P=P+2:IF P>479 THEN 210
230 A=A+1:IF A>1000 THEN 250
240 GOTO 220
250 PRINT TIMER

Not very elegant, but it should do the job. Since I could not easily use a FOR/NEXT loop for the counter, I used A and a check in line 130 or 230 to exit the test.

This prints 771 for the first one, and 1414 for the second one.

This is not what I would have expected. I must be doing something wrong, because I agree with William that…

IF P>479 THEN PRINT:GOTO 210

…should be slower every time P is NOT greater than 479, compared to:

IF P>479 THEN 210

In the first example, each time P is not greater than 479, BASIC should still have to skip everything past then THEN looking for either ELSE or the end of the line. It should be scanning past a PRINT and GOTO token then the number 220.

In the second example, it should only have to skip the number 210.

I think I did something wrong.

What am I missing?

To be continued…

10 PRINT big maze in Color BASIC – part 1

See also: part 1, part 2, part 3 and part 4.

Awhile back, I discussed the famous Commodore 10 PRINT one-line program, inspired by a YouTube video from 8-Bit Show and Tell.

Commodore PET running the 10 PRINT program.

Although most computers could do the same program in BASIC, unless your system had those wonderful diagonal graphics characters, the result could be a bit lacking.

Scrolling maze… Sorta.

On the CoCo, using the 2×2 block graphics characters was not an improvement, either.

But perhaps if you used a 4×4 block it might look more like a maze. This allows 16 maze characters across by 8 down (versus a Commodore VIC-20 with 22×23).

4×4 Maze

Well, it works, but takes up much more than one line. How small can you make it? Here is my version:

0 ' BIGMAZE.BAS
10 C=2
20 B$=CHR$(128)
30 L$=CHR$(128+16*C+9)
40 R$=CHR$(128+16*C+6)
50 M$(0,0)=B$+R$:M$(0,1)=R$+B$
60 M$(1,0)=L$+B$:M$(1,1)=B$+L$
70 P=512-32*2
80 M=RND(2)-1
90 PRINT@P,M$(M,0);:PRINT@P+32,M$(M,1);
100 P=P+2:IF P>479 THEN PRINT:GOTO 70
110 GOTO 80

Make it smaller. Make it faster. Share your work. And someone tell Jim Gerrie since he probably has already done this…

Until next time…

Odd or Even in Color BASIC?

In my 3X+1 post, I needed to check if a value was odd or even. I did so by using “AND 1” which would test the least significant bit. This works, and is fast, but is limited to values 32767 or lower (15 bits).

Comments from William Astle, RogelioP and John offered corrections and updates to my code. I decided to benchmark a few different methods for detecting if a value was odd or even, and here is what I came up with.

0 REM 3x+1 Benchmarking

5 'GOTO 300

100 ' 0-32767 ONLY
110 TIMER=0
120 FOR A=1 TO 1000
130 IF X AND 1 THEN REM
140 NEXT:PRINT TIMER

200 ' ALL RANGES?
210 TIMER=0
220 FOR A=1 TO 1000
230 IF INT(X/2)=X/2 THEN REM
240 NEXT:PRINT TIMER

300 ' ROGELIO P
310 TIMER=0
320 FOR A=1 TO 1000
330 IF INT(X/2)*2=X THEN REM
340 NEXT:PRINT TIMER

400 ' WILLIAM ASTLE
405 T=0:H=0.5
410 TIMER=0
420 FOR A=1 TO 1000
430 T=X*H:IF T=INT(T) THEN REM
440 NEXT:PRINT TIMER

500 ' DIVIDE TEST
505 T=0:H=2
510 TIMER=0
520 FOR A=1 TO 1000
530 T=X/H:IF T=INT(T) THEN REM
540 NEXT:PRINT TIMER

When I run this program in Xroar on my Raspberry Pi 400, I get:

313
508
506
485
485

As expected, AND is the fastest, so use this if you know your values will be 32767 or lower.

Using INT(X/2)=X/2 was a fraction slower as INT(X/2)*2=X, which I guess makes sense since both do an INT and two math functions.

William’s suggestion of multiplying by .5 instead of dividing by two rang a bell. I believe he (or someone) pointed this out to me a few years ago when I was doing similar benchmarks. A big speed up comes just from putting the value in a variable, but I was surprised to see that dividing by a variable of 2 was the same speed as multiplying by a variable of .5.

What ideas do you have? Anything with math (“/2”) can be sped up by using a variable (“/H”), so there are some improvements just from that. Using HEX values (“&H2”) instead of decimal is also faster, as is removing extra spaces.

But are there better approaches we can use?

Thoughts appreciated.

3X+1 and Color BASIC

For the past three weeks, I have found myself out-of-town for work. This week, I decided to bring my Raspberry Pi 400 along so I could play with it in the hotel room.

Raspberry Pi 400

I soon found myself toying around with the XRoar CoCo emulator, and I knew just what I wanted to program…

Last night, YouTube showed me a video about “the most dangerous problem in mathematics.”

The idea is you start with a number. If it is odd, you multiply it by 3 and add 1. If it is even, you divide it by 2. Repeat until you get to the pattern 4, 2, 1, 4, 2, 1, 4, 2, 1.

Math says that, so far, every number ends up at that pattern. No one has figured out a formula that leads to any number that does not end at 4, 2, 1.

With that in mind, I thought it would be fun to write the 3X+1 problem in CoCo Color BASIC. It looks like this:

0 REM 3X+1
10 PRINT:INPUT "STARTING NUMBER";X
20 PRINT X;
30 IF X=1 THEN 10
40 IF X AND 1 THEN X=X*3+1:GOTO 20
50 X=X/2:GOTO 20 

I tried to avoid using any Extended Color BASIC features such as HEX numbers (&H1) to speed things up. I even skipped ELSE so it could run on a VIC-20 or other system without that command.

I use “X AND 1” to test for a number being odd. Any odd number has the first bit set. 1 (00000001) does, 2 does not (00000010), 3 does (00000011), and so on.

It does have one flaw… if any number is greater than 32767, it will crash with a ?FC ERROR. Apparently Color BASIC’s AND cannot handle any value greater than 7 bits (01111111 = 32767).

Do you know of a different way to test for even or odd values? I can think of two, one of which would be terribly inefficient and the other not as inefficient but much worse than using AND.

Give it a shot and see if you can find the largest sequence of numbers a CoCo can calculatae.

Or better yet, find a better way to do this in Color BASIC.

Enjoy!

Benchmarking the CoCo keyboard – part 7

See also: part 1, part 2, part 3, part 4, part 5, part 6, part 7 and more (coming “soon”).

NOTE: This one is gonna jump around a bit, referring to examples in the previous installment, so hang tight…

Some comments from the previous installment, where I shared some code that wasn’t speeding up like I thought it should when I replaced hard-coded values with variables. As the post when live, I read through it and noticed my mistake. I added a comment to see if anyone else could spot it:

0 REM arrowbench9.bas
5 V=&HF7:U=&H155:D=&H156:L=&H157:R=&H158
10 TIMER=0:FOR A=1 TO 1000
20 POKEU,V:POKED,V:POKEL,V:POKER,V
30 IF PEEK(U)=V THEN IF Y>.THEN Y=Y-1:GOTO90
40 IF PEEK(D)=V THEN IF Y<&HDF THEN Y=Y+1:GOTO90
50 IF PEEK(L)=V THEN IF X>.THEN X=X-1:GOTO90
60 IF PEEK(R)=V THEN IF X<&HFE THEN X=X+1:GOTO90
90 NEXT:PRINT TIMER

craig immediately chimed in with the issue:

In arrowbench9.bas it pokes $F7 instead of $FF ?

In test, shouldn’t line 10 should use same as arrowbench9.bas line 20 ?

Can you use ‘next’ instead of ‘goto90’ ?

– craig

Right off the bat, craig noticed my mistake. In like 20, it’s supposed to POKE those four keyboard column values to 255 (&HFF). But, when I substituted the variables, I did not make a variable for 255 — instead, I incorrectly used the V variable which was the &HF7 value the PEEK would change to when that key was being held down. Oops! Thus, I was never resetting it so it was, apparently, always acting as if the key was being held down, processing all the variable X/Y stuff every time.

A fix to the original arrowbench2.bas might look like this:

0 REM arrowbench10.bas
5 Z=&HFF:V=&HF7:U=&H155:D=&H156:L=&H157:R=&H158
10 TIMER=0:FOR A=1 TO 1000
20 POKEU,Z:POKED,Z:POKEL,Z:POKER,Z
30 IF PEEK(U)=V THEN IF Y>.THEN Y=Y-1
40 IF PEEK(D)=V THEN IF Y<&HDF THEN Y=Y+1
50 IF PEEK(L)=V THEN IF X>.THEN X=X-1
60 IF PEEK(R)=V THEN IF X<&HFE THEN X=X+1
90 NEXT:PRINT TIMER

Now the keyboard table is properly reset back to 255 before each scan. While the original arrowbench2.bas reported 2890, this version (using the properly variable to reset those locations) reports 2351 and is indeed faster. My bad.

As to the other comments, that was yet another typo, where I created variables then forgot to use them:

5 V=&HF7:U=&H155:D=&H156:L=&H157:R=&H158
10 POKE&H155,&HFF:POKE&H156,&HFF:POKE&H157,&HFF:POKE&H158,&HFF
20 PRINT HEX$(PEEK(U))" "HEX$(PEEK(D))" "HEX$(PEEK(L))" "HEX$(PEEK(R)),HEX$(PEEK(&H155))" "HEX$(PEEK(&H156))" "HEX$(PEEK(&H157))" "HEX$(PEEK(&H158))
30 GOTO 10

…should have used U/D/L/R and a new Z in line 10, just like the example before.

As to using “NEXT” instead of “GOTO 90”, this refers to trying to bypass additional checks if one key is satisfied:

0 REM arrowbench11.bas
5 Z=&HFF:V=&HF7:U=&H155:D=&H156:L=&H157:R=&H158
10 TIMER=0:FOR A=1 TO 1000
20 POKEU,Z:POKED,Z:POKEL,Z:POKER,Z
30 IF PEEK(U)=V THEN IF Y>.THEN Y=Y-1:GOTO90
40 IF PEEK(D)=V THEN IF Y<&HDF THEN Y=Y+1:GOTO90
50 IF PEEK(L)=V THEN IF X>.THEN X=X-1:GOTO90
60 IF PEEK(R)=V THEN IF X<&HFE THEN X=X+1:GOTO90
90 NEXT:PRINT TIMER

In this benchmark example, I did GOTO 90 to go to the “end of what we are timing” line. But if this was being used in a program, a NEXT would have been faster than scanning forward to find line 90 and then doing the NEXT. BUT, if I tried that here, when NEXT was done (1 to 1000), it would not return and would go to the next line — and if that was 40, 50 or 60, it would do the check then try a NEXT and error with a “?NF ERROR” (next without for).

But, the point is well made — NEXT with a check after it could even be faster than a GOTO (scanning lines) to a next. That would be a fun benchmark.

Faster, even.

craig also pointed out an interesting optimization… Rather than clear all four keyboard values, whether they need it or not, why not just clear the one(s) that changed?

Faster.. what about moving line 20 out of the loop and only poking the matching peeks?

IF PEEK(U)=V THEN POKEU,F:IF Y>.THEN Y=Y-1:GOTO90

F=&HFF

For diagonals, change to GOTO50 on line 30.
Line 60 needs no GOTO90

– craig

This is worthy of an updated benchmark. Let’s take arrowbench10.bas above (2351) and modify it like this:

0 REM arrowbench12.bas
5 Z=&HFF:V=&HF7:U=&H155:D=&H156:L=&H157:R=&H158
10 TIMER=0:FOR A=1 TO 1000
30 IF PEEK(U)=V THENPOKEU,Z:IF Y>.THEN Y=Y-1
40 IF PEEK(D)=V THENPOKED,Z:IF Y<&HDF THEN Y=Y+1
50 IF PEEK(L)=V THENPOKEL,Z:IF X>.THEN X=X-1
60 IF PEEK(R)=V THENPOKER,Z:IF X<&HFE THEN X=X+1
90 NEXT:PRINT TIMER

Now it only clears the value if it was changed. This produces a value of … 1603! We have a winner! Great improvement, craig!

Also, when I added the “GOTO 90” at the end of the example, to bypass the other checks (eliminating the possibility of diagonals), craig is suggesting the code could simply skip checking DOWN if we had an UP (GOTO 50 moves to the Left/Right check next) and, likewise, like 50 could GOTO 90 to skip over the Right check. Thus:

  • If UP is pressed…
    • Check for LEFT. If LEFT is pressed…
      • Exit checks. We have an UP and a LEFT
    • else Check for RIGHT. If RIGHT is pressed…
      • Exit checks. We have an UP and RIGHT.
    • else Exit checks. We have UP.
  • else check DOWN. If DOWN is pressed…
    • Check for LEFT. If LEFT is pressed…
      • Exit checks. We have DOWN and LEFT
    • else Check for RIGHT. If RIGHT is pressed…
      • Exit checks. We have DOWN and RIGHT.
    • else Exit checks. We have DOWN.

When I write it out that way, you can see that this type of logic (adding GOTO to skip steps) means that the program would be fastest checking for UP and LEFT. And slowest for checking for JUST down. This means a game would move at different speeds based on which direction is detected. While this is a great optimization, it may not be desirable since a game may wish consistent speed (i.e. always worst case) than having a speed that varies.

Big thanks to craig for spotting my typo, and providing these two additional optimizations. I really like the “only POKE if it changed” one. It would be fun to benchmark and see if “worst case” all four arrows were being held down, is this slower than just clearing them all at once.

IF ELSE SLOWER

One additional note about using IF… Once BASIC starts processing a line, it has to parse the entire line whether that code is executed or not. For example:

IF A=42 THEN DO THIS:DO THAT:DO THE OTHER:DO NOTHING:DO OVER

Even if A is not 42, BASIC still has to at least scan through all the tokens and such on the rest of the line to skip it. Also, since ELSE could be used on Extended Color BASIC, there could be an ELSE clause that still needs to be processed:

10 IF A=42 THEN DO THIS:DO THAT:DO THE OTHER:DO NOTHING:DO OVER ELSE ...

Because of this, IF/ELSE can actually be slower than doing something like:

10 IF A=42 THEN 30
20 DO OVER:GOTO 40
30 DO THIS:DO THAT:DO THE OTHER:DO NOTHING:GOTO 40
40 ...continue... 

This looks quite awful, but that’s how I had to program on my Commodore VIC-20 because it had no ELSE. And, it turns out, this can be quite a bit faster! Now line 10 checks for A to be 42, and if it is, it skips a line (which is fast) to get to 30 and hande it. If it is NOT 42, it quickly skips two lines instead to having to parse through a whole line of BASIC tokens.

I benchmarked something like this in an earlier article series, and was very impressed at the speedups that can be achieved just by making any IF line use a GOTO… though if it’s likely the line is true (A=42) more often than it’s not, it might not make sense. One size does not fit all.

And with that, I’ll end today’s installment without providing anything new, other than some handy speedups craig showed us.

We now have some quick ideas on using arrow keys to change X and Y coordinates. X and Y coordinates are used in games like Atari Adventure and Pac-Man to know which direction to send an enemy at the player. In the case of Pac-Man (see my earlier article series on that one), the ghosts target spots around (or on top of ) Pac-Man and decide which direction to turn based on which choice would be closer (using the wonderful Pythagorean theorem we learned about in school).

BUT, if our program was not using that, and just wanted a screen location to POKE a player character to, we could probably simplify this keyboard code a bit and use less variables.

To be continued…

Exploring Atari VCS/2600 Adventure – part 4

See also: part 1, part 2, part 3, part 4 … and more to come…

Objectively speaking

Welcome back to the world of Atari Adventure! After spending some time figuring out how the rooms were drawn, it’s time to look at how the game objects are drawn.

Adventure contains many objects that can be displayed:

  1. Bat
  2. Bridge
  3. Castle gate (or Portcullis – “a strong, heavy grating sliding up and down in vertical grooves, lowered to block a gateway to a fortress or town.”)
  4. Chalice
  5. Created by Warren Robinett (author’s name)
  6. Dot (for getting in to the easter egg room)
  7. Dragon (red, green and yellow)
  8. Key
  9. Magnet
  10. Number “1” (for game select screen)
  11. Number “2”
  12. Number “3”
  13. Sword

Some objects have multiple frames. For instance, the bat has two: wings up, and wings down. The dragon has three: open mouth, closed mouth, dead. The dragons can also be drawn facing left, or facing right.

I also found an entry for something called “Surround,” which appears to be the the square around the player in the invisible mazes.

In the ROM disassembly, it looks like these objects are just stored as bytes that represent them:

GfxChallise:
  .byte $81 ;X      X                                                                  
  .byte $81 ;X      X                                                                  
  .byte $C3 ;XX    XX                                                                  
  .byte $7E ; XXXXXX                                                                   
  .byte $7E ; XXXXXX
  .byte $3C ;  XXXX
  .byte $18 ;   XX
  .byte $18 ;   XX
  .byte $7E ; XXXXXX
  .byte $00 

Above, the game-winning chalice appears to be 8×9.

The dragon is much larger:

GfxDrag0:
  .byte $06 ;     XX 
  .byte $0F ;    XXXX 
  .byte $F3 ;XXXX  XX 
  .byte $FE ;XXXXXXX 
  .byte $0E ;    XXX 
  .byte $04 ;     X
  .byte $04 ;     X
  .byte $1E ;   XXXX
  .byte $3F ;  XXXXXX
  .byte $7F ; XXXXXXX
  .byte $E3 ;XXX   XX
  .byte $C3 ;XX    XX
  .byte $C3 ;XX    XX
  .byte $C7 ;XX   XXX
  .byte $FF ;XXXXXXXX
  .byte $3C ;  XXXX
  .byte $08 ;    X
  .byte $8F ;X   XXXX
  .byte $E1 ;XXX    X
  .byte $3F ;  XXXXXX
  .byte $00

But it is still represented as 8 pixels wide. The code to display it must magnify it to make it larger on screen.

Even the bridge, the widest object displayed in the game, is represented as 8-bits wide. This is the first thing we will need to dig in to… What controls how large these small object representations are drawn?

Also, it appears every object is terminated with a $00 rather than having the length at the start. For example, instead of this:

10 READ N:FOR I=1 TO N:READ A$:PRINT A$:NEXT:END
20 DATA 10,a,b,c,d,e,f,g,h,i,j

…it works like this:

10 READ A$:IF A$="0" THEN END ELSE PRINT A$:GOTO 10:END
20 DATA a,b,c,d,e,f,g,h,i,0

Since both would have taken the same amount of data storage space in the ROM, I am betting the code to parse that data may have been smaller to loop and check for 0 versus loading a size and counting down.

Also, this presents a restriction for the graphics — none can contain an “empty” row in the graphic ($00). Because of this, each line (byte) must have at least one pixel set. This explains the dots in the easter egg signature!

;Object #4 : State FF : Graphic
GfxAuthor:
 .byte $F0    ;XXXX
 .byte $80    ;X
 .byte $80    ;X
 .byte $80    ;X
 .byte $F4    ;XXXX X
 .byte $04    ;     X
 .byte $87    ;X    XXX
 .byte $E5    ;XXX  X X
 .byte $87    ;X    XXX
 .byte $80    ;X
 .byte $05    ;     X X
 .byte $E5    ;XXX  X X
 .byte $A7    ;X X  XXX
 .byte $E1    ;XXX    X
 .byte $87    ;X    XXX
 .byte $E0    ;XXX
 .byte $01    ;       X
 .byte $E0    ;XXX
 .byte $A0    ;X X
 .byte $F0    ;XXXX
 .byte $01    ;       X
 .byte $40    ; X
 .byte $E0    ;XXX
 .byte $40    ; X
 .byte $40    ; X
 .byte $40    ; X
 .byte $01    ;       X
 .byte $E0    ;XXX
 .byte $A0    ;X X
 .byte $E0    ;XXX
 .byte $80    ;X
 .byte $E0    ;XXX
 .byte $01    ;       X
 .byte $20    ;  X
 .byte $20    ;  X
 .byte $E0    ;XXX
 .byte $A0    ;X X
 .byte $E0    ;XXX
 .byte $01    ;       X
 .byte $01    ;       X
 .byte $01    ;       X
 .byte $88    ;   X   X
 .byte $A8    ;X X X
 .byte $A8    ;X X X
 .byte $A8    ;X X X
 .byte $F8    ;XXXXX
 .byte $01    ;       X
 .byte $E0    ;XXX
 .byte $A0    ;X X
 .byte $F0    ;XXXX
 .byte $01    ;       X
 .byte $80    ;X
 .byte $E0    ;XXX
 .byte $8F    ;X   XXXX
 .byte $89    ;X   X  X
 .byte $0F    ;    XXXX
 .byte $8A    ;X   X X
 .byte $E9    ;XXX X  X
 .byte $80    ;X
 .byte $8E    ;X   XXX
 .byte $0A    ;    X X
 .byte $EE    ;XXX XXX
 .byte $A0    ;X X
 .byte $E8    ;XXX X
 .byte $88    ;X   X
 .byte $EE    ;XXX XXX
 .byte $0A    ;    X X
 .byte $8E    ;X   XXX
 .byte $E0    ;XXX
 .byte $A4    ;X X  X
 .byte $A4    ;X X  X
 .byte $04    ;     X
 .byte $80    ;X
 .byte $08    ;    X
 .byte $0E    ;    XXX
 .byte $0A    ;    X X
 .byte $0A    ;    X X
 .byte $80    ;X
 .byte $0E    ;    XXX
 .byte $0A    ;    X X
 .byte $0E    ;    XXX
 .byte $08    ;    X
 .byte $0E    ;    XXX
 .byte $80    ;X
 .byte $04    ;     X
 .byte $0E    ;    XXX
 .byte $04    ;     X
 .byte $04    ;     X
 .byte $04    ;     X
 .byte $80    ;X
 .byte $04    ;     X
 .byte $0E    ;    XXX
 .byte $04    ;     X
 .byte $04    ;     X
 .byte $04    ;     X
 .byte $00

Those rows would have been empty, but having a $00 there would have prevented there rest from being drawn. Interesting!

Draw something

For today’s installment, I’ll be using Color BASIC on a CoCo emulator to write a simple program to plot these objects on the screen. I copied all of the assembly definitions into an editor then did a search and replace to turn those .byte instructions into incredibly inefficient one-number-per-line DATA statements like this:

2830 ' Object #10 : State FF : Graphic  
2840 ' GfxChallise:
2850 DATA &H81 ' X      X   
2860 DATA &H81 ' X      X   
2870 DATA &HC3 ' XX    XX   
2880 DATA &H7E '  XXXXXX    
2890 DATA &H7E '  XXXXXX   
2900 DATA &H3C '   XXXX     
2910 DATA &H18 '    XX      
2920 DATA &H18 '    XX      
2930 DATA &H7E '  XXXXXX    
2940 DATA &H00

Unfortunately, READ/DATA in Color BASIC does not allow that. It will READ the first value, then give an ?SN ERROR when it tries to read the next because the parser doesn’t handle the apostrophe “REM” marker!

So I changed it to this:

2830 ' Object #10 : State FF : Graphic  
2840 ' GfxChallise:
2850 DATA &H81:REM X      X   
2860 DATA &H81:REM X      X   
2870 DATA &HC3:REM XX    XX   
2880 DATA &H7E:REM  XXXXXX    
2890 DATA &H7E:REM  XXXXXX   
2900 DATA &H3C:REM   XXXX     
2910 DATA &H18:REM    XX      
2920 DATA &H18:REM    XX      
2930 DATA &H7E:REM  XXXXXX    
2940 DATA &H00

This also fails — and is a problem I only recently discovered when writing my Base-64 articles.

I guess I can’t have this code be as bulky and inefficient as I wanted. I removed them so it was just the DATA statements:

2830 ' Object #10 : State FF : Graphic  
2840 ' GfxChallise:
2850 DATA &H81
2860 DATA &H81
2870 DATA &HC3
2880 DATA &H7E
2890 DATA &H7E
2900 DATA &H3C
2910 DATA &H18
2920 DATA &H18
2930 DATA &H7E
2940 DATA &H00

Oh well. Who needs comments anyway — especially in BASIC where they just bloat the code and slow it down.

A quick-and-dirty routine allowed me to see it was working:

Atari Adventure graphics rendered on a CoCo :)

…but there was no way I was going to be able to fit the “Created by Warren Robinett” graphic on a low-res 64×32 screen. To solve this, I hacked together a version that used a “high resolution” screen:

Atari Adventure graphics rendered in “high resolution” on a CoCo :)

It looks like the data is pretty straight forward. The dragons would have to be drawn reversed to make them face right, and the bridge looks like it needs an X scaling factor added to make it as wide as it is in the game. But, not bad for a first attempt.

Just for fun, here is the brute-force hacky nasty Color BASIC code I quickly put together to do this:

10 REM AdvObjs.bas
20 POKE 65395,0
30 FOR A=0 TO 7:BT(A)=2^A:NEXT
40 CLS0:XS=1:YS=1:Y=1
50 PMODE 1,1:PCLS:SCREEN 1,0
60 READ V:IF V=-1 THEN 120 ELSE IF V=0 THEN 90
70 X=XS:FOR A=7 TO 0 STEP-1:IF V AND BT(A) THEN PSET(X*2,Y*2,C)
80 X=X+1:NEXT:Y=Y+1:GOTO 60
90 XS=XS+9:IF XS>120 THEN XS=1:YS=YS+32
100 C=C+1:IF C=1 THEN C=2 ELSE IF C>3 THEN C=0
110 Y=YS:GOTO 60
120 GOTO 120
130 ' Object #0A : State FF : Graphic  
140 ' GfxBridge:
150 DATA &HC3
160 DATA &HC3
170 DATA &HC3
180 DATA &HC3
190 DATA &H42
200 DATA &H42
210 DATA &H42
220 DATA &H42
230 DATA &H42
240 DATA &H42
250 DATA &H42
260 DATA &H42
270 DATA &H42
280 DATA &H42
290 DATA &H42
300 DATA &H42
310 DATA &H42
320 DATA &H42
330 DATA &H42
340 DATA &H42
350 DATA &HC3
360 DATA &HC3
370 DATA &HC3
380 DATA &HC3
390 DATA &H00
400 ' Object #5 State #1 Graphic :'1'  
410 ' GfxNum1:
420 DATA &H04
430 DATA &H0C
440 DATA &H04
450 DATA &H04
460 DATA &H04
470 DATA &H04
480 DATA &H0E
490 DATA &H00
500 ' Object #0B : State FF : Graphic  
510 ' GfxKey:
520 DATA &H07
530 DATA &HFD
540 DATA &HA7
550 DATA &H00
560 ' Object #5 State #2 Grphic :
570 ' GfxNum2:
580 DATA &H0E
590 DATA &H11
600 DATA &H01
610 DATA &H02
620 DATA &H04
630 DATA &H08
640 DATA &H1F
650 DATA &H00
660 ' Object #5 State #3 Graphic :'3'  
670 ' GfxNum3:
680 DATA &H0E
690 DATA &H11
700 DATA &H01
710 DATA &H06
720 DATA &H01
730 DATA &H11
740 DATA &H0E
750 DATA &H00
760 ' Object #0E : State 03 : Graphic  
770 ' GfxBat1:
780 DATA &H81
790 DATA &H81
800 DATA &HC3
810 DATA &HC3
820 DATA &HFF
830 DATA &H5A
840 DATA &H66
850 DATA &H00
860 ' Object #0E : State FF : Graphic  
870 ' GfxBat2:
880 DATA &H01
890 DATA &H80
900 DATA &H01
910 DATA &H80
920 DATA &H3C
930 DATA &H5A
940 DATA &H66
950 DATA &HC3
960 DATA &H81
970 DATA &H81
980 DATA &H81
990 DATA &H00
1000 ' Object #6 : State #00 : Graphic  
1010 ' GfxDrag0:
1020 DATA &H06
1030 DATA &H0F
1040 DATA &HF3
1050 DATA &HFE
1060 DATA &H0E
1070 DATA &H04
1080 DATA &H04
1090 DATA &H1E
1100 DATA &H3F
1110 DATA &H7F
1120 DATA &HE3
1130 DATA &HC3
1140 DATA &HC3
1150 DATA &HC7
1160 DATA &HFF
1170 DATA &H3C
1180 DATA &H08
1190 DATA &H8F
1200 DATA &HE1
1210 DATA &H3F
1220 DATA &H00
1230 ' Object 6 : State FF : Graphic    
1240 ' GfxDrag1:
1250 DATA &H80
1260 DATA &H40
1270 DATA &H26
1280 DATA &H1F
1290 DATA &H0B
1300 DATA &H0E
1310 DATA &H1E
1320 DATA &H24
1330 DATA &H44
1340 DATA &H8E
1350 DATA &H1E
1360 DATA &H3F
1370 DATA &H7F
1380 DATA &H7F
1390 DATA &H7F
1400 DATA &H7F
1410 DATA &H3E
1420 DATA &H1C
1430 DATA &H08
1440 DATA &HF8
1450 DATA &H80
1460 DATA &HE0
1470 DATA &H00
1480 ' Object 6 : State 02 : Graphic    
1490 ' GfxDrag2:
1500 DATA &H0C
1510 DATA &H0C
1520 DATA &H0C
1530 DATA &H0E
1540 DATA &H1B
1550 DATA &H7F
1560 DATA &HCE
1570 DATA &H80
1580 DATA &HFC
1590 DATA &HFE
1600 DATA &HFE
1610 DATA &H7E
1620 DATA &H78
1630 DATA &H20
1640 DATA &H6E
1650 DATA &H42
1660 DATA &H7E
1670 DATA &H00
1680 ' Object #9 : State FF : Graphics  
1690 ' GfxSword:
1700 DATA &H20
1710 DATA &H40
1720 DATA &HFF
1730 DATA &H40
1740 DATA &H20
1750 DATA &H00
1760 ' Object #0F : State FF : Graphic  
1770 ' GfxDot:
1780 DATA &H80
1790 DATA &H00
1800 ' Object #4 : State FF : Graphic   
1810 ' GfxAuthor:
1820 DATA &HF0
1830 DATA &H80
1840 DATA &H80
1850 DATA &H80
1860 DATA &HF4
1870 DATA &H04
1880 DATA &H87
1890 DATA &HE5
1900 DATA &H87
1910 DATA &H80
1920 DATA &H05
1930 DATA &HE5
1940 DATA &HA7
1950 DATA &HE1
1960 DATA &H87
1970 DATA &HE0
1980 DATA &H01
1990 DATA &HE0
2000 DATA &HA0
2010 DATA &HF0
2020 DATA &H01
2030 DATA &H40
2040 DATA &HE0
2050 DATA &H40
2060 DATA &H40
2070 DATA &H40
2080 DATA &H01
2090 DATA &HE0
2100 DATA &HA0
2110 DATA &HE0
2120 DATA &H80
2130 DATA &HE0
2140 DATA &H01
2150 DATA &H20
2160 DATA &H20
2170 DATA &HE0
2180 DATA &HA0
2190 DATA &HE0
2200 DATA &H01
2210 DATA &H01
2220 DATA &H01
2230 DATA &H88
2240 DATA &HA8
2250 DATA &HA8
2260 DATA &HA8
2270 DATA &HF8
2280 DATA &H01
2290 DATA &HE0
2300 DATA &HA0
2310 DATA &HF0
2320 DATA &H01
2330 DATA &H80
2340 DATA &HE0
2350 DATA &H8F
2360 DATA &H89
2370 DATA &H0F
2380 DATA &H8A
2390 DATA &HE9
2400 DATA &H80
2410 DATA &H8E
2420 DATA &H0A
2430 DATA &HEE
2440 DATA &HA0
2450 DATA &HE8
2460 DATA &H88
2470 DATA &HEE
2480 DATA &H0A
2490 DATA &H8E
2500 DATA &HE0
2510 DATA &HA4
2520 DATA &HA4
2530 DATA &H04
2540 DATA &H80
2550 DATA &H08
2560 DATA &H0E
2570 DATA &H0A
2580 DATA &H0A
2590 DATA &H80
2600 DATA &H0E
2610 DATA &H0A
2620 DATA &H0E
2630 DATA &H08
2640 DATA &H0E
2650 DATA &H80
2660 DATA &H04
2670 DATA &H0E
2680 DATA &H04
2690 DATA &H04
2700 DATA &H04
2710 DATA &H80
2720 DATA &H04
2730 DATA &H0E
2740 DATA &H04
2750 DATA &H04
2760 DATA &H04
2770 DATA &H00
2780 ' Object #10 : State FF : Graphic  
2790 ' GfxChallise:
2800 DATA &H81
2810 DATA &H81
2820 DATA &HC3
2830 DATA &H7E
2840 DATA &H7E
2850 DATA &H3C
2860 DATA &H18
2870 DATA &H18
2880 DATA &H7E
2890 DATA &H00
2900 ' Object #11 : State FF : Graphic  
2910 ' GfxMagnet:
2920 DATA &H3C
2930 DATA &H7E
2940 DATA &HE7
2950 DATA &HC3
2960 DATA &HC3
2970 DATA &HC3
2980 DATA &HC3
2990 DATA &HC3
3000 DATA &H00
3010 ' Object #1 States 940FF (Graphic)
3020 ' GfxPort01:
3030 DATA &HFE
3040 DATA &HAA
3050 'GfxPort02:
3060 DATA &HFE
3070 DATA &HAA
3080 'GfxPort03:
3090 DATA &HFE
3100 DATA &HAA
3110 'GfxPort04:
3120 DATA &HFE
3130 DATA &HAA
3140 'GfxPort05:
3150 DATA &HFE
3160 DATA &HAA
3170 'GfxPort06:
3180 DATA &HFE
3190 DATA &HAA
3200 'GfxPort07:
3210 DATA &HFE
3220 DATA &HAA
3230 'GfxPort08:
3240 DATA &HFE
3250 DATA &HAA
3260 'GfxPort09:
3270 DATA &H00
3280 DATA -1

It ain’t pretty, but it’s mine!

I suppose next I should get back to looking at how the rooms are connected. And there’s some interesting stuff in here, including rooms that “change” as the game is played.

Stay tuned…

Benchmarking the CoCo keyboard – part 6

See also: part 1, part 2, part 3, part 4, part 5, part 6, part 7 and more (coming “soon”).

Let’s jump right in with a mystery… This code:

0 REM arrowbench.bas
10 TIMER=0:FOR A=1 TO 1000
20 POKE&H155,&HFF:POKE&H156,&HFF:POKE&H157,&HFF:POKE&H158,&HFF
30 IF PEEK(&H155)=&HF7 THEN IF Y>. THEN Y=Y-1
40 IF PEEK(&H156)=&HF7 THEN IF Y<&HDF THEN Y=Y+1
50 IF PEEK(&H157)=&HF7 THEN IF X>. THEN X=X-1
60 IF PEEK(&H158)=&HF7 THEN IF X<&HFE THEN X=X+1
90 NEXT:PRINT TIMER

…reports 2139.

This code, using variables instead of five constants…

0 REM arrowbench2.bas
5 V=&HF7:U=&H155:D=&H156:L=&H157:R=&H158
10 TIMER=0:FOR A=1 TO 1000
20 POKEU,V:POKED,V:POKEL,V:POKER,V
30 IF PEEK(U)=V THEN IF Y>.THEN Y=Y-1
40 IF PEEK(D)=V THEN IF Y<&HDF THEN Y=Y+1
50 IF PEEK(L)=V THEN IF X>.THEN X=X-1
60 IF PEEK(R)=V THEN IF X<&HFE THEN X=X+1
90 NEXT:PRINT TIMER

…reports 2890 – slower.

My previous BASIC benchmarks have shown that looking up variables should be faster than parsing HEX values, at least when there are a small amount of variables to parse through.

But in this case, it is slower.

And this code, which just adds some GOTOs to skip additional checking when a key is matched (it will not support diagonals):

0 REM arrowbench8.bas
10 TIMER=0:FOR A=1 TO 1000
20 POKE&H155,&HFF:POKE&H156,&HFF:POKE&H157,&HFF:POKE&H158,&HFF
30 IF PEEK(&H155)=&HF7 THEN IF Y>.THEN Y=Y-1:GOTO90
40 IF PEEK(&H156)=&HF7 THEN IF Y<&HDF THEN Y=Y+1:GOTO90
50 IF PEEK(&H157)=&HF7 THEN IF X>.THEN X=X-1:GOTO90
60 IF PEEK(&H158)=&HF7 THEN IF X<&HFE THEN X=X+1
90 NEXT:PRINT TIMER

…prints 2186 (a bit slower than the first, since it always has to skip the extra characters at the end of each line).

Yet this:

0 REM arrowbench9.bas
5 V=&HF7:U=&H155:D=&H156:L=&H157:R=&H158
10 TIMER=0:FOR A=1 TO 1000
20 POKEU,V:POKED,V:POKEL,V:POKER,V
30 IF PEEK(U)=V THEN IF Y>.THEN Y=Y-1:GOTO90
40 IF PEEK(D)=V THEN IF Y<&HDF THEN Y=Y+1:GOTO90
50 IF PEEK(L)=V THEN IF X>.THEN X=X-1:GOTO90
60 IF PEEK(R)=V THEN IF X<&HFE THEN X=X+1:GOTO90
90 NEXT:PRINT TIMER

…reports 1431 – faster.

A quick test using the TRON command shows that the second version using variables does not always run lines 50 and 60. It is sometimes getting a value back that triggers that extra code, hitting the GOTO and skipping the other lines.

But why does this happen when using variables, but not when using hard-coded constant values? This test program seems to show they display the same values:

5 V=&HF7:U=&H155:D=&H156:L=&H157:R=&H158
10 POKE&H155,&HFF:POKE&H156,&HFF:POKE&H157,&HFF:POKE&H158,&HFF
20 PRINT HEX$(PEEK(U))" "HEX$(PEEK(D))" "HEX$(PEEK(L))" "HEX$(PEEK(R)),HEX$(PEEK(&H155))" "HEX$(PEEK(&H156))" "HEX$(PEEK(&H157))" "HEX$(PEEK(&H158))
30 GOTO 10

What am I not seeing? Where is my typo?

Please help.

UPDATE: I found my typo. And it’s a stupid one. Do you see what I did wrong?

To be continued…