Pac-Man maze in 64x32 semigraphics.

Drawing a maze level on the CoCo – part 5

See Also: part 1, part 2, part 3, part 4 and part 5.

ASCII HEX data to maze graphics

As our spiraling trip down the rabbit hole continues, we finally return to drawing stuff instead of converting stuff.

We now have DATA statements that contain 2-byte hex numbers. Each hex number represents a byte (0-255) and each of those bytes represents 8 maze blocks on the screen.

For use in a Color BASIC program, we might want to take this HEX data and convert it in to string data (containing the final CHR$ values) for fast printing on the screen. If we were doing this in 6809 assembly, we might have the binary data contained in the executable, and convert it to a buffer that can then be copied to the screen as needed.

We’ll start with a simple (verbose) Color BASIC version. Since I am using the XRoar emulator to CLOAD this text file, I had to shorten the DATA lines a bit since they are now on higher lines, thus instead of loading “1DATA” it is loading “360DATA” taking up more bytes. I just moved some hex values to the next line.

I also adjusted my parser a bit. In my binary example, I wanted to handle binary numbers of any length. i.e, “111” would be 7, and “11111111” would be 255. To do this, I started with 0, and each time I saw a 1 I added one. For each new digit, I shifted the bits left by multiplying by 2. That way if it parsed “111” it would be “1 *2 +1 *2 +1” and so on for longer values.

For the maze, everything has to be a byte, so I took the opposite approach by starting with a bit value of 128 (representing the high bit in an 8-bit byte). Each time I found a new digit, I divided the bit value by 2 and either added it (if the new digit was a “1”, or “X” in the case of the maze) or not. That would make a maze of “XXXXXXXX” be “128 + 16 + 32 + 16 + 8 + 4 + 1” giving 255. If the maze data ran short, so the last few bytes were just “XXXX” it would be “128 + 64 + 32 + 16” which is &HF0 in hex (11110000), exactly what we want. If I had done it the other way, it would end up with 1111 (&H0F) and maze data would look like this:

GOOD:
-------- -------- ----
XXXXXXXX XXXXXXXX XXXX &HFF &HFF &HF0 -> 11111111 11111111 11110000

BAD:
-------- -------- ----
XXXXXXXX XXXXXXXX XXXX &HFF &HFF &H0F -> 11111111 11111111 00001111

I hope that makes sense. Anyway, here is the program I came up with… It will scan the hex values and build an array of strings (LN$) for each line. That way, the converted maze can be quickly printed after it has been generated once.

0 REM HEXMAZE.BAS
10 ' STRING SPACE IS MW*(MH+1)
20 CLEAR 28*32
30 ' SIZE OF MAZE IN DATA
40 MW=28:MH=31
50 ' ROOM FOR CONVERTED MAZE
60 DIM MZ$(MH):LN=0
70 CLS
80 ' READ A LINE OF HEX DATA
90 READ A$:IF A$="" THEN 340
100 ' EVERY 2 CHARS IS A HEX
110 FOR A=1 TO LEN(A$) STEP 2
120 ' COVERT HEX TO A VALUE
130 V=VAL("&H"+MID$(A$,A,2))
140 ' CONVERT BITS TO BLOCKS
150 BT=128
160 IF (V AND BT)=0 THEN B$=CHR$(128) ELSE B$=CHR$(175)
170 ' ADD BLOCK/SPACE TO STRING
180 MZ$(LN)=MZ$(LN)+B$
190 ' IF LEN >= MAZE W, NEXT
200 IF LEN(MZ$(LN))>=MW THEN 280
210 ' SHIFT BIT RIGHT
220 BT=INT(BT/2)
230 ' IF MORE BITS, REPEAT
240 IF BT>0 THEN 160
250 ' GET NEXT CHARACTER
260 GOTO 310
270 ' ELSE NEW MAZE LINE
280 PRINT MZ$(LN)
290 LN=LN+1
300 ' NEXT CHARACTER
310 NEXT
320 ' MORE LINES TO DO?
330 IF LN<MH THEN 90
340 END
350 REM MAZE AS HEX DATA
360 DATAFFFFFFF080060010BDF6FBD0BDF6FBD0BDF6FBD080000010BDBFDBD0BDBFDBD081861810FDF6FBF005F6FA0005801A0005BFDA00FDA05BF000204000FDA05BF005BFDA0005801A0005BFDA00FDBFDBF080060010BDF6FBD0BDF6FBD08C000310EDBFDB70EDBFDB7081861810BFF6FFD0BFF6FFD080000010
370 DATAFFFFFFF0,""

