diff options
Diffstat (limited to 'source/game/StarNameGenerator.cpp')
-rw-r--r-- | source/game/StarNameGenerator.cpp | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/source/game/StarNameGenerator.cpp b/source/game/StarNameGenerator.cpp new file mode 100644 index 0000000..44f1512 --- /dev/null +++ b/source/game/StarNameGenerator.cpp @@ -0,0 +1,150 @@ +#include "StarNameGenerator.hpp" +#include "StarRoot.hpp" +#include "StarAssets.hpp" +#include "StarJsonExtra.hpp" + +namespace Star { + +PatternedNameGenerator::PatternedNameGenerator() { + auto assets = Root::singleton().assets(); + auto files = assets->scanExtension("namesource"); + assets->queueJsons(files); + for (auto file : files) { + try { + auto sourceConfig = assets->json(file); + + if (m_markovSources.contains(sourceConfig.getString("name"))) + throw NameGeneratorException::format("Duplicate name source '%s', config file '%s'", sourceConfig.getString("name"), file); + + m_markovSources.insert(sourceConfig.getString("name"), makeMarkovSource(sourceConfig.getUInt("prefixSize", 1), + sourceConfig.getUInt("endSize", 1), jsonToStringList(sourceConfig.get("sourceNames")))); + } catch (std::exception const& e) { + throw NameGeneratorException(strf("Error reading name source config %s", file), e); + } + } + + auto profanityFilter = assets->json("/names/profanityfilter.config").toArray(); + for (auto naughtyWord : profanityFilter) + m_profanityFilter.add(naughtyWord.toString().toLower()); +} + +String PatternedNameGenerator::generateName(String const& rulesAsset) const { + RandomSource random; + return generateName(rulesAsset, random); +} + +String PatternedNameGenerator::generateName(String const& rulesAsset, uint64_t seed) const { + RandomSource random = RandomSource(seed); + return generateName(rulesAsset, random); +} + +String PatternedNameGenerator::generateName(String const& rulesAsset, RandomSource& random) const { + auto assets = Root::singleton().assets(); + auto rules = assets->json(rulesAsset).toArray(); + String res = ""; + int tries = 100; + while ((res.empty() || isProfane(res)) && tries > 0) { + --tries; + res = processRule(rules, random); + } + return res; +} + +String PatternedNameGenerator::processRule(JsonArray const& rule, RandomSource& random) const { + if (rule.empty()) + return ""; + Json meta; + String result; + String mode = "alts"; + size_t index = 0; + bool titleCase = false; + if (rule[0].type() == Json::Type::Object) { + meta = rule[0]; + mode = meta.getString("mode", mode); + titleCase = meta.getBool("titleCase", false); + index++; + } + + if (mode == "serie") { + for (; index < rule.size(); index++) { + auto entry = rule[index]; + if (entry.type() == Json::Type::Array) + result += processRule(entry.toArray(), random); + else + result += entry.toString(); + } + } else if (mode == "alts") { + int i = index + random.randInt(rule.size() - 1 - index); + auto entry = rule[i]; + if (entry.type() == Json::Type::Array) + result += processRule(entry.toArray(), random); + else + result += entry.toString(); + } else if (mode == "markov") { + if (!m_markovSources.contains(meta.getString("source"))) + throw NameGeneratorException::format("Unknown name source '%s'", meta.getString("source")); + + auto source = m_markovSources.get(meta.getString("source")); + auto lengthRange = meta.getArray("targetLength"); + auto targetLength = random.randUInt(lengthRange[0].toUInt(), lengthRange[1].toUInt()); + + size_t tries = 0; + String piece; + do { + ++tries; + piece = random.randFrom(source.starts); + while (piece.length() < targetLength + || !source.ends.contains(piece.slice(piece.length() - source.endSize, piece.length()))) { + String link = piece.slice(piece.length() - source.prefixSize, piece.length()); + if (!source.chains.contains(link)) + break; + piece += random.randFrom(source.chains.get(link)); + } + } while (piece.length() > lengthRange[1].toUInt() && tries < 10); + + result += piece; + } else + throw StarException::format("Unknown mode: %s", mode); + + if (titleCase) + result = result.titleCase(); + + return result; +} + +bool PatternedNameGenerator::isProfane(String const& name) const { + auto matchName = name.toLower().rot13(); + for (auto naughtyWord : m_profanityFilter) { + if (matchName.contains(naughtyWord)) + return true; + } + return false; +} + +MarkovSource PatternedNameGenerator::makeMarkovSource(size_t prefixSize, size_t endSize, StringList sourceNames) { + MarkovSource newSource; + newSource.prefixSize = prefixSize; + newSource.endSize = endSize; + for (auto sourceName : sourceNames) { + if (sourceName.length() < prefixSize || sourceName.length() < endSize) + continue; + + sourceName = sourceName.toLower(); + newSource.ends.add(sourceName.slice(sourceName.length() - endSize, sourceName.length())); + for (int i = 0; i + prefixSize <= sourceName.length(); ++i) { + auto prefix = sourceName.slice(i, i + prefixSize); + if (i == 0) + newSource.starts.append(prefix); + + if (i + prefixSize < sourceName.length()) { + if (!newSource.chains.contains(prefix)) + newSource.chains.insert(prefix, StringList()); + + newSource.chains.get(prefix).append(sourceName.slice(i + prefixSize, i + prefixSize + 1)); + } + } + } + return newSource; +} + +} |