Зарождение идеи
Еще в бытность джунни, я наткнулся на милейшую интерактивную обучалку по селекторам CSS: CSS Diner. Кто не знает — обязательно посмотрите, эстетический кайф гарантирован. Прошел ее минут за 15 на одном дыхании и подумал: а почему бы не сделать что‑то подобное с аналогичной механикой «допиши правильное слово в пропуск в бойлерплейте», только по другой теме? Сходу на ум ничего не пришло, но идею я запомнил.
Три года спустя я решил изучить React, написав на нем какой‑нибудь пет‑проект (имхо, лучший способ изучить новую технологию/язык). Тут‑то я и вспомнил про CSS Diner. Осталось только найти подходящую тему, где эта механика бы зашла.
Примерно в то же время на работе я делал рефакторинг большого проекта на Vue и мне нужно было найти все использования компонента с определенными пропсами и заменить в них пропсы, но так, чтобы другие использования компонента остались без изменений. Мне было лень делать это вручную и к тому же это было бы не спортивно и задело бы мою честь разработчика, поэтому я придумал регулярку для поиска и замены нужных мне вхождений.
Та самая регулярка
/(<TextField\b(?:[\s\S&&[^</>]]*?))(v-bind="attrs.*")((?:[\s\S&&[^</>]]*?)(?:</|/>))/gi
И тут меня осенило: регулярки — идеальный материал для аналога CSS Diner:
можно сделать уровни, где, чтобы пройти уровень, нужно ввести правильную регулярку, а в качестве бойлерплейта — код с методами регулярных выражений, вроде
match()
,replace()
тема конечна и относительно небольшая: я прикинул, что основу по регуляркам можно разбить тем на 30
по регуляркам еще нет игровых обучалок (а это, имхо, лучший способ изучить конкретную нишевую тему), есть только статьи и курсы
Шаг 1. Сеттинг и игровая механика
Я стал думать над сеттингом и механикой. Если вы еще не посмотрели CSS Diner, там игровое поле — это котацу на котором разложена еда: огурцы, яблоки, апельсины. Иногда еда лежат прямо на столе, а иногда — на бенто или на тарелках — на каждом уровне своя сервировка.
Сервировка стола кодируется в виде HTML, где каждому элементу на столе соответствует свой HTML‑элемент. Если на столе лежат бенто, тарелка с яблоком и просто яблоко, это будет закодировано так:
<div class="table">
<bento />
<plate>
<apple />
</plate>
<apple />
</div>
Чтобы пройти уровень, вам нужно ввести такой CSS‑селектор, чтобы выбрать им на столе загаданный объект, например, третью по счету тарелку.

