208 nng& operator=(
nng&&) noexcept = default;
218 static nng inst{auto_probe_tag{}};
244 std::uint64_t call_seed)
const
246 auto r = core::rng{call_seed}.split(
"nickname");
247 auto result = solver(request, r);
248 result._signature = {.seed = call_seed, .data =
data_hash()};
264 const core::batch_options& options)
285 const core::batch_options& options,
286 std::uint64_t batch_seed)
const
288 return core::run_batch(options, batch_seed, [&](std::uint64_t item_seed)
305 template <
typename Sink>
307 std::uint64_t batch_seed, Sink&& sink)
const
309 core::for_each_batch(options, batch_seed,
310 [&](std::uint64_t item_seed) {
return get_nickname(request, item_seed); },
311 std::forward<Sink>(sink));
320 template <
typename Sink>
332 return !_wordlists.empty();
346 void load(
const std::filesystem::path& resource_path)
349 if (!std::filesystem::is_directory(resource_path, ec))
return;
350 apply_(read_directory_(resource_path), core::layer_mode::replace,
true);
365 [[nodiscard]]
bool load([[maybe_unused]] dataset tier)
367 std::vector<std::pair<std::string, core::tsv_document>> docs;
368 const auto root = core::find_resources(library_name_, std::string{shipped_tier_} +
"/" +
369 std::string{marker_list_} +
".words");
370 for (
const auto list : shipped_lists_)
372 const auto path = std::string{shipped_tier_} +
"/" + std::string{list} +
".words";
373 if (
auto doc = core::load_resource(path, embedded_files_(), root, {
"word"}))
375 docs.emplace_back(std::string{list}, std::move(*doc));
378 if (docs.empty())
return false;
379 apply_(std::move(docs), core::layer_mode::replace,
true);
397 void extend(
const std::filesystem::path& path,
398 core::layer_mode mode = core::layer_mode::override)
401 if (std::filesystem::is_directory(path, ec))
403 apply_(read_directory_(path), mode,
false);
406 auto doc = read_file_(path);
409 throw std::invalid_argument(
"extend: expected a .words or .tsv file, got " +
410 path.filename().string());
412 std::vector<std::pair<std::string, core::tsv_document>> docs;
413 docs.emplace_back(core::to_utf8(path.stem().wstring()), std::move(*doc));
414 apply_(std::move(docs), mode,
false);
428 core::layer_mode mode = core::layer_mode::override)
430 std::vector<std::pair<std::string, core::tsv_document>> docs;
431 docs.emplace_back(std::string{list}, core::tsv_document::parse(text, std::move(source)));
432 apply_(std::move(docs), mode,
false);
433 return _lists.at(std::string{list}).
layers().back();
440 std::vector<std::string> names;
441 for (
const auto& [name, table] : _lists)
443 if (!table.empty()) names.push_back(name);
452 [[nodiscard]]
const std::vector<core::layer_info>&
layers(
const std::string& list)
const
454 return _lists.at(list).layers();
463 for (
const auto& [name, table] : _lists)
465 h = core::hash_combine(core::hash_combine(h, core::fnv1a64(name)), table.data_hash());
471 static constexpr std::string_view library_name_ =
"nickname-generator";
472 static constexpr std::string_view shipped_tier_ =
"lite";
473 static constexpr std::string_view marker_list_ =
"animals";
475 static constexpr std::string_view tag_prefix_ =
"tag_";
476 static constexpr std::array<std::string_view, 52> shipped_lists_{
"adjectives",
"animals",
"colors",
"food",
"gems",
"japanese",
"myth",
"nature",
"space",
"tag_academic",
"tag_activity",
"tag_art",
"tag_care",
"tag_caring",
"tag_chaos",
"tag_combat",
"tag_crafting",
"tag_creative",
"tag_desk",
"tag_discipline",
"tag_dreaming",
"tag_duty",
"tag_exploration",
"tag_faith",
"tag_flight",
"tag_haste",
"tag_knowledge",
"tag_labour",
"tag_leadership",
"tag_manual",
"tag_medicine",
"tag_morale",
"tag_outdoor",
"tag_physical",
"tag_planning",
"tag_rest",
"tag_risk",
"tag_routine",
"tag_safety",
"tag_social",
"tag_solitude",
"tag_solo",
"tag_stealth",
"tag_survival",
"tag_team",
"tag_technical",
"tag_trade",
"tag_training",
"tag_violence",
"tag_waiting",
"tag_wealth",
"tag_willpower"};
479 using generator_fn = std::wstring (*)(
const std::wstring&, core::rng&);
482 using generators = std::vector<generator_fn>;
488 static const inline std::wstring _vowels{
489 L
"aeiou\u00E1\u00E0\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB"
490 L
"\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F8"
491 L
"\u0161\u00F9\u00FA\u00FB\u00FC\u00FD\u00FF"};
495 static const inline std::map<wchar_t, wchar_t> _leet_map{
496 {L
'o', L
'0'}, {L
'O', L
'0'}, {L
'i', L
'1'}, {L
'I', L
'1'}, {L
's', L
'2'},
497 {L
'S', L
'2'}, {L
'e', L
'3'}, {L
'E', L
'3'}, {L
'a', L
'4'}, {L
'A', L
'4'},
498 {L
'g', L
'6'}, {L
'G', L
'6'}, {L
't', L
'7'}, {L
'T', L
'7'}, {L
'b', L
'8'},
499 {L
'B', L
'8'}, {L
'q', L
'9'}, {L
'Q', L
'9'}};
501 static core::table_schema schema_()
503 return {.key = {
"word"}, .required = {}, .optional = {}};
508 std::map<std::string, core::layered_table, std::less<>> _lists;
509 std::vector<std::vector<std::wstring>> _wordlists;
510 core::discrete _list_choice{1.0};
511 std::map<std::string, std::vector<std::wstring>, std::less<>> _themes;
513 core::rng _engine{core::rng::from_entropy()};
515 struct auto_probe_tag {};
517 explicit nng(auto_probe_tag )
519 (void)
load(dataset::lite);
522 static std::span<const core::embedded_file> embedded_files_()
524#ifdef DASMIG_NICKNAME_EMBED
525 return embedded::nicknamegen::files;
533 static std::optional<core::tsv_document> read_file_(
const std::filesystem::path& file)
535 const auto ext = file.extension();
536 if (ext ==
".words")
return core::tsv_document::load(file, {
"word"});
537 if (ext ==
".tsv")
return core::tsv_document::load(file);
541 static std::vector<std::pair<std::string, core::tsv_document>> read_directory_(
542 const std::filesystem::path& dir)
544 std::vector<std::filesystem::path> files;
545 for (
const auto& entry : std::filesystem::recursive_directory_iterator(dir))
547 if (entry.is_regular_file() &&
548 (entry.path().extension() ==
".words" || entry.path().extension() ==
".tsv"))
550 files.push_back(entry.path());
553 std::ranges::sort(files);
554 std::vector<std::pair<std::string, core::tsv_document>> docs;
555 docs.reserve(files.size());
556 for (
const auto& file : files)
558 docs.emplace_back(core::to_utf8(file.stem().wstring()), *read_file_(file));
565 void apply_(std::vector<std::pair<std::string, core::tsv_document>> docs, core::layer_mode mode,
568 auto next = reset ?
decltype(_lists){} : _lists;
571 std::map<std::string, bool, std::less<>> started;
572 for (
auto& [name, doc] : docs)
574 auto it = next.try_emplace(name, schema_()).first;
575 auto list_mode = mode;
578 list_mode = started[name] ? core::layer_mode::override : core::layer_mode::replace;
580 started[name] =
true;
581 (void)it->second.extend(std::move(doc), list_mode, [](
const core::tsv_row& row) {
582 (void)core::from_utf8(row[
"word"]);
585 std::vector<std::vector<std::wstring>> wordlists;
586 std::map<std::string, std::vector<std::wstring>, std::less<>> themes;
587 for (
const auto& [name, table] : next)
589 std::vector<std::wstring> words;
590 words.reserve(table.size());
591 for (
const auto& row : table.rows()) words.push_back(core::from_utf8(row[
"word"]));
592 if (words.empty())
continue;
593 if (name.starts_with(tag_prefix_))
595 themes.emplace(name.substr(tag_prefix_.size()), std::move(words));
599 wordlists.push_back(std::move(words));
602 _lists = std::move(next);
606 if (!wordlists.empty())
608 std::vector<double> weights;
609 weights.reserve(wordlists.size());
610 for (
const auto& list : wordlists) weights.push_back(std::sqrt(static_cast<double>(list.size())));
611 _list_choice = core::discrete{weights};
613 _wordlists = std::move(wordlists);
614 _themes = std::move(themes);
623 static wchar_t to_upper_(
wchar_t c)
625 const auto u =
static_cast<std::uint32_t
>(c);
626 if (u >= 0x61U && u <= 0x7AU)
return static_cast<wchar_t>(u - 0x20U);
627 if (u >= 0xE0U && u <= 0xFEU && u != 0xF7U)
return static_cast<wchar_t>(u - 0x20U);
628 if (u == 0xFFU)
return static_cast<wchar_t>(0x178U);
629 if ((u >= 0x100U && u <= 0x12FU) || (u >= 0x132U && u <= 0x137U) ||
630 (u >= 0x14AU && u <= 0x177U))
632 return static_cast<wchar_t>(u & ~1U);
634 if ((u >= 0x139U && u <= 0x148U) || (u >= 0x179U && u <= 0x17EU))
636 return (u % 2U == 0U) ?
static_cast<wchar_t>(u - 1U) : c;
641 static wchar_t to_lower_(
wchar_t c)
643 const auto u =
static_cast<std::uint32_t
>(c);
644 if (u >= 0x41U && u <= 0x5AU)
return static_cast<wchar_t>(u + 0x20U);
645 if (u >= 0xC0U && u <= 0xDEU && u != 0xD7U)
return static_cast<wchar_t>(u + 0x20U);
646 if (u == 0x178U)
return static_cast<wchar_t>(0xFFU);
647 if ((u >= 0x100U && u <= 0x12FU) || (u >= 0x132U && u <= 0x137U) ||
648 (u >= 0x14AU && u <= 0x177U))
650 return static_cast<wchar_t>(u | 1U);
652 if ((u >= 0x139U && u <= 0x148U) || (u >= 0x179U && u <= 0x17EU))
654 return (u % 2U == 1U) ?
static_cast<wchar_t>(u + 1U) : c;
659 static bool is_upper_(
wchar_t c) {
return to_lower_(c) != c; }
664 static std::wstring xfy(
const std::wstring& nickname, core::rng& engine)
666 std::wstring x_nickname{nickname};
667 switch (core::uniform_int(engine, 0, 2))
670 x_nickname.push_back(L
'X');
673 x_nickname.insert(0, 1, L
'X');
676 x_nickname.push_back(L
'X');
677 x_nickname.insert(0, 1, L
'X');
684 static std::wstring reverse(
const std::wstring& nickname,
685 [[maybe_unused]] core::rng& engine)
687 std::wstring reverse_nickname{nickname};
688 std::ranges::reverse(reverse_nickname);
689 return reverse_nickname;
694 static std::wstring yfy(
const std::wstring& nickname,
695 [[maybe_unused]] core::rng& engine)
697 std::wstring nicknamy{nickname};
698 if (_vowels.contains(nickname.back()))
700 nicknamy.back() = L
'y';
704 nicknamy.push_back(L
'y');
710 static std::wstring numify(
const std::wstring& nickname, core::rng& engine)
712 std::wstring nickname_with_number{nickname};
713 static constexpr int max_digit{9};
714 nickname_with_number.push_back(
static_cast<wchar_t>(L
'0' + core::uniform_int(engine, 1, max_digit)));
715 for (
auto i = core::uniform_int(engine, 0, 3); i > 0; i--)
717 nickname_with_number.push_back(L
'0');
719 return nickname_with_number;
723 static std::wstring tracefy(
const std::wstring& nickname,
724 [[maybe_unused]] core::rng& engine)
726 return nickname + L
"-";
731 static std::wstring ingify(
const std::wstring& nickname,
732 [[maybe_unused]] core::rng& engine)
734 std::wstring nicknaming{nickname};
735 if (_vowels.contains(nickname.back()))
737 nicknaming.back() = L
'i';
741 nicknaming.push_back(L
'i');
743 nicknaming.append(L
"ng");
749 static std::wstring duovowel(
const std::wstring& nickname,
750 [[maybe_unused]] core::rng& engine)
752 static constexpr std::wstring_view simple_vowels = L
"aeio";
753 std::wstring nicknamee{nickname};
754 for (
const auto& vowel : simple_vowels)
756 if (
auto it = std::ranges::find(nicknamee, vowel); it != nicknamee.end())
758 nicknamee.insert(it, *it);
766 static std::vector<std::size_t> leet_positions_(
const std::wstring& text)
768 std::vector<std::size_t> positions;
769 for (std::size_t i = 0; i < text.size(); ++i)
771 if (_leet_map.contains(text[i])) positions.push_back(i);
777 static std::wstring oneleet(
const std::wstring& nickname, core::rng& engine)
779 std::wstring leet_nickname{nickname};
780 const auto candidates = leet_positions_(leet_nickname);
781 if (!candidates.empty())
783 const auto pos = core::pick(engine, candidates);
784 leet_nickname[pos] = _leet_map.at(leet_nickname[pos]);
786 return leet_nickname;
791 static std::wstring allleet(
const std::wstring& nickname, core::rng& engine)
793 std::wstring leet_nickname{nickname};
794 static constexpr std::size_t max_replacements{2};
795 auto candidates = leet_positions_(leet_nickname);
796 core::shuffle(engine, candidates);
797 for (std::size_t n = 0; n < std::min(max_replacements, candidates.size()); ++n)
799 const auto pos = candidates[n];
800 leet_nickname[pos] = _leet_map.at(leet_nickname[pos]);
802 return leet_nickname;
807 static nickname leetify(nickname nickname, core::rng& engine,
bool force =
false,
812 static constexpr int max_depth{7};
814 if (force || core::bernoulli(engine, 0.5))
818 if (core::bernoulli(engine, 0.5))
820 static const generators possible_generators{
826 auto before = nickname._internal_string;
827 nickname._internal_string =
828 core::pick(engine, possible_generators)(nickname, engine);
831 if (depth < max_depth)
833 return leetify(nickname, engine, nickname._internal_string == before,
839 static const generators possible_generators{
847 nickname._internal_string =
848 core::pick(engine, possible_generators)(nickname, engine);
857 static std::wstring snake_case(
const std::wstring& name)
860 result.reserve(name.size() + 4);
861 for (std::size_t i = 0; i < name.size(); ++i)
863 if (i > 0 && is_upper_(name[i])) result.push_back(L
'_');
864 result.push_back(name[i]);
870 static std::wstring upper_case(
const std::wstring& name,
871 [[maybe_unused]] core::rng& engine)
873 std::wstring upper_name{name};
874 std::ranges::for_each(upper_name, [](
wchar_t& character) { character = to_upper_(character); });
879 static std::wstring lower_case(
const std::wstring& name,
880 [[maybe_unused]] core::rng& engine)
882 std::wstring lower_name{name};
883 std::ranges::for_each(lower_name, [](
wchar_t& character) { character = to_lower_(character); });
888 static std::wstring title_case(
const std::wstring& name,
889 [[maybe_unused]] core::rng& engine)
896 static std::wstring sentence_case(
const std::wstring& name, core::rng& engine)
898 if (name.empty())
return name;
899 std::wstring sentence_name{lower_case(name, engine)};
900 sentence_name.at(0) = to_upper_(sentence_name.at(0));
901 return sentence_name;
905 static std::wstring camel_case(
const std::wstring& name,
906 [[maybe_unused]] core::rng& engine)
908 if (name.empty())
return name;
909 std::wstring camel_name{name};
910 camel_name.at(0) = to_lower_(camel_name.at(0));
915 static std::wstring reverse_sentence_case(
const std::wstring& name, core::rng& engine)
917 if (name.empty())
return name;
918 std::wstring rsentence_name{lower_case(name, engine)};
919 rsentence_name.back() = to_upper_(rsentence_name.back());
920 return rsentence_name;
924 static std::wstring bathtub_case(
const std::wstring& name, core::rng& engine)
926 if (name.empty())
return name;
927 std::wstring bathtub_name{lower_case(name, engine)};
928 bathtub_name.at(0) = to_upper_(bathtub_name.at(0));
929 bathtub_name.back() = to_upper_(bathtub_name.back());
934 static std::wstring winding_case(
const std::wstring& name, core::rng& engine)
936 std::wstring winding_name{lower_case(name, engine)};
937 for (std::size_t i = 0; i < winding_name.size(); i += 2)
939 winding_name[i] = to_upper_(winding_name[i]);
945 static std::wstring random_case(
const std::wstring& name, core::rng& engine)
947 std::wstring random_name{lower_case(name, engine)};
948 std::ranges::for_each(random_name, [&engine](
wchar_t& character) {
949 if (core::bernoulli(engine, 0.5)) character = to_upper_(character);
956 static std::wstring random_single_case(
const std::wstring& name, core::rng& engine)
958 std::wstring random_name{lower_case(name, engine)};
959 if (random_name.empty())
return random_name;
960 auto& random_char = core::pick(engine, random_name);
961 random_char = to_upper_(random_char);
966 static nickname format(nickname nickname, core::rng& engine)
969 static constexpr double snake_case_probability{0.01};
970 if (core::bernoulli(engine, snake_case_probability))
972 nickname._internal_string = snake_case(nickname);
975 static const generators possible_generators = {
997 reverse_sentence_case,
998 reverse_sentence_case,
1006 nickname._internal_string = core::pick(engine, possible_generators)(nickname, engine);
1014 [[nodiscard]]
static std::vector<std::wstring> split_name(
const std::wstring& name)
1016 std::vector<std::wstring> parts;
1017 std::wstring current;
1018 for (
const wchar_t ch : name)
1022 if (!current.empty()) parts.push_back(std::move(current));
1027 current.push_back(ch);
1030 if (!current.empty()) parts.push_back(std::move(current));
1035 static std::wstring first_name(
const std::wstring& name,
1036 [[maybe_unused]] core::rng& engine)
1038 return split_name(name).front();
1042 static std::wstring last_name(
const std::wstring& name,
1043 [[maybe_unused]] core::rng& engine)
1045 return split_name(name).back();
1049 static std::wstring any_name(
const std::wstring& name, core::rng& engine)
1051 const auto names_list = split_name(name);
1052 return core::pick(engine, names_list);
1056 static std::wstring initials(
const std::wstring& name,
1057 [[maybe_unused]] core::rng& engine)
1059 std::wstring nickname;
1060 for (
const auto& part : split_name(name))
1062 nickname.push_back(part.front());
1068 static std::wstring mix_two(
const std::wstring& name, core::rng& engine)
1070 std::wstring nickname;
1071 auto names_list{split_name(name)};
1072 if (names_list.size() > 2)
1074 names_list.erase(names_list.begin(), names_list.end() - 2);
1076 for (
const auto& part : names_list)
1078 if (part.size() < 2)
1080 nickname.append(part);
1084 nickname.append(part.substr(0, core::uniform_int<std::size_t>(engine, 2, part.size())));
1091 static std::wstring first_plus_initial(
const std::wstring& name,
1092 [[maybe_unused]] core::rng& engine)
1094 const auto names_list{split_name(name)};
1095 if (names_list.size() < 2)
return names_list.front();
1096 return names_list.front() + names_list.back().front();
1100 static std::wstring initial_plus_last(
const std::wstring& name,
1101 [[maybe_unused]] core::rng& engine)
1103 const auto names_list{split_name(name)};
1104 if (names_list.size() < 2)
return names_list.front();
1105 return names_list.front().front() + names_list.back();
1109 static std::wstring reduce_single_name(
const std::wstring& name, core::rng& engine)
1111 std::wstring single_name{any_name(name, engine)};
1112 if (single_name.size() > 3)
1116 single_name.erase(std::remove_if(single_name.begin() + 1, single_name.end() - 1,
1117 [&single_name](
const wchar_t& character) {
1118 return (_vowels.contains(character) &&
1119 (single_name.size() > 3));
1121 single_name.end() - 1);
1128 static constexpr std::size_t username_max_length_ = 24;
1132 [[nodiscard]]
static std::wstring fold_ascii_(
const std::wstring& text)
1136 static constexpr std::string_view extended_a =
1137 "aaaaaacc" "ccccccdd" "ddeeeeee" "eeeegggg" "gggghhhh" "iiiiiiii" "iiiijjkk" "klllllll"
1138 "lllnnnnn" "nnnnoooo" "oooorrrr" "rrssssss" "sstttttt" "uuuuuuuu" "uuuuwwyy" "yzzzzzzs";
1139 std::wstring folded;
1140 for (
const wchar_t original : text)
1142 const wchar_t c = to_lower_(original);
1143 const auto u =
static_cast<std::uint32_t
>(c);
1144 if ((u >= 0x61U && u <= 0x7AU) || (u >= 0x30U && u <= 0x39U))
1146 folded.push_back(c);
1148 else if (u >= 0xE0U && u <= 0xFFU)
1150 if (u <= 0xE5U) folded.push_back(L
'a');
1151 else if (u == 0xE6U) folded.append(L
"ae");
1152 else if (u == 0xE7U) folded.push_back(L
'c');
1153 else if (u <= 0xEBU) folded.push_back(L
'e');
1154 else if (u <= 0xEFU) folded.push_back(L
'i');
1155 else if (u == 0xF0U) folded.push_back(L
'd');
1156 else if (u == 0xF1U) folded.push_back(L
'n');
1157 else if (u <= 0xF6U || u == 0xF8U) folded.push_back(L
'o');
1158 else if (u >= 0xF9U && u <= 0xFCU) folded.push_back(L
'u');
1159 else if (u == 0xFDU || u == 0xFFU) folded.push_back(L
'y');
1160 else if (u == 0xFEU) folded.append(L
"th");
1162 else if (u == 0xDFU)
1164 folded.append(L
"ss");
1166 else if (u >= 0x100U && u <= 0x17FU)
1168 if (u == 0x132U || u == 0x133U) folded.append(L
"ij");
1169 else if (u == 0x152U || u == 0x153U) folded.append(L
"oe");
1170 else folded.push_back(
static_cast<wchar_t>(extended_a[u - 0x100U]));
1177 [[nodiscard]]
static std::vector<std::wstring> username_parts_(
const std::wstring& name)
1179 std::vector<std::wstring> parts;
1180 for (
const auto& part : split_name(name))
1182 if (
auto folded = fold_ascii_(part); !folded.empty()) parts.push_back(std::move(folded));
1188 [[nodiscard]] std::wstring username_word_(
const std::vector<
const std::vector<std::wstring>*>& themes,
1189 bool allow_theme, core::rng& engine)
const
1191 static constexpr double theme_probability{1.0 / 3.0};
1192 static constexpr std::size_t preferred_length = 8;
1193 static constexpr int attempts = 8;
1195 for (
int attempt = 0; attempt < attempts; ++attempt)
1197 const std::vector<std::wstring>* list =
nullptr;
1198 if (allow_theme && !themes.empty() &&
1199 (_wordlists.empty() || core::bernoulli(engine, theme_probability)))
1201 list = core::pick(engine, themes);
1203 else if (!_wordlists.empty())
1205 list = &_wordlists[_list_choice(engine)];
1209 throw std::invalid_argument(
"Received no name and word lists are empty");
1211 word = fold_ascii_(core::pick(engine, *list));
1212 if (!word.empty() && word.size() <= preferred_length)
return word;
1214 return word.empty() ? L
"user" : word;
1218 template <std::
size_t N>
1219 [[nodiscard]]
static std::size_t weighted_index_(core::rng& engine,
const std::array<int, N>& weights)
1221 auto r = core::uniform_int(engine, 0, 99);
1222 for (std::size_t i = 0; i < N; ++i)
1224 if (r < weights[i])
return i;
1231 [[nodiscard]]
static std::wstring year_digits_(
int year,
bool two)
1233 if (!two)
return std::to_wstring(year);
1234 const int yy = year % 100;
1235 return (yy < 10 ? L
"0" : L
"") + std::to_wstring(yy);
1240 [[nodiscard]]
static std::wstring username_digits_(
const std::optional<int>& birth_year,
1243 static constexpr std::array<std::wstring_view, 9> lucky{L
"7", L
"13", L
"21", L
"23", L
"42",
1244 L
"77", L
"88", L
"99", L
"100"};
1245 static constexpr std::array<std::wstring_view, 8> patterns{L
"01", L
"007", L
"123", L
"1234",
1246 L
"11", L
"12", L
"00", L
"10"};
1247 const bool known = birth_year && *birth_year >= 1900 && *birth_year <= 2100;
1249 static constexpr std::array<int, 7> unknown_weights{40, 20, 4, 14, 6, 6, 10};
1250 static constexpr std::array<int, 7> known_weights{30, 28, 14, 8, 5, 5, 10};
1251 std::wstring digits;
1252 switch (weighted_index_(engine, known ? known_weights : unknown_weights))
1257 digits = year_digits_(known ? *birth_year : 1970 + core::uniform_int(engine, 0, 39), true);
1260 digits = year_digits_(known ? *birth_year : core::uniform_int(engine, 1970, 2008), false);
1263 digits = std::to_wstring(core::uniform_int(engine, 1, 99));
1266 digits = std::wstring{core::pick(engine, lucky)};
1269 digits = std::wstring{core::pick(engine, patterns)};
1272 digits = std::to_wstring(core::uniform_int(engine, 100, 9999));
1275 if (core::bernoulli(engine, 0.12)) digits.insert(0, 1, L
'_');
1280 [[nodiscard]]
static bool is_particle_(
const std::wstring& part)
1282 static const std::array<std::wstring_view, 22> particles{
1283 L
"da", L
"das", L
"de", L
"del", L
"della", L
"di", L
"do", L
"dos", L
"du", L
"la", L
"le",
1284 L
"van", L
"von", L
"der", L
"den", L
"ten", L
"ter", L
"bin", L
"ibn", L
"al", L
"el", L
"y"};
1285 return std::ranges::find(particles, part) != particles.end();
1289 [[nodiscard]]
static std::wstring username_from_name_(
const std::vector<std::wstring>& parts,
1293 std::vector<std::wstring> names;
1294 for (std::size_t i = 0; i < parts.size(); ++i)
1296 const bool inner = i > 0 && i + 1 < parts.size();
1297 if (!(inner && is_particle_(parts[i]))) names.push_back(parts[i]);
1299 const std::wstring& given = names.front();
1300 if (names.size() == 1)
return given;
1304 std::wstring family = names.back();
1305 std::wstring middle;
1306 if (names.size() >= 3)
1308 const bool middle_name = core::bernoulli(engine, 0.5);
1313 else if (core::bernoulli(engine, 0.4))
1315 family = names[names.size() - 2];
1318 const std::wstring g = given.substr(0, 1);
1319 const std::wstring f = family.substr(0, 1);
1321 if (!middle.empty() && core::bernoulli(engine, 0.4))
1323 const std::wstring m = middle.substr(0, 1);
1324 switch (core::uniform_int(engine, 0, 5))
1326 case 0:
return given + L
"." + m + L
"." + family;
1327 case 1:
return given + m + family;
1328 case 2:
return g + m + family;
1329 case 3:
return given + L
"_" + m + L
"_" + family;
1330 case 4:
return g + m + L
"." + family;
1331 default:
return given + middle;
1337 static constexpr std::array<int, 10> weights{22, 17, 9, 14, 5, 5, 6, 8, 5, 9};
1338 switch (weighted_index_(engine, weights))
1340 case 0:
return given + L
"." + family;
1341 case 1:
return given + family;
1342 case 2:
return given + L
"_" + family;
1343 case 3:
return g + family;
1344 case 4:
return g + L
"." + family;
1345 case 5:
return given + L
"." + f;
1346 case 6:
return given + f;
1347 case 7:
return family + L
"." + given;
1348 case 8:
return family + L
"_" + given;
1349 default:
return family + g;
1354 static void username_affix_(std::wstring& base, core::rng& engine)
1356 static constexpr std::array<std::wstring_view, 6> prefixes{L
"the", L
"real", L
"its", L
"iam", L
"hey", L
"just"};
1357 static constexpr std::array<std::wstring_view, 6> suffixes{L
"official", L
"real", L
"hq", L
"irl", L
"online", L
"hi"};
1358 if (!core::bernoulli(engine, 1.0 / 13.0))
return;
1359 if (core::bernoulli(engine, 0.5))
1361 const std::wstring prefix{core::pick(engine, prefixes)};
1362 base = prefix + (core::bernoulli(engine, 0.3) ? L
"_" : L
"") + base;
1366 const std::wstring suffix{core::pick(engine, suffixes)};
1367 base += (core::bernoulli(engine, 0.6) ? L
"_" : L
"") + suffix;
1372 [[nodiscard]] nickname username_(
const nickname_request& request, core::rng& engine)
const
1374 std::wstring original;
1376 if (
const auto parts = username_parts_(request.name); !parts.empty())
1378 original = request.name;
1379 base = username_from_name_(parts, engine);
1383 const auto themes = themes_of_(request.tags);
1384 std::wstring first = username_word_(themes,
true, engine);
1387 if (core::bernoulli(engine, 0.5))
1389 const std::wstring second = username_word_(themes,
false, engine);
1390 original += L
" " + second;
1391 switch (core::uniform_int(engine, 0, 4))
1393 case 0:
case 1: base += second;
break;
1394 case 2:
case 3: base += L
"_" + second;
break;
1395 default: base += L
"." + second;
break;
1399 username_affix_(base, engine);
1401 const std::wstring digits = username_digits_(request.birth_year, engine);
1402 if (base.size() + digits.size() > username_max_length_)
1404 base.resize(username_max_length_ - std::min(digits.size(), username_max_length_ - 1));
1405 while (base.size() > 1 && (base.back() == L
'.' || base.back() == L
'_')) base.pop_back();
1407 return {base + digits, original};
1412 [[nodiscard]] std::vector<const std::vector<std::wstring>*> themes_of_(
1413 const std::vector<std::string>& tags)
const
1415 std::vector<const std::vector<std::wstring>*> themes;
1416 for (
const auto& tag : tags)
1418 const auto it = _themes.find(tag);
1419 if (it == _themes.end())
continue;
1420 if (std::ranges::find(themes, &it->second) == themes.end()) themes.push_back(&it->second);
1427 [[nodiscard]] nickname solver(
const nickname_request& request, core::rng& engine)
const
1429 if (request.style == nickname_style::username)
return username_(request, engine);
1430 const std::wstring& name = request.name;
1431 std::wstring original;
1434 static constexpr double name_related_probability{0.25};
1437 static constexpr double theme_probability{1.0 / 3.0};
1439 const bool has_name = !split_name(name).empty();
1440 const bool use_name =
1441 has_name && (_wordlists.empty() || core::bernoulli(engine, name_related_probability));
1446 static const generators possible_generators{
1457 nick = core::pick(engine, possible_generators)(name, engine);
1459 else if (
const auto themes = themes_of_(request.tags);
1460 !themes.empty() && (_wordlists.empty() || core::bernoulli(engine, theme_probability)))
1462 const auto* drawn_theme = core::pick(engine, themes);
1463 nick = original = core::pick(engine, *drawn_theme);
1465 else if (!_wordlists.empty())
1467 const auto& drawn_wordlist = _wordlists[_list_choice(engine)];
1468 nick = original = core::pick(engine, drawn_wordlist);
1472 throw std::invalid_argument(
"Received no name and word lists are empty");
1474 return format(leetify({nick, original}, engine), engine);
1477 friend struct ::nng_test_access;