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

Episode 12: A Screen With a View

It's more than a week since previous episode and we really ought to hurry up for a final run.

RC2016/01:Viewport into the maze

Not exactly the Toscana, but, yay, a perspective into the maze, pseudo-3D.

Nifty Plans

An entire week passed without a chance to continue on the project, bummer. (Caught a cold, bussy catching up, day off for teaching — final presentations, actually pleasant, presentations better than expected, some were really good —, birthdays to be congratulated for and to be celebrated, you name it….) On the good side, there were still some quiet moments to contemplate the project — and so we were able to come up with some really nifty plan for implementing the viewport…

The Masterplan

There is no good masterplan for real without a plan, and here is our's:

The Masterplan

Er — sorry — got the wrong one… — You've seen NOTHING!

Sorry for this one. As for the real one, our considerations are along the following lines:

Since we've found out that we're going to write to the display low-level by talking to its controllers over the serial line, we reconsider the layout of the viewport a bit. We're going for a window size of 100 × 64 pixels, spanning over 4 adjacent display blocks (segments 0 and 1 in the upper half of the display and segments 5 and 6 in the lower half). By this we're able to divide the viewport into 4 symmetrical quadrants, with the vanishing point in the exact center.

And here is the plan to our plan:

Viewport layout

Layout of the viewport for the pseudo-3D perspective into the maze.

Symmetry is nice thing, but for our purpose it's a thing of real beauty: Since we discovered that we may write to the display both left-to-right and right-to-left (by setting the direction of the auto-increment/decrement of the offset counter), we may use — more or less — the same code and definitions to write to left and right sides. And with a few adaptions we may also do the bottom half of the viewport, just add some code to reverse the vertical direction.

At closer inspection, there are some not so few areas (green) that are never to be updated, since there's no state of the viewport, where we would write to these areas. And then, there are some areas that will change only, if there was a frontally blocking wall before and the updated view hasn't any or the new blocking wall is farther away into the distance. Summing these areas up, they make up for the better half of the viewport. Good news!

Moreover, as we divide the viewport into 7 slices of depth, we may compare the new state to the previous one and will only have to update those regions of the screen that have actually changed. Even more good news!

Principally, a given left or righ part of any slice into the depth may have one of the following states:

Edges are to be drawn at corners of the maze only (following the Alto-implementation). With the semantics well defined, we may consider the layout at a per-pixel level.

Based on our layout, we may observe that there are:

Let's dive right into it…

Implementation

We may provide an extensive list of all pixels for any part of a sloped line, so that we may iterate over them:

REM line pixels for iterations
REM 1..128: sloped downwards (k=-0.5), 128..1 sloped upwards (k=0.5)
DATA  1,   1,   2,   2,   4,   4,   8,   8,  16,  16
DATA 32,  32,  64,  64, 128, 128,  64,  64,  32,  32
DATA 16,  16,   8,   8,   4,   4,   2,   2,   1,   1

Quite alike, we may come up with a list of pixels that are used for both horizontal and vertical lines. (Vertical line segments are either from any vertical pixel position filled downwards [x..128] for the upper half of the display, or in reversed direction for the lower part of the viewport [x..1].)

REM pixels for repeats
DATA 0,1,2,4,8,16,32,64,128           :REM single pixels or horizontal lines
DATA 255,254,252,248,240,224,192,128  :REM vertical lines
REM inverse direction (bottom up)
DATA 0,128,64,32,16,8,4,2,1
DATA 255,127,63,31,15,7,3,1

We're going to make use of them in some nifty way: We're going to implement a simple language for path drawing, consisting of just 4 commands:

  1. Move to position row (page) y, offset x.
  2. Repeat, until offset x is reached, the pixel with the given subscript.
  3. Iterate over the line pixel patterns to position x, starting with index i.
  4. Depending on the edge-state, draw either the pixel with the 1st subscript or the 2nd one.

As we may see, each of these commands is just three bytes long. Moreover, thanks to symmetry, we've only to provide the definitions for the first quadrant and may derive directions for the other quadrants from this.

We use this tiny command language to describe 3 states, namely, a wall on the side, a passage to the side, or a frontal blocking wall on any of the 7 depth-slices:

REM viewport commands struct, 1st quadrant, per level and mode
REM 1..set position (page, offset)
REM 2..repeat byte (stop-pos, idx of repeat-pixels)
REM 3..iterate line from offset (stop-pos, idx of line-patterns)
REM 4..alternate pixel ege:no-edge (idx of repeat-pixels, alternate idx)

DATA 0                               :REM level/distance 0
DATA 10                              :REM mode 0: side-wall (10 commands)
DATA 1,0,0, 3,5,10                   :REM  codes for row/page 0
DATA 1,1,0, 2,5,0, 2,6,1, 4,9,1      :REM  codes for row/page 1
DATA 1,2,7, 4,9,0                    :REM  codes for row/page 2
DATA 1,3,7, 4,9,0                    :REM  codes for row/page 3
DATA 9                               :REM mode 1: passage to the side (9 commands)
DATA 1,0,0, 2,5,0
DATA 1,1,0, 2,6,1, 4,9,1
DATA 1,2,7, 4,9,0
DATA 1,3,7, 4,9,0
DATA 6                               :REM mode 2: frontally blocking wall (6 commands)
DATA 1,1,8, 2,49,1
DATA 1,2,8, 2,49,0
DATA 1,3,8, 2,49,0

DATA 1                               :REM level 1
DATA 7
DATA 1,1,8,  3,15,2, 4,14,6
DATA 1,2,16, 4,9,0
DATA 1,3,16, 4,9,0
DATA 7
DATA 1,1,8,  2,15,6, 4,14,6
DATA 1,2,16, 4,9,0
DATA 1,3,16, 4,9,0
DATA 6
DATA 1,1,17, 2,49,6
DATA 1,2,17, 2,49,0
DATA 1,3,17, 2,49,0

