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…