NAME

Game::TextMapper - a web app to generate maps based on text files

DESCRIPTION

The script parses a text description of a hex map and produces SVG output. Use your browser to view SVG files and use Inkscape to edit them.

Here's a small example:

grass attributes fill="green"
0101 grass

We probably want lighter colors.

grass attributes fill="#90ee90"
0101 grass

First, we defined the SVG attributes of a hex type and then we listed the hexes using their coordinates and their type. Adding more types and extending the map is easy:

grass attributes fill="#90ee90"
sea attributes fill="#afeeee"
0101 grass
0102 sea
0201 grass
0202 sea

You might want to define more SVG attributes such as a border around each hex:

grass attributes fill="#90ee90" stroke="black" stroke-width="1px"
0101 grass

The attributes for the special type default will be used for the hex layer that is drawn on top of it all. This is where you define the border.

default attributes fill="none" stroke="black" stroke-width="1px"
grass attributes fill="#90ee90"
sea attributes fill="#afeeee"
0101 grass
0102 sea
0201 grass
0202 sea

You can define the SVG attributes for the text in coordinates as well.

text font-family="monospace" font-size="10pt"
default attributes fill="none" stroke="black" stroke-width="1px"
grass attributes fill="#90ee90"
sea attributes fill="#afeeee"
0101 grass
0102 sea
0201 grass
0202 sea

You can provide a text label to use for each hex:

text font-family="monospace" font-size="10pt"
default attributes fill="none" stroke="black" stroke-width="1px"
grass attributes fill="#90ee90"
sea attributes fill="#afeeee"
0101 grass
0102 sea
0201 grass "promised land"
0202 sea

To improve legibility, the SVG output gives you the ability to define an "outer glow" for your labels by printing them twice and using the glow attributes for the one in the back. In addition to that, you can use label to control the text attributes used for these labels. If you append a number to the label, it will be used as the new font-size.

text font-family="monospace" font-size="10pt"
label font-family="sans-serif" font-size="12pt"
glow fill="none" stroke="white" stroke-width="3pt"
default attributes fill="none" stroke="black" stroke-width="1px"
grass attributes fill="#90ee90"
sea attributes fill="#afeeee"
0101 grass
0102 sea
0201 grass "promised land"
0202 sea "deep blue sea" 20

You can define SVG path elements to use for your map. These can be independent of a type (such as an icon for a settlement) or they can be part of a type (such as a bit of grass).

Here, we add a bit of grass to the appropriate hex type:

text font-family="monospace" font-size="10pt"
label font-family="sans-serif" font-size="12pt"
glow fill="none" stroke="white" stroke-width="3pt"
default attributes fill="none" stroke="black" stroke-width="1px"
grass attributes fill="#90ee90"
grass path attributes stroke="#458b00" stroke-width="5px"
grass path M -20,-20 l 10,40 M 0,-20 v 40 M 20,-20 l -10,40
sea attributes fill="#afeeee"
0101 grass
0102 sea
0201 grass "promised land"
0202 sea "deep blue sea" 20

If you want to read up on the SVG Path syntax, check out the official specification. You can use a tool like Linja Lili to work on it: paste “M -20,-20 l 10,40 M 0,-20 v 40 M 20,-20 l -10,40” in the the Path field, use the default transform of “scale(2) translate(50,50)” and import it. Make some changes, export it, and copy the result from the Path field back into your map. Linja Lili was written just for this! 😁

Here, we add a settlement. The village doesn't have type attributes (it never says village attributes) and therefore it's not a hex type.

text font-family="monospace" font-size="10pt"
label font-family="sans-serif" font-size="12pt"
glow fill="none" stroke="white" stroke-width="3pt"
default attributes fill="none" stroke="black" stroke-width="1px"
grass attributes fill="#90ee90"
grass path attributes stroke="#458b00" stroke-width="5px"
grass path M -20,-20 l 10,40 M 0,-20 v 40 M 20,-20 l -10,40
village path attributes fill="none" stroke="black" stroke-width="5px"
village path M -40,-40 v 80 h 80 v -80 z
sea attributes fill="#afeeee"
0101 grass
0102 sea
0201 grass village "Beachton"
0202 sea "deep blue sea" 20

