Deathmaze 5000 Maze Rendering
The display uses hi-res page 2, treating it as a 40x24 character display. It's divided into three sections:
- The top-left section is the first-person view, covering an area 23 columns wide and 21 rows high. It starts at (0,0), which puts the center at (11,10).
- The top-right section displays the player's inventory, with a separate section for torches and food. It starts in column 26 on row 3, and spans 15 rows.
- The bottom section is 3 lines high. The first line is blank, the next two show messages and display what the user is typing.
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:
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.
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 | used as blinking cursor | |
$01 | diagonal line | |
$02 | diagonal line | |
$03 | vertical line, left edge | |
$04 | vertical line, right edge | |
$05 | far wall at "infinity"; also used for snake | |
$06 | perfect square / keyhole part | |
$07 | perfect square / keyhole part | |
$08 | perfect square / keyhole part | |
$09 | perfect square / keyhole part | |
$0a | ||
$0b | ||
$0c | tiny box | |
$0d | top left of medium/small box | |
$0e | top right of box | |
$0f | bottom right of medium/small box | |
$10 | top-center of small/medium box | |
$11 | ||
$12 | bottom left of box | |
$13 | ||
$14 | elevator on side wall | |
$15 | elevator on side wall | |
$16 | elevator on side wall | |
$17 | elevator on side wall | |
$18 | tiny keyhole | |
$19 | small keyhole left | |
$1a | small keyhole right | |
$1b | medium keyhole bottom right; used for snake | |
$1c | medium keyhole bottom left; used for snake | |
$1d | medium keyhole top right | |
$1e | medium keyhole top left | |
$1f | medium key part | |
$20 | space | |
$5f | medium keyhole mid-bottom left | |
$60 | medium keyhole mid-bottom right | |
$7b | medium key part | |
$7c | ||
$7d | ||
$7e | ||
$7f |
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 | ||||
$0b | $0b | $0b | $0b | $0b | $0b | $0b | $0b |
$0b | $0b | $0b | $0b | ||||
$08 | $0b | $0b | $09 | ||||
$20 | $0b | $0b | $20 | ||||
$20 | $0b | $0b | $20 | ||||
$0b | $0b | $0b | $0b | ||||
$0b | $0b | $0b | $0b |
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 | |||||
$03 | $11 | ||||||||
$03 | (line) | (line) | (line) | $0f |
Maze
Maze rendering is done in three steps:
- Extraction of maze wall data.
- Rendering of maze walls, back to front.
- 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:
- col 11: center; used for infinite hallway or facing wall in 4th cell
- col 10 and 12: walls at dist=4
- col 8-9 and 13-14: walls at dist=3
- col 5-7 and 15-17: walls at dist=2
- col 1-4 and 18-21: walls at dist=1
- col 0 and 22: non-walls at dist=0
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.
(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.