-- Strain name generator - Northern Cosmos 2020
--
-- The dictionary holds the words that will be used.
--
-- Fill in the strain dictionary as you please, add/change/remove words and/or groups.
-- The rule at the start of each group can be used to steer the name composition towards "some style"...
--
-- Each strain name picks words from different groups, thus a word will never appear twice in a name,
-- unless it's repeated in more than one group and the rules allow it. There's no support for probabilities
-- or anything fancy, but some basics can be realized by repeating words within a group.
-- Note that it's possible to construct the dictionary with its rules so that it's impossible,
-- or takes too long, to make the requested name(s) - a timeout occurs if this happens.
--
-- The rules are:
--   rules.ANY           => word can be anywhere in the name
--   rules.FIRST         => word must be first
--   ...
--   rules.FIFTH         => word must be fifth (no symbols provided for later positions, use integers for that!)
--   positive integer N  => word must be Nth
--   rules.LAST          => word must be last
--   rules.MIDDLE        => word must be in the middle (not first or last)
--   rules.NOT_FIRST     => word must not be first
--   rules.NOT_LAST      => word must not be ast

local rules =
{  ANY       = 0,
   FIRST     = 1,
   SECOND    = 2,
   THIRD     = 3,
   FOURTH    = 4,
   FIFTH     = 5,
   LAST      = -1,
   MIDDLE    = -2,
   NOT_FIRST = -3,
   NOT_LAST  = -4
}

local strain_dictionary =
{
  -- origins
  { rules.FIRST, "Jamaica", "Jamaican", "Acapulco", "Cali", "Afghan", "Nepal", "Nepalese", "Thai", "Space", "Moon",
    "African", "Colombian", "Mexican", "Hawaiian", "Panama", "Cambodia", "Panama"
  },

  -- directions
  { rules.FIRST, "Northern", "Southern", "Western", "Eastern" },

  -- occasions
  { rules.FIRST,
    "Birthday", "Wedding", "Burial", "Christmas", "Easter", "Weekend", "Sunday" },

  -- misc prefixes
  { rules.FIRST, "OG", "AK" },

  -- dope terms
  { rules.ANY, "LSD", "Crack", "Meth" },

  -- misc
  { rules.LAST, "Shot", "Kick", "Smash", "Hit" },

  -- first names
  { rules.FIRST, "Jack", "Bruce", "Willy", "Carl", "Bob" },

  -- last names
  { rules.FIRST, "Herer", "Banner", "Nelson", "Sagan", "Marley" },

  -- last names
  { rules.SECOND, "Herer", "Banner", "Nelson", "Sagan", "Marley" },

  -- places
  { rules.ANY, "Pyramid", "Cave", "Dungeon", "Mountain", "Desert", "Savannah", "Cosmos", "Cosmic", "Space" },

  { rules.ANY, "Fruit", "Pineapple", "Banana", "Apple", "Pear", "Orange", "Blackberry", "Blueberry",
    "Lingonberry", "Lemon", "Lime"
  },

  -- fruits/berrys/nuts
  { rules.LAST, "Fruit", "Berry","Strawberry", "Blackberry", "Blueberry", "Peanut", "Lingonberry", "Cherry",
    "Pineapple", "Banana", "Apple", "Pear", "Tangerine", "Orange", "Lemon", "Lime"
  },

  -- other general edibles
  { rules.LAST, "Cookies", "Cake", "Bread", "Pudding", "Gelato", "Ice Cream", "Cream", "Candy", "Fudge", "Chocolate",
    "Punch", "Pie", "Cereal", "Bubble", "Gum", "Cheese", "Coffe", "Mimosa", "Zkittlez", "Snack", "Munchie", "Slush"
  },

  -- other edibles
  { rules.LAST, "Milk", "Juice", "Coffe", "Moonshine", "Poison", "Potion", "Medicine" },

  -- tastes/smells
  { rules.ANY, "Sour", "Sweet", "Bitter", "Pungent", "Vanilla", "Sugar" },

  -- animals
  { rules.ANY, "Animal", "Ape", "Gorillla", "Monkey", "Rhino", "Cow", "Bull", "Tiger", "Lion", "Snake", "Dawg", "Cat",
    "Hog", "Bird", "Shark", "Turtle", "Reptile", "Gator", "Chicken", "Lamb", "Spider", "Scorpion", "Octopus",
    "Mosquito", "Bee", "Wasp", "Gnat"
  },

  -- cryptozoology'ish
  { rules.ANY, "Alien", "Vampire", "Ghost", "Kraken", "Cthulu", "Zombie", "Monster" },

  -- plant terms
  { rules.LAST, "Kush", "Skunk", "Bud", "Tree", "Bush", "Shrubbery", "Stick", "Leaf" },

  { rules.NOT_FIRST, "Haze", "Fog", "Ice", "Snow", "Rain", "Lava" },

  -- colours
  { rules.NOT_LAST, "Red", "Green", "Blue","White", "Black", "Purple", "Yellow" },

  -- metals
  { rules.ANY, "Silver", "Gold", "Golden", "Platinum", "Lead", "Iron", "Steel"},

  -- chemistry
  { rules.ANY, "Diesel", "Gasoline", "Fuel", "Grease", "Oil", "Fire", "Smoke" },

  -- accidents
  { rules.LAST, "Explosion", "Implosion", "Crash", "Death", "Dead" },

  -- relations
  { rules.ANY, "Widow", "Girl", "Sister", "Granddaddy", "Grandma", "Daddy", "Mama", "Babushka" },

  -- spiritual
  { rules.ANY, "Spirit", "Soul", "Brain", "Dream", "Trip", "Thought" },

  -- other attributions
  { rules.ANY, "Hindu", "Shiva", "Priest", "Shaman" },

  -- occupations, activities
  { rules.ANY, "Driver", "Ryder", "Pilot", "Diver" },

  -- body parts
  { rules.ANY, "Head", "Foot", "Hand", "Finger", "Spine" },

  -- bodily functions
  { rules.ANY, "Cough", "Sneeze", "Breath", "Vomit", "Fuck", "Bite" },

  -- misc
  { rules.ANY, "Amnesia", "Flashback", "Clearvoyant" },

  -- music
  { rules.ANY, "Rock", "Reggeae", "Pop", "Riff", "Fuzz" },

  -- numbers
  { rules.NOT_FIRST, "1", "47", "42", "303", "420", "707", "666" },

  -- numbers
  { rules.FIRST, "One", "Single", "Double", "Triple", "Quadruple" },

  -- expediency, size
  { rules.ANY, "Express", "Fast", "Big", "XL", "XXL", "Super", "Extra", "Good", "Great", "Ultra", "Plus" },

  -- vehicles
  { rules.LAST, "Motor", "Airplane", "Jet", "Rocket", "Submarine", "Train" },

  -- positions
  { rules.ANY, "Queen", "King", "Emperor", "Joker", "Devil", "Belzebub", "Saint", "Sinner", "Baby" },
}