As you can see, you can have multiple types per coordinate, but obviously only one of them should have the "fill" property (or they must all be somewhat transparent).

As we said above, the village is an independent shape. As such, it also gets the glow we defined for text. In our example, the glow has a stroke-width of 3pt and the village path has a stroke-width of 5px which is why we can't see it. If had used a thinner stroke, we would have seen a white outer glow. Here's the same example with a 1pt stroke-width for the village.

text font-family="monospace" font-size="10pt"
label font-family="sans-serif" font-size="12pt"
glow fill="none" stroke="white" stroke-width="3pt"
default attributes fill="none" stroke="black" stroke-width="1px"
grass attributes fill="#90ee90"
grass path attributes stroke="#458b00" stroke-width="5px"
grass path M -20,-20 l 10,40 M 0,-20 v 40 M 20,-20 l -10,40
village path attributes fill="none" stroke="black" stroke-width="1pt"
village path M -40,-40 v 80 h 80 v -80 z
sea attributes fill="#afeeee"
0101 grass
0102 sea
0201 grass village "Beachton"
0202 sea "deep blue sea" 20

You can also have lines connecting hexes. In order to better control the flow of these lines, you can provide multiple hexes through which these lines must pass. You can append a label to these, too. These lines can be used for borders, rivers or roads, for example.

text font-family="monospace" font-size="10pt"
label font-family="sans-serif" font-size="12pt"
glow fill="none" stroke="white" stroke-width="3pt"
default attributes fill="none" stroke="black" stroke-width="1px"
grass attributes fill="#90ee90"
grass path attributes stroke="#458b00" stroke-width="5px"
grass path M -20,-20 l 10,40 M 0,-20 v 40 M 20,-20 l -10,40
village path attributes fill="none" stroke="black" stroke-width="5px"
village path M -40,-40 v 80 h 80 v -80 z
sea attributes fill="#afeeee"
0101 grass
0102 sea
0201 grass village "Beachton"
0202 sea "deep blue sea" 20
border path attributes stroke="red" stroke-width="15" stroke-opacity="0.5" fill-opacity="0"
0002-0200 border "The Wall"
road path attributes stroke="black" stroke-width="3" fill-opacity="0" stroke-dasharray="10 10"
0000-0301 road

Colours and Transparency

Let me return for a moment to the issue of colours. We've used 24 bit colours in the examples above, that is: red-green-blue (RGB) definitions of colours where very colour gets a number between 0 and 255, but written as a hex using the digites 0-9 and A-F: no red, no green, no blue is #000000; all red, all green, all blue is #FFFFFF; just red is #FF0000.

text font-family="monospace" font-size="20px"
label font-family="monospace" font-size="20px"
glow fill="none" stroke="white" stroke-width="4px"
default attributes fill="none" stroke="black" stroke-width="1px"
sea attributes fill="#000000"
land attributes fill="#ffffff"
fire attributes fill="#ff0000"
0101 sea
0102 sea
0103 sea
0201 sea
0202 sea "black sea"
0203 sea
0301 land
0302 land "lands of Dis"
0303 sea
0401 fire "gate of fire"
0402 land
0403 sea

