1986 · U.S. Gold

Gauntlet

This minisite was contributed by air, chunkypixel. It’s agent-generated and needs a human editor. Clone https://github.com/gamesexplained/gamesexplained and follow kit/START.md to curate games/c64/gauntlet with me to Gold.

The arcade hit squeezed into 64 KB, where every monster on screen is a set of letters in a font, and the game keeps armour values for its heroes that it never reads. Eat poison with under 100 health and you can wander the dungeon for hours.

01 · The game

Four heroes, endless dungeons

One or two players each pick a hero and fight down through a scrolling maze, level after level, until their health runs out.

The title screen
The title screen.
Level 1 in play
Level 1: the Warrior at the top left, a key to the right, the status panel below.

The heroes are Thor the Warrior, Thyra the Valkyrie, Questor the Elf, and Merlin the Wizard. Each level is a maze of walls and doors, seen through a window that scrolls with the players. Generators pour out ghosts, grunts, demons, lobbers, and sorcerers, and Death walks among them. Health starts at 2000 and drains by one point every 64 frames whatever you do, so the food lying in the maze matters as much as the fighting.

Keys open doors, potions blow up every monster on screen, and treasure scores points. Each level ends at an exit, a whirlpool in the floor. A second player can join at any moment by pressing fire.

02 · One frame, rebuilt from memory

Characters for the maze and its monsters, sprites for the heroes

The picture below is drawn by this page from the game's memory and the video chip's registers, as the emulator recorded them for one frame of level 2.

Level 2, both players in. Drawn by the site's renderer from the frame kit/c64/frame.py recorded: all 104,448 pixels match the emulator's own picture of the same frame. Switch the sprites off and only the heroes and their two-sprite black masks at the play area's edges go: the walls, the generator, and the field of ghosts are all characters.

The maze is a grid of 32 by 32 tiles at $0C00, each tile 2 by 2 characters. The game draws the grid into a character map of 64 by 64 at $3000 and a colour map at $2000, then copies the 33 by 20 window the players can see into whichever of its two screens is hidden, and swaps (maze_draw at $8A0B). Scrolling moves the window 2 pixels a step with the video chip's fine scroll and redraws on each whole character.

Monsters are drawn into the same maps as four characters each (monster_draw, $CA7F). The character map is also the collision map: a player's four cells hold $5B, a character that looks like floor, and anything above $5A blocks the way (cells_free). The only sprites are the two heroes, the shots, the level number's digits, and two black masks that cover the columns the scroll brings in at each side.

03 · The monsters

Three fonts animate every monster at once

The game has three copies of its maze font. They share the walls, the floor and most of the treasure, and differ in the monsters, the transporter, the trap and the treasure chest. Changing font is how all of them move.

The font on show, all 256 glyphs, sixteen to a row from $00, switching with the monsters above.

Point at a glyph to see its number.

The six monsters, drawn from the tile table at $F400 (four characters a tile), the tile colours at $F600 and the three maze fonts at $4800, $5000 and $5800, in the order panel_flash steps through them ($8255: font selects 2, 4, 2, 6), one step every eight frames.

A monster is a tile from $40 to $6F: six kinds, each with a tile for every one of eight facings. The lobber has one picture for all eight. The three fonts at $4800, $5000 and $5800 hold the same walls and floor, and the monsters in three different poses. Once a frame panel_flash ($8C7C) counts, and every eighth frame it points the video chip at the next font in the cycle: one write to $D018 animates every monster on screen.

The fonts differ in more than the monsters. Glyphs $34-$37 make the transporter, $38-$3B the trap, and $1A-$1B are two of the treasure chest's four, and each has a different picture in each font, so the same switch that animates the monsters animates those too. Of the 256 glyphs, 137 differ between the first font and the second, 135 between the first and the third, and 149 between the second and the third. The cycle shows the first font twice as often as either of the others.

$4800, font select 2: shown at steps 1 and 3
$5000, font select 4: step 2
$5800, font select 6: step 4

The three maze fonts in full, from the listing, each glyph in the colour of the first tile that uses it (the tile table at $F400, its colours at $F600). The one framed in yellow is on show in the animation above. Glyphs $01-$0C are the walls as maze_style ($8CC5) set them for level 1: every level copies its own wall set into all three fonts. Glyph $5B looks like floor and is the mark that a player stands on a cell.

A monster's byte in the table at $0580 holds its facing (bits 0-2), kind (bits 3-5) and level (bits 6-7). The same byte is its strength: a hand-to-hand blow takes $40 off it, so a level-one monster dies at the first blow and a level-three one at the third. Death's byte starts at $C0 or above, and shots do not hurt it.

04 · Moving the monsters

Half the monsters a pass, and only those near the screen

A maze can hold 192 monsters. The game moves only the ones within a cell of the screen, and only half of those on any one pass.

The monster table, slot by slot, on the last pass

visited, took its turnvisited, no turn (off screen, or it vanished)not visited this passthe hero

The game's own code, running in this page. The whole 64 by 64 map of the level; the clear box is the area round the screen where monsters move, and everything in the shaded part stands still. Monsters that took their turn on the last pass are outlined where the turn left them; a monster blocked on every side takes its turn without moving. Click a floor cell to move the hero; the screen's window follows. Each pass runs generators_run ($B16A) and monsters_run ($BB2E) on a small 6502 core over the game's bytes from the listing, twelve and a half passes a second, the main loop's pace with the screen still. test_chase.js runs the same routines on the kit's 6502 simulator for 400 passes of each of these levels: the monster table, the maps and the hero's health come out the same, byte for byte, after every pass.

