A TADS3/adv3 module for generating simple random maps for testing

I just put together a little TADS3/adv3 module for generating very simple random maps. It’s now available here: simpleRandomMap github repo.

The map generation uses the same basic algorithm used in the random map example I included in routeTable module: All maps are square, 10x10 by default. If a room is along the north edge of the map, it gets an exit to the east. If a room is along the east edge, it gets an exit to the north. If the room is along neither the north nor east edge it randomly gets either an exit to the north or an exit to the east. All exits are reciprocal, so when room #1 gets an east exit to room #2, room #2 automagically gets a west exit back to room #1. This algorithm guarantees that a path exists between any two arbitrary rooms.

The maps generated this way are really not suitable for any IF game written after about 1984. But it’s not intended for generating content for games to be published, it’s intended to generate test cases for pathfinding, NPC scripting, and so on.

Usage is trivial. By default the module just looks for an declared instance of the SimpleRandomMapGenerator class and generates a map using the instance for configuration options. To use all the defaults you just have to include a line like:

        myMap: SimpleRandomMapGenerator;

…somewhere in the project source. This will create a random 10x10 map (100 total rooms) and place the player (as defined by gameMain.initialPlayerChar) into the first room (the southwest corner).

Optional properties on SimpleRandomMapGenerator are:

  • mapWidth specifies the width of the map. Default is 10, which makes a 10x10 map. Note: no bounds checking is done on this value
  • placePlayer is a flag. If boolean true, the player will be moved to the first room of the generated map after map generation. The default is true
  • player the player object to place if placePlayer is true. By default this is gameMain.initialPlayerChar, but this can be set to be something else if you want to (for example) dump an NPC into the generated map instead

Multiple map instances are theoretically supported, but the builtin behaviors all assume that you’ll only be generating one map at a time (and so you’ll have to do subclassing to make multiple simultaneous generated maps work).

Everything in the module is wrapped in preprocessor conditionals, so everything can be enabled or disabled by twiddling the -D SIMPLE_RANDOM_MAP flag at compile time.

There’s also a -D SIMPLE_RANDOM_MAP_GRID flag, which will tell the generator to make a regularly-connected grid of rooms (every room gets north, south, east, and west exits except at the edges of the map) instead of a random one.

Finally, compiling with -D __DEBUG_SIMPLE_RANDOM_MAP enables the debugging commands. At the moment there’s just one: you can use >M to display a simple ASCII map of the surrounding area:

>m
Showing (1, 2) - (5, 6)
.............................................
...[_]======[_]======[_]======[_]======[_]===
......................|.................|....
......................|.................|....
...[_]======[_]======[_]......[_]======[_]...
....|...................................|....
....|...................................|....
...[_]......[_]======[*]======[_]======[_]...
....|........|.................|........|....
....|........|.................|........|....
...[_]......[_]......[_]======[_]......[_]...
.............|..........................|....
.............|..........................|....
...[_]======[_]......[_]======[_]======[_]...
.............|...............................

The map will auto-center around the player (except at map edges), and the player’s position is indicated by the asterisk.

This will obviously only work if you’re using a proportional font. And it doesn’t work with screen readers (I’m open to suggestions for how to make this sort of thing more accessible).

Not sure how useful this sort of thing will be to general IF authors, but I’d previously written a version of this code for testing pathfinding in the routeTable module and then I found myself re-writing it for testing some NPC behaviour stuff. And whenever I find myself writing the same thing twice that tells me it’s probably time to turn it into a library, so that’s what I did.

4 Likes

Amazing!!

There is a project on my future list that will need something like this!

2 Likes

Cool. I’ll probably be adding a couple of additional map types as I use the module for testing my NPC stuff…specifically I’m thinking of subclasses that intentionally produce “worst case” maps (longest path traversals, equal-weight loops, and other things that can trip up or slow down NPC pathing).

Independent of that, I’m also working on a separate body of code for producing procgen maps suitable for bolting onto a “real” game (instead of just being used for testing purposes). But I think the procgen mapgen stuff probably won’t get publicly released until after the game I’m writing it for is done.

2 Likes

I just pushed an update to the repo that adds two new map generators:

First, SimpleRandomMapGeneratorRB, which generates the map using a recursive backtracking method. This should consistently produce more “rivers”—long dead end paths—compared to the base generator. In other words, while the default generator is “bushy”, producing a lot of little dead ends (usually just a room or two deep) the RB generator should produce a smaller number of longer dead ends. ASCII snapshot:

>m
Showing (2, 2) - (6, 6)
....|........|...............................
...[_]......[_]======[_]======[_]======[_]...
....|...................................|....
....|...................................|....
...[_]......[_]======[_]======[_]======[_]...
....|........|...............................
....|........|...............................
===[_]......[_]======[*]......[_]======[_]===
......................|........|.............
......................|........|.............
...[_]======[_]======[_]......[_]......[_]===
....|..........................|........|....
....|..........................|........|....
...[_]......[_]======[_]......[_]......[_]===
....|........|........|........|.............

The second new generator is a “braided” map generator. It should produce maps with no dead ends. This should consistently produce maps with loops and cycles. Sample map:

>m
Showing (1, 1) - (5, 5)
....|........|........|.................|....
...[_]......[_]......[_]======[_]......[_]...
....|........|........|........|........|....
....|........|........|........|........|....
...[_]......[_]======[_]======[_]......[_]===
....|...................................|....
....|...................................|....
...[_]======[_]======[*]======[_]======[_]...
....|..........................|.............
....|..........................|.............
...[_]......[_]======[_]......[_]======[_]===
....|........|........|......................
....|........|........|......................
...[_]======[_]......[_]======[_]======[_]===
.............................................

I might add additional generator classes in the future, but the three currently implemented take care of most of the generic test cases I care about.

2 Likes

This would be perfect for a roguelike-type game. I don’t know if I’m the only one who’s considered creating such a thing in TADS, but it would make an interesting test case.

1 Like

This is on my project list. Not a typical slash-and-loot, but one of my ideas will be leveraging procedural maps, at least. It’s gonna be a while before I get to it, tho.

Yeah, that’s kinda what I’m working on. Not a full-on IF roguelike, but rather an IF game where there’s a static “overworld” that works more or less like a traditional IF game, and then a dynamic “underworld” that’s (mostly) guided procgen. The idea being that stuff in the procgen part of the game is more or less continuously getting re-rolled (every in-game day the “dungeon” gets reset), but solving puzzles and making changes in the “static” part of the game changes the way the “dungeon” is generated.

Basically the module presented in this thread is all “naive” procgen stuff. There’s a couple of different algorithms, but they’re all pretty simple and they just fill a big square with mostly-identical rooms. Fine for testing, but that’s about it.

The stuff I’m working on for “real” use builds a graph in which the vertices are functional roles (like “start” and “goal”), the graph is expanded by recursive replacement of edges with groups of vertices (via rules determined by the current game state), and then the final graph is converted into a playable game map by substitution of rooms for vertices and placement of appropriate exits.

This allows for development of fairly complicated logical topologies in a guaranteed-consistent state (in terms of ordering/dependencies, like making sure you don’t place a locked door in front of the key needed to unlock it), and makes it easy to make “dynamic” changes to rooms/map topology to reflect progress through the game.

2 Likes