Episode 6: The Round and the Square — Displaying the Maze Map
Last episode we prepared the maze-data, but didn't find time to put it onto the display. Something that should be rectified today.
The Round and the Square Things
We may think that displaying the maze would be trivial. Alas, it's not.
This is because we chose to display the maze by units of 3 × 3 pixels and are so bound to align these with a line height of 8 pixels. Thus we have to match multiples of 3 with multiples of 8. It's a bit like with the air cleaners in Apollo
So, we chose the hard thing, but without any of the Kennedy-heroism. We chose to draw the maze map at 3-pixel units and do the other things, not because they are easy, but because … it's our only choice. — We need at least 3 pixels to display a directional triangle marking the player's position and there just isn't screen real estate for more.
Fortunately, this is neither a beauty contest nor a time-critical issue, since we're going to draw the map only once. The only thing that may be of importance is to maintain some flexibility regarding the position of the map-view on the screen. And this is what we're going to do:
The Round and the Square (Fitting multiples of 3 into 8 bits) Maze A Row n A A --+ 0 B | 1 Row n+1 B | 2 B | 3 <-- current display line C | 4 Row n+2 C | 5 C | 6 D --+ 7 Row n+3 D D
Since we have to write a byte representing a column of 8 pixels at once, we're going to scan and assemble the maze data by a sliding window of a height of 8. We can see that this may involve up to 4 rows of maze-data for a single display line and that there will be an offset in pixel-matching depending on the previous display line or the starting position relative to the top-edge of the screen. This offset may be theoretically in the range of -2 to 7 and we'll encode it in an array of a length of 10:
Vertical Offsets and Bit Patterns (for up to 3 Pixels) -2 1 1 pixel @ y=0 -1 3 2 pixels @ y=0 0 7 3 pixels @ y=0 1 14 -- " -- @ y=1 2 28 -- " -- @ y=2 3 56 -- " -- @ y=3 4 112 -- " -- @ y=4 5 224 -- " -- @ y=5 6 192 2 pixels @ y=6 7 128 1 pixel @ y=7
Another array (see the second column) may come handy for assembling the byte to be sent to the display: Deplorably, there are neither shifts nor rotates in MS BASIC, and we really don't want to do multiple multiplications and modular operations on every single byte. Therefor we encode the values of up to 3 pixels corresponding to the respective offsets (0..9) and OR them into a single byte. For offsets 0 and 9, there will be just a single pixel to display, for offsets 1 and 8 two of them, and for all the others (2..7) three pixels at once. Moreover, we'll send the resulting byte three times to the display, since we have to fill squares of 3 × 3.
Displaying the Maze
Time to come up with some naming conventions: We'll use a "C" for numeric constants, "M" for anything related to the maze, "S" for screen related stuff, "P" for ports, and "B" for anything that deals with bytes and bit-patterns.
Here is an outline of the program (for readability without line numbers, but rather using labels):
<setup>: REM numeric constants C0=0:C1=1:C2=2:C3=3:C4=4:C5=5:C6=6:C7=7:C8=8:C9=9:CA=10:CL=50:CP=64 REM port numbers and segment related PA=185:PB=186:PC=254:PD=255 DIM SA(9),SB(9):SG=-1:SH=0 FOR I=C0 TO C9:READ B1,B2:SA(I)=B1:SB(I)=B2:NEXT REM setup the maze (position and data) MW=31:MH=15:ML=132:MT=5 DIM M(MH,MW),BM(C9) FOR Y=C0 TO C9:READ B:BM(Y)=B:NEXT FOR Y=C0 TO MH:FOR X=C0 TO MW:READ B:M(Y,X)=B:NEXT:NEXT <main>: CLS:GOSUB <maze-map-display>:END <screen-select>: SH=SG:SG=SX\CL:IF SY>C3 THEN SG=SG+C5 IF (SG<>SH) THEN OUT PA,SA(SG):OUT PB,SB(SG) OUT PC,(SY MOD C4)*CP OR SX MOD CL RETURN <maze-map-display>: REM init the display loop SY=MT\C8:Y0=0:D=MT MOD C8+C2 <display-loop>: REM setup Y1..Y3 and B0..B3 according to Y0 and D Y1=Y0+C1:Y2=Y0+C2:Y3=Y0+C3 B0=BM(D) IF (Y1<=MH) AND (D<C7) THEN B1=BM(D+C3):ELSE B1=C0 IF (Y2<=MH) AND (D<C4) THEN B2=BM(D+C6):ELSE B2=C0 IF (Y3<=MH) AND (D=C0) THEN B3=BM(D+C9):ELSE B3=C0 REM display a line SX=ML GOSUB <screen-select> FOR MX=C0 TO MW IF M(Y0,MX)=C0 THEN B=B0 ELSE B=C0 IF B1 THEN IF M(Y1,MX)=C0 THEN B=B OR B1 IF B2 THEN IF M(Y2,MX)=C0 THEN B=B OR B2 IF B3 THEN IF M(Y3,MX)=C0 THEN B=B OR B3 FOR I=C0 TO C2 IF SX MOD CL=C0 THEN GOSUB <screen-select> OUT PD,B SX=SX+C1 NEXT NEXT REM iterate Y0, D, SY Y0=Y0+(CA-D)\C3:IF Y0>MH THEN RETURN D=(D+C1)MOD C3:SY=SY+C1:GOTO <display-loop> <data-section>: REM port patterns for blocks (0..9: PA,PB) DATA 1,0,2,0,4,0,8,0,16,0,32,0,64,0,128,0,0,1,0,2 REM maze bit patterns DATA 1,3,7,14,28,56,112,224,192,128 REM maze data DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DATA 0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0 DATA 0,1,0,1,0,1,0,0,0,1,0,0,1,0,0,0,1,0,0,0,0,1,0,0,0,1,0,0,0,0,1,0 DATA 0,0,0,1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,0,1,1,1,0 DATA 0,1,0,1,0,0,1,0,0,0,0,1,0,1,0,0,0,1,0,1,0,1,0,1,0,0,1,1,1,0,0,0 DATA 0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,0,1,0,1,1,1,1,1,1,0,0,0,1,1,0,0 DATA 0,1,0,1,0,0,0,0,0,0,0,1,0,1,1,1,1,1,0,0,0,0,0,0,1,1,1,1,0,1,1,0 DATA 0,1,0,1,1,1,1,1,1,1,0,1,0,1,0,0,0,1,1,1,1,1,0,0,1,0,0,1,1,0,0,0 DATA 0,1,0,0,0,1,0,0,0,1,1,1,0,1,1,1,0,0,0,0,0,1,0,1,1,1,0,1,0,0,1,0 DATA 0,1,1,1,0,1,1,1,0,1,0,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,0,1,1,1,1,0 DATA 0,0,0,1,0,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,0,1,0,1,0 DATA 0,1,0,1,0,1,0,0,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,1,1,0,1,0 DATA 0,1,0,1,0,1,1,1,1,1,0,1,0,1,0,1,1,1,1,1,1,1,0,1,1,0,1,0,1,0,1,0 DATA 0,1,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,1,0 DATA 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0 DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Mind the double IFs ("IF B1 THEN IF M(Y1,MX)=C0 THEN B=B OR B1
", etc.): No short circuit evalution with AND in BASIC to stay inside array bounds. (Totally forgot about that, had to fix the program.)
An attentive reader may have observed that we're using a simplified version of the display-block/segment select commands, where we just write the two lowest bits directly to port PB (instead of first reading it, masking the 6 higher bits, and write them back with the two lowest bits ORed.) This short-cut approach is found in some other display programs, and a few tests show that we should be ok with this, too.
And this is the proper program to put our map-view of the maze onto the screen:
10 REM Maze display test 20 DEFINT A-Z 29 REM M: 1 = PC-8201A, 2 = M10 (no modem), 3 = Model 100 30 P=PEEK(1):M = (P=148)*-1 + (P=35)*-2 + (P=51)*-3 40 C0=0:C1=1:C2=2:C3=3:C4=4:C5=5:C6=6:C7=7:C8=8:C9=9:CA=10:CL=50:CP=64 50 PA=185:PB=186:PC=254:PD=255:DIM SA(9),SB(9):SG=-1:SH=0 60 FOR I=C0 TO C9:READ B1,B2:SA(I)=B1:SB(I)=B2:NEXT 99 REM setup the maze 100 MW=31:MH=15:ML=132:MT=5:DIM M(MH,MW),BM(C9) 110 FOR Y=C0 TO C9:READ B:BM(Y)=B:NEXT 120 FOR Y=C0 TO MH:FOR X=C0 TO MW:READ B:M(Y,X)=B:NEXT:NEXT 199 REM == main == 200 CLS:GOSUB 400:END 298 REM == subroutines == 299 REM segement/pos select 300 SH=SG:SG=SX\CL:IF SY>C3 THEN SG=SG+C5 310 IF (SG<>SH) THEN OUT PA,SA(SG):OUT PB,SB(SG) 320 OUT PC,(SY MOD C4)*CP OR SX MOD CL:RETURN 399 REM maze display 400 SY=MT\C8:Y0=0:D=MT MOD C8+C2 410 ON M GOSUB 910,920,930 420 Y1=Y0+C1:Y2=Y0+C2:Y3=Y0+C3:B0=BM(D) 430 IF (Y1<=MH) AND (D<C7) THEN B1=BM(D+C3):ELSE B1=C0 440 IF (Y2<=MH) AND (D<C4) THEN B2=BM(D+C6):ELSE B2=C0 450 IF (Y3<=MH) AND (D=C0) THEN B3=BM(D+C9):ELSE B3=C0 460 SX=ML:GOSUB 300 470 FOR MX=C0 TO MW 480 IF M(Y0,MX)=C0 THEN B=B0 ELSE B=C0 490 IF B1 THEN IF M(Y1,MX)=C0 THEN B=B OR B1 500 IF B2 THEN IF M(Y2,MX)=C0 THEN B=B OR B2 510 IF B3 THEN IF M(Y3,MX)=C0 THEN B=B OR B3 520 FOR I=C0 TO C2 530 IF SX MOD CL=C0 THEN GOSUB 300 540 OUT PD,B:SX=SX+C1:NEXT:NEXT 550 Y0=Y0+(CA-D)\C3:IF Y0>MH THEN 570 560 D=(D+C1)MOD C3:SY=SY+C1:GOTO 420 570 ON M GOSUB 960,970,980 580 RETURN 900 REM disable interrupts 910 EXEC 30437:RETURN 920 CALL 29558:RETURN 930 CALL 30300:RETURN 950 REM enable interrupts 960 EXEC 29888:RETURN 970 CALL 28998:RETURN 980 CALL 29756:RETURN 1000 REM port patterns for segments (0..9: PA,PB) 1001 DATA 1,0,2,0,4,0,8,0,16,0,32,0,64,0,128,0,0,1,0,2 1002 REM maze bit patterns 1003 DATA 1,3,7,14,28,56,112,224,192,128 1010 REM maze data 1011 DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 1012 DATA 0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0 1013 DATA 0,1,0,1,0,1,0,0,0,1,0,0,1,0,0,0,1,0,0,0,0,1,0,0,0,1,0,0,0,0,1,0 1014 DATA 0,0,0,1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,0,1,1,1,0 1015 DATA 0,1,0,1,0,0,1,0,0,0,0,1,0,1,0,0,0,1,0,1,0,1,0,1,0,0,1,1,1,0,0,0 1016 DATA 0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,0,1,0,1,1,1,1,1,1,0,0,0,1,1,0,0 1017 DATA 0,1,0,1,0,0,0,0,0,0,0,1,0,1,1,1,1,1,0,0,0,0,0,0,1,1,1,1,0,1,1,0 1018 DATA 0,1,0,1,1,1,1,1,1,1,0,1,0,1,0,0,0,1,1,1,1,1,0,0,1,0,0,1,1,0,0,0 1019 DATA 0,1,0,0,0,1,0,0,0,1,1,1,0,1,1,1,0,0,0,0,0,1,0,1,1,1,0,1,0,0,1,0 1020 DATA 0,1,1,1,0,1,1,1,0,1,0,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,0,1,1,1,1,0 1021 DATA 0,0,0,1,0,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,0,1,0,1,0 1022 DATA 0,1,0,1,0,1,0,0,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,1,1,0,1,0 1023 DATA 0,1,0,1,0,1,1,1,1,1,0,1,0,1,0,1,1,1,1,1,1,1,0,1,1,0,1,0,1,0,1,0 1024 DATA 0,1,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,1,0 1025 DATA 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0 1026 DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
A bit of anxious waiting while the data is read in — and this is what we get (and, yes, it's doesn't appear instantly on the screen, but it is substantially faster than PSET):
Next time (probably later in the day), we'll implement roaming, which should bring us closer to the half-time mark of our little project.
P.S.: About 100 days of battery-life and counting … (compare the previous episode).
▶ Next: Episode 7: Roaming the Maze
◀ Previous: Episode 5: From Mockup to Data
▲ Back to the index.
2016-01-16, Vienna, Austria
www.masswerk.at – contact me.
— This series is part of Retrochallenge 2016/01. —