Cartomancer

The logo for the Cartomancer library

Cartomancer is a library for interpreting and controlling text with conditional output. It was written in pure C++, with no extraneous dependencies. It’s main use case is games but it’s by no way limited to that environment. You could, just as well use it to determine text that needs to change according to application configuration or environment.

Cartomancer is open source and available here

Project Status

Cartomancer is still under development. Currently (1.0) there is still some code cleanup I want to do, and some additional features and fixes I want to implement. If you want to know more or ask for a specific thing, feel free to let me know, I’d be super excited to know people to actually be using this

To me Cartomancer is a “standard priority” project and has to share my time with all of the other things I’m juggling, so no promises on when the next version is out. Check out at the end for version history and known issues.

What is "conditional text"

Cartomancer parses plain text string which have inlined “conditional statements”. These statements are always tied to a controlling variable and the actual displayed text will depend on the value of said variable.

One of those strings might look like this:[ myVar | this is shown if myVar is true| and this if it’s false].
Other than plain Unconditional strings, these statements can represent one of three types of text elements:
  • Substitution conditions just insert the current string value of the condition wherever they’re declared
    Currently using [app.CacheSizeMb] of RAM
  • Switch conditions, like the first example, compare the condition variable to a predetermined set of discrete values and chooses the appropriate output from a set. For boolean type switches, an empty string can be left implied for a “false” value
    Much the same as you, [npc.Pronouns|he|she|they] looked about to break out in tears of joy.
  • Range conditions compare the condition variable, as a float, to a series of thresholds. You can configure both minimum and maximum values for those thresholds
    [rangeVar 5 10 15| rangeVar is between 5 and 9.9 | rangeVar is between 10 and 14.9 | rangeVar is 15 or higher]

Syntax

Cartomancer treats the input text as UTF-8. The examples above all show the default settings but the separators and delimiters are all configurable depending on your own needs. The elements below are all potentially valid configurations

{somevar,10,15,13;text1;text2}
😎myVar[split_here]someText😋

Please consult the config header for more information

How it works

Cartomancer stores its text content in a tree structure, with any number of branches from a text string potentially having its own nested conditionals as well

[var1| [var2| var1 is true and var2 is true| var1 is true and var2 is false] | var1 is false (var2 not relevant)]
Each Text object has a vector of potential outcomes and is attached to a Condition. The Condition itself holds the type of the text/condition pair. If the Condition is a Range, then the Text holds what thresholds it will respond to. If one or more branches in the Text have conditions internal to them, their text content is replaced with a special placeholder marker and a child Text is created.
Photo of a whiteboard with a breakdown of Cartomancer's parsing process steps
Breaking the text down is a recursive process
In order to get the full, current state of the output string, you ask for the root Text for it’s content. It then verifies the value of its Condition and chooses it’s output accordingly. If that output would contain the output of a child, it asks for that output and assembles the final text accordingly.

The Conditions and Configuration are kept in a Context object. Text and Condition consult this context for information on how they should be constructed. The Conditions are stored as a set, this means that every Text that refers to a particular myVar will all refer to the same Condition object. In order to update the variables, you get it from the Context and upon doing so and requesting a text content update, all instances will recalculate their output.

Though the Context behaves, at first glance, like a singleton, it is not one, code-wise and you can have multiple of them if you want. This is required if you want to have different copies of the same variable with different values. As of Cartomancer 1.0, there is no way to process a Text element using a different Context than the one it was created with, so you would have to have a copy of that as well.

Further Information

For information on building Cartomancer, please refer to the repository documentation.
For an example of how to use it, you can check out Foreteller, an application built
with Cartomancer at its core.

Foreteller is built with Qt and wraps the Cartomancer types in QObject elements
so it can forward them to its QML UI.

Version

1.0 (initial release)

Known Issues

  • Cartomancer does not handle some mismatched conditions correctly. Make sure you
    have a consistent number of alternatives for your switch conditions. Should be fixed in 1.1

Version history and Change log

Initial release