(back to project page)

Deathmaze 5000 Maze Rendering

The display uses hi-res page 2, treating it as a 40x24 character display. It's divided into three sections:

example

Nearly everything on the screen is drawn as character glyphs, including the maze. The only exception to this are horizontal lines, which have a dedicated routine. This is useful for two reasons: it's faster, and it blends with whatever was previously drawn. (This could also have been done by configuring the font renderer to blend with the screen contents instead of overwriting them, and then drawing an appropriate horizontal-line glyph.) The line routine stores $ff rather than $7f, which because of the Apple II's peculiar graphics hardware can cause a half-pixel shift to be visible.

Here's a series of images taken as the player walks forward down a long hallway, toward a box at the end:

walk0 walk0 walk0 walk0 walk0 walk0 walk0

Note the box can be seen in four consecutive frames, so the box must be rendered in tiny, small, medium, and large forms.

It would be difficult to render an arbitrary maze with a small handful of glyphs. Deathmaze 5000 works around this by limiting certain aspects of maze construction. The maze is laid out so that there are no open areas, such as 2x2 rooms, and special features are only visible from a short distance.

Glyph Set

Besides the usual collection of letters, numbers, and symbols, the custom font has all of the pieces needed to draw the maze and its contents.

glyphs

The special glyphs are usually referred to directly in code, sometimes as values in a table. Here's a list of all the glyphs used to draw the maze.

Index Image Notes
$00 glyph00 used as blinking cursor
$01 glyph01 diagonal line
$02 glyph02 diagonal line
$03 glyph03 vertical line, left edge
$04 glyph04 vertical line, right edge
$05 glyph05 far wall at "infinity"; also used for snake
$06 glyph06 perfect square / keyhole part
$07 glyph07 perfect square / keyhole part
$08 glyph08 perfect square / keyhole part
$09 glyph09 perfect square / keyhole part
$0a glyph0a
$0b glyph0b
$0c glyph0c tiny box
$0d glyph0d top left of medium/small box
$0e glyph0e top right of box
$0f glyph0f bottom right of medium/small box
$10 glyph10 top-center of small/medium box
$11 glyph11
$12 glyph12 bottom left of box
$13 glyph13
$14 glyph14 elevator on side wall
$15 glyph15 elevator on side wall
$16 glyph16 elevator on side wall
$17 glyph17 elevator on side wall
$18 glyph18 tiny keyhole
$19 glyph19 small keyhole left
$1a glyph1a small keyhole right
$1b glyph1b medium keyhole bottom right; used for snake
$1c glyph1c medium keyhole bottom left; used for snake
$1d glyph1d medium keyhole top right
$1e glyph1e medium keyhole top left
$1f glyph1f medium key part
$20 glyph20 space
$5f glyph5f medium keyhole mid-bottom left
$60 glyph60 medium keyhole mid-bottom right
$7b glyph7b medium key part
$7c glyph7c
$7d glyph7d
$7e glyph7e
$7f glyph7f

Multi-Glyph Examples

Full-sized keyhole (from table at $1e36). There are five sizes of keyholes: the four sizes you see as you look down the hallway with the locked doors, and one where you're looking at it straight on.

$06$0b$0b$07 glyph06 glyph0b glyph0b glyph07
$0b$0b$0b$0b glyph0b glyph0b glyph0b glyph0b
$0b$0b$0b$0b glyph0b glyph0b glyph0b glyph0b
$0b$0b$0b$0b glyph0b glyph0b glyph0b glyph0b
$08$0b$0b$09 glyph08 glyph0b glyph0b glyph09
$20$0b$0b$20 glyph20 glyph0b glyph0b glyph20
$20$0b$0b$20 glyph20 glyph0b glyph0b glyph20
$0b$0b$0b$0b glyph0b glyph0b glyph0b glyph0b
$0b$0b$0b$0b glyph0b glyph0b glyph0b glyph0b