Если для CSS кодировка уровня — это HTML, то для регулярок, понятно, это должен быть текст. Если в CSS Diner ты с помощью CSS‑селектора выбирал нужный объект на столе, то регуляркой можно выбирать из массива строк те строки, которые содержат нужный паттерн.
Осталось придумать такой сеттинг, чтобы текстовое представление уровня было интересно с точки зрения изучения регулярных выражений:
строки массива должны перекликаться между собой
содержать повторяющиеся паттерны
желательно — в разных частях слова, чтобы можно было их различать с помощью
^
,$
,.*
желательно — с различием в один символ, чтобы можно было применить
?
содержать цифры, чтобы можно было их отфильтровывать по
\d
и\D
Я вспомнил, что в названиях растений на английском есть подобная коллизия вокруг «розы»: rose, rosemary, primrose. А цифры можно обыграть как сорта культур. Я понял, что тематика растений — это именно то, что мне нужно.
У меня в голове выкристаллизовалась следующая механика и сеттинг: каждый уровень — это сад. Модель уровня — массив названий растений в саду. Цель уровня — полить, удобрить или срезать определенные растения в саду. Чтобы пройти уровень, нужно ввести регулярку, которая отфильтрует из массива названий растений нужные.
А для групп захвата и проверок я решил сделать представление сада не массивом названий растений, а одной строкой с названиями, разделенными табуляцией, в которой нужно сделать замену паттерна на заданный текст.
Шаг 2. Учебный план и дизайн уровней
Я накидал список тем, расположил их по возрастанию сложности и от частных к общим, и принялся придумывать для каждого уровня задания. ChatGPT мне помог подобрать названия растений, хотя пришлось его сильно помучить.
Как ChatGPT помогал мне придумать названия растений для игры
(Прим.: здесь я использовал технику прайминга: сначала даем ChatGPT простое абстрактное задание, не связанное с нашим основным заданием, но погружающее в контекст. А потом уже просим сделать что нужно. Говорят, такая техника дает хороший результат для творческих заданий)
Я: Привет. Ты — восторженный ученый‑ботаник, весь смысл жизни которого — растения. Ты горишь ботаникой. Вся твоя речь пронизана метафорами и отсылками к ботанике. Ты пишешь письмо своему коллеге о новом найденном тобой цветке из Южной Америки. (напиши мне такое письмо)
ChatGPT: Приветствую, уважаемый коллега и единомышленник в мире растительных чудес!
С радостью и волнением, подобным первому ростку, пробивающемуся сквозь землю весной, пишу тебе о моем последнем открытии. Представь себе, я обнаружил новый вид цветка в глубинах тропических лесов Южной Америки, месте, где природа еще хранит свои самые завораживающие тайны, подобно непроницаемому лиственному покрову.
Этот цветок — настоящее воплощение флористического совершенства. Он имеет удивительно насыщенный и яркий окрас, словно солнечный свет, играющий на каплях утренней росы. Лепестки его нежны и тонки, словно крылья бабочки, а узоры на них напоминают изящные узоры, которые пишет мороз по стеклу. Его аромат — сложная симфония запахов, будто он собрал в себе все самые прекрасные ароматы тропического леса.
Но самое удивительное — это его способность адаптироваться к различным условиям. Он словно маленький ботанический воин, который с грацией и силой противостоит испытаниям природы. Я убежден, что этот цветок откроет нам новые горизонты в понимании растительной адаптации и выживания.
Я уже приступил к детальному изучению этого удивительного растения и планирую опубликовать свои открытия в ближайшее время. Надеюсь, что при следующем нашем встречном рейсе через ботанические миры мы сможем вместе вдохновляться этим чудом природы.
С трепетом и уважением к каждому листку и корешку,
[Твоё Имя], страстный исследователь ботанических тайн.
(Прим.: далее я использую еще одну технику: прошу ChatGPT каждый раз перед ответом вспоминать свою роль — читал где‑то в Телеграмме, что это улучшает результат)
Я: Спасибо. Продолжай играть эту роль ботаника. Каждый раз перед ответом, вспоминай свою роль. Мне нужна твоя помощь, как первоклассного ботаника. Я делаю игру на внимательность, где игрокам нужно будет искать отличия среди похожих названий. Сеттинг игры — растения. Мне нужно придумать как можно больше максимально похожих друг на друга названий растений (это могут быть как таксономические названия на латинице, так и обиходные, на английском языке. Растения могут быть разных видов/семейств/быть вообще никак не связанными между собой: главное — схожесть написания, чем она больше, тем лучше), чтобы игра была как можно сложнее и интереснее. Примеры того, что я ищу (разумеется, вымышленные, абстрактные и совершенно бестолковые, потому как я к своему стыду не имею ни малейшего отношения к ботанике):
1. Rose Foo, Rose Foot, Rose Fool — если бы удалось придумать что‑то вроде такого — это было бы просто замечательно!
2. Rose Foo, Rosed Foo, Roser Foo — еще один пример, что было бы идеально!
3. Rose Foo, Rose Bar, Rose Kar — не так идеально, но вполне приемлемо
Дай мне пожалуйста топ-10 рядов похожих по написанию названий. В каждом ряду может быть как угодно много наименований, но не меньше двух — главное: без повторений и чтобы все были похожи друг на друга. И еще. Все наименования во всех рядах должны быть уникальны. Моя просьба понятна?
Полный диалог доступен здесь
Среди многочисленных галлюцинаций, мне удалось раскопать пару бриллиантов:
Пара Rye и Rue отлично подошла на тему
.
Barberry и Bearberry наглядно показывают применение
?
Heliopsis helianthoides — то что нужно для рекуррентных групп (про иланг‑иланг я сообразил уже гораздо позже. Странно, что мне его не предложил ChatGPT на мой запрос найти название цветка с повторяющейся последовательностью)
В совокупности с уже известными мне rose, rosemary, primrose, этого было более чем достаточно. К концу вечера у меня была готова таблица с садами для каждого уровня.
Таблица с уровнями для игры

Не знаю как оценить пользу от ИИ в этом конкретном примере в часах или деньгах, но я даже не знаю какие другие названия растений могли бы лучше подойти для иллюстрации этих тем.
Шаг 3. Генерация графики
Самую большую пользу от ИИ я ощутил, когда генерировал картинки растений в Midjourney. Всего за один вечер и $10 (стоимость платной подписки Midjourney) я сгенерировал картинки для всех используемых растений, а потом в фотошопе превратил их в png с прозрачным фоном (наверное, это тоже можно было сделать средствами нейронок, но показалось, что в моем случае вручную будет быстрее). Если бы не Midjourney, мне бы пришлось нанимать иллюстратора для отрисовки картинок и это заняло бы гораздо больше времени и денег.
Промпт для Midjourney v6 и его результаты
A very simple flat style image of a single rosemary plant with solid bright colors, presented in a minimalistic design. The rosemary plant should be viewed frontally with a slight angle with no additional details, set against a transparent background. no pot, stem from nowhere
И вот что получилось:




