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!