Как я пытался сделать стратегию из котов в стиральных машинах — и почему правила оказалось сложнее написать, чем код

в 16:46, , рубрики: javascript, UX, браузерная игра, геймдев, игровая механика, искусственный интеллект, процедурные правила

Всё началось не с игровой механики. Я смотрел клип Mr. Oizo — M Seq, где Flat Eric сидит в машине, которую моют, и подумал: под такую музыку должны крутиться коты в стиральных машинах. Мне уже нравилась сама картинка — матрица из машин, внутри которых вращаются коты двух цветов.

Потом появилась проблема: поверх этой картинки всё‑таки хотелось сделать игру.

Первый прототип

Поле довольно быстро стало матрицей 4×4. Игрок и компьютер получают по цвету. Ход выглядит просто: нажимаешь на кота своего цвета. Выбранный кот становится вражеским, зато от него по четырём диагоналям распространяется инерция и перекрашивает остальных котов в твой цвет.

Очки дают замкнутые фигуры: квадрат, ромб и крест. Если за один ход возникли две независимые фигуры, засчитываются обе. Если фигуры накладываются, остаётся более дорогая. После фиксации комбинация пересобирается, поэтому нельзя однажды занять удачную область и бесконечно получать с неё очки.

На бумаге это помещается в несколько предложений. В реальной партии оказалось, что человек одновременно должен понять четыре события:

  1. Почему нажать можно только на своего кота.

  2. Почему именно выбранная клетка стала чужой.

  3. Откуда взялись перекрашенные диагонали.

  4. Какая из возникших фигур принесла очки и почему после этого исчезла.

Если применить всё одним кадром, игрок видит не причинно‑следственную связь, а мигание поля.

Почему мгновенная анимация ломает правила

Первая реализация именно так и работала. После клика состояние массива быстро пересчитывалось, DOM обновлялся, комбинация подсвечивалась, начислялись очки и часть поля пересобиралась. Машина всё делала правильно, но наблюдатель видел только «что‑то дёрнулось».

Технически это была не ошибка игрового движка. Это была ошибка представления времени.

Я разделил ход на фазы:

— выбранная клетка сразу меняет цвет; — первая диагональная волна появляется почти сразу; — следующие волны идут с заметным интервалом; — только после завершения перекраски запускается поиск фигур; — найденные фигуры подсвечиваются и поле ненадолго блокируется; — затем начисляются очки и выполняется пересборка.

Сейчас основные интервалы заданы отдельно: первая волна занимает около 120 мс, дальнейшие шаги диагонали разделены примерно полутора секундами, фиксация фигуры и показ результата тоже имеют собственные задержки.

Это не украшение. Очерёдность анимаций фактически стала частью спецификации правил. Сначала игрок обязан увидеть, как конкретный оранжевый кот стал голубым. Только затем можно подсвечивать замкнувшийся ромб. Если поменять эти события местами, даже правильная комбинация выглядит случайной.

Возможность прервать инерцию

В игре есть ещё одна причина не превращать ход в атомарное изменение состояния. Следующий игрок может не ждать окончания чужой инерции, а сделать свой ход и остановить распространение.

Из этого появляется ограниченное решение на скорость. Иногда выгодно спокойно посмотреть на поле и найти собственную комбинацию. Иногда видно, что следующая диагональная волна закроет сопернику крест, и тогда лучше нажать немедленно.

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

ИИ, который сначала раздражал скоростью

ИИ выбирает ход из допустимых клеток. На сложном уровне он оценивает потенциальные комбинации, но периодически делает случайный ход. Получается соперник, который обычно играет сильно, но не выглядит идеально детерминированным.

Сначала он отвечал почти моментально. После человеческого клика поле начинало показывать одну волну и тут же получало второе событие. Даже когда состояния были корректными, это выглядело как визуальная ошибка.

Поэтому пауза ИИ теперь является обязательной частью поведения: примерно 1,2–2,2 секунды перед выбором. Это немного, но позволяет человеку отделить свой ход от ответа компьютера.

Первая партия специально короткая — до 60 очков вместо обычных 100 — и сразу запускается против сложного ИИ. Здесь есть намеренная провокация: игрок успевает увидеть основные события, довольно быстро проигрывает и получает предложение либо попробовать более простой уровень, либо сменить правила.

У игры есть «изощрённый» и «сладенький» варианты. В основном режиме выбранный кот приносится в жертву: он становится чужим, диагонали — твоими. В упрощённом режиме нажимать нужно на чужого кота, и он вместе с диагоналями становится твоим. Второй вариант объясняется легче, но первый создаёт гораздо больше неприятных и интересных решений.

