וואלהבכירים במפלגתו של קנצלר גרמניה הביעו בו תמיכה על רקע הלחץ להתפטרESPNTransfer rumors, news: Barça, Madrid believe Haaland could leave Man City next summerThe Jerusalem PostIDF arrests Iran's Press TV journalist in West Bank, hands her to police, source confirms to 'Post'UN NewsThe world comes to New York: What’s at stake at UN General Assembly high-level weekRTP DesportoOpen de Portugal promovido à primeira divisão do golfe europeuBollywood HungamaAmit Sadh’s Pratap to release in theatres on November 20, 2026; FIRST look unveiledPunchPublic appointments should be based on competence – PRP chieftainInquirerEjercito vows to ensure funding for major railway projectsDigital SpyEmmerdale's Joe Tate to face new revenge plan after Ruby burialVilaWebEl Partit Demòcrata Europeu recorda a Llarena que ha d’aplicar la llei europea i amnistiar PuigdemontBBC NewsCarney's new love-in with EU has everything to do with TrumpGolem.deElon Musk fordert: Unternehmen sollen ihre KI-Systeme gegenseitig prüfen
The Daily Newsstand · Free, Always
Wednesday, September 16, 2026

«Обратная змейка», часть 2: читаю комментарии, чиню баги и добавляю босса

Translate

Когда я выкладывал первую часть, я, если честно, рассчитывал на десяток лайков и тишину в комментариях: ну кто будет всерьёз разбирать змейку, слеплённую за два вечера. Не угадал — комментариев набралось прилично, и добрая половина оказалась не «мило, годно», а разбором полётов: кто-то поймал баг, кто-то — нечестный спавн змей вплотную к игроку, кто-то сравнил игру то с Pac-Man, то с Asteroids. И вот сижу я, читаю это всё по второму кругу и понимаю: технически игра работала, но местами работала не на 100 процентов своей идеи, а это не то же самое, что работала хорошо. В общем, решил пройтись по всему списку, а не отделаться сердечком в ответ.

Что оказалось реально сломано

Начну с самого стыдного, чего я даже не заметил, когда читал в первый раз. alexs963 прислал текст гейм-овера: «Вас съела жёлтой змея» и свой рекорд. Я подумал, что комментарий обычный (неплохой, просто показать свой максимум), а потом, когда начал читать все комментарии, заметил, что по-русски коряво звучит, и тут я решил проверить: это у него проблемы или у меня? Как оказалось, у меня — субъект должен стоять в именительном падеже, а не в родительном.

Причина очень глупая: в объекте с названиями цветов лежали формы вроде yellow: 'жёлтой', которые потом подставлялись в шаблон Вас съела ${цвет} змея. Работало, если мысленно достроить другое предложение, и калечило, если читать буквально:

const reasons = {
    green: 'Вас съела зелёная змея',
    yellow: 'Вас съела жёлтая змея',
    red: 'Вас съела красная змея'
};

Три минуты работы, но именно такие баги обиднее всего пропускать — их видит буквально каждый, кто хоть раз проиграл.

Второй случай оказался занятнее. Owyn написал, что яблоко при движении:

«мерцает, будто пытается убить эпилепсией»

И что вообще всё происходит быстрее классической змейки, а яблоко:

«будто скользит по льду»

Полез разбираться и нашёл в собственном коде забавную штуку. И очевидно, что дело не в чьём-то мониторе, но одна и та же переменная gridSize в коде отвечала одновременно и за количество клеток на поле, и за размер клетки в пикселях, из-за чего внутреннее разрешение canvas почти никогда не совпадало с тем, как браузер реально растягивал его через CSS. Пиксельный рендеринг плюс нецелый коэффициент масштабирования — ровно то, что даёт дрожание картинки на движении. Развёл эти две сущности по разным углам: разрешение canvas теперь мерится через devicePixelRatio и подгоняется под реальный размер контейнера, число клеток живёт отдельно. Заодно решилась и жалоба на тесноту поля, раз пиксельный размер клетки больше не зависит от их количества — спокойно увеличил поле, не потеряв резкость. Один баг закрыл две жалобы сразу, что, признаю, приятно.

Третья история была самой долгой. gerbert_MX и Metotron0 независимо указали на нечестность: змеи спокойно проходили друг сквозь друга и, по словам gerbert_MX:

