Nation Namer
(Originally published February 22, 2009)
Here’s a little algorithm I’ve been using to generate names for fake nations. I wouldn’t normally have put the randomness method directly into the factory class, and in my implementation I am using log4j, but for the purpose of this post, I decided it would be easy to put everything into one class with no external dependencies.
Some example names: Asmira, Pnohay, Litshala, Thudeacistan, Dismuy, Lienistan.
And the code:
/*
* Created on 2009-01-25
*
* Earl Woodman
* St. John's, NL
*/
package org.earljw.cultures;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class NationNameFactory
{
private NationNameFactory()
{
}
protected static final String consonants = "bcdfghjklmnpqrstvwxyz";
protected static final CharSequence[] eligibleConsonantPairs = {
"bh", "bl", "br", "bw", "by", "ch", "cl", "cr", "cw", "cy", "dh", "dr", "dw", "dy",
"fh", "fl", "fr", "fw", "fy", "gh", "gl", "gn", "gr", "gw", "gy", "hw", "hy", "jw",
"jy", "kh", "kl", "kr", "kw", "ky", "lh", "ly", "mc", "mn", "mw", "my", "ny", "ph",
"pl", "pn", "pr", "pw", "py", "qh", "ql", "qr", "qw", "qy", "rh", "ry", "sc", "sh",
"sk", "sl", "sm", "sn", "sp", "sq", "sr", "st", "sw", "sy", "th", "tl", "ts", "tw",
"ty", "vh", "vl", "vr", "vw", "vy", "wh", "wr", "wy", "xy", "yb", "yc", "yd", "yf",
"yg", "yl", "ym", "yn", "yp", "yq", "yr", "ys", "yt", "yv", "yx", "yz", "zh", "zl",
"zy",
"dl", "hl", "ml", "nl", "rl", "wl"
};
protected static final String weightedAlphabet =
"aaaaaaaaabbbcccddddeeeeeeeeeeffffgggghhhhhiiijklllllmmmmmnnnnnnnnooooooopppqrrrrrrrssssssstttttttuuuvwwxyyz";
protected static final String[] extensions = {
"ica", "ia", "land", "istan", "y", "a", "al", "ala", "ina", "i", "ey"
};
protected static final String[] adjectiveExtensions = {
"ican", "ian", "ish", "i", "ish", "an", "ese", "alan", "inian", "ese", ""
};
protected static final String vowels = "aeiou";
protected static NationNameFactory nationNameFactory;
public static synchronized NationNameFactory getNationNameFactory()
{
if( nationNameFactory == null )
{
nationNameFactory = new NationNameFactory();
}
return nationNameFactory;
}
/**
* Returns a single nation name
*
* @return NationName
*/
public NationName createNationName()
{
Collection<NationName> nationNames = createNationNames(1);
if( nationNames.size() > 0 )
{
Iterator<NationName> iter = nationNames.iterator();
return iter.next();
}
return null;
}
/**
* Returns a collection of NationName objects
*
* @param nameCount
* @return Collection<NationName>
*/
public Collection<NationName> createNationNames(final int nameCount)
{
Collection<NationName> nationNames = new ArrayList<NationName>();
int chars;
StringBuffer thisName;
String rootName;
int extensionPicked;
NationName nationName;
for( int i = 0; i < nameCount; i++ )
{
do
{
thisName = new StringBuffer();
chars = getRandomInt(4, 8);
for( int x = 0; x < chars; x++ )
{
thisName.append(weightedAlphabet.charAt(getRandomInt(0, weightedAlphabet.length() - 1)));
}
rootName = thisName.toString();
}
while( !isAcceptable(thisName.toString()) );
extensionPicked = getRandomInt(0, extensions.length - 1);
thisName.append(extensions[extensionPicked]);
// Capitalize the first letter
String firstChar = thisName.substring(0, 1);
firstChar = firstChar.toUpperCase();
thisName.setCharAt(0, firstChar.charAt(0));
rootName = firstChar.charAt(0) + rootName.substring(1);
// Create the nation name object
nationName = new NationName();
nationName.setAdjectiveName(rootName + adjectiveExtensions[extensionPicked]);
nationName.setBaseName(thisName.toString());
nationNames.add(nationName);
}
for (Iterator<NationName> iterator = nationNames.iterator(); iterator.hasNext();)
{
NationName nationName2 = (NationName) iterator.next();
System.out.println(nationName2.getBaseName() + ", " + nationName2.getAdjectiveName());
}
return nationNames;
}
protected static boolean isAcceptable(final String thisName)
{
// Check for 3 consecutive vowels
int vowelCount = 0;
for( int i = 0; i < thisName.length(); i++ )
{
if( vowels.indexOf(thisName.charAt(i)) != -1 )
{
vowelCount++;
}
else
{
vowelCount = 0;
}
if( vowelCount == 3 )
{
System.out.println("Rejected (three vowels): " + thisName);
return false;
}
}
// Look for illegal consonant pairs
boolean illegal = false;
for( int j = 0; j < thisName.length() - 1; j++ )
{
// If the first two characters are consonants, make sure it's a valid
// consonant pair. Otherwise move on to the next one
if( consonants.indexOf(thisName.charAt(j)) != -1 && consonants.indexOf(thisName.charAt(j + 1)) != -1 )
{
illegal = true;
for( int k = 0; k < eligibleConsonantPairs.length; k++ )
{
if( thisName.subSequence(j, j + 2).equals(eligibleConsonantPairs[k]))
{
illegal = false;
}
}
if( illegal )
{
break;
}
}
}
if( illegal )
{
System.out.println("Rejected (consonant pair): " + thisName);
return false;
}
// There may be more cases added here
return true;
}
protected int getRandomInt( final int lowerBound, final int upperBound )
{
if( lowerBound == upperBound )
return lowerBound;
double d = Math.random();
return (int) ( (d * (upperBound-lowerBound) + lowerBound) + 0.5 );
}
public class NationName
{
protected String baseName;
protected String adjectiveName;
public String getBaseName()
{
return baseName;
}
public void setBaseName(final String baseName)
{
this.baseName = baseName;
}
public String getAdjectiveName()
{
return adjectiveName;
}
public void setAdjectiveName(final String adjectiveName)
{
this.adjectiveName = adjectiveName;
}
}
}