This program works, but it’s very slow, taking about 45 seconds to run. But, it’s verbose enough to (hopefully) explain what is going on.

By removing spaces, swapping out a integer values for faster HEX values, changing a CHR$() to a variable and adding a bit more string space, removing INT (and making one adjustment needed due to that), and combining lines, it can be sped up to just over 32 seconds.

0 REM HEXMAZE2.BAS
10 ' STRING SPACE IS MW*(MH+1)
20 CLEAR 28*32+200
30 ' SIZE OF MAZE IN DATA
40 MW=28:MH=31
50 ' ROOM FOR CONVERTED MAZE
60 DIM MZ$(MH):LN=0:BL$=CHR$(175)
70 CLS
90 READA$:IFA$=""THEN340
110 FORA=1TOLEN(A$)STEP2:V=VAL("&H"+MID$(A$,A,&H2)):BT=&H80
160 IF(V ANDBT)=0THENB$=BK$ELSEB$=BL$
180 MZ$(LN)=MZ$(LN)+B$:IFLEN(MZ$(LN))>=MW THEN280
220 BT=BT/2:IFBT>=1THEN160
260 GOTO310
280 PRINTMZ$(LN):LN=LN+1
310 NEXT:IFLN<MH THEN90
340 END
350 REM MAZE AS HEX DATA
360 DATAFFFFFFF080060010BDF6FBD0BDF6FBD0BDF6FBD080000010BDBFDBD0BDBFDBD081861810FDF6FBF005F6FA0005801A0005BFDA00FDA05BF000204000FDA05BF005BFDA0005801A0005BFDA00FDBFDBF080060010BDF6FBD0BDF6FBD08C000310EDBFDB70EDBFDB7081861810BFF6FFD0BFF6FFD080000010
370 DATAFFFFFFF0,""

And this is before translating that blocky maze into the “rounded corners” and such we want. This may be far too slow to do in BASIC since most folks don’t want to “Please wait 3.5 minutes while loading…” like a program my Commodore 64 friend once showed me. Assembly language helper routines might be needed for the impatient players out there…

Blocky to less blocky

After this code converts HEX values to maze data in a string array, we can process those strings and change the characters to have the “rounded” corners and such. Here is what I came up with, based on the original version that PEEKed and POKEed displayed bytes on the screen. This time, it’s scanning strings using MID$() and altering CHR$() values.

Side Note: Does everyone here know that, while you can’t use LEFT$ or RIGHT$ to change the left or right portion of a string, you can use MID$ to change characters? If you have A$=”1234567890″ and you want to change two characters starting at the third one, you can do MID$(A$,3,2)=”XX” and you get “12XX567890”. I’ll be using that…

Here is what I came up with… Instead of PEEKing memory, I used MID$(). It’s quite the mess, and very slow since I went back to the first version of the hex-to-maze print routine, just for clarity.

0 REM HEXMAZE3.BAS
10 ' STRING SPACE IS MW*(MH+1)
20 CLEAR 28*32
30 ' SIZE OF MAZE IN DATA
40 MW=28:MH=31
50 ' ROOM FOR CONVERTED MAZE
60 DIM MZ$(MH):LN=0
70 CLS
80 ' READ A LINE OF HEX DATA
90 READ A$:IF A$="" THEN 340
100 ' EVERY 2 CHARS IS A HEX
110 FOR A=1 TO LEN(A$) STEP 2
120 ' COVERT HEX TO A VALUE
130 V=VAL("&H"+MID$(A$,A,2))
140 ' CONVERT BITS TO BLOCKS
150 BT=128
160 IF (V AND BT)=0 THEN B$=CHR$(128) ELSE B$=CHR$(175)
170 ' ADD BLOCK/SPACE TO STRING
180 MZ$(LN)=MZ$(LN)+B$
190 ' IF LEN >= MAZE W, NEXT
200 IF LEN(MZ$(LN))>=MW THEN 280
210 ' SHIFT BIT RIGHT
220 BT=INT(BT/2)
230 ' IF MORE BITS, REPEAT
240 IF BT>0 THEN 160
250 ' GET NEXT CHARACTER
260 GOTO 310
270 ' ELSE NEW MAZE LINE
280 PRINT MZ$(LN)
290 LN=LN+1
300 ' NEXT CHARACTER
310 NEXT
320 ' MORE LINES TO DO?
330 IF LN<MH THEN 90