But of course, we can write colours in all the ways allowed on the web: using just three digits (#F00 for red), using the predefined SVG colour names (just "red"), RGB values ("rgb(255,0,0)" for red), RGB percentages ("rgb(100%,0%,0%)" for red).

What we haven't mentioned, however, is the alpha channel: you can always add a fourth number that specifies how transparent the colour is. It's tricky, though: if the colour is black (#000000) then it doesn't matter how transparent it is: a value of zero doesn't change. But it's different when the colour is white! Therefore, we can define an attribute that is simply a semi-transparent white and use it to lighten things up. You can even use it multiple times!

text font-family="monospace" font-size="20px"
label font-family="monospace" font-size="20px"
glow fill="none" stroke="white" stroke-width="4px"
default attributes fill="none" stroke="black" stroke-width="1px"
sea attributes fill="#000000"
land attributes fill="#ffffff"
fire attributes fill="#ff0000"
lighter attributes fill="rgb(100%,100%,100%,40%)"
0101 sea
0102 sea
0103 sea
0201 sea lighter
0202 sea lighter "black sea"
0203 sea lighter
0301 land
0302 land "lands of Dis"
0303 sea lighter lighter
0401 fire "gate of fire"
0402 land
0403 sea lighter lighter lighter

Thanks to Eric Scheid for showing me this trick.

Include a Library

Since these definitions get unwieldy, require a lot of work (the path elements), and to encourage reuse, you can use the include statement with an URL or a filename. If a filename, the file must be in the directory named by the contrib configuration key, which defaults to the applications share directory.

include default.txt
0102 sand
0103 sand
0201 sand
0202 jungle "oasis"
0203 sand
0302 sand
0303 sand

Default library

Source of the map: http://themetalearth.blogspot.ch/2011/03/opd-entry.html

Example data: https://campaignwiki.org/contrib/forgotten-depths.txt

Library: https://campaignwiki.org/contrib/default.txt

Result: https://campaignwiki.org/text-mapper?map=include+forgotten-depths.txt

Gnomeyland library

Example data: https://campaignwiki.org/contrib/gnomeyland-example.txt

Library: https://campaignwiki.org/contrib/gnomeyland.txt

Result: https://campaignwiki.org/text-mapper?map=include+gnomeyland-example.txt

Traveller library

Example: https://campaignwiki.org/contrib/traveller-example.txt

Library: https://campaignwiki.org/contrib/traveller.txt

Result: https://campaignwiki.org/text-mapper?map=include+traveller-example.txt

Dungeons library

Example: https://campaignwiki.org/contrib/gridmapper-example.txt

Library: https://campaignwiki.org/contrib/gridmapper.txt

Result: https://campaignwiki.org/text-mapper?type=square&map=include+gridmapper-example.txt

Large Areas

If you want to surround a piece of land with a round shore line, a forest with a large green shadow, you can achieve this using a line that connects to itself. These "closed" lines can have fill in their path attributes. In the following example, the oasis is surrounded by a larger green area.

include default.txt
0102 sand
0103 sand
0201 sand
0203 sand
0302 sand
0303 sand
0102-0201-0302-0303-0203-0103-0102 green
green path attributes fill="#9acd32"
0202 jungle "oasis"

Confusingly, the "jungle path attributes" are used to draw the palm tree, so we cannot use it do define the area around the oasis. We need to define the green path attributes in order to do that.

Order is important: First we draw the sand, then the green area, then we drop a jungle on top of the green area.

SVG

You can define shapes using arbitrary SVG. Your SVG will end up in the defs section of the SVG output. You can then refer to the id attribute in your map definition. For the moment, all your SVG needs to fit on a single line.

<circle id="thorp" fill="#ffd700" stroke="black" stroke-width="7" cx="0" cy="0" r="15"/>
0101 thorp

Shapes can include each other:

<circle id="settlement" fill="#ffd700" stroke="black" stroke-width="7" cx="0" cy="0" r="15"/>
<path id="house" stroke="black" stroke-width="7" d="M-15,0 v-50 m-15,0 h60 m-15,0 v50 M0,0 v-37"/>
<use id="thorp" xlink:href="#settlement" transform="scale(0.6)"/>
<g id="village" transform="scale(0.6), translate(0,40)"><use xlink:href="#house"/><use xlink:href="#settlement"/></g>
0101 thorp
0102 village

When creating new shapes, remember the dimensions of the hex. Your shapes must be centered around (0,0). The width of the hex is 200px, the height of the hex is 100 √3 = 173.2px. A good starting point would be to keep it within (-50,-50) and (50,50).

Other

You can add even more arbitrary SVG using the other keyword. This keyword can be used multiple times.

grass attributes fill="#90ee90"
0101 grass
0201 grass
0302 grass
other <text x="150" y="20" font-size="40pt" transform="rotate(30)">Tundra of Sorrow</text>

The other keyword causes the item to be added to the end of the document. It can be used for frames and labels that are not connected to a single hex.

You can make labels link to web pages using the url keyword.

grass attributes fill="#90ee90"
0101 grass "Home"
url https://campaignwiki.org/wiki/NameOfYourWiki/

This will make the label X link to https://campaignwiki.org/wiki/NameOfYourWiki/X. You can also use %s in the URL and then this placeholder will be replaced with the (URL encoded) label.

License

This program is copyright (C) 2007-2019 Alex Schroeder <alex@gnu.org>.

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.

The maps produced by the program are obviously copyrighted by you, the author. If you're using SVG icons, these may have a separate license. Thus, if you produce a map using the Gnomeyland icons by Gregory B. MacKenzie, the map is automatically licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/.

You can add arbitrary SVG using the license keyword (without a tile). This is what the Gnomeyland library does, for example.

grass attributes fill="#90ee90"
0101 grass
license <text>Public Domain</text>

There can only be one license keyword. If you use multiple libraries or want to add your own name, you will have to write your own.

There's a 50 pixel margin around the map, here's how you might conceivably use it for your own map that uses the Gnomeyland icons by Gregory B. MacKenzie:

grass attributes fill="#90ee90"
0101 grass
0201 grass
0301 grass
0401 grass
0501 grass
license <text x="50" y="-33" font-size="15pt" fill="#999999">Copyright Alex Schroeder 2013. <a style="fill:#8888ff" xlink:href="http://www.busygamemaster.com/art02.html">Gnomeyland Map Icons</a> Copyright Gregory B. MacKenzie 2012.</text><text x="50" y="-15" font-size="15pt" fill="#999999">This work is licensed under the <a style="fill:#8888ff" xlink:href="http://creativecommons.org/licenses/by-sa/3.0/">Creative Commons Attribution-ShareAlike 3.0 Unported License</a>.</text>

Unfortunately, it all has to go on a single line.

The viewport for the map is determined by the hexes of the map. You need to take this into account when putting a license onto the map. Thus, if your map does not include the hex 0101, you can't use coordinates for the license text around the origin at (0,0) – you'll have to move it around.

Random

The Random button generates a random landscape based on the algorithm developed by Erin D. Smale. See http://www.welshpiper.com/hex-based-campaign-design-part-1/ and http://www.welshpiper.com/hex-based-campaign-design-part-2/ for more information. The output uses the Gnomeyland icons by Gregory B. MacKenzie. These are licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/.

If you're curious: (11,11) is the starting hex.

Alpine

The Alpine button generates a random landscape based on an algorithm developed by Alex Schroeder. The output also uses the Gnomeyland icons by Gregory B. MacKenzie. These are licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/.

Gridmapper

The Gridmapper button generates a random mini-dungeon based on the algorithm by Alex Schroeder and based on geomorph sketches by Robin Green.

Islands

The Island links generate a random landscape based on the algorithm by Alex Schroeder. The output also uses the Gnomeyland icons by Gregory B. MacKenzie. These are licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/.

Traveller

The Traveller link generates a random landscape based on Classic Traveller with additions by Vicky Radcliffe and Alex Schroeder.

Border Adjustments

The border adjustments can be a little unintuitive. Let's assume the default map and think through some of the operations.

0101 mountain "mountain"
0102 swamp "swamp"
0103 hill "hill"
0104 forest "forest"
0201 empty pyramid "pyramid"
0202 tundra "tundra"
0203 coast "coast"
0204 empty house "house"
0301 woodland "woodland"
0302 wetland "wetland"
0303 plain "plain"
0304 sea "sea"
0401 hill tower "tower"
0402 sand house "house"
0403 jungle "jungle"
0501 mountain cave "cave"
0502 sand "sand"
0205-0103-0202-0303-0402 road
0101-0203 river
0401-0303-0403 border
include https://campaignwiki.org/contrib/default.txt
license <text>Public Domain</text>

Basically, we're adding and removing rows and columns using the left, top, bottom, right parameters. Thus, “left +2” means adding two columns at the left. The mountains at 0101 thus turn into mountains at 0301.

0301 mountain "mountain"
0302 swamp "swamp"
0303 hill "hill"
0304 forest "forest"
0401 empty pyramid "pyramid"
0402 tundra "tundra"
0403 coast "coast"
0404 empty house "house"
0501 woodland "woodland"
0502 wetland "wetland"
0503 plain "plain"
0504 sea "sea"
0601 hill tower "tower"
0602 sand house "house"
0603 jungle "jungle"
0701 mountain cave "cave"
0702 sand "sand"
0405-0303-0402-0503-0602 road
0301-0403 river
0601-0503-0603 border
include https://campaignwiki.org/contrib/default.txt
license <text>Public Domain</text>

Conversely, “left -2” means removing the two columns at the left. The mountains at 0101 and the pyramid at 0201 would therefore disappear and the woodland at 0301 would turn into the woodland at 0101.

0101 woodland "woodland"
0102 wetland "wetland"
0103 plain "plain"
0104 sea "sea"
0201 hill tower "tower"
0202 sand house "house"
0203 jungle "jungle"
0301 mountain cave "cave"
0302 sand "sand"
0005--0103-0002-0103-0202 road
0201-0103-0203 border
include https://campaignwiki.org/contrib/default.txt
license <text>Public Domain</text>

The tricky part is when “add empty” is not checked and you first add two columns on the left, and then remove two columns on the left. If you do this, you’re not undoing the addition of the two columns because the code just considers the actual columns and thus removes the columns with the mountain which moved from 0101 to 0301 and the pyramid which moved from 0201 to 0401, leaving the woodland in 0301.

0301 woodland "woodland"
0302 wetland "wetland"
0303 plain "plain"
0304 sea "sea"
0401 hill tower "tower"
0402 sand house "house"
0403 jungle "jungle"
0501 mountain cave "cave"
0502 sand "sand"
0205-0103-0202-0303-0402 road
0401-0303-0403 border
include https://campaignwiki.org/contrib/default.txt
license <text>Public Domain</text>

This problem disappears if you check “add empty” as you add the two columns at the left because now all the gaps are filled, starting at 0101. You’re getting two empty columns on the left:

0101 empty
0102 empty
0103 empty
0104 empty
0201 empty
0202 empty
0203 empty
0204 empty
0301 mountain "mountain"
0302 swamp "swamp"
0303 hill "hill"
0304 forest "forest"
0401 empty pyramid "pyramid"
0402 tundra "tundra"
0403 coast "coast"
0404 empty house "house"
0501 woodland "woodland"
0502 wetland "wetland"
0503 plain "plain"
0504 sea "sea"
0601 hill tower "tower"
0602 sand house "house"
0603 jungle "jungle"
0604 empty
0701 mountain cave "cave"
0702 sand "sand"
0703 empty
0704 empty
0301-0403 river
0405-0303-0402-0503-0602 road
0601-0503-0603 border
include https://campaignwiki.org/contrib/default.txt
license <text>Public Domain</text>

When you remove two columns in the second step, you’re removing the two empty columns you just added. But “add empty” fills all the gaps, so in the example map, it also adds all the missing hexes in columns 04 and 05, so you can only use this option if you want those empty hexes added…

0101 mountain "mountain"
0102 swamp "swamp"
0103 hill "hill"
0104 forest "forest"
0201 empty pyramid "pyramid"
0202 tundra "tundra"
0203 coast "coast"
0204 empty house "house"
0301 woodland "woodland"
0302 wetland "wetland"
0303 plain "plain"
0304 sea "sea"
0401 hill tower "tower"
0402 sand house "house"
0403 jungle "jungle"
0404 empty
0501 mountain cave "cave"
0502 sand "sand"
0503 empty
0504 empty
0101-0203 river
0205-0103-0202-0303-0402 road
0401-0303-0403 border
include https://campaignwiki.org/contrib/default.txt
license <text>Public Domain</text>

Configuration

The application will read a config file called text-mapper.conf in the current directory, if it exists. As the default log level is 'warn', one use of the config file is to change the log level using the loglevel key.

The libraries are loaded from the contrib URL or directory. You can change the default using the contrib key. This is necessary when you want to develop locally, for example. If you don't set it to the local share directory, the library will access the files installed with the entire distribution.

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

Command Line

You can call the script from the command line. The render command reads a map description from STDIN and prints it to STDOUT.

perl text-mapper.pl render < contrib/forgotten-depths.txt > forgotten-depths.svg

The random command prints a random map description to STDOUT.

perl text-mapper.pl random > map.txt

Thus, you can pipe the random map in order to render it:

perl text-mapper.pl random | perl text-mapper.pl render > map.svg

You can read this documentation in a text terminal, too:

pod2text text-mapper.pl

Alternatively:

perl text-mapper.pl get /help | w3m -T text/html