Speccy, Vol 12: Mazeract

January 7th, 2017

The theme for a recent Ludum Dare was "one room". That got me thinking, even though I didn't have time to make a game at the time. And making this game took more than 48 hours anyway, and I got help from Antti Tiihonen in form of tile graphics.

You can play the game in the on-line emulator or download the .tap file and play in an offline emulator or even on an actual spectrum. There's no weird limitations so it should work fine on any model out there.

If you have any plans on playing the game, play the game first. Spoilers ahead.

The idea was to make a 3d maze. A higher-dimension maze, so to say, for our two-dimensional hero. So the name is a portmanteau of maze and tesseract.

The goal of every level is to collect the required keys and to get to the exit. Additionally, one or two pieces of paper appear in the levels occasionally.

First order of business is to figure out how to fit everything needed in the speccy, which means making the level data small. This bit worked out pretty well. Each level consists of 78 bytes, so if I wanted 100 levels, that's just 7800 bytes. I ended up putting only 50 levels in the game to make it more reasonable to play through.

0xcf,0xdf,0x8f,0xbf,0xc7,0xfb,0xff,0xff,
0xff,0xff,0xbf,0xb8,0xf3,0xf7,0xe3,0xf6,
0xff,0xff,0xff,0x9e,0xdf,0x9b,0xbb,0xe7,
0xfd,0xfc,0xdd,0xfc,0xfb,0xef,0x83,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xf1,0x00,
0x22,0x00,
0x03,0x00,
0x51,0x00,
0xb5,0x00,
0xbb,0x00,
0xce,0x00,

Each layer of the maze is 8 bytes, and with 8 layers maximum that's 8*8=64 bytes for walls/floor. Each byte represents one horizontal span of the maze, or 8 tiles; if a bit is on, it's a wall, if it's off, it's floor.

Start, end, three keys and two pieces of paper are all stored as offsets in the 8*8*8 space (max value being 512, so two bytes are needed). Given that hardly any of the levels use all of the layers, I could have shaved quite a bit off that. Let's say there's a maximum of 6 layers and start, end and items must be in the first four, and we'd be at 8*6+7=55 bytes per level. But this was already good enough.

Having all the levels filled with just single tile type was boring, though, so I added 2x2, 1x2 and 2x1 tile types, and asked Antti to draw a couple different of each tiles. The levels are populated with these tiles at run time, so nothing needs to be saved. The tile graphics doesn't take too much space either - a 8x8 pixel bitmap is just 8 bytes, and even the biggest tile - 16 by 16 pixels - takes 32.

The population of various tiles is done through several passes of trying to put various tile sizes on the map in random places, checking if they're still the "basic" box, and if yes, they get overwritten. Otherwise, we ignore this and try again. After all this is done, the map is colored in random colors (from a list of acceptable colors). So if you wonder what's taking so long when switching levels, it's all this.

I realized that going through all 50 levels on one go isn't necessarily feasible, so a code system is needed for people to continue where they left off. (Savegames aren't really a thing on the speccy).

Instead of manually writing codes for all 50 levels, the codes are generated. A checksum is calculated from the level data, and the 4-level code is extracted from the checksum. When a code is entered, the game goes through all the levels, calculates the codes and compares them to whatever the player has entered. If no match is found, first level is used.

Talking of generation.. I did not make a single level by hand. They are all generated.

Making a 3d maze might have been fun given a sane editor, but I didn't go there. Instead, I wrote an algorithm that generates 3d mazes, generated a lot of them, and picked fun ones for the start. Towards the end I just let the generator go crazy.

The algorithm is relatively simple: place the desired items (start, end, keys, papers) at random places. See that there's a route from start to all of the other bits. Add a wall in a random position. Repeat. If no route is found, undo last change and try again.

That's the basics, but to make things more fun more constraints are needed. How many layers? What's he maximum distance between items? Should we leave additional empty space as a decoy? Should we allow an item to be found only by moving up and down the layers?

I tried some levels, added more constraints, generated more levels. Tossed out completely silly ones (straight line start-key-end) or ones that repeated the same "joke" at the beginning (piece of hallway with glitch at the end, piece of hallway and the door). Later half of the levels I didn't even bother trying, relying on the algorithm to make more and more involved mazes. There's some stupid levels in the first half I probably should have tossed. There's very likely some in the second half.

I didn't even consider adding ladder tiles for the up/down movement in layers. I felt that the up/down thing should be a movement in actual dimension, akin to what happens in Douglas Adams' "The Long Dark Tea-Time of the Soul" at one point. A glitch in the universe, so to speak.

And then there's the flavor text. With the whole glitchy multi-universe thing I wanted an eerie atmosphere, a feeling of not knowing what's going on. Feeling a bit lost. Maybe with a pinch of Terry Pratchett's L-Space. So I added random quotes from a bunch of books and similar, which are all rather moody. Except the IKEA bit, that's there just for fun.

There's some levels with two pieces of paper. If there's just one, it's easy to pick what quote to show, but with two, I'd need to separate them by their coordinates. Instead of going that way, I added a random element; there's a collection of quotes shared by those levels, and whenever a paper is picked up, a random quote is shown. This means that sometimes you get the same one twice (or several times), but if you for some reason play the level again, you may end up with something completely different.

There's an ending sequence. I didn't want to just put a text of "congratulations!" up on the screen and that's it.. there's actually something in there to look forward to.

Sound effects are using BeepFX yet again. Loading screen and main menu art I did with photoshop and image spectrumizer. In-game art (as in, tiles) were made by Antti Tiihonen. The border around the game area I ... drew in hex directly in code.

I ended up writing slightly optimized tile drawing functions in assembly this time, just to make the movements a bit faster (and the glitches a bit more glitchy). The glitches draw random bits of the ROM area as tile data. Thanks to my MuCho project, the text doesn't use a 8x8 font, but the proportional font drawing code I developed for that one.

Mackarel 2.1 by Jari Komppa, http://iki.fi/sol
Progname set to "Mazeract  "

"crt0.ihx":
        Exec address : 24417 (0x5f61)
        Image size   : 27910 bytes
        Compressed to: 13104 bytes (46.951%) by ZX7

"loader.scr"
        Image size   : 6912 bytes
        Compressed to: 2315 bytes (33.492%) by RCS

Boot exec address: 52150 (0xcbb6)
BASIC part       : 51 bytes
Screen unpacker  : 122 bytes
App bootstrap    : 114 bytes (69 codec, 45 rest)
"mazeract.tap" written: 15760 bytes
Estimated load time: 115 seconds (21 secs to loading screen).

Memory : 0       2       4       6       8       10      12      14      16
         |-------|-------|-------|-------|-------|-------|-------|-------|
On load: rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
         sssssssssssssssssssssssssss..b..................................
         ................................................................
         ............BCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

On boot: rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
         sssssssssssssssssssssssssss.....CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
         CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
         CCCCCCCCCCCCC...................................................

Key    : r)om s)creen b)asic L)ow block C)ode block
         H)igh block .)unused -)reserved

In the end I didn't even come close to filling the whole RAM. If I wanted, I could have generated another 100 levels, for example, but I think the game is pretty much done.

Someone actually asked for a sequel already. I do have ideas I could use, but I'd have to implement them and playtest to see if they are really playable. We'll see.

For speccy projects, I still would like to make that one "big world" game, but I don't know if I'll ever get there. Time will tell.