A Moving Concentric Maze [Spoilers for Warp (1980)]

Warp is an obscure mainframe game from the 1980s, known (barely) for its strange and experimental parser and its ridiculously unfair puzzles.

One of these puzzles, according to Russell Karlberg, is a circular maze: it’s a series of concentric circles, each made up of eight rooms or absences-of-rooms, and the player can rotate the circles to create a path through the maze.

The source was never released, but if I were to implement this (in C), I would do something like this:

const uint8_t circles[N_CIRCLES][8] = {
    { 0, 1, 1, 0, 1, 1, 1, 0 },
    { 1, 1, 1, 1, 1, 0, 0, 1 },
    ...
};
uint8_t circle_offsets[N_CIRCLES] = { 0 };
bool can_go_north(uint8_t current_circle){
    uint8_t current_offset = circle_offsets[current_circle+1] % 8;
    return circles[current_circle+1][current_offset] == 0;
}
bool can_go_south(uint8_t current_circle){
    uint8_t current_offset = circle_offsets[current_circle-1] % 8;
    return circles[current_circle-1][current_offset] == 0;
}

This has a few significant limitations, though: you can only go north and south (moving radially), never east and west (moving within a single circle), and you can’t rotate the circle the player is currently in.

Those limitations were fine for Warp, but what if I want to do one better? In modern Inform 7, with the power of relations and tables and lists at my command, how could I make a better version of this, without those limitations?

(For simplicity, let’s assume directions don’t need to change at all as the circles move: call them “inward”, “outward”, “clockwise”, and “counterclockwise”. Players will probably appreciate this too.)

I’d reimplement the C algorithm directly, using an array (list of integers). I7 doesn’t have two-dimensional arrays, but a flat list of length (8 * NUM_CIRCLES) is no problem – multiply by 8 in there.

Extending the original code to support cw/ccw movement is equally easy.

Then create a room type with movement rules that check can_go_north, etc. You’ll need to create a lot of rooms of that type, but that’s just a bunch of copy and paste.

On second thought, make it an I7 array of rooms rather than integers, with unenterable spaces represented by a dummy room called “not-a-room”.

(Or “nothing”, but I7 can get fussy about using “nothing” as a generic null reference. I prefer to define dummy objects of a specific kind.)

1 Like

@zarf I couldn’t follow the C coding, since I am not familiar, but I am very curious about what a “dummy object” is and how you use it in Inform 7.

Not-a-room is a room.

Then you use it anywhere that you need a room placeholder that isn’t a “real” room.

@zarf Sorry, I don’t get it. Once you use it, once you place it somewhere, isn’t it fixed there, afterwards? For example if you place it south from another room, how can you use it elsewhere?

I am probably missing the whole point of it. Is there an example in the Inform 7 documentation that uses such a system?

You could check out the dungeon generation rules of Kerkerkruip. You can reset all spatial connections of a room like this:

repeat with way running through cardinal directions:
   change the way exit of place to nothing;

and here is some syntax for creating room connections on the fly:

change the way exit of origin to destination;
let reverse be the opposite of way;
change the reverse exit of destination to origin;

Though of course zarf’s dummy room should precisely not be connected to anything; it’s there so you can put it in lists (or other data structure) of rooms.

1 Like

Oh, I see. It’s to be used like a “nothing,” but for rooms, right?

1 Like

Right, that’s what I meant.

1 Like

I’ve been playing with this in odd moments, and got it to work.

Since I’ve never really looked into Inform 7, this was sort of the vertical cliff version of the learning curve, but I got there eventually. And I’m not particularly planning to do anything with it: but I had fun, so it served its purpose. It may still embody some horrible misunderstandings of Inform, so feel free to point those out if you’re bored enough to look through the code.


I’ve been representing a map ring as a list of objects containing rooms and nothings. I defined “clockwise” and “counter-clockwise” directions, so rooms around the ring are just static connections.

Ring directions
Clockwise is a direction. The opposite of clockwise is counter-clockwise.
Counter-clockwise is a direction. The opposite of counter-clockwise is clockwise.

Understand "cw" as clockwise. Understand "ccw" as counter-clockwise.

Understand "anti-clockwise" as counter-clockwise.
Understand "acw" as counter-clockwise.

Index map with clockwise mapped as west.

Then I made phrases to attach and detach rings at a list of exits. This divides the directions into the number of entries in each ring, so the rings and the exit list can all three be different lengths. For instance, maybe you want to have a ring of eight rooms surrounding a single central hub, and only be attached at the four cardinal directions.

When attaching, it checks for adjacency to avoid making multiple connections between two rooms (in case both rings have fewer rooms than there are exits).

Attach and detach rings
To decide which object is (T - a number) of (N - a number) turns into (Ring - a list of objects):
	Let I be (the number of entries in the Ring) times T divided by N;
	Decide on entry I + 1 of Ring.

To attach (A - a list of objects) to (B - a list of objects) at (exits - a list of directions):
	Let N be the number of entries in exits;
	Repeat with T running from 0 to N - 1:
		Let the way be entry T + 1 of exits;
		Let the other way be the opposite of way;
		Let source be T of N turns into A;
		Let target be T of N turns into B;
		If source is a room and target is a room and target is not adjacent to source:
			Change the way exit of source to target;
			Change the other way exit of target to source.

To detach (A - a list of objects) from (B - a list of objects) at (exits - a list of directions):
	Let N be the number of entries in exits;
	Repeat with T running from 0 to N - 1:
		Let the way be entry T + 1 of exits;
		Let the other way be the opposite of way;
		Let source be T of N turns into A;
		Let target be T of N turns into B;
		If source is a room and target is a room and target is the room way from source:
			Change the way exit of source to nothing;
			Change the other way exit of target to nothing.

Since Inform 7 doesn’t seem to have reference types at all, I haven’t found a good way to represent the room connections. So I’ve been making a bunch more phrases to do the rotation:

Rotating rings
The inner keep exits are always {north, east, south, west}.
The hall ring exits are always {outside, outside, outside, outside,  outside, outside, outside, outside}.

To detach the hall ring from its neighbors:
	Detach the inner keep ring from the hall ring at the inner keep exits;
	Detach the hall ring from the room ring at the hall ring exits.
	Detach the hall ring from the tower ring at {up, up}.

To attach the hall ring to its neighbors:
	Attach the inner keep ring to the hall ring at the inner keep exits;
	Attach the hall ring to the room ring at the hall ring exits;
	Attach the hall ring to the tower ring at {up, up}.

To rotate the hall ring clockwise:
	Say "You hear a grinding noise, and notice that the exits seem to have shifted.";
	Detach the hall ring from its neighbors;
	Rotate the hall ring;
	Attach the hall ring to its neighbors.

To rotate the hall ring counter-clockwise:
	Say "You hear a grinding noise, and notice that the exits seem to have shifted.";
	Detach the hall ring from its neighbors;
	Rotate the hall ring backwards;
	Attach the hall ring to its neighbors.

It’s pretty verbose to set up, but it seems to work just fine. Limitations:

  • It doesn’t do non-uniform exit spacing. If you had a dummy direction that the attach and detach phrases ignored, you could just put a lot of those in the exit list to space things unequally. So that might be pretty easy.
  • It always connects adjacent rooms between rings. I feel like you might want certain rooms to refuse connections in a particular direction. I couldn’t think of a good way to do that. At first I thought, “oh, doors should work for that” but they seem to be static and you can’t change them.
1 Like