DATA 2                               :REM level 2
DATA 8
DATA 1,1,17, 3,21,11
DATA 1,2,17, 2,21,0, 3,23,0, 4,10,2
DATA 1,3,24, 4,9,0
DATA 7
DATA 1,1,17, 2,21,0
DATA 1,2,17, 2,23,2, 4,10,2
DATA 1,3,24, 4,9,0
DATA 4
DATA 1,2,25, 2,49,2
DATA 1,3,25, 2,49,0

DATA 3                               :REM level 3
DATA 5
DATA 1,2,25, 3,30,3, 4,13,5
DATA 1,3,31, 4,9,0
DATA 5
DATA 1,2,25, 2,30,5, 4,13,5
DATA 1,3,31, 4,9,0
DATA 4
DATA 1,2,32, 2,49,5
DATA 1,3,32, 2,49,0

DATA 4                               :REM level 4
DATA 5
DATA 1,2,32, 3,36,10, 4,8,8
DATA 1,3,37, 4,9,0
DATA 5
DATA 1,2,32, 2,36,8,  4,8,8
DATA 1,3,37, 4,9,0
DATA 4
DATA 1,2,38, 2,49,8
DATA 1,3,38, 2,49,0

DATA 5                               :REM level 5
DATA 3
DATA 1,3,38, 3,41,0, 4,11,3
DATA 3
DATA 1,3,38, 2,41,3, 4,11,3
DATA 2
DATA 1,3,43, 2,49,3

DATA 6                               :REM level 6
DATA 3
DATA 1,3,43, 3,45,5, 4,13,5
DATA 3
DATA 1,3,43, 2,45,5, 4,13,5
DATA 2
DATA 1,3,47, 2,49,5

DATA 7                               :REM level 7
DATA 3
DATA 1,3,47, 3,48,9, 4,14,14
DATA 3
DATA 1,3,47, 2,48,6, 4,14,14
DATA 2
DATA 1,3,49, 2,49,14

DATA -1                              :REM end of definition

A Simple Just-In-Time Compiler

Now, we're facing the classical time vs. space paradigm: Are we going to interpet this in realtime, or should we compile this into a form that is instantly of use for drawing any of the 4 quadrants? — You bet, we're opting for the fastest runtime solution!

For this purpose, we implement a tiny compiler to assemble some 3-byte codes for any of these commands, structured by quadrant, distance-level, and mode. We're going to store this in two arrays, one containing a continuous stream of commands, and a second one, providing the individual start and end positions for any state of the 4 quadrants.

This will produce byte code instructions of the following format:

  1. Set LCD address counter to byte [page/offset] (third byte always zero).
  2. Send n times the given byte to the LCD-controller.
  3. Iterate over the line-pixels array from start-index to stop-index while sending it to the LCD.
  4. Depending on the edge-state, send either the first byte or the second one to the display.

Example:

Say, we'd want to draw the third quadrant (q), for the nearest level (l), in mode 1 (m), so we'll look up array VI to get the start-index and stop-index of the sequence of drawing commands required to display this specific part of the view.

q = 2 : l = 0 : m = 1
VI(q,l,m,0) -> start, VI(q,l,m,1) -> end

FOR I = start TO end STEP 3
ON VK(I) GOSUB <instr-1>, <instr-2>, <instr-3>, <instr-4>
NEXT

REM first argument in VK(I+1), second argument in VK(I+2).

And this is how we compile these instructions (using rather labels than line numbers):

REM segment addresses (port A)
DATA 1,32,64,2

DIM PV(3):FOR I=0 TO 3:READ B:PV(I)=B:NEXT      :REM segement codes (for port A)
DIM BL(29):FOR I=0 TO 29:READ B:BL(I)=B:NEXT    :REM bytes for lines
DIM BP(33):FOR I=0 TO 33:READ B:BP(I)=B:NEXT    :REM bytes for repeats
DIM VI(3,7,2,1)                                 :REM segment,level,mode,idx-from/idx-to
DIM VK(1392)                                    :REM compiled code

REM compile viewport instructions
I4=0
<loop>:
READ B                                           :REM readlevel
IF B<0 THEN RETURN                               :REM -1: end
FOR J=0 TO 2                                     :REM mode 0..2
READ B0                                          :REM get length
JL=B0*3
I0=I4:I1=I0+JL:I2=I1+JL:I3=I2+JL:I4=I3+JL        :REM code offsets
VI(0,B,J,0)=I0:VI(0,B,J,1)=I1-3                  :REM store them in VI
VI(1,B,J,0)=I1:VI(1,B,J,1)=I2-3
VI(2,B,J,0)=I2:VI(2,B,J,1)=I3-3
VI(3,B,J,0)=I3:VI(3,B,J,1)=I4-3
FOR K=1 TO B0                                    :REM process 3 bytes
READ B1,B2,B3                                     
VK(I0)=B1:VK(I1)=B1:VK(I2)=B1:VK(I3)=B1          :REM reuse 1st opcode as-is
ON B1 GOTO <instr1>,<instr2>,<instr3>,<instr4>   :REM assemble args for each instr.
<instr1>:                                        :REM instr. 1: set row/offset
VK(I0+1)=(B2*64) OR B3                           :REM quadrant 0 (top left)
VK(I1+1)=((3-B2)*64) OR B3                       :REM quadrant 1 (bottom left)
VK(I2+1)=((3-B2)*64) OR (CN-B3)                  :REM quadrant 2 (bottom right)
VK(I3+1)=(B2*64) OR (CN-B3)                      :REM quadrant 3 (top right)
P=B3                                             :REM update position
GOTO <iterate>
<instr2>:                                        :REM instr 2: repeat byte
L=B2-P:P=B2+1
VK(I0+1)=L:VK(I0+2)=BP(B3)
VK(I1+1)=L:VK(I1+2)=BP(B3+17)
VK(I2+1)=L:VK(I2+2)=BP(B3+17)
VK(I3+1)=L:VK(I3+2)=BP(B3)
GOTO <iterate>
<instr3>:                                        :REM instr 3: iterate from..to
L=B2-P+B3:P=B2+1
VK(I0+1)=B3:VK(I0+2)=L
VK(I1+1)=B3+14:VK(I1+2)=L+14
VK(I2+1)=B3+14:VK(I2+2)=L+14
VK(I3+1)=B3:VK(I3+2)=L
GOTO <iterate>
<instr4>:                                        :REM instr 4: alternate bytes
VK(I0+1)=BP(B2):VK(I0+2)=BP(B3)
VK(I1+1)=BP(B2+17):VK(I1+2)=BP(B3+17)
VK(I2+1)=BP(B2+17):VK(I2+2)=BP(B3+17)
VK(I3+1)=BP(B2):VK(I3+2)=BP(B3)
P=P+1
<iterate>:
I0=I0+3:I1=I1+3:I2=I2+3:I3=I3+3                  :REM increment subscripts
NEXT K
NEXT J
GOTO <loop>

