Choose random entries from sequences in a YAML file
Project description
Introduction
There are a number of RPG accessories that allow a GM to build all sorts of things from groups of tables: caverns, catacombs, solar systems, galaxy sectors, names of every description... Now, while it is certainly fun to roll dice it is not always practical and can be time consuming. Hence gametables, a Python program that will read in a YAML file containing the specification of group of tables that will then be randomly rolled. The output is printed on the terminal or sent to a file.
Installation
$ pip install --upgrade gametables
Usage
The gametables script simply takes one or two arguments: the YAML file containing the data and optionally the output (text) file name:
$ python -m gametables monsters.yaml
Or use the console script:
$ gametables monsters.yaml result.txt
YAML file format
Tables and table groups are stored in a YAML 1.2 file. It must contain at least two keys: name, which maps to a string and acts as a reference (or ID) for the table and table which maps to a sequence of entries. For example, the running the file:
---
name: Wandering Monsters
table:
- goblins
- orcs
- hobgoblins
will choose a random entry from the table section. As there are three entries there is a one in three chance of each one being output. In effect, the program is rolling "1d3" on the table. The weighting of an entry can be adjusted by prefixing it with N* where N is the increased weighting. For example:
---
name: Wandering Monsters
table:
- 2* goblins
- orcs
- hobgoblins
is the same as:
---
name: Wandering Monsters
table:
- goblins
- goblins
- orcs
- hobgoblins
An entry can also contain a sequence in "flow" style. For example, consider this file:
---
name: Wandering Monsters
table:
- [skeletons, zombies]
- [goblins, hobgoblins]
There is a fifty-fifty chance of choosing the "skeletons, zombies" line, and then a fifty-fifty chance of choosing either "skeletons" or "zombies". Flow sequences can also have weights, e.g. [3* skeletons, zombies], but an entry containing a flow sequence cannot.
An alternative is to use lookup tables, which will be what are printed in most RPGs. Use the lookup parameter, with a value or expression to "roll" and include the ranges on each line. For example:
---
name: Wandering Monsters
lookup: $1d4$
table:
- 1-2 goblins
- 3 orcs
- 4 hobgoblins
Multiple tables can be specified in the same file:
---
name: Wandering Monsters
format: '^'
table:
- 2* goblins
- orcs
- hobgoblins
...
---
name: Time of Day
table:
- morning
- afternoon
- night
which would produce something similar to the following:
orcs night
Table Specification
The full specification for a table, with all the allowable options, is as follows:
name
The name of the table, as a string. This is used as a heading (if the header parameter is set to 'True') and to reference the table via a link (see below). Names should be unique, but this is not enforced which might lead to odd results.
table
A sequence of entries that will be selected from. An entry can be:
- A string, e.g. "goblins", with an optional weighting, e.g. "3* goblins", or
- A YAML flow sequence, e.g. "['goblins', 'orcs']". Elements of the flow sequence can have weightings as above, but the flow sequence as a whole cannot.
- It is tricky to have a blank line as an entry in a table. The double underscore "__" sequence will be removed from any entry to facilitate this.
lookup
Instructs the program to treat the table as a lookup table. The ranges must be supplied for each line in A-B format, although if this is not supplied then a "1" is assumed. The default value is false. If true then the dice roll used for the lookup will be calculated from the number of entries in the table, i.e. five entries will lead to a "1d5". (However, this will ignore any ranges that have been given).
However, most of the time lookup field will be an expression, e.g. $2d6$. Variables can also be included (provided they have been set beforehand), e.g. $2d6 + terrain_mod$
format
A string used to format the output of the table. This string should contain the ^ character, which will be replaced by the results of the "roll" on the table. The default is simply ^\n (i.e. the result itself, followed by a newline) and the string ^ (i.e. space and caret) will be added to the format if is does not contain a caret. For example:
---
name: Wandering Monsters
format: There are some ^ in the room
table:
- goblins
- orcs
- hobgoblins
might produce:
There are some orcs in the room
Format strings can contain control characters like tab and newline, but make sure the string is double quoted, e.g. format: "\tNumber: ^ \n". To prevent the table adding a newline replace the default format with the caret character only, i.e. "^".
show
A boolean value determining if the output of the table is displayed. The default is 'true'; set to 'false' if the table will only be referred to via a link.
order
The order (an integer) in which the output from the tables should be displayed. The default order is the order in the YAML document. Specifying two tables with the same order may result in undefined results.
header
A string that is prepended to the output of the entire table (i.e. after all the repeats). As with the format field this will need to be quoted if it contains control characters. For example:
---
name: Wandering Monsters
header: "You have encountered:\n"
format: " ^\n"
repeat: 2
table:
- goblins
- orcs
- hobgoblins
should result in something similar to:
You have encountered:
goblins
orcs
Note that the trailing colon also means that the string must be quoted (as per the YAML specification). Also note that the header will not be printed if the result of the table is empty (i.e. a zero length string).
footer
A string that is appended to the output of the entire table (i.e. after all the repeats).
repeat
The number of times to roll on the table. The default is 1. This can be either an integer or a string. When the table is "rolled" this parameter is evaluated for variables (see below), and converted to an integer (defaulting to 1, if it cannot). So the following are all equivalent (see also Variables, below):
---
name: Wandering Monsters
repeat: 2
table:
- goblins
- orcs
- hobgoblins
---
name: Wandering Monsters
repeat: $num$
vars: $num=2$
table:
- goblins
- orcs
- hobgoblins
variables
A string containing any number of variable assignments (see below). This parameter will be evaluated when the table is read (in fact, when all the tables are read) so it can be used to set up variables for use elsewhere. Note that dice rolls are evaluated before the variables are set so:
variables: $num=1d6$
will set num to a value between 1 and 6.
Note that the variables field of each table is read and evaluated in the order they are read, so it is quite possible to overwrite variables. This field is useful when using modifiers to lookup rolls, as it can be used to set default values.
Table Links
Table entries can contain "links" to other tables. The "linked" table is then rolled and the results replace the "link". To link to another table simply enclose the name of the table in caret characters. For example:
---
name: Wandering Monsters
table:
- ^Goblinoids^
- ^The Undead^
...
---
name: Goblinoids
show: false
table:
- goblins
- orcs
- hobgoblins
...
---
name: The Undead
show: false
table:
- skeletons
- zombies
...
has a equal chance of "rolling" on the "Goblinoids" or "The Undead" tables. Note that linked tables typically have show: false to prevent that table being printed out and the output repeated. Table names (and therefore links) are case and space sensitive. Linked tables can themselves contain links, but each table can only be visited or "rolled" 20 times (to prevent infinite recursion).
Links can contain variable references, for example: ^$size$ $type$^ might refer to tables called "Large Undead", "Small Beasts" etc, depending on the value of $size$ and $type$.
Expressions
Table entries can contain simple arithmetic expressions. Variables that have been set will be used in these expressions (but recall that the order in which the tables are "rolled" is important). Any strings of the form XdY will be replaced by the appropriate dice roll (with X as the number of dice and Y the number of sides). This makes it possible to construct tables like this:
---
name: Wandering Monsters
variables: $danger=1d4$
table:
- $1d6+1+danger$ goblins
- $1d4-1+danger$ orcs
- $1d7+danger$ hobgoblins
- $(2d6*2)+danger-1d4$ kobolds
Dice rolls can also appear in the variables field as part of a variable definition, as well as the lookup field.
Variables
Variables can be defined in the variables parameter, as described above. They can also be defined as part of a table entry, in which case they are set when that entry is output.
Variables are set using the notation $name=value$. They are used simply using the name in an expression.
Variables can evaluate to either strings or integers: if a variable (or, for that matter, the result of an expression) can be converted to an integer it will be. Otherwise it will be a string. This means that if, say, the variable num is not defined when the expression $1d4 + num$ is evaluated the result will be the string 1d4 + num. Integer variables are also formatted using comma separators.
For example:
---
name: Wandering Monsters
table:
- goblins $num=4$
- orcs $num=1d2+1$
- hobgoblins $num=3$
...
---
name: Monster Type
repeat: $num$
table:
- Warrior
- Soldier
- Mage
...
Variables are set first, removing the expression from the entry. Then all expressions are evaluated with the result replacing the expression in the entry. Note that variables are defined in the order they are encountered when outputting the tables: if a table has show: false then any variables it defines will not be set unless some other table runs it via a table link.
All variables are "global" and there is no checking for or protection from overwriting an existing variable.
Example Files
The examples folder in the repo contain some annotated examples of input YAML file. The first is the "Adventure Title" generator from Modiphious' Gamemasters Toolkit (conan_adv_titles.yaml). Some sample results are quite good:
Shadow in the Pool
A/an Empress Is/Will Be/Shall Be Cursed
The Iron Gem of the Skull
A/An Storm of Shadow
Others might need some work:
Bone
The Lost of Queen
Fallen of Specter
Circle of Ring
although "Bone" is not a bad name for a Conan adventure!
Another example is from "Catacombs of Hyboria", Second Edition by Mongoose Publishing (conan_catacombs.yaml). This is a much more complex generator with numerous tables (some of them two dimensional) and various modifiers. Some sample output is:
Type: Hollow
Size: Average
Area: 400 sq. ft.
Depth: 85 ft.
Terrain:
Man-Made: Burial Chamber (decrepit, 50% chance of treasure worth 1200 silver pieces)
and
Type: Cavern System
Size: Average
Area: 1,800,000 sq. ft.
Depth: 2,550 ft.
Terrain:
Flora: Common Growth (area 50%)
Surface: Gorge/Cliff (span 60 ft., depth 30 ft.)
Surface: Pitted Stone (area 40%)
Vast Depth: Ancient Art/Symbols (area 40 sq. ft.)
Surface: Stalagmite Field (area 300 sq. ft., average height 18 in.)
Fauna: Crawling (insect, 100, waste is prodigious and slick)
Fauna: Aerial (gliding mammal, pop. 10,000, has perfect sonar and blind sight)
Fauna: Burrowing (mammal, pop. 1,500, naturally venomous and predatory)
Fauna: Burrowing (mammal, pop. 500, possesses dangerous self-defence toxins in its flesh)
Fauna: Large Creature (reptile, pop. 60, advanced intelligence with problem-solving)
Surface: Stalagmite Field (area 450 sq. ft., average height 2 ft.)
Fauna: Aerial (lacewing, pop. 6,000, small sized, fast)
Ceiling: Optical Illusion of Height
Vast Depth: Phosphorescent Growths (area 50%)
Ceiling: Optical Illusion of Height
Vast Depth: Abyssal Crevasse (width 100 ft.)
Fauna: Subterranean (worm, pop. 40, found below 100 ft., has a secondary trait)
Surface: Waterway Access (underground river, depth 30 ft.)
Vast Depth: Abyssal Crevasse (width 160 ft.)
Surface: Stalagmite Field (area 200 sq. ft., average height 1 ft.)
Unique: Lair of the Dead
Plots:
Unearthed Graves
Note that the order of the terrain elements is not sorted. It would be nice if this was an option but one could consider this the order in which the elements are encountered. Also note that full descriptions of elements have not been included, so if you want to know what "Pitted Stone" and "Lair of the Dead" refer to you will need to source the book.
Another example is from the "Star Wars Roleplaying Game: Revised, Expanded, Updated" by Womp Rat Press. This is an expansion of "Star Wars: The Role Playing Game" by West End Games. The file star_wars_planets.yaml contains the world creator in the REUP version. Sample planets generated are:
Star Wars Planet
Function: Military
Government: Organized Crime
Planet Type: Terrestrial
Terrain: Volcanic
Temperature: Temperate
Gravity: Heavy
Atmosphere: Type I (breathable)
Hydrosphere: Moist
Day Length: 15 standard hours
Year Length: 465 local days
Starport: Imperial/Republic class
Population: 5,000
Tech Level: Hyperspace
and
Star Wars Planet
Function: Homeworld
Government: Dictatorship
Planet Type: Terrestrial
Terrain: Special
Temperature: Temperate
Gravity: Standard
Atmosphere: Type I (breathable)
Hydrosphere: Saturated
Day Length: 25 standard hours
Year Length: 345 local days
Starport: Stellar class
Population: 82,000,000,000
Tech Level: Hyperspace
Note that the one thing not included here are the "Incompatible Conditions", such as not having an Agricultural planet in an Asteroid Belt.
Development Notes
Unit testing
A small number of tests are included in the test_gametables.py file and can be run using the pytest application.
Packaging a distribution
When ready for a release use the bumpversion application to update the version number, e.g.
$ bumpversion major --tag
This will update the source file and the setup configuration. Then build the distribution:
$ python setup.py bdist_wheel
To Do
- Catch more YAML and expression parser exceptions
- Warn of unknown YAML fields?
- Test literals, folded newlines and multi-line flow scalars
- Functionality to set up a library of files, then roll a table by name (not file itself)
- Option to dump YAML file in Markdown format (--dump)
- Option to toggle integer formatting (--integer)
- Options for tables to run without output (like show: false but with variables being set etc)
- Read a folder of YAML files (would have to define order)
- Read a zip file of YAML files
- Overall headers and footers (possible as template files)
- Warning of overwriting existing variables, with option to allow/deny?
- For lookup tables, check ranges are consistent
- More formatting options, ideally for input to something like Pandoc? Or test have HTML header/footer
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gametables-0.1.0-py3-none-any.whl.
File metadata
- Download URL: gametables-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.2.0 pkginfo/1.5.0.1 requests/2.24.0 setuptools/51.1.1 requests-toolbelt/0.9.1 tqdm/4.49.0 CPython/3.8.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
acc66ed11fabfaa7e4320ed349afb3e1dc5f82059a37a584db7719acf3c19bfb
|
|
| MD5 |
35f58b77d890242d87bace48167ecc7c
|
|
| BLAKE2b-256 |
fe945b1c6f785e73e1ee9d7ea8d9b0ace345203b336baeeea4f4b96c4dacc598
|