Last year my D&D group played an awesome pirate themed campaign; you can check out a log of the adventures at our DM's blog. Our merry band included an insane Aztec blood priest, a flamboyant elf bladesinger, a stoic half-orc barbarian, and my character, a charming elf bard with loose morals. Part of his repertoire of combat abilities was insulting his opponents so viciously they suffered potentially fatal psychic damage. Nothing that could hear his mockery was safe, whether they could understand what was being said or not (many seagulls were slain).
To make the insults interesting, I wrote a small program to generate them using templating similar to Madlibs (no GPT's here). You can check out the results for yourself at pirates.anardil.net.
Here's a small sample 🏴☠️:
Yer a stinkin' parrot. Whinin' or barterin' won't help ye now.
Blimey! I'd rather be a bilge-drinkin', stinkin' fool than see yer lazy face a second longer.
Die ye stinkin', cockroach-jugglin' salt-water-slurper. Why's a hideous, rotten, borin' squid like ye have a death wish?
Take yer blubberin' talkin' to Davy Jones' locker. Ye look like a pitiful prick was yer papy and a soulless, evil scum-stain was yer mama. I'll feed ye to the fishs, ye rogue. Ye remind me of a deformed ****! Go back to yer papa ye ****-lovin', ***-thievin', stupid kelp-lover! I'd rather be a pox-faced, ignorant son-of-a-****** than smell your ****-jugglin', friendless stink a second longer. I'll cut yer desperate throat then shove a misshapen cockroach up yer ***, ye scurvy-infested bilge-gargler. Batten down the hatches!
As you can see there's quite a range of possibilities! Luckily there are sliders at the top to control the kind of insults to generate:
- G to R rated language
- Jeering to murderous tone
- Simple to haughty vocabulary
How it works
All of these where generated by insults.py
Python program I wrote, using the pirates.yml
vocabulary file.
You can check out the project in its entirety at
public.anardil.net/code/pirate-insults.
Feel free to use it for your own pirate themed needs!
Madlibs have fill in the blank sentences where the blanks are restricted
to a specific part of speech. Not all verbs make logical sense in a verb
blank, but it works out grammatically at least. insults.py
works in a similar
but more powerful way. Rather than blanks being verb
or noun
, they can be
as specific as desired, like liquid_activity
or container
. Further, and
most importantly, they can contain other blanks! This is called recursion in
programming.
Here's an example:
phrase:
- ye {liquid} {liquid_activity} fool!
liquid:
- booze
- salt-water
liquid_activity:
- slurpin'
- guzzlin'
There are two blanks, each with two choices, so phrase
has 2 x 2 = 4 possible
results. Repetition quickly makes things boring, so it's key that the vocabulary
is large enough. Luckily, the number of possible combinations grows very
quickly with the number of options. If we add 5 more liquids and 3 more liquid
activities, we're up to 7 x 5 = 35 possible phrases. insults.py
works by
randomly choosing from the possible choices for each blank, repeatedly until all
the blanks are filled in.
This has some fun properties. Blanks can contain themselves to turn sentences into paragraphs. Adjectives can be nested to create more complex descriptions.
For example:
adjective:
- {adjective, adjective}
- blubberin'
- lazy
- clumsy
- slimy
Since there are 5 choices, every {adjective}
blank has a 20% chance of
splitting into 2 adjective blanks. Each of those adjectives also has a 20%
chance of splitting, and so on. To keep this in check, the chance for splits
needs to be fairly low. Typically (80% of the time), there won't be any splits,
but there's a slight chance for a huge expansion chain resulting in something
like this:
yer a festerin', blubberin', sorry, hideous, annoyin', borin', half-wit, desperate, worthless, pitiful, pox-faced parrot
Which is a bit much! Tuning the chance for each part of the vocabulary was the main challenge in generating the insults. Too restrictive and the insults are repetitive, short, or bland. Too loose and they're nonsensical or way too long.
For pirates.anardil.net, I generated 26 lists with 2,000 insults in advance (which you can inspect for yourself here). When the webpage loads, a random insult list is chosen as the source list. The page consults the sliders to determine which insults are permissible and presents them. That's it! Nothing happens server side whatsoever.
Sliders?
Our D&D campaign drifted between PG-13 and R rated territory, so I wanted control over the language used. Thinking some more, I decided on three axes so the insults could be more tailored the situation and speaker. Plus, I wanted some insurance that random visitors to my website wouldn't be bludgeoned with a barrage of profanity without warning.
- Vulgarity: Is this a G, PG, PG-13, or R rated situation?
- Viciousness: Is the speaker poking fun or threatening with murderous intent?
- Intelligence: Is the speaker a simpleton or a scholar?
The way this works is by adding a bit of metadata to key items in the vocabulary list. For entries where it matters, there's a 3 digit code where each digit corresponds to a slider axis. 0 is lowest value (least vulgar ) and 9 highest (most profane) for example. Profanity has a vulgarity score >7, which is purposely higher than the default slider value!
Here are some examples:
- 009 litigious # low vulgarity, low viciousness, high intelligence
- 047 misshapen # low vulgarity, med viciousness, med intelligence
- 840 son-of-a-***** # high vulgarity, med viciousness, low intelligence
During generation, insults.py
reads these metadata values for each blank
that's filled and keeps track of the highest value its seen for each axis. The
final score is saved along with the insult, which the website knows how to
interpret. The score must be tracked as a maximum value rather than an
average. If you include even a single f-bomb in a Dr. Seuss book, it's no longer a
children's book!
This has an interesting effect on the distribution of insults. The vocabulary doesn't have a particular bias towards high or low scores, so you might expect the distribution to be uniform. However, the longer the insult, the higher the chance a single high value will be present. This skews the distribution towards the high end, meaning there are many more vulgar, vicious, and intelligent insults than the opposite.
I plotted the distribution in 3D, which you can see below:
Feel free to play around with pirates.anardil.net and use it for your own sessions! There are other vocabulary examples in the project directory that can be used as jumping off points to create anything you can imagine.
Thanks for reading ye water-guzzlin' kelp-lover!