By assigning quadrants as in 0 (top left), 1 (bottom left), 2 (bottom right), 3 (top right), we may not only process the left side first and then the right one (reusing the path information for the two sides), we may also update our viewport in a nice fashion, sprialling in clockwise from the closest level painted at the outer regions of the display into the more distant levels towards the center.

Now we've to collect the data of what's lying in front of us first, using two arrays VL (left side) and VR (right side). A 1 represents a passage, a zero a wall, and if there's an edge to draw, we OR a 2:

REM assemble view path data
REM CM = -1
REM M:  maze data (2-dim, encodes directions as bit-vectors of powers of 2)
REM MD: current viewing direction (0..3)
REM DL: direction to the left relative to current direction (0..3)
REM DR: direction to the right relative to current direction (0..3)
REM DD: direction encodings as in M (powers of 2: 1,2,4,8)
REM MX: current position x
REM MY: current position y
REM DX: delta x of current direction (of -1, 0, 1)
REM DY: delta y of current direction (of -1, 0, 1)
REM arrays DX, DY: delta x, delta y values per direction code
REM W1: distance of last frontal wall, if any
REM results in
REM   W0: distance of frontal wall to draw, if any
REM   arrays VL, VR: codes for drawing left and right sides

Y=MY:X=MX:VL=DD(DL):VR=DD(DR):VF=DD(MD):W0=CM:FW=0
FOR I=0 TO 7                                            :REM depth levels
B=M(Y,X)                                                :REM maze code
IF B AND VL THEN VL(I)=1 ELSE VL(I)=0                   :REM passage/wall left
IF B AND VR THEN VR(I)=1 ELSE VR(I)=0                   :REM passage/wall right
IF I=0 THEN <skip-1>
IF VL(I)<>VL(I-1) THEN VL(I-1)=VL(I-1) OR 2             :REM edge left
IF VR(I)<>VR(I-1) THEN VR(I-1)=VR(I-1) OR 2             :REM edge right
<skip-1>:
IF B AND VF THEN <skip-2>                               :REM no wall in front
REM wall in front, fix up codes for sides: 4 = junction
IF VL(I)=0 THEN VL(I)=2 ELSE IF M(Y+DY(DL),X+DX(DL)) AND VF THEN VL(I)=4 ELSE VL(I)=1
IF VR(I)=0 THEN VR(I)=2 ELSE IF M(Y+DY(DR),X+DX(DR)) AND VF THEN VR(I)=4 ELSE VR(I)=1
W0=I:FW=I+1:I=7:GOTO <skip-3>                           :REM break out
<skip-2>:
X=X+DX:Y=Y+DY                                           :REM increment position
<skip-3>:
NEXT
IF W0=W1 THEN W1=CM
REM clear any side walls/paths behind a frontal wall
IF (FW) AND (FW<8) THEN FOR I=FW TO 7:VL(I)=CM:VR(I)=CM:NEXT       
RETURN

Variable W0 is the distance of a blocking wall, if any, or -1 else. The code at label "<skip-1>" is processing the blocking wall branch. Here we've actuall to cover an edge case: There are some Y-shaped junctions in the maze, and in this case, there will be no walled passage shown on the side(s), but merely the empty space of the passage way leading into the distance, sideways ahead. In this case, we've to draw the edge only, but none of the other definitions of the passage. The code for this is a 4 and it is to be handled specially by the code for drawing a frontal wall.

Conveying Bytes in RT

And this is the code for actually drawing the viewport, our little byte-code interpreter:

REM constants used to address the LCD
PA=185:PB=186:PC=254:PD=255                    :REM LCD ports (A,B,Cmd,Data)
PL=33:PR=66                                    :REM LCD blocks (2^n), L:0+5, R:1+6
PU=59:PQ=58                                    :REM LCD setting: counter up/down
DIM PV(3)                                      :REM quadrant:block (port A: 1,32,64,2)


