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.
-
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
andtable
parameters in order to determine the table data to use. - add_links
-
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 referencename 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 callpick_description
to pick a text from the table and then we go through the text and calldescribe
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 callsprocess
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.