Making a maze of blocks look less blocky
Okay, where were we?
Right. We wanted to take this…
…and turn it in to this…
…while storing it like this…
To round or not to round…
If the maze were stored as bits representing blocks that were either set or unset (32×16 blocks will fit on the CoCo 32 column screen), we could make the program handle changing those blocks in to ones with “rounded” corners, or open areas in the middle or large blocks.
First, I needed to decide what the rules are for this, so I did some drawings.
For each corner (top left, top right, bottom left, bottom right) it seemed safe to round if the block above, beside, and diagonally from the corner were empty (empty red square). BUT, only round if the opposite sides were NOT vacant (red X). If that second part isn’t there, a single block with a path all around it would have all of its corners rounded. That’s fine on high resolution graphics (it would be a circle?), but when each block is made up of 2×2 mini squares, you can’t remove each corner or you’ll have nothin’ left!
As I found out.
Next, there are large areas made up of double blocks. On the Pac-Man maze these are larger “open” areas with thin walls around them, like the four along the top of the arcade Pac-Man game:
Don’t fill me in…
To figure out what the rules were for this, I did more drawings…
This one took me a bit longer to figure out. Above, each mini square of block is represented by the graph. Each white box is 2 blocks by 2 blocks.
After way too much trial and error, I worked it out that any corner that has an occupied square above, beside, and diagonally qualifies to be removed.
At first I tried to brute-force this in BASIC. It was way too long, and way too slow.
Then I decided as I was scanning each block location, I’d use variables to PEEK the contents of what was up, down, left or right from the blocks, as well as up-left, up-right, down-left and down-right from it. Using variables greatly sped things up (though they are still way too slow).
Once I had those variables, I could apply some simply logic.
I noticed that in both CORNERS and CENTERS, there is a need to check for an OCCUPIED block beside and either above or below. I started there, since I could do that check once, then dispatch to a second routine to determine if it was doing a corner, a center, or both.
I ran in to many bugs during all of things, and learned I had to pass each block through all the checks… Originally, I’d find a corner, remove it, and move to the next block. That would leave other corners that should have been removed, or center bits…
Finally, I came up with this (slow) proof-of-concept:
0 REM MAZEVERT.BAS 10 CLS:FOR I=0 TO 15:READ A$:PRINT@2+32*I,A$;:NEXT 20 BL=96 30 FOR M=1024 TO 1535 40 IF PEEK(M)=BL THEN 140 50 POKE M,175:V=175 60 IF M>1055 THEN U=PEEK(M-32):UR=PEEK(M-31) ELSE U=BL:UR=BL 70 IF M>1056 THEN UL=PEEK(M-33) ELSE UL=BL 80 L=PEEK(M-1):R=PEEK(M+1) 90 DL=PEEK(M+31):D=PEEK(M+32):DR=PEEK(M+33) 100 IF U<>BL THEN IF L<>BL THEN GOSUB 200 110 IF U<>BL THEN IF R<>BL THEN GOSUB 300 120 IF D<>BL THEN IF L<>BL THEN GOSUB 400 130 IF D<>BL THEN IF R<>BL THEN GOSUB 500 140 NEXT 150 FOR M=1024 TO 1535:IF PEEK(M)=96 THEN POKE M,128 160 NEXT 170 GOTO 170 200 ' UP and LEFT set 210 IF UL<>BL THEN V=V AND NOT 8 220 IF R=BL THEN IF DR=BL THEN IF D=BL THEN V=V AND NOT 1 230 POKE M,V:RETURN 300 ' UP and RIGHT set 310 IF UR<>BL THEN V=V AND NOT 4 320 IF L=BL THEN IF DL=BL THEN IF D=BL THEN V=V AND NOT 2 330 POKE M,V:RETURN 400 ' DOWN and LEFT set 410 IF DL<>BL THEN V=V AND NOT 2 420 IF U=BL THEN IF UR=BL THEN IF R=BL THEN V=V AND NOT 4 430 POKE M,V:RETURN 500 ' DOWN and RIGHT set 510 IF DR<>BL THEN V=V AND NOT 1 520 IF L=BL THEN IF UL=BL THEN IF U=BL THEN V=V AND NOT 8 530 POKE M,V:RETURN 999 GOTO 999 1000 DATA "XXXXXXXXXXXXXXXXXXXXXXXXXXXX" 1010 DATA "X XX X" 1020 DATA "X XXXX XXXXX XX XXXXX XXXX X" 1030 DATA "X XXXX XXXXX XX XXXXX XXXX X" 1040 DATA "X XXXX XXXXX XX XXXXX XXXX X" 1050 DATA "X X" 1060 DATA "X XXXX XX XXXXXXXX XX XXXX X" 1070 DATA "X XXXX XX XXXXXXXX XX XXXX X" 1080 DATA "X XX XX XX X" 1090 DATA "XXXXXX XXXXX XX XXXXX XXXXXX" 1100 DATA " X XXXXX XX XXXXX X " 1110 DATA " X XX XX X " 1120 DATA " X XX XXXXXXXX XX X " 1130 DATA "XXXXXX XX X X XX XXXXXX" 1140 DATA " X X " 1150 DATA "XXXXXX XX X X XX XXXXXX" 1160 DATA " X XX XXXXXXXX XX X " 1170 DATA " X XX XX X " 1180 DATA " X XX XXXXXXXX XX X " 1190 DATA "XXXXXX XX XXXXXXXX XX XXXXXX" 1200 DATA "X XX X" 1210 DATA "X XXXX XXXXX XX XXXXX XXXX X" 1220 DATA "X XXXX XXXXX XX XXXXX XXXX X" 1230 DATA "X XX XX X" 1240 DATA "XXX XX XX XXXXXXXX XX XX XXX" 1250 DATA "XXX XX XX XXXXXXXX XX XX XXX" 1260 DATA "X XX XX XX X" 1270 DATA "X XXXXXXXXXX XX XXXXXXXXXX X" 1280 DATA "X XXXXXXXXXX XX XXXXXXXXXX X" 1290 DATA "X X" 1300 DATA "XXXXXXXXXXXXXXXXXXXXXXXXXXXX"
The program has three phases:
- Draw the ASCII maze (or at least what will fit on the screen for this test).
- Scan the screen and convert non-blanks to semigraphics blocks, then check to see if a corner needs to be removed (because it is a corner or in the center of a larger group of blocks).
- Scan the screen one more time, changing any blank space blocks to black.
The end result is this:
The whole process takes just over 40 seconds. It could be made faster by going in to double speed mode with POKE 65495,0 (or 65497,0 on a CoCo 3), but even then it would still be way too long for the user to wait for a game screen to draw.
Plus, this doesn’t even have the code to convert the less-memory hex string representation of the maze into the actual maze.
Which means we have more to do…
To be continued…