«могли создавать закрытые объёмы»

— то есть буквально запирали игрока без шанса на манёвр. Тут я сразу убрал идею, чтобы они умирали при столкновении (да, это интересно, но как-то слишком лёгкая игра получилась у меня, по крайней мере), поэтому я просто дал жёлтым и красным (позже червю) простую логику объезда — если следующий шаг ведёт прямо в чужое тело, змея поворачивает перпендикулярно, а не идёт напролом:

const free = perpOptions.find(o =>
    !obstacles.some(ob => ob.x === head.x + o.dx && ob.y === head.y + o.dy)
);
if (free) { this.dx = free.dx; this.dy = free.dy; }

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

if (occupiedByTick[tick].has(this.cellKey(part))) return false;

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

Баланс: слишком много всего сразу

Я и сам ещё в комментариях к первой части признавался, что баланс на счёте 200–300 разваливается: змей уже прилично, скорость фиксированная и высокая с самого начала, поле маленькое. Sfekss сформулировал точнее всех: в оригинальной змейке скорость растёт постепенно вместе со съеденными яблоками, а у меня игра стартует сразу «на реактивной тяге».

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

Формулу очков поменял по мотивам сразу двух комментариев. gerbert_MX предлагал считать результат как «скорость × время», а jouilk23 удачно объяснил зачем:

«это будет мотивировать игроков рисковать ради больших результатов, а не просто прятаться в углах на минималке»

Спорить не с чем, поэтому теперь за каждый тик начисляется не единица, а текущий уровень сложности:

this.score += this.currentStageLevel;

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

Кто или что такое это яблоко

лор яблока

лор яблока

Отдельная ветка комментариев меня искренне повеселила, хотя объективно я сам напросился. Wesha написал:

«Яблоко б на лягушку заменили, а то яблоко, которое само по себе ездит…»

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

«моглиб хоть ноги ему приделать»

Спорить с логикой было не с чем, идея действительно абсурдная с самого начала, но менять саму суть игры не хотелось — шарм именно в этой нелепости, а не вопреки ей.

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

Новый враг: червь

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

Фотография врага — червя — и его путь

Фотография врага — червя — и его путь

Кстати, поначалу вместо червя думал сделать крота — он тоже вылезает из-под земли, и предупреждающая метка на поле для него даже логичнее, но потом полез проверять и узнал: нет, кроты яблоки не едят, они как раз едят червей. Хотя, если так подумать, змеи яблоки тоже не едят — так что биологическая логика тут и не ночевала, просто червя было проще нарисовать и оживить.

Способности и лишние жизни

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

Способности

Способности

После потери жизни игрок возрождается в центре и автоматически получает тот же щит на три секунды — иначе респавн ощущался как повторное наказание за одну и ту же ошибку.

Главное новое — битва с боссом

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

  1. Жетоны. Королева насылает зелёных змей, нужно собрать три золотых жетона, уворачиваясь.

  2. Удар по площади. Зоны атаки: сначала предупреждающая оранжевая заливка, через секунду — уже настоящий удар. Параллельно нужно успевать собирать растущие яблоки — каждое визуально увеличивает игрока.

  3. Корона. Королева роняет корону, вокруг неё нужно обойти восемь меток по кольцу. Ближе к завершению кольца

Как кольцо замкнуто — короткая немая сценка: яблоко надевает корону, оставшиеся змеи разворачиваются и разбегаются с поля, и только потом экран победы с честным временем прохождения. Саму сценку в зачёт не включаю, иначе «победить как можно быстрее» перестало бы быть честным.

Вместо итога

На первую версию ушло два вечера, на вторую — кажется, ещё больше, хотя формально код там был готовый и работающий, просто пришлось признать, что «работающий» и «честный» — не одно и то же. Из отложенного — второй босс с другой механикой, достижения и, может, локальный кооп на одном поле, если получится не превратить это в кашу (пока не знаю, как, но идея зудит).

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

Код лежит на GitHub, поиграть можно прямо в браузере: novelros.github.io/reverse-snake.

P.S. Если найдёте очередной баг, придумаете, чего игре не хватает, или просто хочется похвастаться рекордом — пишите в комментариях, как в прошлый раз. Судя по всему, это реально работает.

View the original on Хабр

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.