REM subroutine: render-viewport
ON G GOSUB <disable-interrupts>
OUT PB,0                                       :REM deselect port B (segm. 8,9)
OUT PA,PL:OUT PC,PU                            :REM select segm 0,5, set to increment
OUT PA,PR:OUT PC,PQ                            :REM select segm 1,6, set to decrement
FOR I=0 TO 7                                   :REM iterate over distance levels
IF W0=I THEN GOSUB <draw-wall>:I=7:GOTO <cont> :REM we reached a wall
IF W1=I THEN GOSUB <erase-wall>                :REM erase wall drawn previously
IF VL(I)=HL(I) THEN <right-side>               :REM same as previous frame, skip
VM=VL(I) AND 1:VE=VL(I) AND 2                  :REM process left side
FOR VS=0 TO 1:OUT PA,PV(VS)                    :REM quadrants/segm. 0, 1
FOR K=VI(VS,I,VM,0) TO VI(VS,I,VM,1) STEP 3
ON VK(K) GOSUB <i1>,<i2>,<i3>,<i4>             :REM exec draw commands
NEXT K
NEXT VS
<right-side>:
IF VR(I)=HR(I) THEN <cont>                     :REM same as previous, skip
VM=VR(I) AND 1:VE=VR(I) AND 2                  :REM process right side
FOR VS=2 TO 3:OUT PA,PV(VS)
FOR K=VI(VS,I,VM,0) TO VI(VS,I,VM,1) STEP 3
ON VK(K) GOSUB <i1>,<i2>,<i3>,<i4>
NEXT:NEXT
<cont>:
NEXT
FOR I=0 TO 7:HL(I)=VL(I):HR(I)=VR(I):NEXT      :REM copy current to history
W1=W0                                          :REM same for wall distance
OUT PA,PR:OUT PC,PU                            :REM reset drawing directions
ON G GOSUB <enable-interrupts>
RETURN

REM viewport drawing commands
<i1>:                                          :REM set pos
OUT PC,VK(K+1)
RETURN
<i2>:                                          :REM repeat byte
B=VK(K+C2):FOR J=0 TO VK(K+1):OUT PD,B:NEXT
RETURN
<i3>:                                          :REM iterate pattern
FOR J=VK(K+1) TO VK(K+2):OUT PD,BL(J):NEXT
RETURN
<i4>:                                          :REM alternate bytes
IF VE THEN OUT PD,VK(K+1) ELSE OUT PD,VK(K+2)
RETURN

We'll skip the code for erasing and drawing frontal walls for shortness sake (this was a good one, wasn't it?). — Please refer to the complete program listed below.

Reality

I was expecting to hit some wall with some complex code like this, written up at once and loaded into the target machine hoping the best. But, apart from a few simple typos, no critical errors. So, if we didn't hit a wall in code, we expected to see some on the screen. Or any lines at least. Some pixels?

Crickets!

Hmm. Why not any? Can't see why, really. — Stares at code. Tries to inspect code (see below for more on this). Time passes. — Eventually I discovered, I got confused with my variables. Wrong, erroneous, undefined name for the array containing the segment/port assignments. So we were talking to none of the LCD controllers at all. (Since BASIC does auto-DIMs for small arrays of a length of up to 10, there's no runtime error. Aaargh!)

Then, some pixels at last, even some lines. And a big mess on the righthand side of the viewport.

Let's debug.

Virtual T + German Keyboard = Punishment

US-ISO and German keyboards (Mac)

We really have to illustrate this: US-ISO and German keyboard mappings (Mac).

Sloppy coding deserves some punishment, but not this one. Trying to debug with this setup is like playing a game of blind chess while trying to compete with Google's newest AI in Go. You really do not want to try this one. In average, I get about 70% of the non-anums wrong, trying to concentrate on the task. (And there are lots of non-anums in subscripted variables, and there are lots of subscripts used in the code.) If you risk a glance at the labeled keyboard, you're lost for sure. Given the tiny size of the display, just 8 lines less 2 for the "OK" prompt (or, more often, "SN") and a new-line, it's near to impossible to do some decent debugging with some lines of output to refer to and to compare.

On the other hand, using the real thing, the serial uploads are now in the minutes at 1200 bauds. And still, there's only a tiny display and very little of the code to be seen at once.

By this we're in retro-retro mode, comparable to punchcard programming: Submit your code — Get errors returned — Take them home — Stare at the listing in order to figure out, what the code might be actually doing — If sufficently confident, resubmit — Take errors home …

Punchcard Mode

Punchcard Mode.

(Punchcards anyone? Visit the Virtual Keypunch.)

Still an awfull mess. Saturday goes by.

Retro Programming

Why retro-programming may be dangerous to your health.

Note: Like Walter Benjamin's Angel of History (Angelus Novus) the true retro-hacker is racing blindly through time, facing the past behind him — a grim view ornamented by the unresolved issues of previous turns of the hack-debug-cycle.

Sunday — Ultimo

It's the last day of January and the final day of Rectrochallenge. — We really should come up with some, a viable dungeon crawler, at least.

With a fresh head and a quick test, we confirm a nagging suspicion: Our code for setting a LCD-controller to auto-decrement isn't working at all. Comparing our settings to some sample code and a few experiments later, there's certainty: The documentation, as quoted in Episode #4, is wrong.

And this is how it actually works:

LCD: Select Address Counter Mode

  7   6   5   4   3   2   1   0     bit
+---+---+---+---+---+---+---+---+
| 0 | 0 | 1 | 1 | 1 | 0 | 1 |D/U|   OUT 0xFE
+---+---+---+---+---+---+---+---+

D/U: 0 = count down, 1 = count up (0x3A: down, 0x3B: up)

At least, it does work on the real machine. Not so in Virtual T, no support for LCD-segement counter auto-decrement. (To be fair, it's not used once by the code in ROM and we actually have to include some code for resetting writing directions in order to clean up. Else, the display will be left in some quite quirky state, since the operating system isn't resetting the block writing direction at all.)

Virtual T and LCD writing directions

This is what we get on Virtual T.

However, at least, we're getting some (if only the left half on Virtual T) and are able to identify some quirks in our drawing definitions:

Testing on Virtual T

What's this? (Virtual T)

