Home

Mapping Choices

Estimated Reading Time: 2.70 minutes.

Choices is a game I started work on a while back.

As the name implies, it is an Interactive Fiction game that involves allowing the player to make a truly ridiculous number of decisions.

A number of existing IF engines are actually visual editors. Allowing you to basically program a game by creating giant flowcharts. The approach is entirely intuitive, quick to get going. I tried a few of these before deciding they were inadequate for Choices, and hopefully I'm about to demonstrate why.

As part of a some recent-ish architecture changes to Choices, I made it so that a normalised JSON file is produced, holding the rooms and state of each section of the game. Thanks to my experience with genes, I immediately realised that it was Graphviz-able.

We basically just have to walk the tree, and point one node to all linking nodes.

The following code is downright boring.

from graphviz import Digraph

def main(rooms):
    print("Rendering map")
    dot = Digraph(graph_attr = {"concentrate": "true", 'strict': 'true', 'overlap': 'false', 'splines': 'ortho'})
    names = []

    for k, v in rooms.items():
        names.append(k)

        for item in v['locs']:
            names.append(item['value'])

    names = list(set(names))

    idents = {}
    for index, name in enumerate(names):
        idents[name] = str(index)

    choice_id = 0
    choice_color = ["black", "red", "green", "blue", "purple", "fuchsia", "darkorange"]

    choice_id2 = 0
    choice_style = ["dashed", "solid", "bold"]

    for k, v in rooms.items():
        with dot.subgraph() as sub:
            sub.node(name=idents[k])

            label=v['title'].split()
            label.append("\n")
            for enum, x in enumerate("<{}>".format(k).split('_')):
                label.append(x)

            step = 5
            label='\n'.join([' '.join(label[i:i+step]) for i in range(0, len(label), step)])

            sub.node(idents[k], label=label, shape='rect')

            for item in v['locs']:
                sub.node(name=idents[item['value']])
                sub.node(idents[item['value']], shape='rect')

                color = choice_color[choice_id]
                choice_id = choice_id + 1
                if choice_id > len(choice_color) - 1:
                    choice_id = 0

                style = choice_style[choice_id2]
                choice_id2 = choice_id2 + 1
                if choice_id2 > len(choice_style) - 1:
                    choice_id2 = 0

                sub.edge(idents[k], idents[item['value']], color = color, style = style)

    dot.format = 'png'
    dot.render("map")
    print("Map rendered")

Near the end there, I've added in some code that prettifies things:

Turns out, these are all essential to end up with something readable at the end.

(Note: Some versions of graphviz have a regression in the ortho layout style that will prevent this from compiling. See this bug for that very unfun bug.)

When you toss the above code at our JSON payload, then you'll end up with this:

Map

In point of fact, you can run this code yourself. Just need to load the JSON file. The IDs are obfuscated on the live version, so you might need to tweak/remove the lines:

for enum, x in enumerate("<{}>".format(k).split('_')):
    label.append(x)

Something more appropriate might be:

label.append(k[:10])

The above image involves so many mappings that tracking it in a convential IF engine would be absolutely impossible, or at least incredibly tedious. Forcing you to reduce the number of decisions a player can make, or coming up with clever hacks to try and have some kind of sensible organisation.

On the other hand, I generate these mappings for Choices via a static generator that builds the JSON piecemeal for me where each story prefix you see in the above map is another library.

Either way, this was a fun exploration, and I intend to eventually produce a map of the complete game, once I've finished writing it.

It will undoubtedly be way too massive for any decent kind of print (considering the above is an incomplete copy of 1/3rd of a single prologue out of nine possible starting points), but it'd be fun to try and do that anyway.


Comments

Submit comment...

Subscribe to this comment thread.