Retrochallenge 2016/01:
Maze War for Olivetti M10 and NEC PC-8201A

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.

Putting the maze map on the Olivetti M10's display

Fitting 9 things (or more) into 8.  (Yes, this is the real thing.)

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 XIII, and we're not going to lose our head this time.

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):

The maze map on the Olivetti M10's display

Success — Not a mockup anymore.

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.

— This series is part of Retrochallenge 2016/01. —