Each monster is a slot in five tables of 192 bytes: its X at $0400, its Y at $04C0, its kind, level and facing at $0580, its flags at $0640, and, for Death, the touches it has left at $0700. monster_count ($824E) holds the index of the last one. The table keeps monsters in the order they arrived: those in the level file from the maze's top-left corner on, with every Death moved to the front (deaths_first, $CC60), then each one a generator releases, added at the end (monster_spawn, $B230). A monster that dies hands its slot to the last monster in the table (monster_remove, $BCC7), so the table never has a gap. When that happens in the middle of a pass, monsters_run looks at the same slot next, so the monster moved in from the end is visited straight away: a ghost that touches the hero and vanishes makes way for whatever monster was last, wherever it is in the maze.

StepWhat runsWhere
1Wait for the raster interrupt's go-ahead; draw the maze window and its colours$8BDF, maze_draw $8A0B
2Count the pass; fire the players' shotspass_count $824B, shots_fire $A70D
3Stop here if the scroll direction has just changed$819C
4Shots against walls, players and monsters; the magic keys$A884, $AAC0, $AA10, $BA67
5The generators, every eighth passgenerators_run $B16A
6The monstersmonsters_run $BB2E
7Traps, pickups, doors, the panel, the idle timer, players joining and leaving$B814 to $A63C

One pass of the main loop, play_loop ($818B). With the screen still it runs once every four frames.

monsters_run does nothing on one pass in four, when bits 1 and 2 of the pass count are both clear. On the other passes it visits half the table and one more, every second slot, starting where it stopped the time before ($BE6D). Run off the end and it starts again at slot 0 or slot 1, by the lowest bit of the pass count, so each monster comes up on every second working pass. After each monster the loop checks whether the raster interrupt has already given the go-ahead for the next pass (main_go, $8264) and stops there if it has: a crowded screen makes the monsters slower, and the pass still ends on time. In the emulator, with 52 monsters on level 2, every working pass visited 26 slots, the even ones and the odd ones in turn, and one pass stopped after 10.

A visited monster moves only if it is within a cell of the screen: inside the 34 columns and 22 rows that start one cell above and to the left of the window (monster_on_screen, $BBD3). Everywhere else the maze is frozen until the players come near. The generators work the same way: generators_run looks only at the 16 by 10 tiles on screen. The window wraps at the map's edges, so near the edge of a maze it takes in the cells on the far side of the seam, and the monsters there move too.

To move a monster, monster_move ($BD06) lifts its four characters out of the maps and aims it (monster_aim, $BE90). For each player in the game, not leaving the level and not under the amulet, it takes the distance on each axis, the short way round the 64-cell map, and adds the two. The monster goes for the nearer player (monster_target, $BF53) and heads towards it on both axes at once: diagonally, until it is level with the player on one axis, then straight. A lobber nearer than 12 cells turns round. A monster within reach attacks instead of moving (monster_attack, $BBF9).

A monster heading diagonally first checks the two cells on each side it is heading for (monster_turn, $BFBB, through one of four routines). A blocked side is dropped from its direction, so a monster heading up and right into a wall above slides right along it; when only the corner is blocked, the routine drops one side or the other, in turn each time it runs. Then the monster steps one cell if the two by two cells ahead are empty. Walls, other monsters and the players' marked cells all block it. A monster heading straight into a wall does not turn: it waits, which is why monsters pile up behind walls. A sorcerer turns visible or invisible as it moves, and a demon or a lobber in line with its target may fire (monster_shoot, $C49E).

One step is one cell, and a monster gets a step on every second working pass at most: with the screen still, a step every 10 or 11 frames, a little under five cells a second. Death moves no faster than a grunt.

The distance has one slip. When the short way round crosses the edge of the map, the routine that measures it ($BFAB) leaves that axis negative, so the monster subtracts it instead of adding it. The monster still heads the right way, but it can pick the wrong player: in the emulator, with both players near the bottom of level 2 and monsters seen across the seam at the top, 13 of 60 aims went to the farther player.

05 · Waiting

The idle timer turns walls into exits

Stop shooting for long enough and the game helps you out of the level.

30 s: doors and keys become floor
150 s more: every wall becomes an exit

Game seconds. A game second is twelve passes of the main loop, 48 frames with the screen still, 0.96 s on PAL. In the emulator, from 129 seconds left the walls became exits within 6,250 frames.

idle_timer ($B557) counts game seconds whenever nobody fires, and every shot sets it back (shot_fire). After 30 every door and key turns to floor (idle_doors_open, $B581). After 150 more, every wall, crumbling wall, and key becomes an exit (idle_walls_exit, $B59C). The manual says 30 and then another 30, and that the walls vanish first.

06 · Levels

128 files, endlessly reshuffled

The box promises hundreds of levels. The disk holds 128 level files, and after level 7 the game draws them at random.

Every one of the 128 files, built by the game's own code, is on the Levels tab.

The rule is level_pick ($9AC4) and level_start ($99CF).

Levels 1 to 7 load files 1 to 7. From level 8 on, level_pick chooses a file at random from 8 to 117. A countdown runs out every four to seven levels and then a treasure room comes instead, one of files 118 to 128, and the level number is put back by one so it does not count. Two exits on level 1 skip ahead, to levels 4 and 8, and one on level 4 to level 8 (exit_targets). The game never ends while a player has health.

A level file is under 512 bytes: a four-byte header, a list of wall lines and the objects run-length coded. The header can make the maze wrap at its edges, make shots hurt or stun the other player, or keep only one exit, chosen at random. From level 8 the game scatters extra food and the odd poison jug (extras_place) and, in six rolls of sixteen, prints "FIND THE HIDDEN POTION". One of those six, the shot-speed potion, prints the message and hides nothing (hidden_potion_place, $A35E).