Otherwise, it's still the same torture with the keyboard as before, even with some sleep and a somewhat fresh head. On the other hand, there are still those long uploads and setup periods. — Did we have this already?

Retro Programming

Still the same hamster.

But eventually, we arrive at a viable program, and it looks like this:

10 REM Maze War Dungeon Roamer
20 DEFSNG A:DEFINT B-Z:SCREEN 0,0:CLS:PRINT"Setting up...":GOTO 200
35 REM == time critical subroutines ==
30 REM viewport draw commands
31 OUT PC,VK(K+C1):RETURN
32 B=VK(K+C2):FOR J=C0 TO VK(K+C1):OUT PD,B:NEXT:RETURN
33 FOR J=VK(K+C1) TO VK(K+C2):OUT PD,BL(J):NEXT:RETURN
34 IF VE THEN OUT PD,VK(K+C1) ELSE OUT PD,VK(K+C2)
35 RETURN
36 FOR J=C0 TO VK(K+C1):OUT PD,C0:NEXT:RETURN
37 FOR J=VK(K+C1) TO VK(K+C2):OUT PD,C0:NEXT:RETURN
39 REM subroutine: render-viewport
40 ON G GOSUB 910,920,930,930,940
42 OUT PB,C0
44 OUT PA,PL:OUT PC,PU:OUT PA,PR:OUT PC,PQ
46 FOR I=C0 TO C7
48 IF W0=I THEN GOSUB 100:I=C7:GOTO 76
50 IF W1=I THEN GOSUB 90
52 IF VL(I)=HL(I) THEN 64
54 VM=VL(I) AND C1:VE=VL(I) AND C2
56 FOR VS=C0 TO C1:OUT PA,PV(VS)
58 FOR K=VI(VS,I,VM,C0) TO VI(VS,I,VM,C1) STEP C3
60 ON VK(K) GOSUB 31,32,33,34
62 NEXT:NEXT
64 IF VR(I)=HR(I) THEN 76
66 VM=VR(I) AND C1:VE=VR(I) AND C2
68 FOR VS=C2 TO C3:OUT PA,PV(VS)
70 FOR K=VI(VS,I,VM,C0) TO VI(VS,I,VM,C1) STEP C3
72 ON VK(K) GOSUB 31,32,33,34
74 NEXT:NEXT
76 NEXT
78 FOR I=C0 TO C7:HL(I)=VL(I):HR(I)=VR(I):NEXT:W1=W0
80 OUT PA,PR:OUT PC,PU
82 ON G GOSUB 960,970,980,980,990
84 RETURN
89 REM erase a wall 
90 IF W1=C7 THEN RETURN
92 FOR VS=C0 TO C3:OUT PA,PV(VS):K=VI(VS,W1,C2,C0):OUT PC,VK(K+C1)
94 FOR J=C0 TO VK(K+C4):OUT PD,C0:NEXT
96 NEXT:RETURN
98 REM draw blocking wall; handle left side
100 IF VL(W0)=C4 THEN 112
102 VM=VL(W0) AND C1:VE=VL(W0) AND C2
104 FOR VS=C0 TO C1:OUT PA,PV(VS)
106 FOR K=VI(VS,W0,VM,C0) TO VI(VS,W0,VM,C1) STEP C3
108 ON VK(K) GOSUB 31,32,33,34
110 NEXT:NEXT:GOTO 124
112 VE=C1:REM passage to the left and ahead
114 FOR VS=C0 TO C1:OUT PA,PV(VS)
116 FOR K=VI(VS,W0,C1,C0) TO VI(VS,W0,C1,C1) STEP C3
118 ON VK(K) GOSUB 31,36,37,34
120 NEXT:NEXT
122 REM handle right side
124 IF VR(W0)=C4 THEN 136
126 VM=VR(W0) AND C1:VE=VL(W0) AND C2
128 FOR VS=C2 TO C3:OUT PA,PV(VS)
130 FOR K=VI(VS,W0,VM,C0) TO VI(VS,W0,VM,C1) STEP C3
132 ON VK(K) GOSUB 31,32,33,34
134 NEXT:NEXT:GOTO 148
136 VE=C1:REM passage to the right and ahead
138 FOR VS=C2 TO C3:OUT PA,PV(VS)
140 FOR K=VI(VS,W0,C1,C0) TO VI(VS,W0,C1,C1) STEP C3
142 ON VK(K) GOSUB 31,36,37,34
144 NEXT:NEXT
146 REM finally draw the wall
148 IF W0=C7 THEN RETURN
150 FOR VS=C0 TO C3:OUT PA,PV(VS)
152 FOR K=VI(VS,W0,C2,C0) TO VI(VS,W0,C2,C1) STEP C3
154 ON VK(K) GOSUB 31,32,33,34
156 NEXT:NEXT
158 RETURN
160 REM == setup, execution starts here ==
195 REM G: 1=PC-8201A, 2=M10 (w/o modem), 3=Model 100, 4=Model 102, 5=KC85
200 B=PEEK(1):G= -(B=148) -(B=35)*2 -(B=51)*3 -(B=167)*4 -(B=225)*5
205 IF G=0 THEN SCREEN 0,1:PRINT "Sorry, model not supported. Gestalt:";B:END
210 IF G=1 THEN AK=65128! ELSE IF G=2 THEN AK=65389! ELSE IF G=5 THEN AK=65387! ELSE AK=65450!
215 C0=0:C1=1:C2=2:C3=3:C4=4:C5=5:C6=6:C7=7:C8=8:C9=9
220 CA=10:CC=12:CE=14:CN=49:CL=50:CP=64:CM=-1
225 UC=223:LC=97:CK=27:ES=27:EB=32:E8=17
230 PA=185:PB=186:PC=254:PD=255:DIM SA(9),SB(9):SG=-1:SH=0
235 FOR I=C0 TO C9:READ B1,B2:SA(I)=B1:SB(I)=B2:NEXT
240 DX=0:DY=0:DIM DX(3),DY(3):FOR I=C0 TO C3:READ B1,B2:DX(I)=B1:DY(I)=B2:NEXT
245 DL=0:DR=0:DIM DL(3),DR(3)
250 FOR I=C0 TO C3:READ B:DL(I)=B:NEXT
255 FOR I=C0 TO C3:READ B:DR(I)=B:NEXT
260 DIM DD(3):FOR I=C0 TO C3:READ B:DD(I)=B:NEXT
269 REM setup the maze
270 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
275 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
280 MW=31:MH=15:ML=132:MT=5:DIM M(MH,MW),BM(C9)
285 FOR Y=C0 TO C9:READ B:BM(Y)=B:NEXT
290 FOR Y=C0 TO MH:FOR X=C0 TO MW:READ B:M(Y,X)=B:NEXT:NEXT
295 GOSUB 1010:GOSUB 1110:REM setup viewport
299 REM == main  ==
300 CLS:GOSUB 700:MX=11:MY=7:MD=0:DX=DX(MD):DY=DY(MD):DL=DL(MD):DR=DR(MD)
310 PRINT CHR$(ES);"Y";CHR$(EB+C7);CHR$(EB+22);"Crsr,IJKL,Q:quit";
315 GOSUB 460:GOSUB 1770:GOSUB 1510:GOSUB 40
319 REM main loop
320 B=PEEK(AK):IF B=C0 THEN 320
330 K=PEEK(AK+B):POKE AK,C0:IF K>CK THEN ON K-CK GOTO 500,520,540,560
340 IF K>=LC THEN K=K AND UC
350 ON INSTR("JLKI Q",CHR$(K)) GOTO 520,500,560,540,580,590
360 GOTO 320
398 REM == subroutines ==
399 REM segement/pos select
400 SH=SG:SG=SX\CL:IF SY>C3 THEN SG=SG+C5
410 IF (SG<>SH) THEN OUT PA,SA(SG):OUT PB,SB(SG)
420 OUT PC,(SY MOD C4)*CP OR SX MOD CL:RETURN
449 REM clear/draw marker
450 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
460 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
470 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 470:MD=DR:DX=DX(MD):DY=DY(MD):DL=DL(MD):DR=DR(MD):GOTO 610
520 MD=DL:T0=C0:T1=C1:GOSUB 470:DX=DX(MD):DY=DY(MD):DL=DL(MD):DR=DR(MD):GOTO 610
540 GOSUB 450:MX=MX+DX:MY=MY+DY:IF M(MY,MX)=C0 THEN MX=MX-DX:MY=MY-DY:GOSUB 460:GOTO 320
550 GOSUB 460:GOTO 610
560 GOSUB 450:MX=MX-DX:MY=MY-DY:IF M(MY,MX)=C0 THEN MX=MX+DX:MY=MY+DY:GOSUB 460:GOTO 320
570 GOSUB 460:GOTO 610
580 GOTO 320:REM shoot
590 SCREEN 0,1:END
600 REM view port: get path and display it
610 GOSUB 1510:GOSUB 40:GOTO 320
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 400
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 400
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 setup viewport
1010 PL=33:PR=66:REM LCD blocks 0+5, 1+6
1020 PU=59:PQ=58:REM LCD counter setting increment/decrement
1030 DIM PV(3):FOR I=0 TO 3:READ B:PV(I)=B:NEXT
1040 DIM BL(29):FOR I=0 TO 29:READ B:BL(I)=B:NEXT
1050 DIM BP(33):FOR I=0 TO 33:READ B:BP(I)=B:NEXT
1060 DIM VL(7),VR(7),HL(7),HR(7)
1070 DIM VI(3,7,2,1):REM segment,level,mode,idx-from/idx-to
1080 DIM VK(1392)
1090 RETURN
1100 REM compile-viewport-instr
1110 I4=C0
1120 READ B:IF B<C0 THEN RETURN:REM level
1130 FOR J=C0 TO C2:READ B0:REM mode, get length
1140 JL=B0*C3:I0=I4:I1=I0+JL:I2=I1+JL:I3=I2+JL:I4=I3+JL
1150 VI(C0,B,J,C0)=I0:VI(C0,B,J,C1)=I1-C3
1160 VI(C1,B,J,C0)=I1:VI(C1,B,J,C1)=I2-C3
1170 VI(C2,B,J,C0)=I2:VI(C2,B,J,C1)=I3-C3
1180 VI(C3,B,J,C0)=I3:VI(C3,B,J,C1)=I4-C3
1190 FOR K=C1 TO B0
1193 READ B1,B2,B3:VK(I0)=B1:VK(I1)=B1:VK(I2)=B1:VK(I3)=B1
1196 ON B1 GOTO 1200,1260,1320,1380
1200 VK(I0+C1)=(B2*CP) OR B3
1210 VK(I1+C1)=((C3-B2)*CP) OR B3
1220 VK(I2+C1)=((C3-B2)*CP) OR (CN-B3)
1230 VK(I3+C1)=(B2*CP) OR (CN-B3)
1240 P=B3
1250 GOTO 1430
1260 L=B2-P:P=B2+C1
1270 VK(I0+C1)=L:VK(I0+C2)=BP(B3)
1280 VK(I1+C1)=L:VK(I1+C2)=BP(B3+E8)
1290 VK(I2+C1)=L:VK(I2+C2)=BP(B3+E8)
1300 VK(I3+C1)=L:VK(I3+C2)=BP(B3)
1310 GOTO 1430
1320 L=B2-P+B3:P=B2+C1
1330 VK(I0+C1)=B3:VK(I0+C2)=L
1340 VK(I1+C1)=B3+CE:VK(I1+C2)=L+CE
1350 VK(I2+C1)=B3+CE:VK(I2+C2)=L+CE
1360 VK(I3+C1)=B3:VK(I3+C2)=L
1370 GOTO 1430
1380 VK(I0+C1)=BP(B2):VK(I0+C2)=BP(B3)
1390 VK(I1+C1)=BP(B2+E8):VK(I1+C2)=BP(B3+E8)
1400 VK(I2+C1)=BP(B2+E8):VK(I2+C2)=BP(B3+E8)
1410 VK(I3+C1)=BP(B2):VK(I3+C2)=BP(B3)
1420 P=P+C1
1430 I0=I0+C3:I1=I1+C3:I2=I2+C3:I3=I3+C3
1440 NEXT:NEXT:GOTO 1120
1500 REM assemble view path data
1510 Y=MY:X=MX:VL=DD(DL):VR=DD(DR):VF=DD(MD):W0=CM:FW=C0
1520 FOR I=C0 TO C7:B=M(Y,X)
1523 IF B AND VL THEN VL(I)=C1 ELSE VL(I)=C0
1526 IF B AND VR THEN VR(I)=C1 ELSE VR(I)=C0
1530 IF I=C0 THEN 1570
1540 IF VL(I)<>VL(I-C1) THEN VL(I-C1)=VL(I-C1) OR C2
1560 IF VR(I)<>VR(I-C1) THEN VR(I-C1)=VR(I-C1) OR C2
1570 IF B AND VF THEN 1610
1580 IF VL(I)=C0 THEN VL(I)=C2 ELSE IF M(Y+DY(DL),X+DX(DL)) AND VF THEN VL(I)=C4 ELSE VL(I)=C1
1590 IF VR(I)=C0 THEN VR(I)=C2 ELSE IF M(Y+DY(DR),X+DX(DR)) AND VF THEN VR(I)=C4 ELSE VR(I)=C1
1600 W0=I:FW=I+C1:I=C7:GOTO 1620
1610 X=X+DX:Y=Y+DY
1620 NEXT
1630 IF W0=W1 THEN W1=CM
1640 IF (FW) AND (FW<C8) THEN FOR I=FW TO C7:VL(I)=CM:VR(I)=CM:NEXT
1650 RETURN
1700 REM clear/init viewport
1710 ON G GOSUB 910,920,930,930,940
1720 OUT PA,PL OR PR:OUT PB,C0:REM enable blocks 0,1,5,6 at once
1730 FOR Y=C0 TO C3:OUT PC,Y*CP
1740 FOR X=C0 TO CN:OUT PD,C0:NEXT
1750 NEXT
1760 ON G GOSUB 960,970,980,980,990
1770 FOR I=0 TO 7:HL(I)=CM:HR(I)=CM:NEXT:W0=CM:W1=CM
1780 RETURN
2000 REM port patterns for segments (0..9: PA,PB)
2001 DATA 1,0,2,0,4,0,8,0,16,0,32,0,64,0,128,0,0,1,0,2
2002 REM directions (0..3:dx,dy)
2003 DATA 0,-1,-1,0,0,1,1,0
2004 REM turns (L: 0..3, R: 0..3)
2005 DATA 1,2,3,0, 3,0,1,2
2006 REM directional codes (2^0..3)
2007 DATA 1,2,4,8
2008 REM maze maker defs (0..3:Y,X)
2009 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
2010 REM maze marker turn pixel mods (Y,X)
2011 DATA 2,1,1,0, 1,2,2,1, 0,1,1,2, 1,0,0,1
2012 REM maze bit patterns
2013 DATA 1,3,7,14,28,56,112,224,192,128
2019 REM maze data
2020 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
2021 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
2022 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
2023 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
2024 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
2025 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
2026 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
2027 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
2028 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
2029 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
2030 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
2031 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
2032 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
2033 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
2034 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
2035 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
3000 REM segment addresses (port A)
3001 DATA 1,32,64,2
3005 REM line pixels
3006 DATA  1,   1,   2,   2,   4,   4,   8,   8,  16,  16
3007 DATA 32,  32,  64,  64, 128, 128,  64,  64,  32,  32
3008 DATA 16,  16,   8,   8,   4,   4,   2,   2,   1,   1
3010 REM pixels for repeats
3011 DATA 0,1,2,4,8,16,32,64,128
3012 DATA 255,254,252,248,240,224,192,128
3013 REM inverse direction (bottom up)
3014 DATA 0,128,64,32,16,8,4,2,1
3015 DATA 255,127,63,31,15,7,3,1
3020 REM viewport commands struct, 1st quadrant, per level and mode
3021 DATA 0:REM level 0
3022 DATA 10
3023 DATA 1,0,0, 3,5,10
3024 DATA 1,1,0, 2,5,0, 2,6,1, 4,9,1
3025 DATA 1,2,7, 4,9,0
3026 DATA 1,3,7, 4,9,0
3027 DATA 9
3028 DATA 1,0,0, 2,5,0
3029 DATA 1,1,0, 2,6,1, 4,9,1
3030 DATA 1,2,7, 4,9,0
3031 DATA 1,3,7, 4,9,0
3032 DATA 6
3033 DATA 1,1,8, 2,49,1
3034 DATA 1,2,8, 2,49,0
3035 DATA 1,3,8, 2,49,0
3036 DATA 1:REM level 1
3037 DATA 7
3038 DATA 1,1,8,  3,15,2, 4,14,6
3039 DATA 1,2,16, 4,9,0
3040 DATA 1,3,16, 4,9,0
3041 DATA 7
3042 DATA 1,1,8,  2,15,6, 4,14,6
3043 DATA 1,2,16, 4,9,0
3044 DATA 1,3,16, 4,9,0
3045 DATA 6
3046 DATA 1,1,17, 2,49,6
3047 DATA 1,2,17, 2,49,0
3048 DATA 1,3,17, 2,49,0
3049 DATA 2:REM level 2
3050 DATA 8
3051 DATA 1,1,17, 3,21,11
3052 DATA 1,2,17, 2,21,0, 3,23,0, 4,10,2
3053 DATA 1,3,24, 4,9,0
3054 DATA 7
3055 DATA 1,1,17, 2,21,0
3056 DATA 1,2,17, 2,23,2, 4,10,2
3057 DATA 1,3,24, 4,9,0
3058 DATA 4
3059 DATA 1,2,25, 2,49,2
3060 DATA 1,3,25, 2,49,0
3061 DATA 3:REM level 3
3062 DATA 5
3063 DATA 1,2,25, 3,30,3, 4,13,5
3064 DATA 1,3,31, 4,9,0
3065 DATA 5
3066 DATA 1,2,25, 2,30,5, 4,13,5
3067 DATA 1,3,31, 4,9,0
3068 DATA 4
3069 DATA 1,2,32, 2,49,5
3070 DATA 1,3,32, 2,49,0
3071 DATA 4:REM level 4
3072 DATA 5
3073 DATA 1,2,32, 3,36,10, 4,8,8
3074 DATA 1,3,37, 4,9,0
3075 DATA 5
3076 DATA 1,2,32, 2,36,8,  4,8,8
3077 DATA 1,3,37, 4,9,0
3078 DATA 4
3079 DATA 1,2,38, 2,49,8
3080 DATA 1,3,38, 2,49,0
3081 DATA 5:REM level 5
3082 DATA 3
3083 DATA 1,3,38, 3,41,0, 4,11,3
3084 DATA 3
3085 DATA 1,3,38, 2,41,3, 4,11,3
3086 DATA 2
3087 DATA 1,3,43, 2,49,3
3089 DATA 6:REM level 6
3090 DATA 3
3091 DATA 1,3,43, 3,45,5, 4,13,5
3092 DATA 3
3093 DATA 1,3,43, 2,45,5, 4,13,5
3094 DATA 2
3095 DATA 1,3,47, 2,49,5
3096 DATA 7:REM level 7
3097 DATA 3
3098 DATA 1,3,47, 3,48,9, 4,14,14
3099 DATA 3
3100 DATA 1,3,47, 2,48,6, 4,14,14
3101 DATA 2
3102 DATA 1,3,49, 2,49,14
3103 DATA -1

