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


// просто сравните длину строк
this.this.this.this.
var s=this;s.s.s.s.

Я использовал этот и некоторые другие упоротые способы для участия в конкурсе js13kGames, цель которого — написать игру, размер которой не превысит 13 килобайт.


Скриншот ранней версии игры

Игра почти готова, осталось всего-то пару дней не спать...



Что за конкурс?


js13kGames, кажется, ещё не очень популярен в России, поэтому, кратко:


  • он проводится каждый год с 13 августа до 13 сентября, начиная с 2012 года;
  • весь код должен находиться в одном html-файле;
  • размер zip-архива с этим файлом не должен превышать 13 килобайт;
  • игра должна запускаться в последних стабильных версиях Chrome и Firefox;
  • желательно, чтобы игра соотносилась с темой, которую озвучивают 13 августа.

В ущерб читаемости


Приведённый выше пример с this не добавляет коду красоты, зато в конструкторах и методах, где this используется интенсивно, такой подход экономит по 3 байта на каждом обращении, начиная с пятого. Например, в одном из конструкторов было 39 штук this. Заменив их на self, получилось сэкономить более 100 байт.


Думаю, во всём проекте только эти замены сохранили более килобайт��.


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


t.r() // tools.random
r() // глобальная функция

При минификации все эти функции будут занимать один символ (вместо, например, трёх, если мы поместим их в объект), что даёт весьма впечатляющую экономию: одна только функция random() встречается в коде 77 раз, и её «глобальность» спасает 150 байт.


Совсем специфичная ситуация: я решил хранить спрайты в gif'ках, закодированных в base64, и заметил, что все получившиеся строки начинаются на R0lGODlh. Всего спрайтов получилось 14 (хотя, по изначальной задумке, должно было быть больше), и, вынеся этот начальный кусок строки в функцию, занимающуюся превращением строк в объекты Image, я смог спасти ещё примерно 100 байт.


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


Откусив от геймплея


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


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


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


Анимация


Как я уже говорил, все спрайты хранятся в GIF-файлах, завёрнутых в base64. Размера они минимального, и при создании анимации увеличиваются в 16 раз (это размер внутриигрового «пикселя»). Объект с описанием спрайта также используются конструкторами юнитов для определения размеров; то есть, не анимация подгоняется под размер юнита, а наоборот.


Неиспользованное


В самом начале одной из идей было повсеместно заменить true и false на 1 и 0, но, по ходу разработки, я совершенно забыл об этом, и вспомнил только в конце. К счастью, делать этого не пришлось: к моменту отправки работы на конкурс она проходила по размеру, и я даже не представляю, сколько ужаса пришлось бы пе��ежить, прибегая к такому ненадёжному средству.


Для создания музыки я использовал нотацию, где каждые два символа представляют звук: строка — ноту, число — знаменатель её длительности (ноль вместо строки — пауза). Реальная длительность в милисекундах рассчитывается делением длительности целой ноты на знаменатель длительности ноты.


notes: [
    'A4', 4, 0, 8, 'G4', 8,
    'A4', 8, 'A4', 16, 'G4', 16, 'C5', 8, 'D5', 8,
    0, 4, 'A4', 8, 'A4', 16, 0, 16,
    'A4', 8, 0, 8, 'G4', 8, 0, 8
]

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


Заключение


Как ни смешно, но большая часть этих оптимизаций для сжатия оказалась излишней: даже с оригинальными именами глобальных переменных файл с игрой превратился в zip-архив размером 10.1 Кб (при размере index.html в 31.9 Кб). Чего не хватило по-настоящему — так это времени. Особенно его не хватило на level-дизайн, внятный саундтрек и хотя бы небольшое количество плейтестов.


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


Для энтузиастов минификации: код доступен на GitHub.


Интересно узнать и о ваших tinycode-проектах, делитесь в комментариях!

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
А вы когда-нибудь участвовали в js13kGames?
99.37%Ни разу этого не делал314
0%Однажды попробовал0
0.32%Время от времени участвую1
0.32%Да я ветеран таких соревнований!1
Проголосовали 316 пользователей. Воздержались 79 пользователей.