[]
Banner for TYCHE NOTATION

TYCHE NOTATION

2023-04-15
An extensible dice syntax for tabletop gaming with support for nested dice expressions.
Last updated on 

The below demo can handle a simple subset of the Tyche syntax. It can handle basic arithmetic(+, -, *, /, **) and dice rolls—but limitlessly nested!

Then hit evaluate and watch the dice roller give you a detailed breakdown of the roll.

Try it out!



Table of Contents

Huh?

It’s pronounced ”tishe“(try saying “ties” with a lisp), like after the Greek goddess of fortune.

Here’s the thing: 2D digital dice suck. They’ve sucked for a while. It seems like every form of digital dice was hacked together as part of a bigger project. “We’re trying to make a [CHARACTER SHEET / VIRTUAL TABLETOP / DISCORD BOT], just get it working.” Even worse, it makes perfect sense—I don’t blame them. No individual should let themselves descend into the delusion of believing they can make the next panacea. The individual wants to get their work done—no more no less. Their job isn’t to fix the system. The job entry they applied to didn’t have the text “fix everybody’s problems lol xD” at the bottom. Or, at the very least, I hope they’re getting paid pretty damn well if it did.

And yet, I have made the lapse in judgement of falling into the aforementioned delusion.

In the tech world, the answer to the question “has anybody figured out a better way to do this?” is usually yes—they have and it’s an open source project that’ll wash your dishes and shine your shoes the first time you try it. It’ll be like heaven on earth. Some smattering of humans came together to make, for free, something that perfectly solves your particular problem. You smile at humanity’s kindness and pure generosity, and type your pip install’s or cargo add’s or pnpm i‘s.

And then you make the mistake of interacting with the real word, where things only get done if they really—and I do mean really—need to get done. Hazardous, confusing, sluggish, (and frankly broken) systems permeate the computers of every university, business(tech or otherwise), and individual. If the world of open source is a fragrant lavender, the real word pushes your nose into a three-year-old moldy sandwich with burnt hair in place of lettuce.

So sometimes, you just have to make your own damn sandwich.

Why?

Once upon a time, I, a Dungeons & Dragons nerd through-and-through, decided to make a dice calculator. A little interface like the app that comes on every smartphone and desktop computer, the kind of interface that you’ll get access to if you type “calculator” into Google—but for dice.

Then I realized I’d have to make a notation. Just like how calculators need a syntax for the order and form in which you input numbers and operations, so did my dice calculator.

And then I realized that there’s a lot more the average user wants out of dice than just “roll XX dice with YY sides(and maybe add or subtract a little)“. You might want to keep the XX highest dice. Or the XX lowest dice. Or reroll on an XX result. Or, well, you get the idea. There are as many ways to roll dice as there are people in the pews. There exist a million and one tabletop roleplaying games that have made up their own stupid types of dice rolls. Which begs the question: why are current dice roll offerings so opinionated?

If you don’t know the current state of the digital dice rolling world(which is the position of the vast majority of people), don’t worry about it. However, if you do, please think to yourself: when using a dice bot on Discord, or a dice roller online, or anything of the sort, how quickly can you find something that it can’t do—and not only can’t do, but can’t ever possibly do? I’d venture pretty quickly.

Imagine you’re a game designer. You want to make the next big tabletop roleplaying game. Immediately, you are forced to choose: either alienate anybody who wants to roll dice digitally and use a quirky/innovative new system, or make your game compatible with the bog-standard D&D style. So, either sacrifice an avenue of potential creativity, or your audience.

As a game master for tabletop gaming myself, I strongly believe that your table is your own. There is a wonderful beauty to allowing each and every combination of people to infuse the hobby with whatever quirks and nuances they desire. As such, I believe every aspect of the hobby should facilitate this. Just as Matt Colville says, every DM becomes, is, and always was a game designer. Designers deserve flexible tools. Players, busy with jobs, studies, or responsibilities, deserve to have the tools they use to be as straightforward as possible, consistent across applications and platform. A standard—that’s what we need. A way for humans and computers to both understand how the dice should be, will be, and have been tossed. A flexible and extensible standard that anyone can add to. A new dice notation for the digital age.

This is where Tyche comes in. The Tyche Notation: one standard notation to rule them all—to serve every purpose and every game.

What?

⚠️ Here begins the Tyche Notation Specification

This specification describes the syntax for the Tyche Notation, a notation for dice rolls that is human & machine readable, unopinionated, comprehensive, rigorous, flexible, and extensible. This is a a draft, and will be updated as lessons are learned during the implementation process.

  • Draft Version: 0.1.1
    • Changes: Fixed spelling errors, added “A Final Note” section, changed “Technical Specification” from a table to a more readable list.
  • Date: 2023-04-14

Above all, Tyche follows a number of patterns:

Please also note that Tyche is unimplemented. This is a specification that defines the rules an implementation should follow. It, by consequence, defines the usage of the notation. At time of writing, an implementation is currently being written in the Rust programming language. A working implementation of a subset of Tyche’s features(infinitely nestable dice rolls and arithmetic) is available at the top of this page. Or, if you’re reading this standard on the Github repo for this project, at mirth.cc/work/tyche.

Pieces

The atomic components of Tyche are as follows:

