Как стать автором
Обновить

Наш опыт участия в 10K Apart или как ужать 40 Кбайт кода в 10

Время на прочтение 6 мин
Количество просмотров 8.6K
Не так давно на Хабре уже писали о контесте 10К Apart — соревновании на лучшее веб-приложение общим объемом до 10К, созданное с использованием только клиентских технологий: (HTML, CSS, Javascript, SVG и т.д).

image

Я хочу представить вашему внимаю нашу работу для этого контеста, которую мы с private_face делали по вечерам в течение двух недель: адвенчуру в стиле dungeon-crawler под названием «Fontanero» (исп. водопроводчик).

  • Генератор случайных связных подземелий.
  • Раскрытие карты по мере прохождения, раскрытие комнат.
  • 13 монстров c примитивным AI и алгоритмом нахождения пути.
  • Еда, питьё, 4 разных книги заклинаний: vision, heal, cure и genocide.
  • Эффекты отравления и ослепления.
  • Мини-игра с вращением и починкой труб
  • И даже чудовищный босс в конце.

Всё это многообразие в трёх файлах общим весом 10230 байт. Если вы когда-нибудь найдете на антресолях дискету 1.44МБ, то вы сможете записать на нее 144 такие игры.
После того, как мы определились с характером приложения (адвенчура), жанром (dungeon crawler в стиле nethack) и сеттингом (водопроводчик спускается в подвал, чтобы починить трубу и попадает в ад), настало время прикинуть сможем ли мы вообще реализовать задуманное и при этом уложиться в 10К.

Zip


Первые наброски кода (генератор карт и шаблон игрового поля) показали, что 10К не хватит даже на треть игры. Нужно было или все бросать или как-то увеличивать доступное место.

Меня, как низкоуровнего программиста, сразу же очень заинтересовала возможность зажать js в zip, чтобы потом распаковать его во время выполнения. Первой мыслью было написать свой LZSS, но чуть позже родилась гораздо более простая мысль: положить js в PNG, ведь данные в нем сжимаются всё тем же zip-ом. (Как выяснилось позже — мы были не первыми, кому эта идея пришла в голову).

Изучив технологию, мы проверили возможность загрузить произвольный код через Canvas из палитровой png, закодировав символы в одной из компонент цвета. Тест успешно выполнился во всех требуемых браузерах, включая IE 9 preview (который теперь тоже поддерживает Canvas). Это был успех. Восьмибитная палитра может хранить 256 цветов, но после обфускатора остались только символы с кодами 32-94, и \n (10). Кодирование их в 64 значения, а не в 256, тоже дало значительную экономию (Странно, но png не умеет компрессить палитру).

Таким образом, стала ясна структура нашего будущего приложения: HTML страница-загрузчик со скриптом, восстанавливающим javascript из PNG, который содержит всю игровую логику, а так же динамически создает весь необходимый HTML и CSS. В финальной версии проекта загрузчик занимал 850 байт, включая даже alert(“no canvas”); (Нельзя же оставить пользователей старых браузеров перед белым экраном).

Обфускация


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

Из представленных на рынке бесплатных решений мы выбирали между yui compressor и google closure compiler. В соревновании, разумеется, победил google closure compiler (далее gcc), который, положа руку на сердце, на сегодняшний день является лучшим по степени сжатия, плюс, выводит предупреждения, в большинстве своём, полезные.

Мы использовали gcc в аdvanced режиме, в котором он выкидывает неиспользуемый код, переименовывает все идентификаторы в одно-двухбуквенные, перестраивает ветвления, инлайнит функции и всячески курочит код, в погоне за байтами экономии.

Однако даже при использовании gcc в advanced mode все равно остается место для оптимизации. Во-первых, gcc боится переименовывать поля со стандартными, по его мнению, именами: left, right, top, bottom, name, type, width, height и т. п. Поэтому мы препроцессили наш код перед запуском gcc переименовывая такие имена сами. Во-вторых, после обфускации в коде остаются множество часто повторяющихся слов “function” и “this”. Чтобы сэкономить, мы заменяли их символы @ и `. Загрузчик при этом немного вырос на обратную замену: replace(/@/g, ‘function’).replace(/\`/g. ‘this’) и код после такой обрабоки выглядел достаточно чудовищно:
;`.g=n;`.K=`.v=o;`.F=@(d){var c=`.e;,
но это того стоило: в целом, с каждой замены получалось байт по 50.

