diff options
author | Your Name <you@example.com> | 2023-05-31 17:04:47 -0400 |
---|---|---|
committer | Your Name <you@example.com> | 2023-05-31 17:04:47 -0400 |
commit | 19a226f512a924def233dbd723d6d7a66ed382d3 (patch) | |
tree | a08fffcd160208e251eb7eb32ee7669c6761c130 /src/namegen/namegen.cc | |
parent | 321fcc04b7f77a35c2099b6724de3f780adbd580 (diff) | |
download | dmtool-19a226f512a924def233dbd723d6d7a66ed382d3.tar.gz dmtool-19a226f512a924def233dbd723d6d7a66ed382d3.tar.bz2 dmtool-19a226f512a924def233dbd723d6d7a66ed382d3.zip |
Added automatic creature name generationv1.0.0
Diffstat (limited to 'src/namegen/namegen.cc')
-rw-r--r-- | src/namegen/namegen.cc | 555 |
1 files changed, 555 insertions, 0 deletions
diff --git a/src/namegen/namegen.cc b/src/namegen/namegen.cc new file mode 100644 index 0000000..80b7102 --- /dev/null +++ b/src/namegen/namegen.cc @@ -0,0 +1,555 @@ +/** + * + * @file A fantasy name generator library. + * @version 1.0.1 + * @license Public Domain + * @author German M. Bravo (Kronuz) + * + */ + +#include "namegen.h" + +#include <algorithm> // for move, reverse +#include <string> +#include <chrono> // for rng seed +#include <cwchar> // for size_t, mbsrtowcs, wcsrtombs +#include <cwctype> // for towupper +#include <memory> // for std::make_unique +#include <random> // for mt19937, uniform_real_distribution +#include <stdexcept> // for invalid_argument, out_of_range +#include "../rules.h" + +using namespace NameGen; + +std::string NameGen::generate(const creature::Creature& c) { + // Assemble a name based on intelligence + int intAdj = c.getScore(rules::Ability::Int()); + // Adjust based on creature type + auto type = c.getType(); + std::transform(type.begin(), type.end(), type.begin(), ::tolower); + if(type.find("undead") != std::string::npos) intAdj *= 2; + else if(type.find("beast") != std::string::npos) intAdj += 7; + std::string pattern = "!<<"; + if(intAdj < 10) pattern += "|Dv|D"; + if(intAdj < 6) pattern += "|i"; + if(intAdj >= 6) pattern += "|Bv"; + pattern += "><s"; + if(intAdj < 10) pattern += "|d"; + if(intAdj >= 6) pattern += "|ss"; + if(intAdj >= 14) pattern += "|sV's"; + if(intAdj >= 16) pattern += "|sV'CVs"; + pattern += ">><|||"; + if(intAdj < 8) pattern += "| !ii"; + if(type.find("humanoid") != std::string::npos) pattern += "| !s| !ss| s!ss"; + pattern += "><||"; + if(intAdj < 8) pattern += "|( the )!I"; + if(c.getScore(rules::Ability::Cha()) > 10) pattern += "|( of )!<sVC|s|ss|BV>(|ville|town|land|polis|polia| Forest| Mountain)"; + pattern += ">"; + //printf((pattern + "\n").c_str()); + NameGen::Generator generator(pattern); + return generator.toString(); +} + +static std::mt19937 rng(std::chrono::high_resolution_clock::now().time_since_epoch().count()); + +// https://isocpp.org/wiki/faq/ctors#static-init-order +// Avoid the "static initialization order fiasco" +const std::unordered_map<std::string, const std::vector<std::string>>& Generator::SymbolMap() +{ + static auto* const symbols = new std::unordered_map<std::string, const std::vector<std::string>>({ + { + "s", { + "ach", "ack", "ad", "age", "ald", "ale", "an", "ang", "ar", "ard", + "as", "ash", "at", "ath", "augh", "aw", "ban", "bel", "bur", "cer", + "cha", "che", "dan", "dar", "del", "den", "dra", "dyn", "ech", "eld", + "elm", "em", "en", "end", "eng", "enth", "er", "ess", "est", "et", + "gar", "gha", "hat", "hin", "hon", "ia", "ight", "ild", "im", "ina", + "ine", "ing", "ir", "is", "iss", "it", "kal", "kel", "kim", "kin", + "ler", "lor", "lye", "mor", "mos", "nal", "ny", "nys", "old", "om", + "on", "or", "orm", "os", "ough", "per", "pol", "qua", "que", "rad", + "rak", "ran", "ray", "ril", "ris", "rod", "roth", "ryn", "sam", + "say", "ser", "shy", "skel", "sul", "tai", "tan", "tas", "ther", + "tia", "tin", "ton", "tor", "tur", "um", "und", "unt", "urn", "usk", + "ust", "ver", "ves", "vor", "war", "wor", "yer" + } + }, + { + "v", { + "a", "e", "i", "o", "u", "y" + } + }, + { + "V", { + "a", "e", "i", "o", "u", "y", "ae", "ai", "au", "ay", "ea", "ee", + "ei", "eu", "ey", "ia", "ie", "oe", "oi", "oo", "ou", "ui" + } + }, + { + "c", { + "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", + "s", "t", "v", "w", "x", "y", "z" + } + }, + { + "B", { + "b", "bl", "br", "c", "ch", "chr", "cl", "cr", "d", "dr", "f", "g", + "h", "j", "k", "l", "ll", "m", "n", "p", "ph", "qu", "r", "rh", "s", + "sch", "sh", "sl", "sm", "sn", "st", "str", "sw", "t", "th", "thr", + "tr", "v", "w", "wh", "y", "z", "zh" + } + }, + { + "C", { + "b", "c", "ch", "ck", "d", "f", "g", "gh", "h", "k", "l", "ld", "ll", + "lt", "m", "n", "nd", "nn", "nt", "p", "ph", "q", "r", "rd", "rr", + "rt", "s", "sh", "ss", "st", "t", "th", "v", "w", "y", "z" + } + }, + { + "i", { + "air", "ankle", "ball", "beef", "bone", "bum", "bumble", "bump", + "cheese", "clod", "clot", "clown", "corn", "dip", "dolt", "doof", + "dork", "dumb", "face", "finger", "foot", "fumble", "goof", + "grumble", "head", "knock", "knocker", "knuckle", "loaf", "lump", + "lunk", "meat", "muck", "munch", "nit", "numb", "pin", "puff", + "skull", "snark", "sneeze", "thimble", "twerp", "twit", "wad", + "wimp", "wipe" + } + }, + { + "I", { + "airhead", "butterball", "bum", "bumbler", "bump", "chicken", "clod", "clot", "clown", "deadbeat", "dipstick", "dolt", "doof", "dork", "dummy", "dweeb", "fruitcake", "gasbag", "git", "goof", "hick", "idiot", "jerk", "klutz", "kook", "loaf", "louse", "lump", "meathead", "nut", "pig", "prat", "creampuff", "scumbag", "snark", "sneeze", "stupid", "tool", "twerp", "twit", "wimp" + } + }, + { + "m", { + "baby", "booble", "bunker", "cuddle", "cuddly", "cutie", "doodle", + "foofie", "gooble", "honey", "kissie", "lover", "lovey", "moofie", + "mooglie", "moopie", "moopsie", "nookum", "poochie", "poof", + "poofie", "pookie", "schmoopie", "schnoogle", "schnookie", + "schnookum", "smooch", "smoochie", "smoosh", "snoogle", "snoogy", + "snookie", "snookum", "snuggy", "sweetie", "woogle", "woogy", + "wookie", "wookum", "wuddle", "wuddly", "wuggy", "wunny" + } + }, + { + "M", { + "boo", "bunch", "bunny", "cake", "cakes", "cute", "darling", + "dumpling", "dumplings", "face", "foof", "goo", "head", "kin", + "kins", "lips", "love", "mush", "pie", "poo", "pooh", "pook", "pums" + } + }, + { + "D", { + "b", "bl", "br", "cl", "d", "f", "fl", "fr", "g", "gh", "gl", "gr", + "h", "j", "k", "kl", "m", "n", "p", "th", "w" + } + }, + { + "d", { + "elch", "idiot", "ob", "og", "ok", "olph", "olt", "omph", "ong", + "onk", "oo", "oob", "oof", "oog", "ook", "ooz", "org", "ork", "orm", + "oron", "ub", "uck", "ug", "ulf", "ult", "um", "umb", "ump", "umph", + "un", "unb", "ung", "unk", "unph", "unt", "uzz" + } + } + }); + + return *symbols; +} + +Generator::Generator() +{ +} + + +Generator::Generator(std::vector<std::unique_ptr<Generator>>&& generators_) : + generators(std::move(generators_)) +{ +} + + +size_t Generator::combinations() +{ + size_t total = 1; + for (auto& g : generators) { + total *= g->combinations(); + } + return total; +} + + +size_t Generator::min() +{ + size_t final = 0; + for (auto& g : generators) { + final += g->min(); + } + return final; +} + + +size_t Generator::max() +{ + size_t final = 0; + for (auto& g : generators) { + final += g->max(); + } + return final; +} + + +std::string Generator::toString() { + std::string str; + for (auto& g : generators) { + str.append(g->toString()); + } + return str; +} + + +void Generator::add(std::unique_ptr<Generator>&& g) +{ + generators.push_back(std::move(g)); +} + + +Random::Random() +{ +} + +Random::Random(std::vector<std::unique_ptr<Generator>>&& generators_) : + Generator(std::move(generators_)) +{ +} + +size_t Random::combinations() +{ + size_t total = 0; + for (auto& g : generators) { + total += g->combinations(); + } + return total ? total : 1; +} + +size_t Random::min() +{ + size_t final = -1; + for (auto& g : generators) { + size_t current = g->min(); + if (current < final) { + final = current; + } + } + return final; +} + +size_t Random::max() +{ + size_t final = 0; + for (auto& g : generators) { + size_t current = g->max(); + if (current > final) { + final = current; + } + } + return final; +} + + +std::string Random::toString() +{ + if (!generators.size()) { + return ""; + } + std::uniform_real_distribution<double> distribution(0, generators.size() - 1); + int rnd = distribution(rng) + 0.5; + return generators[rnd]->toString(); +} + + +Sequence::Sequence() +{ +} + +Sequence::Sequence(std::vector<std::unique_ptr<Generator>>&& generators_) : + Generator(std::move(generators_)) +{ +} + +Literal::Literal(const std::string &value_) : + value(value_) +{ +} + +size_t Literal::combinations() +{ + return 1; +} + +size_t Literal::min() +{ + return value.size(); +} + +size_t Literal::max() +{ + return value.size(); +} + +std::string Literal::toString() +{ + return value; +} + +Reverser::Reverser(std::unique_ptr<Generator>&& g) +{ + add(std::move(g)); +} + + +std::string Reverser::toString() +{ + std::wstring str = towstring(Generator::toString()); + std::reverse(str.begin(), str.end()); + return tostring(str); +} + +Capitalizer::Capitalizer(std::unique_ptr<Generator>&& g) +{ + add(std::move(g)); +} + +std::string Capitalizer::toString() +{ + std::wstring str = towstring(Generator::toString()); + str[0] = std::towupper(str[0]); + return tostring(str); +} + + +Collapser::Collapser(std::unique_ptr<Generator>&& g) +{ + add(std::move(g)); +} + +std::string Collapser::toString() +{ + std::wstring str = towstring(Generator::toString()); + std::wstring out; + int cnt = 0; + wchar_t pch = L'\0'; + for (auto ch : str) { + if (ch == pch) { + cnt++; + } else { + cnt = 0; + } + int mch = 2; + switch(ch) { + case 'a': + case 'h': + case 'i': + case 'j': + case 'q': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + mch = 1; + } + if (cnt < mch) { + out.push_back(ch); + } + pch = ch; + } + return tostring(out); +} + + +Generator::Generator(const std::string &pattern, bool collapse_triples) { + std::unique_ptr<Generator> last; + + std::stack<std::unique_ptr<Group>> stack; + std::unique_ptr<Group> top = std::make_unique<GroupSymbol>(); + + for (auto c : pattern) { + switch (c) { + case '<': + stack.push(std::move(top)); + top = std::make_unique<GroupSymbol>(); + break; + case '(': + stack.push(std::move(top)); + top = std::make_unique<GroupLiteral>(); + break; + case '>': + case ')': + if (stack.size() == 0) { + throw std::invalid_argument("Unbalanced brackets"); + } else if (c == '>' && top->type != group_types::symbol) { + throw std::invalid_argument("Unexpected '>' in pattern"); + } else if (c == ')' && top->type != group_types::literal) { + throw std::invalid_argument("Unexpected ')' in pattern"); + } + last = top->produce(); + top = std::move(stack.top()); + stack.pop(); + top->add(std::move(last)); + break; + case '|': + top->split(); + break; + case '!': + if (top->type == group_types::symbol) { + top->wrap(wrappers::capitalizer); + } else { + top->add(c); + } + break; + case '~': + if (top->type == group_types::symbol) { + top->wrap(wrappers::reverser); + } else { + top->add(c); + } + break; + default: + top->add(c); + break; + } + } + + if (stack.size() != 0) { + throw std::invalid_argument("Missing closing bracket"); + } + + std::unique_ptr<Generator> g = top->produce(); + if (collapse_triples) { + g = std::make_unique<Collapser>(std::move(g)); + } + add(std::move(g)); +} + + +Generator::Group::Group(group_types_t type_) : + type(type_) +{ +} + +void Generator::Group::add(std::unique_ptr<Generator>&& g) +{ + while (!wrappers.empty()) { + switch (wrappers.top()) { + case reverser: + g = std::make_unique<Reverser>(std::move(g)); + break; + case capitalizer: + g = std::make_unique<Capitalizer>(std::move(g)); + break; + } + wrappers.pop(); + } + if (set.size() == 0) { + set.push_back(std::make_unique<Sequence>()); + } + set.back()->add(std::move(g)); +} + +void Generator::Group::add(char c) +{ + std::string value(1, c); + std::unique_ptr<Generator> g = std::make_unique<Random>(); + g->add(std::make_unique<Literal>(value)); + Group::add(std::move(g)); +} + +std::unique_ptr<Generator> Generator::Group::produce() +{ + switch (set.size()) { + case 0: + return std::make_unique<Literal>(""); + case 1: + return std::move(*set.begin()); + default: + return std::make_unique<Random>(std::move(set)); + } +} + +void Generator::Group::split() +{ + if (set.size() == 0) { + set.push_back(std::make_unique<Sequence>()); + } + set.push_back(std::make_unique<Sequence>()); +} + +void Generator::Group::wrap(wrappers_t type) +{ + wrappers.push(type); +} + +Generator::GroupSymbol::GroupSymbol() : + Group(group_types::symbol) +{ +} + +void Generator::GroupSymbol::add(char c) +{ + std::string value(1, c); + std::unique_ptr<Generator> g = std::make_unique<Random>(); + try { + static const auto& symbols = SymbolMap(); + for (const auto& s : symbols.at(value)) { + g->add(std::make_unique<Literal>(s)); + } + } catch (const std::out_of_range&) { + g->add(std::make_unique<Literal>(value)); + } + Group::add(std::move(g)); +} + +Generator::GroupLiteral::GroupLiteral() : + Group(group_types::literal) +{ +} + +std::wstring towstring(const std::string & s) +{ + const char *cs = s.c_str(); + const size_t wn = std::mbsrtowcs(nullptr, &cs, 0, nullptr); + + if (wn == static_cast<size_t>(-1)) { + return L""; + } + + std::vector<wchar_t> buf(wn); + cs = s.c_str(); + const size_t wn_again = std::mbsrtowcs(buf.data(), &cs, wn, nullptr); + + if (wn_again == static_cast<size_t>(-1)) { + return L""; + } + + return std::wstring(buf.data(), wn); +} + +std::string tostring(const std::wstring & s) +{ + const wchar_t *cs = s.c_str(); + const size_t wn = std::wcsrtombs(nullptr, &cs, 0, nullptr); + + if (wn == static_cast<size_t>(-1)) { + return ""; + } + + std::vector<char> buf(wn); + const size_t wn_again = std::wcsrtombs(buf.data(), &cs, wn, nullptr); + + if (wn_again == static_cast<size_t>(-1)) { + return ""; + } + + return std::string(buf.data(), wn); +} |