rjbs forgot what he was saying

random tables with Roland

This post is tagged programminganddnd. I don't get to do that often,
and I am pleased.

For quite a while, I've been using random tables to avoid responsibility for
the things that happen in my D&D games. Instead of deciding on the events that
occur at every turn, I create tables that describe the general feeling of a
region and then let the dice decide what aspects are visible at any given
moment. It has been extremely freeing. There's definitely a different kind of
skill needed to get things right and to deal with what the random number gods
decide, but I really enjoy it. Among other things, it means that I can do more
planning well in advance and have more options at any moment. I don't need to
plan a specific adventure or module each week, but instead prepare general
ideas of regions on different scales, depending on the amount of time likely
to be spent in each place.

I was happy with some stupid little gimmicks. I color-coded tables to remind
me which dice they'd need. The color codes matched up to colored boxes that
showed me the distribution of probability on those dice, so I could build the
tables with a bit more confidence. It was easy, but I found myself wanting to
be able to drill further and further down. What would happen is this: I'd
start with an encounter table with 19 entries, using 1d20+1d8 as the number
generator. This would do pretty well for a while, but after you've gotten
"goblin" a few times, you need more variety. So, next up "goblin" would stop
being a result and would start being a redirection. "Go roll on the goblin
encounter table."

As these tables multiplied, they became impossible to deal with in Numbers.
Beyond that, I wanted more detail to be readily available. The encounter entry
might have originally been "2d4 goblins," but now I wanted it to pick between
twelve possible kinds of goblin encounters, each with their own number
appearing, hit dice, treasure types, reaction modifiers, and so on. I'd be
flipping through pages like a lunatic. It would have been possible to inch a
bit closer to planning the adventure by pre-rolling all the tables to set up
the encounter beforehand and fleshing it out with time to spare, but I wasn't
interested in that. Even if I had been, it would have been a lot of boring
rolling of dice. That's not what I want out of a D&D game. I want exciting
rolling of dice!

I started a program for random encounters in the simplest way I could. A table
might look something like this:

type: list
pick: 1
items:
- Cat
- Dog
- Wolf

When that table is consulted, one of its entries is picked at random, all with
equal probability. If I wanted to stack the odds, I could put an entry in
there multiple times. If I wanted to add new options, I'd just add them to the
list. If I wanted to make the table more dice-like, I'd write this:

This rolls a d4 to get a result, then rolls it again for another result, and
gives both. If either of the results is a 3, then it rolls 1-4 more times for
additional options. The output looks like this:

Why are some of those things indented? Because the whole presentation of
results stinks, because it's just good enough to get the point across. Oh
well.

In the end, in the above examples, the final result is always a string. This
isn't really all that useful. There are a bunch of other kinds of results that
would be useful. For example, when rolling for an encounter on the first level
of a dungeon, it's nice to have a result that says "actually, go roll on the
second level, because something decided to come upstairs and look around."
It's also great to be able to say, "the encounter is goblins; go use the goblin
encounter generator."

(No, this is not from an actual campaign. "Instant death" is a bit much, even
for me.)

Here, we see a few of Roland's other features. The mapping with file in it
tells us to go roll the table found in another file, sometimes (as in the case
of the first result under result 5) with extra parameters. We can mix table
types. The top-level table is a die-rolling table, but result 5 is not. It's
a list table, meaning we get each thing it includes. One of those things is a
list table with a pick option, meaning we get that many things picked
randomly from the list. Result 7 says "roll again on this table two more times
and keep both results." Result 8 says, "nothing happens after all."

Result 6 under result 6 is one I've used pretty rarely. It returns a
hash of data. In this case, the encounter is with a spy, but he has a cover
job, found by consulting the job table.

Sometimes, in table like this, I know that I need to force a given result. If
I haven't factored all the tables into their own results, I can pass -m to
Roland to tell it to let me manually pick the die results, but to let each
result have a default-random value. If I want to force result six on the above
table, but want its details to be random, I can enter 6 manually and then hit
enter until it's done:

In other words, it's basically a YAML-ified version of a
Basic D&D monster block. There are a few additional fields that can be put on
here, and we see some of them. For example, per-unit can decorate each unit.
(We're expecting 2d4 men, because of the num field, but if you look up at the
previous encounter table, you'll see that we can override this to do things
like force an encounter with a single creature.) In this case, we'll get a
bunch of men, some of whom may be infected or zombified.

Not every value is treated the same way. The number encountered is rolled and
used to generate units, and the hd value is used to produce hit points for
each one. Even though it looks like a dice specification, damage is left
verbatim, since it will get rolled during combat. It's all a bit too
special-casey for my tastes, but it works, and that's what matters.

Here, one time out of ten, roboelfs are encountered with a Monolith. That
could've been a redirect to describe a monolith, but for now I've just used a
string. Later, I can write up a monolith table using whatever form I want.
(Most likely, this kind of thing would become a dict with different
properties all having embedded subtables.)

Right now, I'm really happy with Roland. Even though it's sort of a mess on
many levels, it's good enough to let me get the job done. I think the problem
I'm trying to solve is inherently wobbly, and trying to have an extremely
robust model for it is going to be a big pain. Even though it goes against my
impulses, I'm trying to leave things sort of a mess so that I can keep up with
my real goal: making cool random tables.