aboutsummaryrefslogtreecommitdiff
path: root/src/namegen/namegen.cc
diff options
context:
space:
mode:
authorYour Name <you@example.com>2023-05-31 17:04:47 -0400
committerYour Name <you@example.com>2023-05-31 17:04:47 -0400
commit19a226f512a924def233dbd723d6d7a66ed382d3 (patch)
treea08fffcd160208e251eb7eb32ee7669c6761c130 /src/namegen/namegen.cc
parent321fcc04b7f77a35c2099b6724de3f780adbd580 (diff)
downloaddmtool-32b7506f78382a3a08628b183e65cf66d037187b.tar.gz (sig)
dmtool-32b7506f78382a3a08628b183e65cf66d037187b.tar.bz2 (sig)
dmtool-32b7506f78382a3a08628b183e65cf66d037187b.zip (sig)
Added automatic creature name generationv1.0.0
Diffstat (limited to 'src/namegen/namegen.cc')
-rw-r--r--src/namegen/namegen.cc555
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);
+}