Episode 11: A Path to the Maze
In the previous episode we were, once again, exploring the past, now it's time to look ahead, to see, what lies in front of us.
DATAism
Currently, we've got a roamable maze, which isn't that bad at all, but we really should be advancing in our project. Today we're going to implement a path view relative to the player's position. It won't be the final perspective view, it's merely about drawing a symbolic representation, something of a distant remembrance of Rogue.
Maybe you were wondering, back in episode #5 when we were encoding the maze data, why we hadn't used some nifty 8-bit compression, so that our quite massive amount of encoded maze data would have looked some like this:
1000 REM maze data as bit vectors, just 3 bytes per row 1001 DATA 0,0,0,255,191,255,42,19,32,233,251,175,74,40,170,255,142,251,10,233,3,251 1002 DATA 42,63,34,238,161,238,170,174,168,186,170,42,170,170,235,171,191,10,8,5 1003 DATA 255,255,255,0,0,0
I've to confess, I had some ulteriors on this, and they were all about today's episode.
And here is our evil plan for maze domination. … Maze Domination! … Today, it may be just the Maze, but tomorrow, tomorrow, it's the World! … harhar … (exit, hallways echo of hollow laughter).
Er … Sorry … er … got carried away … somehow … er … however …
I was going to write some about a little plan (rubs hands), and this is what it is about: We're going to encode directions to any neighboring maze-cells into the individual data points. For this, we'll stay with our directional encoding (as in 0: up, 1: left, 2: down, 3: right) and raise them to powers of two. As we collect the data, we also encode the connections as directional bit-vectors. As before, a data point representing a wall will be just zero.
1019 REM maze data (encoded connections: 1: up, 2: left, 4: down, 8: right) 1020 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 1021 DATA 0,12,10,14,10,14,10,10,10,14,10,10,14,2,0,8,14,10,10,10,10,14,10,10,10,14,10,10,10,10,6,0 1022 DATA 0,1,0,5,0,5,0,0,0,5,0,0,5,0,0,0,5,0,0,0,0,5,0,0,0,5,0,0,0,0,5,0 1023 DATA 0,0,0,5,0,9,14,10,10,3,0,12,11,14,10,10,11,14,10,6,0,5,0,12,10,11,6,0,12,10,3,0 1024 DATA 0,4,0,5,0,0,5,0,0,0,0,5,0,1,0,0,0,5,0,5,0,5,0,5,0,0,9,10,7,0,0,0 1025 DATA 0,13,10,15,10,10,11,10,10,10,10,7,0,0,0,4,0,5,0,9,10,11,10,11,6,0,0,0,9,6,0,0 1026 DATA 0,5,0,5,0,0,0,0,0,0,0,5,0,12,10,11,10,7,0,0,0,0,0,0,13,10,10,6,0,9,2,0 1027 DATA 0,5,0,9,10,14,10,10,10,6,0,5,0,5,0,0,0,9,10,10,10,6,0,0,5,0,0,13,2,0,0,0 1028 DATA 0,5,0,0,0,5,0,0,0,13,10,7,0,13,10,6,0,0,0,0,0,5,0,12,11,6,0,5,0,0,4,0 1029 DATA 0,9,10,6,0,13,10,6,0,5,0,5,0,5,0,5,0,12,10,6,0,5,0,5,0,5,0,9,14,10,7,0 1030 DATA 0,0,0,5,0,5,0,1,0,5,0,13,10,7,0,5,0,5,0,5,0,5,0,5,0,5,0,0,5,0,5,0 1031 DATA 0,4,0,5,0,5,0,0,0,5,0,5,0,5,0,5,0,5,0,5,0,5,0,5,0,9,14,10,7,0,5,0 1032 DATA 0,5,0,5,0,9,10,10,10,3,0,5,0,1,0,9,10,11,14,11,10,3,0,9,6,0,5,0,1,0,5,0 1033 DATA 0,5,0,5,0,0,0,0,0,0,0,5,0,0,0,0,0,0,5,0,0,0,0,0,5,0,5,0,0,0,5,0 1034 DATA 0,9,10,11,10,10,10,10,10,10,10,11,10,10,10,10,10,10,11,10,10,10,10,10,11,10,11,10,10,10,3,0 1035 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
See the modified JS-program used to aggregate the maze data ▶here.
We already know, what's left and right of us, thank's to the modular operations we apply when performing a turn. Now we add some DATA statements to provide the respective connection codes. And while we're at it, we may encode as well the relative turns, to save some runtime. By this, we get some like the following:
1002 REM directions (0..3:dx,dy) 1003 DATA 0,-1, -1,0, 0,1, 1,0 1004 REM turns (L: 0..3, R: 0..3) 1005 DATA 1,2,3,0, 3,0,1,2 1006 REM directional codes (2^0..3) 1007 DATA 1,2,4,8
We'll read the turns into arrays DL and DR respectively, and do the same with our new directional connection codes for array DD. Moreover, we save some other happy little bits (no Bob Ross didn't paint those) and runtime, as well, and store the current values of DX, DY, DL, and DR in simple variables of the same name (yes, these are separate namespaces), like "DX=DX(MD)
", where MD is our current heading/direction.
And while we're at optimizations, you may recall the active pixels of the map-marker for any of the four headings, being 4 pixels each, which have to be cleared and repainted whenever the player moves or turns:
1010 DATA 0,1, 1,0, 1,1, 1,2 1020 DATA 0,1, 1,0, 1,1, 2,1 1030 DATA 1,0, 1,1, 1,2, 2,1 1040 DATA 0,1, 1,1, 1,2, 2,1
Obviousy, for any turn, there's only a single pixel that must be erased and another one that must be set. We can optimize on this by storing the changing pixels in another data statement, to be read into array ST:
1010 REM maze marker turn pixel mods (Y,X) 1011 DATA 2,1,1,0, 1,2,2,1, 0,1,1,2, 1,0,0,1
Whenever the player turns left, the pixel described by the first pair of relative offsets is to be erased and the second pair gives the position of the pixel to be newly set. If a right turn occurs, it's the other way round, but using the old direction as the subscript.
Drawing the Path
Back to our little plan (no, we had already enough of this villain theme). Now, that we have the connections encoded in our maze data and know what's relatively left and right of us, it's easy to collect a path (array V) of data points lying straight ahead of the player, starting at the very position in the maze:
DIM V(7) :REM path for view-port MW=31:MH=15 :REM maze dimensions MX=11:MY=7:MD=0 :REM init player DX=DX(MD):DY=DY(MD) DL=DL(MD):DR=DR(MD) GOTO <collect-path> <collect-path>: Y=MY:X=MX :REM copy current position FOR I=0 TO 7 V(I)=M(Y,X) :REM collect path X=X+DX:Y=Y+DY :REM advance the data cursors IF (X<0) OR (X>MW) OR (Y<0) OR (Y>MH) THEN <render-view> NEXT <render-view>: VW=0 :REM flag: hit wall VL=DD(DL):VR=DD(DR) :REM get code for left & right FOR I=0 TO 5 PRINT CHR$(27);"Y";CHR$(32+7-I);CHR$(32+6); :REM set cursor using ANSI escapes IF VW THEN PRINT " ";:GOTO <iterate> :REM Hit wall, just spaces IF V(I) AND VL THEN PRINT ".";:ELSE PRINT " "; :REM left of the path IF I=0 THEN PRINT "o"; ELSE PRINT "."; :REM on the path IF V(I) AND VR THEN PRINT ".";:ELSE PRINT " "; :REM right of the path VW=VW OR V(I+1)=0 :REM update wall flag <iterate>: NEXT GOTO <next-task>
We render the path from the bottom up, starting at the current player position, represented by an "o". For any passage in front of us, or to the left or right, we'll render a dot, if there's a wall, or it's hidden by a wall in front of us, it's just blanks.
And this is what we get (Virtual T):
Note: Contrary to my last rant, the key detection of Virtual T isn't that bad, if running at speeds beyond realistic emulation.
And this is the program at its current state. We also added support for the TRS-80 Model 102 and the Kyotronic 85, as promissed in last episode. Also, we rename the model-ID from M to G for clarity, with a bow to the Gestalt-ID of classic Macs.
10 REM Maze Roamer with Path View 20 DEFSNG A:DEFINT B-Z:SCREEN 0,0:CLS:PRINT"Setting up..." 29 REM G: 1=PC-8201A, 2=M10 (no modem), 3=Model 100, 4=Model 102, 5=KC85 30 B=PEEK(1):G= -(B=148) -(B=35)*2 -(B=51)*3 -(B=167)*4 -(B=225)*5 35 IF G=0 THEN SCREEN 0,1:PRINT "Sorry, model not supported. Gestalt:";B:END 40 IF G=1 THEN AK=65128!:ELSE IF G=2 THEN AK=65389!:ELSE IF G=5 THEN AK=65387!:ELSE AK=65450! 50 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 60 UC=223:LC=97:CK=27:ES=27:EB=32 70 PA=185:PB=186:PC=254:PD=255:DIM SA(9),SB(9):SG=-1:SH=0 80 FOR I=C0 TO C9:READ B1,B2:SA(I)=B1:SB(I)=B2:NEXT 90 DX=0:DY=0:DIM DX(3),DY(3):FOR I=C0 TO C3:READ B1,B2:DX(I)=B1:DY(I)=B2:NEXT 92 DL=0:DR=0:DIM DL(3),DR(3) 94 FOR I=C0 TO C3:READ B:DL(I)=B:NEXT 96 FOR I=C0 TO C3:READ B:DR(I)=B:NEXT 98 DIM DD(3):FOR I=C0 TO C3:READ B:DD(I)=B:NEXT 99 REM setup the maze 100 DIM SM(3,3,2):FOR I=C0 TO C3:FOR Y=C0 TO C3:READ B1,B2:SM(I,Y,C1)=B1:SM(I,Y,C0)=B2:NEXT:NEXT 110 DIM ST(3,1,1):FOR I=C0 TO C3:FOR Y=C0 TO C1:READ B1,B2:ST(I,Y,C1)=B1:ST(I,Y,C0)=B2:NEXT:NEXT 120 MW=31:MH=15:ML=132:MT=5:DIM M(MH,MW),BM(C9) 130 FOR Y=C0 TO C9:READ B:BM(Y)=B:NEXT 140 FOR Y=C0 TO MH:FOR X=C0 TO MW:READ B:M(Y,X)=B:NEXT:NEXT 150 REM setup view port 160 DIM V(C7) 199 REM == main == 200 CLS:GOSUB 700:MX=11:MY=7:MD=0:DX=DX(MD):DY=DY(MD):DL=DL(MD):DR=DR(MD) 210 PRINT CHR$(ES);"Y";CHR$(EB+C7);CHR$(EB+22);"Crsr,IJKL,Q:quit"; 215 GOSUB 410:GOSUB 610 220 B=PEEK(AK):IF B=C0 THEN 220 230 K=PEEK(AK+B):POKE AK,C0:IF K>CK THEN ON K-CK GOTO 500,520,540,560 240 IF K>=LC THEN K=K AND UC 250 ON INSTR("JLKI Q",CHR$(K)) GOTO 520,500,560,540,580,590 260 GOTO 220 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 clear/draw marker 400 X=ML+MX*C3:Y=MT+MY*C3:FOR I=C0 TO C3:PRESET(X+SM(MD,I,C0),Y+SM(MD,I,C1)):NEXT:RETURN 410 X=ML+MX*C3:Y=MT+MY*C3:FOR I=C0 TO C3:PSET(X+SM(MD,I,C0),Y+SM(MD,I,C1)):NEXT:RETURN 420 X=ML+MX*C3:Y=MT+MY*C3:PRESET(X+ST(MD,T0,C0),Y+ST(MD,T0,C1)):PSET(X+ST(MD,T1,C0),Y+ST(MD,T1,C1)):RETURN 499 REM key handling (right, left, bkwd, fwd, fire, quit) 500 T0=C1:T1=C0:GOSUB 420:MD=DR:DX=DX(MD):DY=DY(MD):DL=DL(MD):DR=DR(MD):GOTO 610 520 MD=DL:T0=C0:T1=C1:GOSUB 420:DX=DX(MD):DY=DY(MD):DL=DL(MD):DR=DR(MD):GOTO 610 540 GOSUB 400:MX=MX+DX:MY=MY+DY:IF M(MY,MX)=C0 THEN MX=MX-DX:MY=MY-DY:GOSUB 410:GOTO 220 550 GOSUB 410:GOTO 610 560 GOSUB 400:MX=MX-DX:MY=MY-DY:IF M(MY,MX)=C0 THEN MX=MX+DX:MY=MY+DY:GOSUB 410:GOTO 220 570 GOSUB 410:GOTO 610 580 GOTO 220:REM shoot 590 SCREEN 0,1:END 600 REM view port, get cells 610 Y=MY:X=MX 620 FOR I=C0 TO C7:V(I)=M(Y,X):X=X+DX:Y=Y+DY:IF (X<C0) OR (X>MW) OR (Y<C0) OR (Y>MH) THEN 640 630 NEXT 639 REM render *ersatz* view 640 VW=C0:VL=DD(DL):VR=DD(DR) 645 FOR I=C0 TO C5:PRINT CHR$(ES);"Y";CHR$(EB+C7-I);CHR$(EB+C6); 650 IF VW THEN PRINT " ";:GOTO 675 655 IF V(I) AND VL THEN PRINT ".";:ELSE PRINT " "; 660 IF I=0 THEN PRINT "o"; ELSE PRINT "."; 665 IF V(I) AND VR THEN PRINT ".";:ELSE PRINT " "; 670 VW=VW OR V(I+C1)=C0 675 NEXT:GOTO 220 699 REM maze display 700 SY=MT\C8:Y0=0:D=MT MOD C8+C2:ON G GOSUB 910,920,930,930,940 710 Y1=Y0+C1:Y2=Y0+C2:Y3=Y0+C3:B0=BM(D) 720 IF (Y1<=MH) AND (D<C7) THEN B1=BM(D+C3):ELSE B1=C0 730 IF (Y2<=MH) AND (D<C4) THEN B2=BM(D+C6):ELSE B2=C0 740 IF (Y3<=MH) AND (D=C0) THEN B3=BM(D+C9):ELSE B3=C0 750 SX=ML:GOSUB 300 760 FOR MX=C0 TO MW:IF M(Y0,MX)=C0 THEN B=B0 ELSE B=C0 770 IF B1 THEN IF M(Y1,MX)=C0 THEN B=B OR B1 780 IF B2 THEN IF M(Y2,MX)=C0 THEN B=B OR B2 790 IF B3 THEN IF M(Y3,MX)=C0 THEN B=B OR B3 800 FOR I=C0 TO C2:IF SX MOD CL=C0 THEN GOSUB 300 810 OUT PD,B:SX=SX+C1:NEXT:NEXT 820 Y0=Y0+(CA-D)\C3:IF Y0>MH THEN 840 830 D=(D+C1)MOD C3:SY=SY+C1:GOTO 710 840 ON G GOSUB 960,970,980,980,990:RETURN 900 REM disable interrupts 910 EXEC 30437:RETURN 920 CALL 29558:RETURN 930 CALL 30300:RETURN 940 CALL 29450:RETURN 950 REM enable interrupts 960 EXEC 29888:RETURN 970 CALL 28998:RETURN 980 CALL 29756:RETURN 990 CALL 28906: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 directions (0..3:dx,dy) 1003 DATA 0,-1,-1,0,0,1,1,0 1004 REM turns (L: 0..3, R: 0..3) 1005 DATA 1,2,3,0, 3,0,1,2 1006 REM directional codes (2^0..3) 1007 DATA 1,2,4,8 1008 REM maze maker defs (0..3:Y,X) 1009 DATA 0,1,1,0,1,1,1,2, 0,1,1,0,1,1,2,1, 1,0,1,1,1,2,2,1, 0,1,1,1,1,2,2,1 1010 REM maze marker turn pixel mods (Y,X) 1011 DATA 2,1,1,0, 1,2,2,1, 0,1,1,2, 1,0,0,1 1012 REM maze bit patterns 1013 DATA 1,3,7,14,28,56,112,224,192,128 1019 REM maze data 1020 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 1021 DATA 0,12,10,14,10,14,10,10,10,14,10,10,14,2,0,8,14,10,10,10,10,14,10,10,10,14,10,10,10,10,6,0 1022 DATA 0,1,0,5,0,5,0,0,0,5,0,0,5,0,0,0,5,0,0,0,0,5,0,0,0,5,0,0,0,0,5,0 1023 DATA 0,0,0,5,0,9,14,10,10,3,0,12,11,14,10,10,11,14,10,6,0,5,0,12,10,11,6,0,12,10,3,0 1024 DATA 0,4,0,5,0,0,5,0,0,0,0,5,0,1,0,0,0,5,0,5,0,5,0,5,0,0,9,10,7,0,0,0 1025 DATA 0,13,10,15,10,10,11,10,10,10,10,7,0,0,0,4,0,5,0,9,10,11,10,11,6,0,0,0,9,6,0,0 1026 DATA 0,5,0,5,0,0,0,0,0,0,0,5,0,12,10,11,10,7,0,0,0,0,0,0,13,10,10,6,0,9,2,0 1027 DATA 0,5,0,9,10,14,10,10,10,6,0,5,0,5,0,0,0,9,10,10,10,6,0,0,5,0,0,13,2,0,0,0 1028 DATA 0,5,0,0,0,5,0,0,0,13,10,7,0,13,10,6,0,0,0,0,0,5,0,12,11,6,0,5,0,0,4,0 1029 DATA 0,9,10,6,0,13,10,6,0,5,0,5,0,5,0,5,0,12,10,6,0,5,0,5,0,5,0,9,14,10,7,0 1030 DATA 0,0,0,5,0,5,0,1,0,5,0,13,10,7,0,5,0,5,0,5,0,5,0,5,0,5,0,0,5,0,5,0 1031 DATA 0,4,0,5,0,5,0,0,0,5,0,5,0,5,0,5,0,5,0,5,0,5,0,5,0,9,14,10,7,0,5,0 1032 DATA 0,5,0,5,0,9,10,10,10,3,0,5,0,1,0,9,10,11,14,11,10,3,0,9,6,0,5,0,1,0,5,0 1033 DATA 0,5,0,5,0,0,0,0,0,0,0,5,0,0,0,0,0,0,5,0,0,0,0,0,5,0,5,0,0,0,5,0 1034 DATA 0,9,10,11,10,10,10,10,10,10,10,11,10,10,10,10,10,10,11,10,10,10,10,10,11,10,11,10,10,10,3,0 1035 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
As for the real thing, the state of affairs on the Olivetti M10:
And on the NEC PC-8201A:
Now, we've only to render this as a perspective in a view-port, pseudo-3D …
— Stay tuned! —
▶ Next: Episode 12: A Screen With a View
◀ Previous: Episode 10: Cross-Platform ROM Diving
▲ Back to the index.
2016-01-21, Vienna, Austria
www.masswerk.at – contact me.
— This series is part of Retrochallenge 2016/01. —