diff --git a/backend/internal/robot/namevariety.go b/backend/internal/robot/namevariety.go index be0dade..53e831a 100644 --- a/backend/internal/robot/namevariety.go +++ b/backend/internal/robot/namevariety.go @@ -122,23 +122,28 @@ func chineseName() string { return family + given } -// latinNick builds a Latin handle from the adjective and noun stem pools. +// latinNick builds a Latin handle from the adjective and noun stem pools. English +// adjectives do not inflect, so any pairing reads naturally. func latinNick() string { - return composeNick(adjLatin, nounLatin) + return assembleHandle(adjLatin[rand.IntN(len(adjLatin))], nounLatin[rand.IntN(len(nounLatin))]) } -// cyrillicNick builds a Cyrillic handle from the adjective and noun stem pools. +// cyrillicNick builds a Cyrillic handle, agreeing the adjective's form with the noun's +// grammatical gender ("Дикий Волк", "Дикая Рысь"). func cyrillicNick() string { - return composeNick(adjCyr, nounCyr) + noun := nounCyr[rand.IntN(len(nounCyr))] + adj := adjCyr[rand.IntN(len(adjCyr))] + form := adj.m + if noun.female { + form = adj.f + } + return assembleHandle(form, noun.word) } -// composeNick assembles a handle from an adjective and noun pool: a bare noun, an -// adjective and noun joined by "_" or "." or camel-cased, optionally followed by a -// trailing number. It keeps at most one separator so the result, the number aside, -// reads like a name a human could pick. -func composeNick(adjectives, nouns []string) string { - adj := adjectives[rand.IntN(len(adjectives))] - noun := nouns[rand.IntN(len(nouns))] +// assembleHandle joins an adjective and noun into a handle: a bare noun, the two joined +// by "_" or "." or camel-cased, optionally followed by a trailing number. It keeps at +// most one separator so the result, the number aside, reads like a name a human could pick. +func assembleHandle(adj, noun string) string { var base string switch rand.IntN(4) { case 0: @@ -242,17 +247,37 @@ var nounLatin = []string{ "Phantom", "Comet", "Nova", "Blade", "Arrow", "Storm", "Wizard", "Ninja", } -// adjCyr and nounCyr are the adjective and noun stems for Cyrillic handles. -var adjCyr = []string{ - "Тёмный", "Ночной", "Снежный", "Лунный", "Звёздный", "Огненный", "Ледяной", "Стальной", "Дикий", "Тихий", - "Быстрый", "Гордый", "Грозный", "Хитрый", "Смелый", "Весёлый", "Рыжий", "Серый", "Золотой", "Дерзкий", - "Лютый", "Бравый", "Мудрый", "Шальной", "Вольный", "Яростный", "Седой", "Жгучий", +// cyrAdjective is a Russian adjective in its masculine and feminine forms, so a handle's +// stem can agree with the gender of its noun. +type cyrAdjective struct{ m, f string } + +// cyrNoun is a Russian noun for a handle, tagged feminine so the paired adjective agrees. +type cyrNoun struct { + word string + female bool } -var nounCyr = []string{ - "Волк", "Лис", "Ворон", "Тигр", "Орёл", "Дракон", "Сокол", "Ястреб", "Лев", "Медведь", - "Барс", "Кот", "Кит", "Странник", "Охотник", "Рыцарь", "Маг", "Призрак", "Воин", "Комета", - "Клинок", "Ветер", "Гром", "Шторм", "Пилот", "Капитан", "Мастер", "Енот", +// adjCyr and nounCyr are the adjective and noun stems for Cyrillic handles; cyrillicNick +// renders the adjective in the form that agrees with the chosen noun's gender. +var adjCyr = []cyrAdjective{ + {"Тёмный", "Тёмная"}, {"Ночной", "Ночная"}, {"Снежный", "Снежная"}, {"Лунный", "Лунная"}, + {"Звёздный", "Звёздная"}, {"Огненный", "Огненная"}, {"Ледяной", "Ледяная"}, {"Стальной", "Стальная"}, + {"Дикий", "Дикая"}, {"Тихий", "Тихая"}, {"Быстрый", "Быстрая"}, {"Гордый", "Гордая"}, + {"Грозный", "Грозная"}, {"Хитрый", "Хитрая"}, {"Смелый", "Смелая"}, {"Весёлый", "Весёлая"}, + {"Рыжий", "Рыжая"}, {"Серый", "Серая"}, {"Золотой", "Золотая"}, {"Дерзкий", "Дерзкая"}, + {"Лютый", "Лютая"}, {"Бравый", "Бравая"}, {"Мудрый", "Мудрая"}, {"Шальной", "Шальная"}, + {"Вольный", "Вольная"}, {"Яростный", "Яростная"}, {"Седой", "Седая"}, {"Жгучий", "Жгучая"}, +} + +var nounCyr = []cyrNoun{ + {"Волк", false}, {"Лис", false}, {"Ворон", false}, {"Тигр", false}, {"Орёл", false}, + {"Дракон", false}, {"Сокол", false}, {"Ястреб", false}, {"Лев", false}, {"Медведь", false}, + {"Барс", false}, {"Кот", false}, {"Кит", false}, {"Странник", false}, {"Охотник", false}, + {"Рыцарь", false}, {"Маг", false}, {"Призрак", false}, {"Воин", false}, {"Клинок", false}, + {"Ветер", false}, {"Гром", false}, {"Шторм", false}, {"Пилот", false}, {"Капитан", false}, + {"Мастер", false}, {"Енот", false}, + {"Комета", true}, {"Звезда", true}, {"Молния", true}, {"Пантера", true}, {"Рысь", true}, + {"Буря", true}, {"Сова", true}, {"Акула", true}, } // firstNamesRUWide is a broad pool of Russian first names — full and colloquial forms