NAME

Game::HexDescribe - a web app to add random table driven data to map data

DESCRIPTION

Hex Describe is a web application which uses recursive random tables to create the description of a map. A map in this context is a hex map. This is different from other such tools like Tracery because a collection of locations on a maps differ from a list of unrelated items. Neighbouring locations can share features and thus a river can flow through many locations, a forest can cover many locations, and so on.

On a technical level, Hex Describe is a web app based on the Mojolicious framework. This class in particular uses Mojolicious::Lite.

See Mojolicious::Guides for more information.

Configuration

As a Mojolicious application, it will read a config file called hex-describe.conf in the same directory, if it exists. As the default log level is 'debug', one use of the config file is to change the log level using the loglevel key, and if you're not running the server in a terminal, using the logfile key to set a file.

The default map and table are stored in the contrib directory. You can change this directory using the contrib key. By default, the directory included with the distribution is used. Thus, if you're a developer, you probably want to use something like the following to use the files from the source directory.

{
  loglevel => 'debug',
  logfile => undef,
  contrib => 'share',
};

Default Map, Default Table

The code comes with a default map created using Text Mapper's Alpine algorithm, and a default table. In fact, most of the actual effort went into this default table. In order to understand how the table actually gets used, you should probably read the tutorial.

URLs

The code needs to know where Text Mapper and the Face Generator can be found. You can add these to the same config file we mentioned above. This is what you probably want as a developer:

{
  text_mapper_url => 'http://localhost:3010',
  face_generator_url => 'http://localhost:3020',
};

This assumes you are running the two locally. See Game::TextMapper for Text Mapper.

Entry Points

As this is a web app, the URLs you can call are basically the API it exposes. Each URL can accept either get or post requests, or any.

get /

The default entry point is where you edit your map and table. map is the map, url is the URL to an external table, table is the text of the table if you want to paste it. See /describe below if you want to display the result instead of allow the user to edit the form.

get /load/random/smale

This shows you the edit page again, with a new random map generated by Text Mapper using the Smale algorithm.

get /load/random/apocalypse

This shows you the edit page again, with a new random map generated by Text Mapper using the Apocalypse algorithm.

get /load/random/traveller

This shows you the edit page again, with a new random map generated by Text Mapper using the Traveller algorithm.

get /load/random/alpine

This shows you the edit page again, with a new random map generated by Text Mapper using the Alpine algorithm.

get /stats/random/alpine

This uses a random map and the Alpine algorithm, and describes the map, and then it presents you with some stats.

any /describe

This is where the actual map is described.

map is the map, url is the URL to an external table, table is the text of the table, and a table will be loaded based on the load parameter. Current valid values are seckler, strom, schroeder, johnston, and traveller.

If we want to call this from the command line, we will need to request a map from Text Mapper, too.

text-mapper get /alpine.txt > map.txt
hex-describe get /describe --form map=@map.txt --form load=schroeder

Pipe through lynx -stdin -dump -nolist to get text instead of HTML.

get /describe/random/smale

This variant is for when you want to just keep reloading and getting different maps with different descriptions. Note that you may pass a url parameter, which determines the map retrieved by Text Mapper. This allows you to refer to an existing, random map, if you use the seed parameter in that URL. If you don't provide a URL, a random map using the Smale algorithm will get used. The description will be generated using the Seckler tables.

get /describe/random/alpine

Same thing for a map using the Alpine algorithm and the Schroeder random tables.

get /describe/random/strom

Same thing for a map using the Smale algorithm and the Strom random tables.

get /describe/random/johnston

Same thing for a map using the Apocalypse algorithm and the Johnston random tables.

get /describe/random/traveller

Same thing for a map using the Traveller algorithm and the Traveller random tables.

markdown

This allows us to generate Markdown output.

get /nomap

This shows you the edit page for use cases without a map. Now you're using Hex Describe like many of the existing random table driven text generators. This is where you can test your tables. If you've changed the code for the village table, for example, generate a few villages to see some examples:

[village]
[village]
[village]
[village]
[village]

input is your source text. This is no longer a map. url is the URL to an external table, table is the text of the table if you want to paste it. See /describe/text below if you want to display the result instead of allow the user to edit the form.

/rules

This lists all the rules we have and allows you to pick one.

any /describe/text

This is where the text input is rendered. input is the text, url is the URL to an external table. If not provided, table is the text of the table. If neither is provided, the default table is used.

To call this from the command line:

hex-describe get /describe/text --form input=[village] --form load=schroeder

Pipe through lynx -stdin -dump -nolist to get text instead of HTML.

get /default/map

This shows you the default map.

get /schroeder/table

This shows you the table by Alex Schroeder.

get /seckler/table

This shows you the table by Peter Seckler.

get /strom/table

This shows you the table by Matt Strom.

get /johnston/table

This shows you the table by Josh Johnston.

get /traveller/table

This shows you the Traveller table by Vicky Radcliffe and Alex Schroeder.

get /source

This gets you the source code of Hex Describe in case the source repository is no longer available.

get /authors