-- Default min/max words per name and number of names to generate

local min_words_in_name = 2
local max_words_in_name = 4
local n_names           = 10

-- Give up after MAX_ATTEMPTS, could be garbage dictionary data or a really unlucky RNG sequence.

local MAX_ATTEMPTS = 1000000

-- Tests if an element exists in a table.

function find_element( element, table )
  local k,v
  for k,v in pairs(table) do
    if v == element then
      return true
    end
  end
  return false
end

function make_name( n_words, dictionary )
  if n_words > #dictionary then
    error "More words than dictionary word groups"
  end
  local groups, name = {}, ""
  local group_i, word_i, group_attempts, name_attempts = 1, 1, 0, 0
  repeat
    name_attempts = name_attempts + 1;
    if name_attempts > MAX_ATTEMPTS then
      error "Can't make name - Timeout due to impossible dictionary or bad luck"
    end
    repeat
      group_attempts = group_attempts + 1;
      if group_attempts > MAX_ATTEMPTS then
        error "Can't find unique groups - Timeout"
      end
      group_i = math.random( #dictionary )
      if #dictionary[group_i] < 2 then
        error "Empty dictionary word group"
      end
    until find_element( group_i, groups ) == false
    groups[word_i] = group_i
    if    dictionary[group_i][1] == rules.ANY
       or dictionary[group_i][1] == rules.FIRST     and word_i == 1
       or dictionary[group_i][1] > 0                and dictionary[group_i][1] == word_i
       or dictionary[group_i][1] == rules.LAST      and word_i == n_words
       or dictionary[group_i][1] == rules.MIDDLE    and word_i > 1 and word_i < n_words
       or dictionary[group_i][1] == rules.NOT_FIRST and word_i ~= 1
       or dictionary[group_i][1] == rules.NOT_LAST  and word_i ~= n_words
    then
      name = name .. dictionary[group_i][ 1 + math.random( 2, #dictionary[group_i] - 1 )]
      if word_i < n_words then
        name = name .. " "
      end
      word_i = word_i + 1
    end
  until word_i > n_words
  return name
end

function print_table( t )
 local k,v
 for k,v in pairs(t) do
   print(v)
 end
end

-- Make a list of N unique names and print them out

function make_strain_names( min_words, max_words, n_names )
  if min_words < 1 or max_words < min_words or n_names < 1 then
    error "Your arguments are rubish!"
  end
  math.randomseed(os.time())
  local names, attempts = {}, 0
  repeat
    attempts = attempts + 1
    if attempts > MAX_ATTEMPTS then
      error "Can't makes list of names - Timeout or impossible dictionary"
    end
    local name = make_name( math.random(min_words, max_words), strain_dictionary )
    if find_element( name, names ) == false then
      table.insert(names, name)
    end
  until #names == n_names
  print_table( names )
end

-- Command line arguments overrides defaults

function get_arg( t, n, default )
  if arg ~= nil and #arg >= n then
    return arg[n]
  end
 return default
end

-- Make it happen!

make_strain_names( tonumber( get_arg( arg, 1, min_words_in_name ) ),
                   tonumber( get_arg( arg, 2, max_words_in_name ) ),
                   tonumber( get_arg( arg, 3, n_names           ) )
                 )

-- EOF

Embed on website

To embed this program on your website, copy the following code and paste it into your website's HTML: