Як вичавити максимум з минификации коду

Ви замислювалися над тим, що якщо в конструкторі і методи використовувати
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-проектах, діліться у коментарях!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.