The above can stand alone or be combined with arithmetic operators—+, -, *, /, ^, (, and )—to form expressions. Expressions can be nested infinitely. Expressions can also be acted on by ”Transformers”, which take the form expression.T(arg1, arg2, arg3, ...), where expression is the expression to be transformed, T is the transformer’s identifier, and arg1, arg2, arg3, etc. are the arguments to the transformer.

In practice

Below are some basic examples of Tyche expressions.

2d6 + 4 - 1 — arithmetic with dice

4d10 / 2 * 3 — division and multiplication

3d6 + (d4 / 2) — parentheses for order of operations

Expressions can be combined in Tyche in the form of a ”Tychain”, a comma-separated list of expressions. A Tychain is the top-level unit that a Tyche parser works with. You give it a list of expressions, and it rolls them and returns the result.

Below are some more advanced examples of Tyche expressions.

2d6K3 — a dice roll with a manipulator

3d10D{<2, >8} — a dice roll with a manipulator with parameters

cos(2d6) — a function call

(d4)d6 - a dice roll with a dice roll as a parameter

2d6 + 4 - 1, 4d10 / 2 * 3, 3d6 + (d4 / 2), (d20/3) + (1d4, 1d6) — a Tychain

(d20 + 1).R(3) - an expression with a transformer

Technical Specification

Here are the elements of the syntax(square brackets are used to represent variability):

Notes

On Nesting

Nesting is allowed and prioritized. Since functions, manipulators, and transformers all evaluate to values that can be used as input to other functions, manipulators, and transformers, there can be nested calls. Subexpressions can be nested in other subexpressions. Subexpressions can contain FUNC/MANI/TRNF and complex arithmetical operations, which are then nested within other operations, ad nauseaum.

On Operator Precedence

Arithmetic operators follow the same precedence as Python. If same precedence, left-to-right evaluation is followed.The deepest nested FUNC/MANI/TRNF/EXPR/SEXP gets evaluated first.

On Extensibility

The goal of Tyche is to be extensible. This is done through FUNC, MANI, and TRNF. Developers can write their own FUNC, MANI, and TRNF in a scripting language(to be decided on later, likely Python or Lua), and then use them as plugins.

You’ll notice I haven’t defined a strict set of FUNC/MANI/TRNF. That’s on purpose. The parser will assume that any valid FUNC/MANI/TRNF name is valid, and then if it isn’t, it’ll be found out during runtime.

On Invalid Inputs

Since extensibility is the goal, a lot of unpredictability is introduced. What happens when a string input is given to something that expects a number, or vice versa? Answer: an error is thrown during runtime, giving a detailed output of where the error happened. String vs. number inputs aren’t educated in the grammar itself.

Since the parser cannot know all valid FUNC/MANI/TRNF names, it should parse them as generic identifiers following the specified naming rules. During the evaluation phase, the system will attempt to resolve these identifiers to actual implementations. If an identifier cannot be resolved to a valid FUNC/MANI/TRNF, an error will be reported at runtime.

On Whitespace

Unless a part of the notation explicitly states that something must “immediately follow” another piece of notation, whitespace is allowed as much as you want, and promptly ignored.

On Dice Trays

Dice trays are an internal data structure used during the evaluation of rolls and manipulators. They represent the intermediate state of a set of dice as they undergo transformations by manipulators. Dice trays are not part of the parsing process, as they are only used in the evaluation phase after the input has been successfully parsed.

Grammars

Full Grammar

tyche_notation  ::= tych (',' tych)*
tych            ::= expr (operator expr)*
expr            ::= roll | crol | func | texp | number | sexp | string
sexp           ::= '(' expr ')'
roll            ::= [number | sexp]? 'd' (number | sexp) [mani]*
crol            ::= [number | sexp]? 'd<' par (',' par)+ '>'
mani            ::= 'M' ('{' par (',' par)* '}' | number)
texp            ::= expr trnf
trnf            ::= '.' identifier '(' [par (',' par)*]? ')'
func            ::= identifier '(' [par (',' par)*]? ')'
par             ::= number | string | expr
number          ::= ['-']? digit+ ['.' digit+]?
string          ::= '"' (character | '\')* '"'
identifier      ::= letter (letter | digit | '_')*
operator        ::= '+' | '-' | '*' | '/' | '//' | '%'
letter          ::= 'A'..'Z' | 'a'..'z'
digit           ::= '0'..'9'
character       ::= any printable character

Precursor Grammar

tyche_notation  ::= tych (',' tych)*
tych            ::= expr (operator expr)*
expr            ::= roll | number | sexp | string
sexp           ::= '(' expr ')'
roll            ::= [number | sexp]? 'd' (number | sexp)
number          ::= ['-']? digit+ ['.' digit+]?
string          ::= '"' (character | '\')* '"'
identifier      ::= letter (letter | digit | '_')*
operator        ::= '+' | '-' | '*' | '/' | '//' | '%'
letter          ::= 'A'..'Z' | 'a'..'z'
digit           ::= '0'..'9'
character       ::= any printable character

More examples

A Final Note

The key takeaways from this specification should be that:

The eventual goal is to offer a straightforward extensible dice parser, specifically catered to developers. Hence, the implementation is being written in Rust. This will allow for a solid and fast core library, which can be built into Python and JavaScript bindings. Keep an eye out for the first release!