CSS сжимали при помощи yui compressor и подкладывали прямо в конец javascript, предварительно порезав точки с запятыми перед закрывающимися скобками (60 байт экономии!).

Когда мы определялись с внешним видом игры, наши мнения с private_face разошлись. Я предлагал классический 2D вид:


Вова-2 настаивал на изометрии типа такого:


Поэтому далее я, набросав простой олдскульный рендер (всё рисовалось текстом внутри тэга textarea), продолжил писать игровую логику, и генератор карт, а вова начал возиться с своей изометрией. Он еще не знал, какой сюрприз ему готовит IE9. Ахахахахахахахах :((

Страсти по изометрии


private_face:
Реализация изометрии предполагалась следующая: прямоугольные блоки с наложенными на них текстурами переводились в изометрическую проекцию CSS-трансформациями skew и scale, после чего абсолютно позиционировались на нужное место.

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

Спустя некоторое время прототип был готов. Рендер получился достаточно компактным (все рисование было вынесено в CSS, javascript только позиционировал блоки)
и выдавал вполне неплохую картинку (хотя и ощутимо подтормаживал в Firefox 3.6):


Жизнь казалась прекрасной и безоблачной. Однако, загоревшись идеей изометрии, я забыл сделать одну важную вещь: убедиться, что CSS-трансформации поддерживаются новым Internet Explorer 9. Эта ошибка стоила мне бессмыслено потраченного времени и тонны невинно убиенных нервных клеток в финале. Потому что выяснилось, что единственный вариант реализовать трансформацию в IE9 — это фильтр Matrix. Но производительность такого решения была просто никакая.
В общем, от изометрии пришлось отказаться.

Назад к истокам


После провала 3D версии мы решили вернуться к простой тайловой версии.

Все тайлы рисовались в обычном GIMP-е и объединялись в PNG спрайт с 4-х битной (16 цветов) палитрой, после чего последний пережимался утилитой pngcrush с ключом -brute.

Некоторую объемность картинке удалось придать за счет установки свойства box-shadow тайлам стены и пола. Однако это неожиданно отрицательно повлияло на производительность Internet Explorer 9, в то время как остальные браузеры работали с нормальной скоростью. Чтобы не расстраивать пользователей IE9 тормозами, пришлось добавить к интерфейсу игры опцию “Включить тени”, которая по умолчанию отключена в этом браузере. Будем надеяться, что к релизу производительность box-shadow в IE9 поправят.

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

Эпилог


Такова краткая история того, как мы делали эту игру. Начиная работать над этим проектом никто из нас не предполагал, что это будет так интересно — работать в жёстких ограничениях и экономя драгоценные байты. Мы получили огромное количество фана, а 10k отлично справились со своей задачей — reinspire the web.

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

А в завершение этого топика приведу еще раз краткий список оптимизаций, позволивших нам впихнуть почти 40К кода в 10К.
Оптимизация CSS:
  1. Упаковка CSS YUI Compressor-ом в строку, добавление результата в исходный .js файл.
  2. Удаление необязательных точек с запятой в конце каждого блока правил.
  3. Вставка полученной строки стилей к JS(то бишь упаковка css + js в одном архивном блоке).
Оптимизация JS:
  1. До обфускации: замена полей с именами left, right, name, type и т.д. на другие (т.к. GCC не переименовывает их даже в advanced режиме).
  2. Обфускация кода GCC в режиме advanced mode.
  3. После обфускации: замена ключевых слов function и this на однобуквенные сокращения ('@' и ' `').
  4. Инкапсуляция в протонный контейнер Упаковка в PNG.
Оптимизация картинок:
  1. Все изображения хранились в одном спрайте.
  2. Количество цветов в палитре было ограничено 16.
  3. После любого изменения, файл пережимался pngcrush'ем с параметром -brute, чтобы гарантировать наилучшее сжатие.

Небольшое обновление: Сегодня по многочисленным просьбам мы починили отображение миниигры в опере, хотя этого нет в требованиях конкурса. Патч на пути в 10K Apart.

Большое спасибо за внимание, мы будем очень рады, если вы оцените нашу игру на странице конкурса.
PS Жмём дальше, продолжение.
PPS Конкурс закончился, всем спасибо!
Теги:
Хабы:
+262
Комментарии 123
Комментарии Комментарии 123

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн