mirror of
https://bitbucket.org/theswgsource/src-1.2.git
synced 2026-07-15 00:08:07 -04:00
651 lines
17 KiB
C++
651 lines
17 KiB
C++
//
|
|
// FileName.h
|
|
// asommers 5-26-99
|
|
//
|
|
// copyright 1999, bootprint entertainment
|
|
//
|
|
|
|
#include "sharedUtility/FirstSharedUtility.h"
|
|
#include "sharedUtility/NameGenerator.h"
|
|
|
|
#include "sharedRandom/Random.h"
|
|
#include "sharedUtility/DataTable.h"
|
|
#include "sharedUtility/DataTableManager.h"
|
|
|
|
#include "UnicodeUtils.h"
|
|
|
|
#include <algorithm>
|
|
#include <set>
|
|
#include <stdio.h>
|
|
|
|
//====================================================================
|
|
|
|
NameGenerator::NameGenerator(const std::string &i_language, const std::string &directory)
|
|
{
|
|
std::string datatablePath("datatables/" + directory + "/");
|
|
m_initialized = false;
|
|
|
|
if (i_language.empty())
|
|
return;
|
|
|
|
DataTable * vowelTable = DataTableManager::getTable(datatablePath+"vowels.iff", true);
|
|
DataTable * consonantTable = DataTableManager::getTable(datatablePath+"consonants.iff", true);
|
|
DataTable * specialTable = DataTableManager::getTable(datatablePath+"special.iff", true);
|
|
DataTable * professionPatternTable = DataTableManager::getTable(datatablePath+"profession_patterns.iff", true);
|
|
DataTable * professionTable = DataTableManager::getTable(datatablePath+"profession.iff", true);
|
|
DataTable * ruleTable = DataTableManager::getTable(datatablePath+"rules.iff", true);
|
|
|
|
if (!ruleTable ||
|
|
!professionTable ||
|
|
!professionPatternTable ||
|
|
!specialTable ||
|
|
!consonantTable ||
|
|
!vowelTable)
|
|
{
|
|
WARNING_STRICT_FATAL(true, ("Couldn't open a naming data table with path %s language %s\n", datatablePath.c_str(), i_language.c_str()));
|
|
return;
|
|
}
|
|
|
|
int ruleRow = findRow(ruleTable, "id", i_language);
|
|
if (ruleRow < 0)
|
|
{
|
|
WARNING(true, ("Couldn't find language [%s] in rule table path %s\n", i_language.c_str(), datatablePath.c_str()));
|
|
return;
|
|
}
|
|
|
|
pushNameTemplate(ruleTable, ruleRow, "fname_");
|
|
pushNameTemplate(ruleTable, ruleRow, "lname_");
|
|
|
|
loadColumn(m_initialConsonants, consonantTable, i_language, "placement", 'b');
|
|
loadColumn(m_medialConsonants, consonantTable, i_language, "placement", 'm');
|
|
loadColumn(m_finalConsonants, consonantTable, i_language, "placement", 'e');
|
|
|
|
loadColumn(m_vowels, vowelTable, i_language);
|
|
loadColumn(m_specialSyllables, specialTable, i_language);
|
|
loadColumn(m_professionPatterns, professionPatternTable, i_language);
|
|
|
|
// now extract all of the word types from profession patterns
|
|
std::vector<OddsValue>::iterator ppi;
|
|
bool inLiteral = false;
|
|
for (ppi = m_professionPatterns.begin(); ppi != m_professionPatterns.end(); ++ppi)
|
|
{
|
|
const std::string &value = ppi->value;
|
|
std::string::const_iterator c;
|
|
for (c=value.begin(); c!=value.end(); ++c)
|
|
{
|
|
if (*c == '\\') // skip literals
|
|
{
|
|
inLiteral = true;
|
|
}
|
|
else if (*c == '*') // skip
|
|
{
|
|
}
|
|
else
|
|
{
|
|
if (!inLiteral)
|
|
{
|
|
std::map<char, std::vector<OddsValue> *>::iterator wordIter = m_professions.find(*c);
|
|
if (wordIter == m_professions.end())
|
|
{
|
|
m_professions[*c] = new std::vector<OddsValue>();
|
|
loadColumn(*m_professions[*c], professionTable, i_language, "type", *c);
|
|
}
|
|
}
|
|
inLiteral = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!m_nameTemplates.empty())
|
|
{
|
|
m_initialized = true;
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
NameGenerator::~NameGenerator()
|
|
{
|
|
std::map<char, std::vector<OddsValue> * >::iterator i;
|
|
for (i=m_professions.begin(); i!=m_professions.end(); ++i)
|
|
{
|
|
delete i->second;
|
|
}
|
|
m_professions.clear();
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
Unicode::String NameGenerator::generateRandomName() const
|
|
{
|
|
if (!m_initialized)
|
|
return Unicode::String();
|
|
|
|
std::vector<NameTemplate>::const_iterator i;
|
|
bool first = true;
|
|
std::string result;
|
|
for (i=m_nameTemplates.begin(); i!=m_nameTemplates.end(); ++i)
|
|
{
|
|
if (Random::randomReal() < i->m_odds)
|
|
{
|
|
if (!first)
|
|
{
|
|
result += " ";
|
|
}
|
|
result += generateSingleName(*i);
|
|
first = false;
|
|
}
|
|
}
|
|
if (first)
|
|
{
|
|
result = generateSingleName(m_nameTemplates.front());
|
|
}
|
|
return Unicode::narrowToWide(capitalizeName(result));
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
int NameGenerator::findRow(DataTable * table, const std::string &column, const std::string &value)
|
|
{
|
|
int i;
|
|
for (i=0; i<table->getNumRows(); ++i)
|
|
{
|
|
if (value == table->getStringValue(column, i))
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
void NameGenerator::pushNameTemplate(DataTable * table, int row, const std::string &prefix)
|
|
{
|
|
float odds = table->getIntValue(prefix+"chance", row) / 100.f; // don't convert this to *0.01f, we need to insure that int(100)/100.0f equals exactly 1.
|
|
if (odds == 0)
|
|
return;
|
|
|
|
NameTemplate temp;
|
|
temp.m_odds = odds;
|
|
temp.m_chanceProfession = table->getIntValue(prefix+"chance_profession", row) * 0.01f;
|
|
temp.m_syllablesMin = table->getIntValue(prefix+"syllables_min", row);
|
|
temp.m_syllablesMax = table->getIntValue(prefix+"syllables_max", row);
|
|
temp.m_charsMin = table->getIntValue(prefix+"chars_min", row);
|
|
temp.m_charsMax = table->getIntValue(prefix+"chars_max", row);
|
|
temp.m_specialCharChance = table->getIntValue(prefix+"special_char_chance", row) * 0.01f;
|
|
temp.m_specialChars = table->getStringValue(prefix+"special_char", row);
|
|
temp.m_specialCharMax = table->getIntValue(prefix+"special_char_max", row);
|
|
temp.m_chanceBeginsSpecial = table->getIntValue(prefix+"chance_begins_special", row) * 0.01f;
|
|
temp.m_chanceMedialSpecial = table->getIntValue(prefix+"chance_medial_special", row) * 0.01f;
|
|
temp.m_chanceEndsSpecial = table->getIntValue(prefix+"chance_ends_special", row) * 0.01f;
|
|
temp.m_maxSpecialSyllables = table->getIntValue(prefix+"max_special_syllables", row);
|
|
|
|
m_nameTemplates.push_back(temp);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
std::string NameGenerator::generateSingleName(const NameTemplate &nt) const
|
|
{
|
|
if (Random::randomReal() < nt.m_chanceProfession)
|
|
return generateProfessionName(nt);
|
|
else
|
|
return generateRandomName(nt);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
std::string NameGenerator::generateRandomName(const NameTemplate &nt) const
|
|
{
|
|
for (;;)
|
|
{
|
|
int syllables = nt.m_syllablesMin;
|
|
while (syllables < nt.m_syllablesMax)
|
|
{
|
|
float syllableChance = 0.6f - syllables / (nt.m_syllablesMax*2.0f);
|
|
if (Random::randomReal() > syllableChance)
|
|
break;
|
|
++syllables;
|
|
}
|
|
|
|
bool nextSyllableIsFirst = true;
|
|
std::string result;
|
|
int specialCharCount = 0;
|
|
std::set<std::string> selectedSpecialSyllables;
|
|
int i;
|
|
for (i=0; i<syllables; ++i)
|
|
{
|
|
std::string oldResult(result);
|
|
bool firstSyllable = nextSyllableIsFirst;
|
|
nextSyllableIsFirst = false;
|
|
bool finalSyllable = (i == syllables - 1);
|
|
bool appendSpecial = false;
|
|
if (!finalSyllable)
|
|
{
|
|
if (specialCharCount < nt.m_specialCharMax && Random::randomReal() < nt.m_specialCharChance)
|
|
{
|
|
appendSpecial = true;
|
|
finalSyllable = true;
|
|
nextSyllableIsFirst = true;
|
|
}
|
|
}
|
|
|
|
float specialSyllableChance = nt.m_chanceMedialSpecial;
|
|
if (firstSyllable)
|
|
specialSyllableChance = nt.m_chanceBeginsSpecial;
|
|
else if (finalSyllable)
|
|
specialSyllableChance = nt.m_chanceEndsSpecial;
|
|
if (Random::randomReal() < specialSyllableChance && (int)selectedSpecialSyllables.size() < nt.m_maxSpecialSyllables)
|
|
{
|
|
for (;;)
|
|
{
|
|
std::string specialSyllable = selectSpecialSyllable();
|
|
if (selectedSpecialSyllables.find(specialSyllable) != selectedSpecialSyllables.end())
|
|
continue;
|
|
result = appendSyllable(result, specialSyllable);
|
|
selectedSpecialSyllables.insert(specialSyllable);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::string syllablePattern;
|
|
float syllableSelect = Random::randomReal();
|
|
if (syllableSelect < 0.40f)
|
|
{
|
|
syllablePattern = "cv";
|
|
}
|
|
else if (syllableSelect < 0.80f)
|
|
{
|
|
syllablePattern = "vc";
|
|
}
|
|
else
|
|
{
|
|
syllablePattern = "v";
|
|
}
|
|
std::string::const_iterator syllIter;
|
|
std::string syllable;
|
|
for (syllIter = syllablePattern.begin(); syllIter != syllablePattern.end(); ++syllIter)
|
|
{
|
|
if (*syllIter == 'c')
|
|
{
|
|
if (firstSyllable && syllIter == syllablePattern.begin())
|
|
{
|
|
syllable += selectInitialConsonant();
|
|
}
|
|
else if (finalSyllable && (syllIter+1) == syllablePattern.end())
|
|
{
|
|
syllable += selectFinalConsonant();
|
|
}
|
|
else
|
|
{
|
|
syllable += selectMedialConsonant();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
syllable += selectVowel();
|
|
}
|
|
}
|
|
result = appendSyllable(result, syllable);
|
|
}
|
|
if (result == oldResult)
|
|
--i; // it's still alright to append because if the endings matched, the old ending must have been a valid final consonant.
|
|
// append a special character
|
|
if (appendSpecial && !result.empty())
|
|
{
|
|
int s = nt.m_specialChars.size();
|
|
char toInsert = nt.m_specialChars[Random::random(s-1)];
|
|
result = appendSyllable(result, std::string(1, toInsert));
|
|
++specialCharCount;
|
|
}
|
|
}
|
|
// now do sanity checks
|
|
// retry if the length is bad
|
|
int resultSize = static_cast<int>(result.size());
|
|
if (resultSize < nt.m_charsMin)
|
|
continue;
|
|
else if (resultSize > nt.m_charsMax)
|
|
continue;
|
|
|
|
// continue if we have a lonely special syllable
|
|
if (getFragmentType(result) == kSpecial)
|
|
continue;
|
|
|
|
// now capitalize
|
|
return result;
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
std::string NameGenerator::generateProfessionName(const NameTemplate &nt) const
|
|
{
|
|
std::string professionPattern = selectProfessionPattern();
|
|
if (professionPattern.empty())
|
|
return std::string();
|
|
|
|
std::set<std::string> usedRoots;
|
|
std::string result;
|
|
|
|
std::string::const_iterator i;
|
|
bool append = false;
|
|
for (i=professionPattern.begin(); i!=professionPattern.end(); ++i)
|
|
{
|
|
if (*i == '\\')
|
|
{
|
|
++i;
|
|
if (i != professionPattern.end())
|
|
{
|
|
result += *i;
|
|
}
|
|
}
|
|
else if (*i == '*')
|
|
{
|
|
result = appendSyllable(result, generateRandomName(nt));
|
|
}
|
|
else
|
|
{
|
|
for (;;)
|
|
{
|
|
std::string profession, root;
|
|
profession = selectProfession(*i, &root);
|
|
if (!root.empty() && usedRoots.find(root) != usedRoots.end())
|
|
continue;
|
|
if (append)
|
|
{
|
|
result = appendSyllable(result, profession);
|
|
append = false;
|
|
}
|
|
else
|
|
{
|
|
result += profession;
|
|
}
|
|
usedRoots.insert(root);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
void NameGenerator::balanceOdds(std::vector<OddsValue>::iterator begin, std::vector<OddsValue>::iterator end)
|
|
{
|
|
std::sort(begin, end);
|
|
|
|
std::vector<OddsValue>::iterator i;
|
|
float total = 0;
|
|
for (i=begin; i!=end; ++i)
|
|
total += i->odds;
|
|
|
|
float accum = 0;
|
|
for (i=begin; i!=end; ++i)
|
|
{
|
|
float odds = i->odds / total;
|
|
accum += odds;
|
|
i->odds = accum;
|
|
}
|
|
// correct rounding errors
|
|
if (end > begin)
|
|
(end-1)->odds = 1.0f;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
std::string NameGenerator::selectRandomFrom(std::vector<OddsValue>::const_iterator begin, std::vector<OddsValue>::const_iterator end, std::string * extra) const
|
|
{
|
|
if (begin == end)
|
|
{
|
|
if (extra)
|
|
*extra = std::string();
|
|
return std::string();
|
|
}
|
|
|
|
float rr = Random::randomReal();
|
|
FATAL(rr >= 1.0f, ("randomReal returned an invalid random number %g\n", rr));
|
|
|
|
std::vector<OddsValue>::const_iterator i = std::lower_bound(begin, end, rr, OddsValue::findFloat());
|
|
/*
|
|
std::vector<OddsValue>::const_iterator i;
|
|
for (i=begin; i!=end; ++i)
|
|
{
|
|
if (rr < i->odds)
|
|
{
|
|
if (extra)
|
|
*extra = i->extra;
|
|
return i->value;
|
|
}
|
|
rr -= i->odds;
|
|
}
|
|
// just in case of rounding errors
|
|
return (end-1)->value;
|
|
*/
|
|
if (extra)
|
|
*extra = i->extra;
|
|
return i->value;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
std::string NameGenerator::capitalizeName(const std::string &result) const
|
|
{
|
|
std::string capitalizedResult;
|
|
std::string::const_iterator capIter;
|
|
bool capNext = true;
|
|
for (capIter = result.begin(); capIter != result.end(); ++capIter)
|
|
{
|
|
if (capNext)
|
|
{
|
|
capitalizedResult += (char)toupper(*capIter);
|
|
}
|
|
else
|
|
{
|
|
capitalizedResult += *capIter;
|
|
}
|
|
if (*capIter == ' ')
|
|
{
|
|
capNext = true;
|
|
}
|
|
else if (*capIter == '\'' || *capIter == '-')
|
|
{
|
|
capNext = Random::randomReal() > 0.5f;
|
|
}
|
|
else
|
|
{
|
|
capNext = false;
|
|
}
|
|
}
|
|
return capitalizedResult;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
void NameGenerator::loadColumn(std::vector<OddsValue> &result, DataTable * source, const std::string &i_language)
|
|
{
|
|
int rows = source->getNumRows();
|
|
int languageColumnIndex = source->findColumnNumber(i_language);
|
|
|
|
int i;
|
|
for (i=0; i<rows; ++i)
|
|
{
|
|
std::string value = source->getStringValue(0, i);
|
|
|
|
int odds = source->getIntValue(languageColumnIndex, i);
|
|
if (odds == 0)
|
|
continue;
|
|
|
|
OddsValue temp;
|
|
temp.value = value;
|
|
temp.odds = (float)odds;
|
|
result.push_back(temp);
|
|
}
|
|
|
|
balanceOdds(result.begin(), result.end());
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
void NameGenerator::loadColumn(std::vector<OddsValue> &result, DataTable * source, const std::string &i_language, const std::string &maskColumn, const char maskChar)
|
|
{
|
|
int rows = source->getNumRows();
|
|
int maskColumnIndex = source->findColumnNumber(maskColumn);
|
|
int languageColumnIndex = source->findColumnNumber(i_language);
|
|
|
|
int i;
|
|
for (i=0; i<rows; ++i)
|
|
{
|
|
std::string value = source->getStringValue(0, i);
|
|
|
|
int odds = source->getIntValue(languageColumnIndex, i);
|
|
if (odds == 0)
|
|
continue;
|
|
|
|
std::string mask = source->getStringValue(maskColumnIndex, i);
|
|
if (mask.find(maskChar) == std::string::npos)
|
|
continue;
|
|
|
|
OddsValue temp;
|
|
temp.value = value;
|
|
temp.odds = (float)odds;
|
|
if (maskColumnIndex >= 2)
|
|
{
|
|
temp.extra = source->getStringValue(1, i);
|
|
}
|
|
result.push_back(temp);
|
|
}
|
|
|
|
balanceOdds(result.begin(), result.end());
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
std::string NameGenerator::appendSyllable(const std::string &left, const std::string &right) const
|
|
{
|
|
if (left.empty())
|
|
return right;
|
|
|
|
// now we try to merge/simplify repeated letters. If there are too many repeats, we insert
|
|
// a remedial vowel or consonant to try and make the word more pronounceable
|
|
|
|
// grab the longest ending vowel or consonant string from left
|
|
std::string::const_iterator l = left.end();
|
|
eFragmentType leftFragmentType = kMixed;
|
|
std::string leftFragment;
|
|
while (l != left.begin())
|
|
{
|
|
--l;
|
|
std::string leftEnd(l, left.end());
|
|
eFragmentType fragment = getFragmentType(leftEnd);
|
|
if (fragment == kMixed || fragment == kSpecial)
|
|
break;
|
|
leftFragment = leftEnd;
|
|
leftFragmentType = fragment;
|
|
}
|
|
|
|
eFragmentType rightFragmentType = kMixed;
|
|
std::string rightFragment;
|
|
std::string::const_iterator r = right.begin();
|
|
while (r != right.end())
|
|
{
|
|
++r;
|
|
std::string rightBegin(right.begin(), r);
|
|
eFragmentType fragment = getFragmentType(rightBegin);
|
|
if (fragment == kMixed || fragment == kSpecial)
|
|
break;
|
|
rightFragment = rightBegin;
|
|
rightFragmentType = fragment;
|
|
}
|
|
|
|
if (leftFragmentType != rightFragmentType)
|
|
return left + right;
|
|
|
|
if (leftFragment == rightFragment)
|
|
return left;
|
|
|
|
if (leftFragmentType == kConsonant)
|
|
return left + selectVowel() + right;
|
|
else
|
|
return left + selectMedialConsonant() + right;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
NameGenerator::eFragmentType NameGenerator::getFragmentType(const std::string &test) const
|
|
{
|
|
if (hasString(m_vowels, test))
|
|
return kVowel;
|
|
if (hasString(m_initialConsonants, test))
|
|
return kConsonant;
|
|
if (hasString(m_medialConsonants, test))
|
|
return kConsonant;
|
|
if (hasString(m_finalConsonants, test))
|
|
return kConsonant;
|
|
if (hasString(m_specialSyllables, test))
|
|
return kSpecial;
|
|
return kMixed;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
bool NameGenerator::hasString(const std::vector<OddsValue> &array, const std::string &test) const
|
|
{
|
|
std::vector<OddsValue>::const_iterator i;
|
|
i = std::lower_bound(array.begin(), array.end(), test, OddsValue::findString());
|
|
if (i == array.end())
|
|
return false;
|
|
if (i->value != test)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
bool NameGenerator::verifyName(const Unicode::String &name) const
|
|
{
|
|
size_t minNameCount = 0;
|
|
std::vector<NameTemplate>::const_iterator i;
|
|
for (i = m_nameTemplates.begin(); i!=m_nameTemplates.end(); ++i)
|
|
{
|
|
if (i->m_odds >= 1.0f)
|
|
{
|
|
++minNameCount;
|
|
}
|
|
}
|
|
|
|
int pos=0;
|
|
size_t nameindex = 0;
|
|
for (;;)
|
|
{
|
|
if (nameindex >= m_nameTemplates.size())
|
|
return false;
|
|
|
|
int newpos = name.find(' ', pos);
|
|
int namelength = newpos - pos;
|
|
if (newpos == int(Unicode::String::npos))
|
|
{
|
|
namelength = name.size() - pos;
|
|
}
|
|
if (namelength < m_nameTemplates[nameindex].m_charsMin)
|
|
return false;
|
|
if (namelength > m_nameTemplates[nameindex].m_charsMax)
|
|
return false;
|
|
pos = newpos + 1;
|
|
++nameindex;
|
|
if (newpos == int(Unicode::String::npos))
|
|
{
|
|
if (nameindex < minNameCount)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
}
|
|
return true; // just in case, should always return from the newpos == npos test
|
|
}
|
|
|
|
//====================================================================
|