From 2eebbb38c280cb47e49fff73132dbaf328a90adb Mon Sep 17 00:00:00 2001 From: AquariusPower Date: Wed, 30 May 2018 21:49:56 -0300 Subject: [PATCH] (ready) Fantasy name generator (#363) * added fantasy name generator * bug fix: autosave name was not substituting " " by "_" from player's name * fantasyname: added debug log * apply fix from: https://github.com/skeeto/fantasyname/issues/10 --- CMakeLists.txt | 1 + Main/CMakeLists.txt | 4 +- Main/Include/game.h | 2 +- Main/Include/iconf.h | 3 + Main/Source/game.cpp | 12 +- Main/Source/iconf.cpp | 19 ++ fantasyname/CMakeLists.txt | 5 + fantasyname/Makefile | 18 ++ fantasyname/README.md | 47 ++++ fantasyname/UNLICENSE | 24 ++ fantasyname/example.cc | 33 +++ fantasyname/namegen.cc | 536 +++++++++++++++++++++++++++++++++++++ fantasyname/namegen.h | 268 +++++++++++++++++++ 13 files changed, 968 insertions(+), 4 deletions(-) create mode 100644 fantasyname/CMakeLists.txt create mode 100644 fantasyname/Makefile create mode 100644 fantasyname/README.md create mode 100644 fantasyname/UNLICENSE create mode 100644 fantasyname/example.cc create mode 100644 fantasyname/namegen.cc create mode 100644 fantasyname/namegen.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c4fa1ea6..f4d6531f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,3 +37,4 @@ add_subdirectory(Main) add_subdirectory(igor) add_subdirectory(mihail) add_subdirectory(xbrzscale) +add_subdirectory(fantasyname) diff --git a/Main/CMakeLists.txt b/Main/CMakeLists.txt index b33e7dc3d..6d37f3c7a 100644 --- a/Main/CMakeLists.txt +++ b/Main/CMakeLists.txt @@ -15,8 +15,8 @@ set_source_files_properties( PROPERTIES HEADER_FILE_ONLY TRUE) add_executable(ivan ${IVAN_SOURCES} Resource/Ivan.rc) -target_include_directories(ivan PUBLIC Include ../Felib/Include ../audio) -target_link_libraries(ivan FeLib FeAudio SDL2_mixer pcre xbrzscale) +target_include_directories(ivan PUBLIC Include ../Felib/Include ../audio ../fantasyname) +target_link_libraries(ivan FeLib FeAudio SDL2_mixer pcre xbrzscale fantasyname) if(UNIX) install(TARGETS ivan DESTINATION "${CMAKE_INSTALL_BINDIR}") diff --git a/Main/Include/game.h b/Main/Include/game.h index 995fc0369..9dc6a7d56 100644 --- a/Main/Include/game.h +++ b/Main/Include/game.h @@ -235,7 +235,7 @@ class game static void CalculateGodNumber(); static void IncreaseTick() { ++Tick; } static ulong GetTick() { return Tick; } - static festring GetAutoSaveFileName() { return game::GetSaveDir() + PlayerName + ".AutoSave"; } + static festring GetAutoSaveFileName() { return SaveName() + ".AutoSave"; } static int DirectionQuestion(cfestring&, truth = true, truth = false); static void RemoveSaves(truth = true,truth onlyBackups=false); static truth IsInWilderness() { return InWilderness; } diff --git a/Main/Include/iconf.h b/Main/Include/iconf.h index 0b852af15..fb2773f6f 100644 --- a/Main/Include/iconf.h +++ b/Main/Include/iconf.h @@ -20,6 +20,7 @@ class ivanconfig { public: static cfestring& GetDefaultName() { return DefaultName.Value; } + static cfestring& GetFantasyNamePattern() { return FantasyNamePattern.Value; } static cfestring& GetDefaultPetName() { return DefaultPetName.Value; } static long GetAutoSaveInterval() { return AutoSaveInterval.Value; } static long GetContrast() { return Contrast.Value; } @@ -94,6 +95,7 @@ class ivanconfig static void ContrastDisplayer(const numberoption*, festring&); static void DirectionKeyMapDisplayer(const cycleoption*, festring&); static truth DefaultNameChangeInterface(stringoption*); + static truth FantasyNameChangeInterface(stringoption* O); static truth DefaultPetNameChangeInterface(stringoption*); static truth AutoSaveIntervalChangeInterface(numberoption*); static truth XBRZSquaresAroundPlayerChangeInterface(numberoption* O); @@ -143,6 +145,7 @@ class ivanconfig static void BackGroundDrawer(); static stringoption DefaultName; + static stringoption FantasyNamePattern; static stringoption DefaultPetName; static numberoption AutoSaveInterval; static truthoption AltAdentureInfo; diff --git a/Main/Source/game.cpp b/Main/Source/game.cpp index 09005e856..92dfae08d 100644 --- a/Main/Source/game.cpp +++ b/Main/Source/game.cpp @@ -55,6 +55,7 @@ #include "team.h" #include "whandler.h" #include "wsquare.h" +#include "namegen.h" #define DBGMSG_BLITDATA #include "dbgmsgproj.h" @@ -619,14 +620,23 @@ void game::PrepareStretchRegionsLazy(){ // the ADD order IS important IF they ov UpdateSRegionsXBRZ(); } +void FantasyName(festring& rfsName){ DBG2(rfsName.CStr(),ivanconfig::GetFantasyNamePattern().CStr()); + if(ivanconfig::GetFantasyNamePattern().IsEmpty())return; + + NameGen::Generator gen(ivanconfig::GetFantasyNamePattern().CStr()); + rfsName << gen.toString().c_str(); DBG1(rfsName.CStr()); +} + truth game::Init(cfestring& Name) -{ +{ DBG2(Name.CStr(),ivanconfig::GetDefaultName().CStr()); if(Name.IsEmpty()) { if(ivanconfig::GetDefaultName().IsEmpty()) { PlayerName.Empty(); + FantasyName(PlayerName); DBG1(PlayerName.CStr()); + if(iosystem::StringQuestion(PlayerName, CONST_S("What is your name? (1-20 letters)"), v2(30, 46), WHITE, 1, 20, true, true) == ABORTED || PlayerName.IsEmpty()) diff --git a/Main/Source/iconf.cpp b/Main/Source/iconf.cpp index d8a3ceacf..8f2a16630 100644 --- a/Main/Source/iconf.cpp +++ b/Main/Source/iconf.cpp @@ -27,6 +27,11 @@ stringoption ivanconfig::DefaultName( "DefaultName", "", &configsystem::NormalStringDisplayer, &DefaultNameChangeInterface); +stringoption ivanconfig::FantasyNamePattern("FantasyNamePattern", + "fantasy name generator pattern", + "!ss !sV", + &configsystem::NormalStringDisplayer, + &FantasyNameChangeInterface); stringoption ivanconfig::DefaultPetName( "DefaultPetName", "starting pet's default name", CONST_S("Kenny"), @@ -433,6 +438,19 @@ truth ivanconfig::DungeonGfxScaleChangeInterface(cycleoption* O) return true; } +truth ivanconfig::FantasyNameChangeInterface(stringoption* O) +{ + festring String; + + if(iosystem::StringQuestion(String, CONST_S("Set name generator pattern (recommended \"!ss !sV\"):"), + GetQuestionPos(), WHITE, 0, 20, !game::IsRunning(), true) == NORMAL_EXIT) + O->ChangeValue(String); + + clearToBackgroundAfterChangeInterface(); + + return false; +} + truth ivanconfig::DefaultNameChangeInterface(stringoption* O) { festring String; @@ -762,6 +780,7 @@ void ivanconfig::Initialize() fsCategory="Core Game Setup"; configsystem::AddOption(fsCategory,&DefaultName); + configsystem::AddOption(fsCategory,&FantasyNamePattern); configsystem::AddOption(fsCategory,&DefaultPetName); configsystem::AddOption(fsCategory,&AutoSaveInterval); configsystem::AddOption(fsCategory,&AltAdentureInfo); diff --git a/fantasyname/CMakeLists.txt b/fantasyname/CMakeLists.txt new file mode 100644 index 000000000..dea020012 --- /dev/null +++ b/fantasyname/CMakeLists.txt @@ -0,0 +1,5 @@ +file(GLOB FANTASYNAME_SOURCES namegen.cc namegen.h) + +set(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}") + +add_library(fantasyname ${FANTASYNAME_SOURCES}) diff --git a/fantasyname/Makefile b/fantasyname/Makefile new file mode 100644 index 000000000..0bba830a7 --- /dev/null +++ b/fantasyname/Makefile @@ -0,0 +1,18 @@ +.POSIX: +.SUFFIXES: .cc +CXX = c++ +CXXFLAGS = -std=c++11 -Wall -Wextra -O3 -g3 + +all: namegen + +namegen: namegen.o example.o + $(CXX) $(LDFLAGS) -o $@ namegen.o example.o $(LDLIBS) + +namegen.o: namegen.cc +example.o: example.cc + +clean: + rm -rf namegen namegen.o example.o + +.cc.o: + $(CXX) -c $(CXXFLAGS) -o $@ $< diff --git a/fantasyname/README.md b/fantasyname/README.md new file mode 100644 index 000000000..9c451ec18 --- /dev/null +++ b/fantasyname/README.md @@ -0,0 +1,47 @@ +# Fantasy Name Generator + +Four implementations -- JavaScript, C++, Elisp, and Perl -- of the +[name generator described at RinkWorks](http://rinkworks.com/namegen/). +The JavaScript and C++ implementations are by far the most mature. + + +## JavaScript + +The JavaScript version uses an optimizing template-compiler to create +an efficient generator object. + +```javascript +var generator = NameGen.compile("sV'i"); +generator.toString(); // => "entheu'loaf" +generator.toString(); // => "honi'munch" +``` + +## C++ + +The C++ version also uses a template-compiler (based on the one for JavaScript) +to create an efficient generator object. It requires C++11. + +```c++ +NameGen::Generator generator("sV'i"); +generator.toString(); // => "drai'sneeze" +generator.toString(); // => "ardou'bumble" +``` + +## Emacs Lisp + +The Emacs Lisp version doesn't include a parser. It operates on +s-expressions. + +```el +(fset 'generator (apply-partially #'namegen '(s V "'" i))) +(generator) ; => "essei'knocker" +(generator) ; => "tiaoe'nit" +``` + +## Perl + +The Perl version is exceptionally slow, due to a slow parser. + +```perl +generate("sV'i"); # => "echoi'bum" +``` diff --git a/fantasyname/UNLICENSE b/fantasyname/UNLICENSE new file mode 100644 index 000000000..68a49daad --- /dev/null +++ b/fantasyname/UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/fantasyname/example.cc b/fantasyname/example.cc new file mode 100644 index 000000000..00afa81a8 --- /dev/null +++ b/fantasyname/example.cc @@ -0,0 +1,33 @@ +#include "namegen.h" + +#include +#include +#include + + +int main(int argc, char **argv) +{ + int num = 1; + std::setlocale(LC_CTYPE, ""); + if (argc < 2 || argc > 3 || (argc == 3 && (num = atoi(argv[2])) && num == -1)) { + std::cerr << "Usage: " << argv[0] << " [num]\n"; + std::cerr << " pattern - Template for names to generate.\n"; + std::cerr << " num - Number of names to generate.\n"; + return 64; + } + + char * pattern = argv[1]; + // std::cerr << "> pattern = " << pattern << "\n"; + + NameGen::Generator generator(pattern); + + std::cerr << "> combinations = " << generator.combinations() << "\n"; + // std::cerr << "> min = " << generator.min() << "\n"; + // std::cerr << "> max = " << generator.max() << "\n"; + + for (int i = 0; i < num; i++) { + std::cout << generator.toString() << "\n"; + } + + return 0; +} diff --git a/fantasyname/namegen.cc b/fantasyname/namegen.cc new file mode 100644 index 000000000..c8bf964f2 --- /dev/null +++ b/fantasyname/namegen.cc @@ -0,0 +1,536 @@ +/** + * + * @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 // for size_t, mbsrtowcs, wcsrtombs +#include // for towupper +#include // for make_unique +#include // for mt19937, random_device, uniform_real_distribution +#include // for invalid_argument, out_of_range + + +using namespace NameGen; + + +static std::random_device rd; // Random device engine, usually based on /dev/random on UNIX-like systems +static std::mt19937 rng(rd()); // Initialize Mersennes' twister using rd to generate the seed + + +// 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" + } + }, + { + "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; +} + + +#ifdef HAVE_CXX14 +using std::make_unique; +#else +// make_unique is not available in c++11, so we use this template function +// to maintain full c++11 compatibility; std::make_unique is part of C++14. +template +std::unique_ptr make_unique(Args&&... args) +{ + return std::unique_ptr(new T(std::forward(args)...)); +} +#endif + + +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 = make_unique(); + + for (auto c : pattern) { + switch (c) { + case '<': + stack.push(std::move(top)); + top = make_unique(); + break; + case '(': + stack.push(std::move(top)); + top = 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 = 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 = make_unique(std::move(g)); + break; + case capitalizer: + g = make_unique(std::move(g)); + break; + } + wrappers.pop(); + } + if (set.size() == 0) { + set.push_back(make_unique()); + } + set.back()->add(std::move(g)); +} + +void Generator::Group::add(char c) +{ + std::string value(1, c); + std::unique_ptr g = make_unique(); + g->add(make_unique(value)); + Group::add(std::move(g)); +} + +std::unique_ptr Generator::Group::produce() +{ + switch (set.size()) { + case 0: + return make_unique(""); + case 1: + return std::move(*set.begin()); + default: + return make_unique(std::move(set)); + } +} + +void Generator::Group::split() +{ + if (set.size() == 0) { + set.push_back(make_unique()); + } + set.push_back(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 = make_unique(); + try { + static const auto& symbols = SymbolMap(); + for (const auto& s : symbols.at(value)) { + g->add(make_unique(s)); + } + } catch (const std::out_of_range&) { + g->add(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); +} diff --git a/fantasyname/namegen.h b/fantasyname/namegen.h new file mode 100644 index 000000000..c90fa30e0 --- /dev/null +++ b/fantasyname/namegen.h @@ -0,0 +1,268 @@ +/** + * + * @file A fantasy name generator library. + * @version 1.0.1 + * @license Public Domain + * @author German M. Bravo (Kronuz) + * + * This library is designed after the RinkWorks Fantasy Name Generator. + * @see http://www.rinkworks.com/namegen/ + * + * @example + * NameGen::Generator generator("sV'i"); + * generator.toString(); // Returns a new name each call with produce() + * // => "entheu'loaf" + * + * ## Pattern Syntax + * + * The compile() function creates a name generator based on an input + * pattern. The letters s, v, V, c, B, C, i, m, M, D, and d represent + * different types of random replacements. Everything else is produced + * literally. + * + * s - generic syllable + * v - vowel + * V - vowel or vowel combination + * c - consonant + * B - consonant or consonant combination suitable for beginning a word + * C - consonant or consonant combination suitable anywhere in a word + * i - insult + * m - mushy name + * M - mushy name ending + * D - consonant suited for a stupid person's name + * d - syllable suited for a stupid person's name (begins with a vowel) + * + * All characters between parenthesis () are produced literally. For + * example, the pattern "s(dim)", produces a random generic syllable + * followed by "dim". + * + * Characters between angle brackets <> produce patterns from the table + * above. Imagine the entire pattern is wrapped in one of these. + * + * In both types of groupings, a vertical bar | denotes a random + * choice. Empty groups are allowed. For example, "(foo|bar)" produces + * either "foo" or "bar". The pattern "" produces a constant, + * vowel, or nothing at all. + * + * An exclamation point ! means to capitalize the component that + * follows it. For example, "!(foo)" will produce "Foo" and "v!s" will + * produce a lowercase vowel followed by a capitalized syllable, like + * "eRod". + * + * A tilde ~ means to reverse the letters of the component that + * follows it. For example, "~(foo)" will produce "oof". To reverse an + * entire template, wrap it in brackets. For example, to reverse + * "sV'i" as a whole use "~". The template "~sV'i" will only + * reverse the initial syllable. + * + * ## Internals + * + * A name generator is anything with a toString() method, including, + * importantly, strings themselves. The generator constructors + * (Random, Sequence) perform additional optimizations when *not* used + * with the `new` keyword: they may pass through a provided generator, + * combine provided generators, or even return a simple string. + * + * New pattern symbols added to Generator.symbols will automatically + * be used by the compiler. + */ + +#pragma once + +#include // for size_t +#include // for wstring +#include // for unique_ptr +#include // for stack +#include // for string +#include // for unordered_map +#include // for vector + + +namespace NameGen { + +// Middle Earth +#define MIDDLE_EARTH "(bil|bal|ban|hil|ham|hal|hol|hob|wil|me|or|ol|od|gor|for|fos|tol|ar|fin|ere|leo|vi|bi|bren|thor)(|go|orbis|apol|adur|mos|ri|i|na|ole|n)(|tur|axia|and|bo|gil|bin|bras|las|mac|grim|wise|l|lo|fo|co|ra|via|da|ne|ta|y|wen|thiel|phin|dir|dor|tor|rod|on|rdo|dis)" + +// Japanese Names (Constrained) +#define JAPANESE_NAMES_CONSTRAINED "(aka|aki|bashi|gawa|kawa|furu|fuku|fuji|hana|hara|haru|hashi|hira|hon|hoshi|ichi|iwa|kami|kawa|ki|kita|kuchi|kuro|marui|matsu|miya|mori|moto|mura|nabe|naka|nishi|no|da|ta|o|oo|oka|saka|saki|sawa|shita|shima|i|suzu|taka|take|to|toku|toyo|ue|wa|wara|wata|yama|yoshi|kei|ko|zawa|zen|sen|ao|gin|kin|ken|shiro|zaki|yuki|asa)(||||||||||bashi|gawa|kawa|furu|fuku|fuji|hana|hara|haru|hashi|hira|hon|hoshi|chi|wa|ka|kami|kawa|ki|kita|kuchi|kuro|marui|matsu|miya|mori|moto|mura|nabe|naka|nishi|no|da|ta|o|oo|oka|saka|saki|sawa|shita|shima|suzu|taka|take|to|toku|toyo|ue|wa|wara|wata|yama|yoshi|kei|ko|zawa|zen|sen|ao|gin|kin|ken|shiro|zaki|yuki|sa)" + +// Japanese Names (Diverse) +#define JAPANESE_NAMES_DIVERSE "(a|i|u|e|o|||||)(ka|ki|ki|ku|ku|ke|ke|ko|ko|sa|sa|sa|shi|shi|shi|su|su|se|so|ta|ta|chi|chi|tsu|te|to|na|ni|ni|nu|nu|ne|no|no|ha|hi|fu|fu|he|ho|ma|ma|ma|mi|mi|mi|mu|mu|mu|mu|me|mo|mo|mo|ya|yu|yu|yu|yo|ra|ra|ra|ri|ru|ru|ru|re|ro|ro|ro|wa|wa|wa|wa|wo|wo)(ka|ki|ki|ku|ku|ke|ke|ko|ko|sa|sa|sa|shi|shi|shi|su|su|se|so|ta|ta|chi|chi|tsu|te|to|na|ni|ni|nu|nu|ne|no|no|ha|hi|fu|fu|he|ho|ma|ma|ma|mi|mi|mi|mu|mu|mu|mu|me|mo|mo|mo|ya|yu|yu|yu|yo|ra|ra|ra|ri|ru|ru|ru|re|ro|ro|ro|wa|wa|wa|wa|wo|wo)(|(ka|ki|ki|ku|ku|ke|ke|ko|ko|sa|sa|sa|shi|shi|shi|su|su|se|so|ta|ta|chi|chi|tsu|te|to|na|ni|ni|nu|nu|ne|no|no|ha|hi|fu|fu|he|ho|ma|ma|ma|mi|mi|mi|mu|mu|mu|mu|me|mo|mo|mo|ya|yu|yu|yu|yo|ra|ra|ra|ri|ru|ru|ru|re|ro|ro|ro|wa|wa|wa|wa|wo|wo)|(ka|ki|ki|ku|ku|ke|ke|ko|ko|sa|sa|sa|shi|shi|shi|su|su|se|so|ta|ta|chi|chi|tsu|te|to|na|ni|ni|nu|nu|ne|no|no|ha|hi|fu|fu|he|ho|ma|ma|ma|mi|mi|mi|mu|mu|mu|mu|me|mo|mo|mo|ya|yu|yu|yu|yo|ra|ra|ra|ri|ru|ru|ru|re|ro|ro|ro|wa|wa|wa|wa|wo|wo)(|(ka|ki|ki|ku|ku|ke|ke|ko|ko|sa|sa|sa|shi|shi|shi|su|su|se|so|ta|ta|chi|chi|tsu|te|to|na|ni|ni|nu|nu|ne|no|no|ha|hi|fu|fu|he|ho|ma|ma|ma|mi|mi|mi|mu|mu|mu|mu|me|mo|mo|mo|ya|yu|yu|yu|yo|ra|ra|ra|ri|ru|ru|ru|re|ro|ro|ro|wa|wa|wa|wa|wo|wo)))(|||n)" + +// Chinese Names +#define CHINESE_NAMES "(zh|x|q|sh|h)(ao|ian|uo|ou|ia)(|(l|w|c|p|b|m)(ao|ian|uo|ou|ia)(|n)|-(l|w|c|p|b|m)(ao|ian|uo|ou|ia)(|(d|j|q|l)(a|ai|iu|ao|i)))" + +// Greek Names +#define GREEK_NAMES "(tia)|s(os)|Bc(ios)|Bv(ios|os)>" + +// Hawaiian Names (1) +#define HAWAIIAN_NAMES_1 "((h|k|l|m|n|p|w|')|)(a|e|i|o|u)((h|k|l|m|n|p|w|')|)(a|e|i|o|u)(((h|k|l|m|n|p|w|')|)(a|e|i|o|u)|)(((h|k|l|m|n|p|w|')|)(a|e|i|o|u)|)(((h|k|l|m|n|p|w|')|)(a|e|i|o|u)|)(((h|k|l|m|n|p|w|')|)(a|e|i|o|u)|)" + +// Hawaiian Names (2) +#define HAWAIIAN_NAMES_2 "((h|k|l|m|n|p|w|)(a|e|i|o|u|a'|e'|i'|o'|u'|ae|ai|ao|au|oi|ou|eu|ei)(k|l|m|n|p|)|)(h|k|l|m|n|p|w|)(a|e|i|o|u|a'|e'|i'|o'|u'|ae|ai|ao|au|oi|ou|eu|ei)(k|l|m|n|p|)" + +// Old Latin Place Names +#define OLD_LATIN_PLACE_NAMES "sv(nia|lia|cia|sia)" + +// Dragons (Pern) +#define DRAGONS_PERN "<|>>(th)" + +// Dragon Riders +#define DRAGON_RIDERS "c'" + +// Pokemon +#define POKEMON "v(mon|chu|zard|rtle)" + +// Fantasy (Vowels, R, etc.) +#define FANTASY_VOWELS_R "(|(|s|h|ty|ph|r))(i|ae|ya|ae|eu|ia|i|eo|ai|a)(lo|la|sri|da|dai|the|sty|lae|due|li|lly|ri|na|ral|sur|rith)(|(su|nu|sti|llo|ria|))(|(n|ra|p|m|lis|cal|deu|dil|suir|phos|ru|dru|rin|raap|rgue))" + +// Fantasy (S, A, etc.) +#define FANTASY_S_A "(cham|chan|jisk|lis|frich|isk|lass|mind|sond|sund|ass|chad|lirt|und|mar|lis|il|)(jask|ast|ista|adar|irra|im|ossa|assa|osia|ilsa|)(|(an|ya|la|sta|sda|sya|st|nya))" + +// Fantasy (H, L, etc.) +#define FANTASY_H_L "(ch|ch't|sh|cal|val|ell|har|shar|shal|rel|laen|ral|jh't|alr|ch|ch't|av)(|(is|al|ow|ish|ul|el|ar|iel))(aren|aeish|aith|even|adur|ulash|alith|atar|aia|erin|aera|ael|ira|iel|ahur|ishul)" + +// Fantasy (N, L, etc.) +#define FANTASY_N_L "(ethr|qil|mal|er|eal|far|fil|fir|ing|ind|il|lam|quel|quar|quan|qar|pal|mal|yar|um|ard|enn|ey)(|(|on|us|un|ar|as|en|ir|ur|at|ol|al|an))(uard|wen|arn|on|il|ie|on|iel|rion|rian|an|ista|rion|rian|cil|mol|yon)" + +// Fantasy (K, N, etc.) +#define FANTASY_K_N "(taith|kach|chak|kank|kjar|rak|kan|kaj|tach|rskal|kjol|jok|jor|jad|kot|kon|knir|kror|kol|tul|rhaok|rhak|krol|jan|kag|ryr)(|in|or|an|ar|och|un|mar|yk|ja|arn|ir|ros|ror)(|(mund|ard|arn|karr|chim|kos|rir|arl|kni|var|an|in|ir|a|i|as))" + +// Fantasy (J, G, Z, etc.) +#define FANTASY_J_G_Z "(aj|ch|etz|etzl|tz|kal|gahn|kab|aj|izl|ts|jaj|lan|kach|chaj|qaq|jol|ix|az|biq|nam)(|(|aw|al|yes|il|ay|en|tom||oj|im|ol|aj|an|as))(aj|am|al|aqa|ende|elja|ich|ak|ix|in|ak|al|il|ek|ij|os|al|im)" + +// Fantasy (K, J, Y, etc.) +#define FANTASY_K_J_Y "(yi|shu|a|be|na|chi|cha|cho|ksa|yi|shu)(th|dd|jj|sh|rr|mk|n|rk|y|jj|th)(us|ash|eni|akra|nai|ral|ect|are|el|urru|aja|al|uz|ict|arja|ichi|ural|iru|aki|esh)" + +// Fantasy (S, E, etc.) +#define FANTASY_S_E "(syth|sith|srr|sen|yth|ssen|then|fen|ssth|kel|syn|est|bess|inth|nen|tin|cor|sv|iss|ith|sen|slar|ssil|sthen|svis|s|ss|s|ss)(|(tys|eus|yn|of|es|en|ath|elth|al|ell|ka|ith|yrrl|is|isl|yr|ast|iy))(us|yn|en|ens|ra|rg|le|en|ith|ast|zon|in|yn|ys)" + + +class Generator +{ + typedef enum wrappers { + capitalizer, + reverser + } wrappers_t; + + typedef enum group_types { + symbol, + literal + } group_types_t; + + + class Group { + std::stack wrappers; + std::vector> set; + + public: + group_types_t type; + + Group(group_types_t type_); + + std::unique_ptr produce(); + void split(); + void wrap(wrappers_t type); + void add(std::unique_ptr&& g); + + virtual void add(char c); + }; + + + class GroupSymbol : public Group { + public: + GroupSymbol(); + void add(char c); + }; + + + class GroupLiteral : public Group { + public: + GroupLiteral(); + }; + +protected: + std::vector> generators; + +public: + static const std::unordered_map>& SymbolMap(); + + Generator(); + Generator(const std::string& pattern, bool collapse_triples=true); + Generator(std::vector>&& generators_); + + virtual ~Generator() = default; + + virtual size_t combinations(); + virtual size_t min(); + virtual size_t max(); + virtual std::string toString(); + + void add(std::unique_ptr&& g); +}; + + +class Random : public Generator +{ +public: + Random(); + Random(std::vector>&& generators_); + + size_t combinations(); + size_t min(); + size_t max(); + std::string toString(); +}; + + +class Sequence : public Generator +{ +public: + Sequence(); + Sequence(std::vector>&& generators_); +}; + + +class Literal : public Generator +{ + std::string value; + +public: + Literal(const std::string& value_); + + size_t combinations(); + size_t min(); + size_t max(); + std::string toString(); +}; + + +class Reverser : public Generator { +public: + Reverser(std::unique_ptr&& g); + + std::string toString(); +}; + + +class Capitalizer : public Generator +{ +public: + Capitalizer(std::unique_ptr&& g); + + std::string toString(); +}; + + +class Collapser : public Generator +{ +public: + Collapser(std::unique_ptr&& g); + + std::string toString(); +}; + +} + +std::wstring towstring(const std::string& s); +std::string tostring(const std::wstring& s);