Боты, которые говорят по-разному
Каждая реплика завсегдатаев была одной из шести фраз. Теперь их сотни, у каждого персонажа своя манера, и ничего не повторяется, пока предыдущее ещё на экране.

Простыми словами
Через два дня после появления завсегдатаев в лобби претензия стала очевидной, стоило присмотреться: они все говорят одно и то же. Один за вечер написал «quick game anyone», «lets play», «quick game anyone», «need one more», «need one more». Другой трижды за полтора часа сказал «anyone up for a game».
База данных не оставила сомнений: каждая реплика любого из них была одной из шести фраз. Внутри партии то же самое — один никнейм девять раз написал «had to have it». Был и третий признак, о котором никто не сообщал: все двенадцать писали ровно в одной манере — строчными, обрывочно, без знаков препинания, — тогда как люди в той же комнате набирали «Hi guys!», «Someone wanne play?» и «Let’s goooooooooo!».
Теперь у каждого завсегдатая своя манера речи, выведенная из того, кто он, а не выбираемая заново при каждой реплике. По всему составу это значит:
- кто-то пишет строчными, кто-то — нормальными предложениями, и это не меняется;
- кто-то щедр на знаки препинания, кто-то не ставит их вовсе;
- у каждого два-три эмодзи, которыми он действительно пользуется, вместо общего набора;
- кто-то растягивает слова, кто-то сокращает, кто-то иногда делает опечатки;
- сказанное соответствует комнате: никто не просит «ещё одного», когда свободны четыре места.
Пишут они везде только по-английски — то же решение, которому подчинено и остальное поведение ботов в игре.
Для тех, кому интересны детали
Корпус реплик организован по намерениям — поздороваться, искать партию, попросить ещё одного, только что зайти, отметить режим, ждать, пошутить, подбодрить, позвать на реванш, — и два из этих намерений заполняются данными живой комнаты, поэтому фраза не может противоречить тому, что игрок видит перед собой.
Манера речи персонажа выводится из его никнейма, а не хранится: новое поле в типе персонажа не нужно, и после перезапуска процесса она остаётся прежней:
// botChat.voice.ts — the voice is a pure function of the nickname
const rng = createRng(`voice:${persona.nickname}`);
return {
sentenceCase: rng.float() < 0.34,
punctuation: pick(rng, ['none', 'light', 'heavy']),
emoji: pickN(rng, EMOJI_POOL, rng.int(2, 3)), // habitual, not shared
elongation: rng.float() * 0.2,
typoRate: rng.float() * 0.06,
};За повторы отвечает небольшая память, областью действия которой служат одновременно комната и персонаж: одна и та же фраза не вернётся, пока она ещё на экране, а два персонажа в одной комнате не могут повторить друг друга.
Самой интересной оказалась ошибка в преобразовании опечаток. Чат ботов пишется с сервисной ролью, которая проходит мимо фильтра нецензурной лексики, обязательного для человеческих сообщений, — поэтому «реалистичная» опечатка могла собрать заблокированное слово («and», набранное как «nad», есть в списке). Тест на 2600 выборок спокойно прошёл; чтобы проблема проявилась, потребовалось около 68 000 розыгрышей. Теперь тест — исчерпывающий перебор около 260 000 возможных строк за 1,4 секунды, и он вызывает те же генераторы, что и слой чата, а не переписывает их: первая версия переписала и тут же разошлась с реализацией.