Medium-sized box. Boxes are drawn in four sizes, and all four can be on the screen at the same time. The tiny form is simply glyph $0c. Small and medium are mostly drawn with specialized glyph cells, while large uses some of the general maze-wall glyphs.

The bottom of the medium-sized box is drawn with a horizontal-line drawing command. (It's essentially the same as glyphs $12 and $13 in this case, so I've used them here.) The blank cells in the middle aren't actually drawn, as the entire screen is cleared to black on each frame.

$0d$10$10$10$0e glyph0d glyph10 glyph10 glyph10 glyph0e
$03$11 glyph03 glyph20 glyph20 glyph20 glyph11
$03(line)(line)(line)$0f glyph12 glyph13 glyph13 glyph13 glyph0f

Maze

Maze rendering is done in three steps:

  1. Extraction of maze wall data.
  2. Rendering of maze walls, back to front.
  3. Rendering of maze features (pits, holes in ceiling, elevators, keyholes) and item boxes.

Getting the wall data

The first step uses the maze data at $6000. For each of the five floors there is a rectangular 11x12 array of cells. Each cell may have a wall to the south and a wall to the west. Encoding this requires only two bits, so each column requires 3 bytes, and an entire floor can be contained in 33 bytes.

Because walls can only be placed at the south and west sides, the last row and column of cells cannot be entered by the player, so the effective size of the map is 10x11.

The renderer draws the four cells in front of the player, as well as a tiny sliver of the cell the player is standing in. If a given side wall exists it is drawn as a wall, and if it doesn't then the same space is filled with part of the perpendicular wall for the side corridor. Because of the limitations on maze design, no other situation exists: each wall segment is either parallel to the viewer or exposes a perpendicular chunk.

This means the view in front of the player can be contained in a pair of bytes:

 left: ---43210  right: ddd43210

The first byte specifies whether there are walls on the left side of the viewer's square and the next 4 in front. The second byte holds the same information for the right side, but also has the distance to the nearest facing wall (0-N) encoded in it. If the distance is greater than 4, the bits are set to 5, and the renderer draws the hallway as extending into "infinity".

Rendering the wall data

The maze is rendered with character glyphs and a routine that draws glyph-width horizontal line segments. The 13 bits of information generated in the first step determine what gets drawn.

The screen is essentially divided into 5 vertical segments:

It's not quite that simple; for example, when drawing a side hallway at dist=2, the vertical line representing the near corner is drawn at the right edge of column 4, rather than the left edge of column 5. This is done so it aligns properly with the diagonal line that ends in column 4.

Consider the following example, which shows the same section of maze, with a wall added at dist=2 in the second image.

hall-open hall-wall

(The dashed red/green line is in the same place in both images. It spans columns 4 through 7, and is just above glyph row 7.)

The code starts by drawing the facing wall at the end of the hallway, then draws from farthest to nearest. If the viewer is standing on a cell with walls on the left and right, the end columns are (0 and 22) are left empty. If there's a hallway, the game draws a vertical line to represent the corner, and a short horizontal line at the top and bottom to indicate a hallway.

Visual features

Visual features such as pits and elevators are placed at the ends of short hallways, and are only visible when the cell they're in or the wall they're part of is in front of the viewer. Elevators in particular are always around a corner, so they only need to be rendered to the side from one square away, or straight on from point-blank range.

Instead of placing features in the maze and determining their relation to the viewer, the game has an explicit table for every cell that can see a feature (located at $60a5). For example, the pit at (8,5) on level 2 is visible from (8,3) and (8,4) looking north, and (8,6) looking south. The feature table has one entry for each, keyed by location and facing. The entry holds the type of feature and how far it is from the viewer.

Keyholes are more complicated, since you can have up to 4 visible on the walls ahead. The entry holds a bit mask identifying which side walls have keyholes.

Boxes are identified by scanning the inventory list for items that are at a visible location in the maze.

Other notes

The end-game puzzle isn't actually part of the maze. Instead, the walls and features are hard-coded into the instructions. Because you can describe all visible walls with two bytes, this is easier than it sounds.