340 ' CONVERT LINES
350 BL$=CHR$(128):PRINT
360 FOR LN=0 TO MH-1
370 FOR C=1 TO LEN(MZ$(LN))
380 IF MID$(MZ$(LN),C,1)=BL$ THEN 510
390 V$=CHR$(175)
400 IF LN>0 THEN U$=MID$(MZ$(LN-1),C,1):UR$=MID$(MZ$(LN-1),C+1,1) ELSE U$=BL$:UR$=BL$
410 IF LN>0 THEN IF C>1 THEN UL$=MID$(MZ$(LN-1),C-1,1) ELSE ELSE UL$=BL$
420 IF C>1 THEN L$=MID$(MZ$(LN),C-1,1) ELSE L$=BL$
430 IF C<LEN(MZ$(LN)) THEN R$=MID$(MZ$(LN),C+1,1) ELSE R$=BL$
440 IF LN<MH-1 THEN D$=MID$(MZ$(LN+1),C,1) ELSE D$=BL$
450 IF LN<MH-1 THEN IF C>1 THEN DL$=MID$(MZ$(LN+1),C-1,1) ELSE ELSE DL$=BL$
460 IF LN<MH-1 THEN IF C<MW THEN DR$=MID$(MZ$(LN+1),C+1,1) ELSE ELSE DR$=BL$
470 IF U$<>BL$ THEN IF L$<>BL$ THEN GOSUB 600
480 IF U$<>BL$ THEN IF R$<>BL$ THEN GOSUB 700
490 IF D$<>BL$ THEN IF L$<>BL$ THEN GOSUB 800
500 IF D$<>BL$ THEN IF R$<>BL$ THEN GOSUB 900
510 NEXT:PRINT MZ$(LN):NEXT
520 END

600 ' UP and LEFT set
610 IF UL$<>BL$ THEN V$=CHR$(ASC(V$) AND NOT 8)
620 IF R$=BL$ THEN IF DR$=BL$ THEN IF D$=BL$ THEN V$=CHR$(ASC(V$) AND NOT 1)
630 MID$(MZ$(LN),C,1)=V$:RETURN

700 ' UP and RIGHT set
710 IF UR$<>BL$ THEN V$=CHR$(ASC(V$) AND NOT 4)
720 IF L$=BL$ THEN IF DL$=BL$ THEN IF D$=BL$ THEN V$=CHR$(ASC(V$) AND NOT 2)
730 MID$(MZ$(LN),C,1)=V$:RETURN

800 ' DOWN and LEFT set
810 IF DL$<>BL$ THEN V$=CHR$(ASC(V$) AND NOT 2)
820 IF U$=BL$ THEN IF UR$=BL$ THEN IF R$=BL$ THEN V$=CHR$(ASC(V$) AND NOT 4)
830 MID$(MZ$(LN),C,1)=V$:RETURN

900 ' DOWN and RIGHT set
910 IF DR$<>BL$ THEN V$=CHR$(ASC(V$) AND NOT 1)
920 IF L$=BL$ THEN IF UL$=BL$ THEN IF U$=BL$ THEN V$=CHR$(ASC(V$) AND NOT 8)
930 MID$(MZ$(LN),C,1)=V$:RETURN