Для других растений я использовал этот же промпт, заменяя rosemary на нужное название
Не будет преувеличением оценка, что Midjourney сэкономил мне недели времени, тысячи рублей бюджета и сотни тысяч нервных клеток.
Шаг 4. Дизайн
Дизайн решает. Если бы у CSS Diner был дизайн в стиле сайтов *.narod.ru, думаю я бы о нем никогда не узнал. Поэтому я решил не экономить и заплатить хорошему дизайнеру, а не лепить самопал. Я разместил заказ на одной из самых известных фриланс‑бирж в России и в тот же вечер выбрал фрилансера с небольшим опытом, но хорошими отзывами. Договорились на 10 тыс. рублей и неделю на выполнение работы. Через неделю дизайнер предоставил мне макеты в фигме. Результат мне так понравился, что правок почти не было.
Шаг 5. MVP
Настало время запилить MVP. Я накидал дорожную карту. Получился такой список вех:
пустое Next приложение
есть модель данных
готова верстка и дизайн‑система, но пока без функционала и роутинга
есть роутинг, готов функционал: можно ввести ответ. Если ответ правильный — переход на следующий новый уровень, если нет — ошибка
прогресс сохраняется
есть все виды анимаций. В случае правильного ответа показывается анимация
для всех уровней есть обучающие материалы
украшательства: адаптив, подсветка при ховере по растению, подсказки и проч.
деплой и автоматизация деплоя
Про создание пустого Next приложения командой create-next-app
я рассказывать не буду, кратко расскажу про модель данных. Основные структуры данных — это енам названий растений PlantName
, словарик картинок и названий для всех PlantName
, описание игрового уровня — BaseLevelParams
.
Основные структуры данных
//енам всех используемых растений
export enum PlantName {
AppleGalaMust696 = "AppleGalaMust696",
Barberry = "Barberry",
Bearberry = "Bearberry",
Blackberry = "Blackberry",
ChristmasRose = "ChristmasRose",
CornPioneer3751 = "CornPioneer3751",
EchinaceaSunrise = "EchinaceaSunrise",
HeliopsisHelianthoides = "HeliopsisHelianthoides",
Primrose = "Primrose",
Raspberry = "Raspberry",
Roscoea = "Roscoea",
Rice = "Rice",
Rose = "Rose",
Roseberry = "Roseberry",
RoseDoubleDelight = "RoseDoubleDelight",
RoseIceberg = "RoseIceberg",
Rosemary = "Rosemary",
RoseOfSharon = "RoseOfSharon",
RoseQueenElizabeth = "RoseQueenElizabeth",
Rue = "Rue",
Rye = "Rye",
SnowInSummer = "SnowInSummer",
SnowOnTheMountain = "SnowOnTheMountain",
Strawberry = "Strawberry",
TomatoRomaVF1 = "TomatoRomaVF1",
WheatNorin10 = "WheatNorin10",
}
//словарик картинок и названий для всех PlantName
export const PlantsDict: Record<PlantName, { img: string; label: string }> = {
[PlantName.AppleGalaMust696]: {
label: "Apple 'Gala Must 696'",
img: Apple.src,
},
//.. и так для всех растений из PlantName
};
//обощенное описание уровня
export type BaseLevelParams = {
//название разбираемого токена или просто заголовок уровня
title: string;
//токен
titleToken: string;
//краткий синопсис что делает токен
titleDescription: string | FunctionComponent;
//задание
task: string | FunctionComponent;
//краткий экскурс в тему
description: string | FunctionComponent;
//подсказки
hint: FunctionComponent[];
//возможное решение (на самом деле избыточные данные - можно и без них)
possibleSolution: RegExp;
//валидаторы, например, что не используется “|” для перечисления “правильных” цветов вместо написания правильной регулярки
validators: Validator[];
//по умолчанию, все уровни содержат валидатор на “|”. Для уровня с такой темой делаем исключение
shouldUseEnumerationValidation?: boolean;
};
//описание уровня, где сад представлен массивом названий и нужно этот массив отфильтровать
export type ArrayFilterMatchLevelParams = BaseLevelParams & {
//сад
garden: PlantName[];
//подмножество сада, которое является правильным решением
correctSolutionGarden: PlantName[];
};
//описание уровня, где в саду нужно заменять растения на другие
export type BaseReplacePlantLevelParams = BaseLevelParams & {
//текст замены. фиксированный для данного уровня
replacer: string;
//описание что на что в саду нужно заменить
gardenTransitions: GardenTransition[];
};
//описание трансформации растения при замене на другое
export type GardenTransition = { from: PlantName; to: PlantName };
На основании объектов с описанием уровней потом создаются инстансы классов для уровней, содержащие вспомогательные геттеры, методы, плейсхолдеры отображаемого кода. Я выделил следующие классы уровней по типу анимации и механики:
CutPlantsByArrayFilterMatchLevel
,FertilizePlantsByArrayFilterMatchLevel
,WaterPlantsByArrayFilterMatchLevel
— фильтрация массива названий растений, черезArray.prototype.match()
. Отфильтрованные растения обрезаются, удобряются или поливаютсяReplacePlantsByArrayMapReplaceLevel
— маппинг массива названий с использованиемString.prototype.replace()
ReplacePlantsByStringReplaceLevel
— названия растений записаны не массивом, а одной строкой через табуляцию, и к этой строке применяетсяString.prototype.replace()
Не буду подробно рассказывать про рутину написания приложения на React, скажу лишь, что ChatGPT мне очень помог после Vue пересесть на React и NextJS. Я просто задавал вопросы нейронке по конкретным темам без какого либо промпта и этих ответов мне было достаточно, чтобы понять что к чему и написать мое приложение. После Vue очень непривычно, что в React приходится управлять реактивностью вручную. К счастью, для таких, как я, есть MobX (я про makeAutoObservable
), preact‑signals и десятки других подобных библиотек.

