/** * * @file A fantasy name generator library. * @version 1.0.1 * @license Public Domain * @author German M. Bravo (Kronuz) * */ #include "namegen.h" #include // for move, reverse #include #include // for rng seed #include // for size_t, mbsrtowcs, wcsrtombs #include // for towupper #include // for std::make_unique #include // for mt19937, uniform_real_distribution #include // 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 += ">= 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 )!(|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>& Generator::SymbolMap() { static auto* const symbols = new std::unordered_map>({ { "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>&& 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&& g) { generators.push_back(std::move(g)); } Random::Random() { } Random::Random(std::vector>&& 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 distribution(0, generators.size() - 1); int rnd = distribution(rng) + 0.5; return generators[rnd]->toString(); } Sequence::Sequence() { } Sequence::Sequence(std::vector>&& 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&& 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&& 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&& 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 last; std::stack> stack; std::unique_ptr top = std::make_unique(); for (auto c : pattern) { switch (c) { case '<': stack.push(std::move(top)); top = std::make_unique(); break; case '(': stack.push(std::move(top)); top = std::make_unique(); 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 g = top->produce(); if (collapse_triples) { g = std::make_unique(std::move(g)); } add(std::move(g)); } Generator::Group::Group(group_types_t type_) : type(type_) { } void Generator::Group::add(std::unique_ptr&& g) { while (!wrappers.empty()) { switch (wrappers.top()) { case reverser: g = std::make_unique(std::move(g)); break; case capitalizer: g = std::make_unique(std::move(g)); break; } wrappers.pop(); } if (set.size() == 0) { set.push_back(std::make_unique()); } set.back()->add(std::move(g)); } void Generator::Group::add(char c) { std::string value(1, c); std::unique_ptr g = std::make_unique(); g->add(std::make_unique(value)); Group::add(std::move(g)); } std::unique_ptr Generator::Group::produce() { switch (set.size()) { case 0: return std::make_unique(""); case 1: return std::move(*set.begin()); default: return std::make_unique(std::move(set)); } } void Generator::Group::split() { if (set.size() == 0) { set.push_back(std::make_unique()); } set.push_back(std::make_unique()); } 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 g = std::make_unique(); try { static const auto& symbols = SymbolMap(); for (const auto& s : symbols.at(value)) { g->add(std::make_unique(s)); } } catch (const std::out_of_range&) { g->add(std::make_unique(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(-1)) { return L""; } std::vector buf(wn); cs = s.c_str(); const size_t wn_again = std::mbsrtowcs(buf.data(), &cs, wn, nullptr); if (wn_again == static_cast(-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(-1)) { return ""; } std::vector buf(wn); const size_t wn_again = std::wcsrtombs(buf.data(), &cs, wn, nullptr); if (wn_again == static_cast(-1)) { return ""; } return std::string(buf.data(), wn); }