1000 REM MAZE AS HEX DATA
1010 DATAFFFFFFF080060010BDF6FBD0BDF6FBD0BDF6FBD080000010BDBFDBD0BDBFDBD081861810FDF6FBF005F6FA0005801A0005BFDA00FDA05BF000204000FDA05BF005BFDA0005801A0005BFDA00FDBFDBF080060010BDF6FBD0BDF6FBD08C000310EDBFDB70EDBFDB7081861810BFF6FFD0BFF6FFD080000010
1020 DATAFFFFFFF0,""

Testing on the XRoar emulator shows this whole process takes about 161 seconds.

That’s so slow. Perhaps removing some of the CHR$() stuff and changing strings to values (like the originally PEEK version), skipping some of the extra IFs (like, if you are on line 0, there is no up, up left, or up right; if you are on the last line, there is no bottom, bottom left, or bottom right) and some other minor things might help…

340 ' CONVERT LINES
350 BL=128:PRINT
360 FOR LN=0 TO MH-1
365 LL=LEN(MZ$(LN))
370 FOR C=1 TO LL
380 IF ASC(MID$(MZ$(LN),C,1))=BL THEN 510
390 V=175

395 IF LN=0 THEN U=BL:UR=BL:UL=BL:GOTO 420
400 U=ASC(MID$(MZ$(LN-1),C,1)) ELSE U=BL
405 IF C<LL THEN UR=ASC(MID$(MZ$(LN-1),C+1,1)) ELSE UR=BL
410 IF C>1 THEN UL=ASC(MID$(MZ$(LN-1),C-1,1)) ELSE UL=BL

420 IF C>1 THEN L=ASC(MID$(MZ$(LN),C-1,1)) ELSE L=BL
430 IF C<LL THEN R=ASC(MID$(MZ$(LN),C+1,1)) ELSE R=BL

435 IF LN=MH-1 THEN D=BL:DL=BL:DR=BL:GOTO 470
440 D=ASC(MID$(MZ$(LN+1),C,1)) ELSE D=BL
450 IF C>1 THEN DL=ASC(MID$(MZ$(LN+1),C-1,1)) ELSE DL=BL
460 IF C<LL THEN DR=ASC(MID$(MZ$(LN+1),C+1,1)) ELSE DR=BL

470 IF U<>BL THEN IF L<>BL THEN GOSUB 600
480 IF U<>BL THEN IF R<>BL THEN GOSUB 700
490 IF D<>BL THEN IF L<>BL THEN GOSUB 800
500 IF D<>BL THEN IF R<>BL THEN GOSUB 900
510 NEXT:PRINT MZ$(LN):NEXT
515 PRINT TIMER,TIMER/60
520 END

600 ' UP and LEFT set
610 IF UL<>BL THEN V=(V AND NOT 8)
620 IF R=BL THEN IF DR=BL THEN IF D=BL THEN V=(V AND NOT 1)
630 MID$(MZ$(LN),C,1)=CHR$(V):RETURN

700 ' UP and RIGHT set
710 IF UR<>BL THEN V=(V AND NOT 4)
720 IF L=BL THEN IF DL=BL THEN IF D=BL THEN V=(V AND NOT 2)
730 MID$(MZ$(LN),C,1)=CHR$(V):RETURN

800 ' DOWN and LEFT set
810 IF DL<>BL THEN V=(V AND NOT 2)
820 IF U=BL THEN IF UR=BL THEN IF R=BL THEN V=(V AND NOT 4)
830 MID$(MZ$(LN),C,1)=CHR$(V):RETURN

900 ' DOWN and RIGHT set
910 IF DR<>BL THEN V=(V AND NOT 1)
920 IF L=BL THEN IF UL=BL THEN IF U=BL THEN V=(V AND NOT 8)
930 MID$(MZ$(LN),C,1)=CHR$(V):RETURN

Well, that brings it down to 155 seconds and that simply won’t do. There are many other optimizations to this BASIC program that could be done, and I bet we could even double the speed. But even then, that’s far too long to wait for a maze level to be drawn.

It looks like an assembly language routine might be our only option. We’ll take a break for now, but revisit this later after some discussion on assembly language. At some point. Eventually.

Until next time…

Leave a Reply

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