Mind the time critical subroutines placed at the beginning of the program, because MS BASIC.

! Note: When loading this from a "*.DO"-file, you've to KILL the document before running the program in order to free the memory required by the JIT-compiled drawing code, or you'll get an "OM" error else. There is no need for any preparations, if loaded into BASIC directly (via the serial interface).

It's a real, fully working dungeon crawler with a pseudo-3D 1st person perspective into the maze. And it works, for real!

But, it's not as fast as expected. True, we could speed up the thing a little by removing all the spaces that are still in the code for legibility and by removing the few REMarks, but, still, this wouldn't make this an exciting, fast-paced realtime game.

Moreover, we haven't implemented the iconic eyeball representing the respective other player, yet, and we were to paint this upon the viewport by repeating quite the same procedures. By this, we would become even slower, when the game heats up. Not so good.

As for the networking part, this could be easy: Just open two ports, one outgoing, on incoming, setup a command "ON COM GOSUB ..." and exchange a few bytes for positions. One computer would feature the host, managing player names, initial positions and syncronization, and the other one would connect to it as a client.

But in reality, we've turned off any of the means to receive an incoming message, while we're talking to the LCD-controllers with interrupts disabled. We would have to implement some handshaking protocol of sorts, to synchronize, and we'll loose some runtime for this, too.