Поиск правил как отдельный эксперимент

До нынешней механики я пробовал искать локальные правила автоматически. Для этого появился отдельный Python‑скрипт mechanic_search.py.

В эксперименте использовалось поле 3×3. Состояние клетки включало питание и направление. Правило описывалось четырьмя частями: состоянием самой клетки, количеством соседей, способом подсчёта соседей и эффектом.

Набор из нескольких таких фраз прогонялся на разных последовательностях ходов. Правила мутировали: менялось условие, эффект, добавлялась или удалялась фраза. Кандидатов оценивали несколько политик игрока — случайная, жадная, намеренно плохая и две ограниченные стратегии.

В полном эксперименте получилось более 90 тысяч симулированных партий. В метриках я пытался приблизительно измерить простоту, богатство состояний, частоту цепочек, хаос, риск доминирующей стратегии и разницу между разумной и случайной игрой.

Лучшим кандидатом оказалось ядро из двух правил: одиночная включённая машина выключается, а машина с одним включённым соседом противоположного направления тоже выключается.

Численно результат выглядел убедительно: жадная политика жила в среднем около 39,6 хода из 60, случайная — только 2,6; цепочки из нескольких волн возникали примерно в 19,7% ходов.

Но этот эксперимент дал мне более полезный вывод, чем конкретный набор правил: метрика не умеет проверить, понимает ли человек происходящее.

Можно получить систему без циклов, с хорошей разницей между сильной и случайной политикой и с разнообразными состояниями. А затем показать её человеку — и он скажет, что поле просто живёт собственной жизнью.

Обучение, которое тоже пришлось переделывать

Сначала перед партией было окно с правилами. Оно честно объясняло клики, диагонали и фигуры. Люди его закрывали или читали, но всё равно не связывали объяснение с полем.

Следующая версия стала интерактивной, но повторила ту же ошибку: события проходили автоматически слишком быстро. Пользователь нажимал на предложенную клетку, после чего несколько учебных сцен пролетали подряд.

Теперь обучение разбито на небольшие причинно связанные шаги. Игрок совершает действие, видит только его непосредственный результат и сам нажимает «Дальше». Отдельно показывается перекраска диагоналей, отдельно — фиксация фигуры, отдельно — её утилизация. Есть ситуации, где нужно успеть прервать чужую волну, и ситуации, где опасности нет и можно потратить время на выгодный ход.

Параллельно те же подсказки пришлось переносить в обычную партию. Обучение всё равно будут пропускать. Поэтому поле должно само показывать:

— кто сделал ход; — какую клетку нажали; — откуда идёт инерция; — какие клетки изменятся следующими; — какая фигура замкнулась; — сколько очков она принесла.

Мультиплеер и серверное состояние

Для игры с человеком одного локального массива уже недостаточно. Сервер хранит состояние матча, активный цвет, текущую волну, следующую волну, дедлайн хода, найденные фигуры и версию состояния. Клиент опрашивает матч коротким polling‑интервалом и перерисовывает поле только при изменении версии.

Игроки сначала видят друг друга в лобби. Один отправляет приглашение, второй может принять или отклонить его, и лишь после согласия начинается матч. Внутри есть простой чат и персональная ссылка для приглашения друга.

Особенно важно, что сервер должен передавать не только итоговую доску. Клиенту нужны промежуточные данные: источник волны, её фронт, уже пройденные клетки и предполагаемый следующий шаг. Иначе в сетевой партии снова получится то самое «поле мигнуло одним кадром».

Что получилось

Cat Laundry сейчас работает в браузере: можно сыграть без регистрации против компьютера или выбрать ник и позвать живого соперника. Внутри есть музыка, две версии правил, обучение, таймер, аналитика партий и все те паузы, которые пришлось добавить ради читаемости.

Самая трудная часть проекта оказалась не в том, чтобы пересчитать поле 4×4 или научить компьютер оценивать ход. Труднее всего было показать последовательность событий так, чтобы игрок видел ту же логику, которую видит код.

Поэтому главный вопрос к тем, кто попробует игру: на каком именно событии перестаёт быть понятно, почему поле изменилось? Не «понравилось или нет», а конкретно: клик, диагональ, фигура, начисление или пересборка.

Сыграть в Cat Laundry: https://straightfunctor.xyz/cat‑laundry/?source=habr

Код эксперимента с поиском правил, воспроизводимые параметры и результаты пока лежат рядом с основным прототипом. Если тема окажется интересной, отдельно разберу функцию оценки и покажу, почему некоторые статистически хорошие правила оказались плохими для человека.

Автор: ilhamramzan

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js