Project: Fantasy Name Generator (Part 2)

In my last post, I described a method of procedurally building syllables that I devised after taking exception to a particular syllable-based name generator I found online.  This was the core functionality and beginning of a program which had, as its goal, the ability to generate lists of name suggestions for fantasy RPG characters.  When I left off, we had a set of rule for defining the logic of vowel and consonant sounds, and a set of rules for combining those sounds into syllables.

Rather than take one single approach to procedurally run all of those rules, I elected to create a handful of functions which all operated in parallel.

This ended up looking something like the following:

string get_vowel_a();
string get_vowel_e();
string get_cons_b();
string get_cons_bb_start();
string get_cons_bb_end();

Each of those is, in turn called according to the logic in my earlier post from another function:

string make_syllable();

Finally, the main() function contains a loop which handles the whole thing:

int main(int argc, char** argv)
{

...

   // main loop.  Each iteration generates one name.
   for (i = 0; i < names_to_gen; i++)
   {
      // sub-loop. Each iteration generates one syllable.
      name.clear();

      ...
      
      for (ii = 0; ii < syl; ii++)
         {
            name += make_syllable();     
         }
      }

      name = capitalize(name);
      cout << name << endl;
   }

   return 0;
}

And there we have the basic functionality of the name generator.

Next, I ended up running it a few dozen times to generate samples, and then identify consonant combinations which didn’t work too well.  This ended up becoming a tricky hack and probably the ugliest part of the code entirely.  Ultimately though, the goal was to procedurally generate names, not to generate elegant, clean code.

string get_cons_bb_end()
{
   string second, cons = "cdflmnrstw",
   bb = cons.substr(random(cons.length()), 1);
   ...
   if (bb == string("c")) second += "chkstxz";
   if (bb == string("d")) second += "dsz";
   if (bb == string("f")) second += "f";
   ...
   bb += second.substr(random(second.length()), 1);
   return bb;
}

The first line there generates a string “cons” which is all potential consonants which are allowed to be the first character of a bB sound pattern — recall that this represents a two-consonant combination found at the end of a syllable.  It then generates a second, single-character string “bb” which is a random selection from “cons”.

From there, we have predefined combinations: if the first letter is a “c”, then the second letter of the pattern can be any one of “c”, “h”, “k”, “s”, “t”, “x”, or “z”.  But, if the first letter is a “d”, then the second letter can be only one of “d”, “s”, or “z”.  As I mentioned earlier, this is a bit ugly and hackish, not least because it’s a load of if statements instead of a more elegant construct.  I’ve become accustomed to LPC, where it’s absolutely valid to perform a switch() upon arbitrary datatypes, and going back to C++ where switch() only functions on numerics was frustrating.

Walking through the logic here, when executed, the function chain might look something like this:

main() ->
   make_syllable() ->
      get_cons_bb_start() +
      get_vowel_a() +
      get_cons_bb_end()

And then we’d end up with a name, ideally, something like “brant” given our Bb a bB pattern.

From there, most of the remaining work was cleanup and extra functionality — an external .cfg file to set global variables governing things like “How many names should be generated?” and “What are the minimum and maximum number of syllables and letters per name?”  Not much of design, logic, or code interest in the doing there, so I’ll skip it unless somebody shows particular interest.

If any readers should be interested in a copy of namegen, let me know.

Leave a comment