This lists the contributors to Hex Describe.

get /help

This shows you a little tutorial. Unlike this documentation, which is for programmers, the tutorial is for the users of the app.

Code

This chapter is used to document the code.

get_data

This is is the basic work horse to get data from a URL. It is used to download the table from a URL, if provided. This uses a simple GET request.

get_post_data

This is is used to get data from a URL when we need a POST request instead of a GET request. We need this for Text Mapper when rendering the map since we send the entire map to Text Mapper in order to render it. A simple GET request will not do.

get_table

This function gets a Mojolicious Controller object and looks for map, load, url and table parameters in order to determine the table data to use.

After we get the SVG map from Text Mapper, we need to add links to the hex descriptions. Text Mapper already allows us to define an URL such that labels get linked to that URL. This feature is of no use to us because we're not using labels. Basically, we want to add links to the coordinates. This function does that: it goes through the SVG and adds appropriate anchor elements.

init

When starting a description, we need to initialize our data. There are two global data structures beyond the map.

$extra is a reference to a hash of lists of hashes used to keep common data per line. In this context, lines are linear structures like rivers or trails on the map. The first hash uses the hex coordinates as a key. This gets you the list of hashes, one per line going through this hex. Each of these hashes uses the key "type" to indicate the type of line, "line" for the raw data (for debugging), and later "name" will be used to name these lines.

$extra->{"0101"}->[0]->{"type"} eq "river"

%names is just a hash of names. It is used for all sorts of things. When using the reference name for a bugbear band1, then "name for a bugbear band1" will be a key in this hash. When using the reference name for forest foo, then "name for forest foo: 0101" and will be set for every hex sharing that name.

$names{"name for a bugbear band1"} eq "Long Fangs"
$names{"name for forest foo: 0101"} eq "Dark Wood"

Note that for /describe/text, init is called for every paragraph.

%locals is a hash of all the "normal" table lookups encountered so far. It is is reset for every paragraph. To refer to a previous result, start a reference with the word "same". This doesn't work for references to adjacent hexes, dice rolls, or names. Here's an example:

;village boss
1,[man] is the village boss. They call him Big [same man].
1,[woman] is the village boss. They call her Big [same woman].

Thus:

$locals{man} eq "Alex"

%globals is a hash of hashes of all the table lookups beginning with the word "here" per hex. In a second phase, all the references starting with the word "nearby" will be resolved using these. Here's an example:

;ingredient
1,fey moss
1,blue worms
;forest
3,There is nothing here but trees.
1,You find [here ingredient].
;village
1,The alchemist needs [nearby ingredient].

Some of the forest hexes will have one of the two possible ingredients and the village alchemist will want one of the nearby ingredients. Currently, there is a limitation in place: we can only resolve the references starting with the word "nearby" when everything else is done. This means that at that point, references starting with the word "same" will no longer work since %locals will no longer be set.

Thus:

$globals->{ingredient}->{"0101"} eq "fey moss"
parse_map_data

This does basic parsing of hexes on the map as produced by Text Mapper, for example:

0101 dark-green trees village
parse_map_lines

This does basic parsing of linear structures on the map as produced by Text Mapper, for example:

0302-0101 trail

We use compute_missing_points to find all the missing points on the line.

process_map_merge_lines

As we process lines, we want to do two things: if a hex is part of a linear structure, we want to add the type to the terrain features. Thus, given the following hex and river, we want to add "river" to the terrain features of 0101:

0801-0802-0703-0602-0503-0402-0302-0201-0101-0100 river

The (virtual) result:

0101 dark-green trees village river

Furthermore, given another river like the following, we want to merge these where they meet (at 0302):

0701-0601-0501-0401-0302-0201-0101-0100 river

Again, the (virtual) result:

0302 dark-green trees town river river-merge

If you look at the default map, here are some interesting situations:

A river starts at 0906 but it immediately merges with the river starting at 1005 thus it should be dropped entirely.

A trail starts at 0206 and passes through 0305 on the way to 0404 but it shouldn't end at 0305 just because there's also a trail starting at 0305 going north to 0302.

process_map_start_lines

As we process lines, we also want to note the start of lines: sources of rivers, the beginning of trails. Thus, given the following hex and river, we want to add "river-start" to the terrain features of 0801:

0801-0802-0703-0602-0503-0402-0302-0201-0101-0100 river

Adds a river to the hex:

0801 light-grey mountain river river-start

But note that we don't want to do this where linear structures have merged. If a trail ends at a town and merges with other trails there, it doesn't "start" there. It can only be said to start somewhere if no other linear structure starts there.

In case we're not talking about trails and rivers but things like routes from A to B, it might be important to note the fact. Therefore, both ends of the line get a "river-end" (if a river).

parse_map

This calls all the map parsing and processing functions we just talked about.

parse_table

This parses the random tables. This is also where *bold* gets translated to HTML. We also do some very basic checking of references. If we refer to another table in square brackets we check whether we've seen such a table.

Table data is a reference to a hash of hashes. The key to the first hash is the name of the table; the key to the second hash is "total" for the number of options and "lines" for a reference to a list of hashes with two keys, "count" (the weight of this lines) and "text" (the text of this line).

A table like the following:

;tab
1,a
2,b

Would be:

$table_data->{tab}->{total} == 3
$table_data->{tab}->{lines}->[0]->{count} == 1
$table_data->{tab}->{lines}->[0]->{text} eq "a"
$table_data->{tab}->{lines}->[1]->{count} == 2
$table_data->{tab}->{lines}->[1]->{text} eq "b"
pick_description

Pick a description from a given table. In the example above, pick a random number between 1 and 3 and then go through the list, addin up counts until you hit that number.

If the result picked is unique, remove it from the list. That is, set it's count to 0 such that it won't ever get picked again.

resolve_redirect

This handles the special redirect syntax: request an URL and if the response code is a 301 or 302, take the location header in the response and return it.

pick

This function picks the appropriate table given a particular word (usually a map feature such as "forest" or "river").

This is where context is implemented. Let's start with this hex:

0101 dark-green trees village river trail

Remember that parsing the map added more terrain than was noted on the map itself. Our function will get called for each of these words, Let's assume it will get called for "dark-green". Before checking whether a table called "dark-green" exists, we want to check whether any of the other words provide enough context to pick a more specific table. Thus, we will check "trees dark-green", "village dark-green", "river dark-green" and "trail dark-green" before checking for "dark-green".

If such a table exists in $table_data, we call pick_description to pick a text from the table and then we go through the text and call describe to resolve any table references in square brackets.

Remember that rules for the remaining words are still being called. Thus, if you write a table for "trees dark-green" (which is going to be picked in preference to "dark-green"), then there should be no table for "trees" because that's the next word that's going to be processed!

describe

This is where all the references get resolved. We handle references to dice rolls, the normal recursive table lookup, and all the special rules for names that get saved once they have been determined both globally or per terrain features. Please refer to the tutorial on the help page for the various features.

normalize_elvish

We do some post-processing of words, inspired by these two web pages, but using our own replacements. http://sindarinlessons.weebly.com/37---how-to-make-names-1.html http://sindarinlessons.weebly.com/38---how-to-make-names-2.html

process

We do some post-processing after the description has been assembled: we move all the IMG tags in a SPAN element with class "images". This makes it easier to lay out the result using CSS.

resolve_appends

This removes text marked for appending and adds it at the end of a hex description. This modifies the third parameter, $descriptions.

resolve_nearby

We have nearly everything resolved except for references starting with the word "nearby" because these require all of the other data to be present. This modifies the third parameter, $descriptions.

closest

This picks the closest instance of whatever we're looking for, but not from the same coordinates, obviously.

distance

Returns the distance between two hexes. Either provide two coordinates (strings in the form "0101", "0102") or four numbers (1, 1, 1, 2).

resolve_other

This is a second phase. We have nearly everything resolved except for references starting with the word "other" because these require all of the other data to be present. This modifies the third parameter, $descriptions.

some_other

This picks some other instance of whatever we're looking for, irrespective of distance.

resolve_later

This is a second phase. We have nearly everything resolved except for references starting with the word "later" because these require all of the other data to be present. This modifies the third parameter, $descriptions. Use this for recursive lookup involving "nearby" and "other".

This also takes care of hex references introduced by "nearby" and "other". This is also why we need to take extra care to call quotemeta on various strings we want to search and replace: these hex references contain parenthesis!

describe_map

This is one of the top entry points: it simply calls describe for every hex in $map_data and calls process on the result. All the texts are collected into a new hash where the hex coordinates are the key and the generated description is the value.

add_labels

This function is used after generating the descriptions to add the new names of rivers and trails to the existing map.

get_label

This function returns the name of a line.

xy

This is a helper function to turn "0101" into ("01", "01") which is equivalent to (1, 1).

coordinates

This is a helper function to turn (1, 1) back into "0101".

neighbour

This is a helper function that takes the coordinates of a hex, a reference like [1,1] or regular coordinates like "0101", and a direction from 0 to 5, and returns the coordinates of the neighbouring hex in that direction.

neighbours

This is a helper function that takes map_data and the coordinates of a hex, a reference like [1,1] or regular coordinates like "0101", and returns a list of existing neighbours, or the string "[…]". This makes a difference at the edge of the map.

one

This is a helper function that picks a random element from a list. This works both for actual lists and for references to lists.

one_step_to

Given a hex to start from, check all directions and figure out which neighbour is closer to your destination. Return the coordinates of this neighbour.

compute_missing_points

Return a list of coordinates in string form. Thus, given a list like ("0302", "0101") it will return ("0302", "0201", "0101").

same_direction

Given two linear structures and a point of contact, return 1 if the these objects go in the same direction on way or the other.

spread_name

This function is used to spread a name along terrain features.

describe_text

This function does what describe does, but for simple text without hex coordinates.

helper example

This Mojolicious helper is used on the help page to make all the examples clickable.

Finally

Start the app at the very end. The rest is templates for the various web pages.