Шаг 6. Анимации
Я писал игру еще до того, как стали известны Cursor и Windsurf, поэтому использовал good old ChatGPT для помощи с кодом анимации. Но сейчас я бы скорее всего использовал эти редакторы для этих задач — dev‑experience с ними лучше.
ChatGPT радикально упростил создание анимаций. Простую рабочую анимацию падающих капель воды из лейки он сгенерировал с первого раза
Анимация полива
Потом ChatGPT помог мне сделать более сложную анимацию сотни частиц удобрений выпадающих из мешка: написал рандомизацию формы, цвета и движения для сотен частиц на SCSS. Подсказал сделать движение частиц через css motion path — в моем случае это был единственный рабочий способ рандомизировать траекторию движения частиц. Позже, когда делал адаптив, столкнулся с тем, что в CSS нельзя параметризовать offset-path
через переменные css. ChatGPT написал мне код для динамического перерасчета параметров рандомизации при изменениях размера экрана через JS.
Анимация удобрения
Я думаю, по меньшей мере пару дней я сэкономил благодаря ChatGPT.
Шаг 7. Учебные материалы
Самым трудозатратным было написать обучающие материалы по каждой из тем. Я из принципа не стал использовать ChatGPT и написал все сам: в этом я видел свое самовыражение и получил большое удовольствие от продумывания и написания каждого урока.
Шаг 8. Последние штрихи и деплой
Когда все уроки были написаны, я добавил пару мелких фич, причесал код и задеплоил на удаленную тачку. Подумал было бы круто автоматизировать деплой и перезапуск приложения на мерж в репу. ChatGPT с первого раза написал рабочий скрипт для github actions. Я ни разу раньше не писал Github actions, поэтому без ChatGPT на это ушло бы, думаю, 1–2 часа минимум, а реалистично — пару дней. А здесь за минуту и все работает.
Выводы
Я буду очень рад, если мой пример вдохновит тех, кто хотел бы написать свой пет‑проект, но по каким‑то причинам откладывает это. Если вы сомневаетесь стоит ли тратить время на пет‑проект, я скажу однозначно да: это отличный способ изучить новую технологию, пописать код в свое удовольствие, не говоря уже что пет‑проект — это отличное дополнение к резюме.
А еще написание пет‑проекта — это отличная возможность для разработчика побыть в роли менеджера: чтобы работа над пет‑проектом была эффективной, надо заранее продумать роадмэп, расписать основные вехи и прикинуть сколько времени займет достижение той или иной вехи. Мне, привыкшему быть винтиком в цепочке IT‑конвеера, который получает задачи сверху и выполняет их, это было немного непривычно.
С нейронками делать пет‑проекты стало проще в разы. Лучшие вектора использования нейронок (как для пет‑проектов, так и для промышленной разработки, если позволяет NDA и/или есть инхаус ИИ‑решения) я вижу так:
изучение новых технологий
генерация картинок и прочих ассетов
настройка CI/CD
генерация шаблонного кода
А с появлением Windsurf и Cursor, последние 2 пункта стало делать еще проще.
Тем не менее есть вещи, которые, я не доверю нейронкам и буду делать сам: продумывание архитектуры, модели данных, написание кода на новой технологии, которую я изучаю, написание текстов для людей, ну и вообще все то, в чем я вижу свое самовыражение. Например, умей я рисовать, думаю, сам отрисовал бы цветы для своей игры. При написании этой статьи, кстати, я ни разу не использовал ИИ.
А какие пет‑проекты вы хотели бы написать, но все откладываете?
?Игра: https://regexp-garden.madewith.fun
?Исходный код: https://github.com/avfirsov/regexp-garden