So, it's actually a good point to put this project to a rest. We've come as far, as it does make any sense, for real.

RC2016/01: Final state of the project, Olivetti M10

Our Portable Dungeon Crawler running on the Olivetti M10.

Closing Considerations / Retrospect

In retrospect, this didn't work out too badly. We knew from the beginning that BASIC would be hard on the limits for this job (but I personally expected it to perform a little better, though). Ironically, BASIC wasn't of any specific help — how I longed for some kind of assignable objects at times! So we ended up doing it all by lookups and subscripts. On the plus side, we arrived at an algorithm that could be ported to assembler with ease and would be really performant — but this is another project.

Anyway, exploring the Kyocera Siblings was some fun, and revisiting BASIC was some fun, too, for sure. This was actually my first complex BASIC programs in 30 years, since I left my C64 behind. Under these circumstances, it turned out quite amazing. — Had we reached our final goal of a full implementation, I had already dreamed up some box art with a sticker on it, reading, 'FROM THE MAKER OF "TEST.BA".' (And, maybe, another one, 'ONE MONTH IN THE MAKING!', too.)

So, we close our humble tribute to Retrochallenge 2016/01 with two laughing eyes, as we enjoyed ourselves quite a bit. (Apart from this debugging cycle. — No, calm down, I won't include the image once again. Here is a better one:)

RC2016/01: Final state of the project, NEC PC-8201A

Final state of affairs on the NEC PC-8201A.

(Still a small bug concerning the final wall and edges to be seen here. I'll fix that later.
Probably, it's about the encoding done in the path collection. Turns out to be a copy'n'paste thing.)

Cheers, thanks to all who followed these posts, and, finally, — finis —

Please mind the final update, including the release code (compressed for faster runtime performance)!

P.S.: And both of the machines are still running on their first set of AA batteries! Amazing!

 

Next:   Addendum: A Final Fix

Previous:   Episode 11: A Path to the Maze

Back to the index.

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