📜 ⬆️ ⬇️

Our experience of participation in 10K Apart or how to shrink 40 KB of code in 10

Not so long ago, Habré already wrote about the 10K Apart contest - competition for the best web application with a total volume of up to 10K, created using only client technologies: (HTML, CSS, Javascript, SVG, etc.).


I want to introduce your attention to our work for this contest, which we did in the evening with a private_face in the evenings for two weeks: a dungeon-crawler style adventure game called “Fontanero” ( Spanish plumber).

All this variety in three files with a total weight of 10230 bytes. If you ever find a 1.44MB diskette on the mezzanine, then you can write 144 such games to it.
After we decided on the nature of the application (adventure), genre (dungeon crawler in the style of nethack) and the setting (the plumber goes down to the basement to fix the pipe and goes to hell), it’s time to figure out keep within 10K.


The first code sketches (card generator and playing field pattern) showed that 10K is not enough even for a third of the game. It was necessary either to drop everything or to somehow increase the available space.

As a low-level programmer, I was immediately very interested in the possibility of clamping js in a zip, in order to later unpack it at runtime. My first thought was to write my own LZSS, but a little more simple idea was born a bit later: put js in PNG, because the data in it is compressed with the same zip. (As it turned out later - we were not the first to whom this idea occurred ).

Having studied the technology, we checked the ability to load arbitrary code through the Canvas from the palette png, by encoding the symbols in one of the color components. The test was successfully executed in all required browsers, including IE 9 preview (which now also supports Canvas). It was a success. The eight-bit palette can store 256 colors, but only characters with codes 32-94, and \ n (10) remained after the obfuscator. Encoding them to 64 values, and not to 256, also gave significant savings (Strange, but png does not know how to compress the palette).

Thus, the structure of our future application has become clear: an HTML uploader page with a script that restores javascript from PNG, which contains all the game logic, and also dynamically creates all the necessary HTML and CSS. In the final version of the project, the loader occupied 850 bytes, including even alert (“no canvas”); (You can not leave the users of old browsers in front of a white screen).


Zip increased the actual amount of code we could afford, but this was still not enough. Therefore, the next step in optimizing the size was the choice of obfuscator for JS.

From the free solutions on the market, we chose between the yui compressor and the google closure compiler. In the competition, of course, won the google closure compiler (hereinafter gcc), which, hand on heart, is by far the best in terms of compression, plus, it displays warnings, mostly useful.

We used gcc in advanced mode, in which it throws out unused code, renames all identifiers to one-two-letter, rebuilds branching, inline functions, and in every possible way smokes code, in pursuit of saving bytes.

However, even when using gcc in advanced mode, there is still room for optimization. First, gcc is afraid of renaming fields with standard, in his opinion, names: left, right, top, bottom, name, type, width, height , etc. Therefore, we preprocess our code before running gcc by renaming such names ourselves. Secondly, after obfuscation, a lot of frequently repeated words “function” and “this” remain in the code. To save money, we replaced their @ ​​and `characters. At the same time, the loader grew a bit for reverse replacement: replace(/@/g, 'function').replace(/\`/g. 'this') and the code after such processing looked quite monstrous:
;`.g=n;`.K=`.v=o;`.F=@(d){var c=`.e; ,
but it was worth it: in general, with each replacement it was a byte of 50.

CSS compressed with the help of the yui compressor and enclosed the javascript directly at the end, after cutting the dots with commas before the closing brackets (60 bytes of savings!).

When we were determined with the appearance of the game, our opinions diverged from private_face . I offered a classic 2D view:

Vova-2 insisted on an isometry of the following type:

Therefore, then I, having scribbled a simple old-school render (everything was drawn with text inside the textarea tag), continued to write the game logic, and the map generator, and began to tinker with my isometry. He did not yet know what surprise IE9 was preparing for him. Ahahahahahahaha: ((

Isometric passion

private_face :
The implementation of the isometry was assumed as follows: rectangular blocks with textures superimposed on them were translated into an isometric projection by the CSS transformations skew and scale, after which they were absolutely positioned at the right place.

This solution required the creation of a separate DOM element for each tile, but it looked simpler and more compact (and, most importantly, more productive) than drawing on the canvas.
In addition, the use of DOM elements made it possible not only to impose textures, but also to easily write any text on the walls, which could become a killer feature for our game.

After some time, the prototype was ready. Render turned out to be quite compact (all drawing was rendered in CSS, javascript only positioned blocks)
and gave quite a good picture (although it significantly slowed down in Firefox 3.6):

Life seemed beautiful and cloudless. However, igniting the idea of ​​isometry, I forgot to do one important thing: to make sure that the CSS transformations are supported by the new Internet Explorer 9. This error cost me meaningless time spent and tons of innocently killed nerve cells in the final. Because it turned out that the only option to implement the transformation in IE9 is the Matrix filter. But the performance of such a decision was simply none.
In general, the isometry had to be abandoned.

Back to basics

After the failure of the 3D version, we decided to return to the simple tile version.

All tiles were drawn in the usual GIMP and were combined into a PNG sprite with a 4-bit (16 colors) palette, after which the latter was clamped by the pngcrush utility with the -brute key.

We managed to impart some volume to the picture by setting the box-shadow property of wall and floor tiles. However, this unexpectedly negatively affected the performance of Internet Explorer 9, while other browsers worked at normal speed. In order not to upset IE9 users with brakes, we had to add the option “Enable shadows” to the game interface, which is disabled by default in this browser. Hopefully, the release of box-shadow performance in IE9 will be improved.

The transition to 2D had a good side: the total volume of the drawing code was significantly reduced compared to the isometric version, and we used the space we finally used to add a mini-game about repairing pipes to the gameplay.


This is a brief history of how we made this game. Starting to work on this project, none of us had imagined that it would be so interesting - to work in strict constraints and saving precious bytes. We got a huge amount of fun, and 10k did an excellent job with their task - reinspire the web.

There are many other tasks left overboard of the narration that we had to solve during the work on this project. Such, for example, as a compact level generation algorithm with connectivity testing, an ultracompact algorithm for moving monsters that could walk along the corridors and would not look too stupid.

And at the end of this topic, I will cite once again a short list of optimizations that allowed us to cram almost 40K of code into 10K.
CSS Optimization:
  1. Wrapping CSS YUI Compressor into a string, adding the result to the original .js file.
  2. Remove optional semicolons at the end of each rule block.
  3. Inserting the resulting style line to JS (I mean, packing css + js in one archive block).
JS Optimization:
  1. Before obfuscation: replacing fields with the names left, right, name, type, etc. to others (since GCC does not rename them even in advanced mode).
  2. Obfuscating GCC code in advanced mode.
  3. After obfuscation: replacing the function and this keywords with single-letter abbreviations ('@' and '' ').
  4. Encapsulation in a proton container. Packaging in PNG.
Image optimization:
  1. All images were stored in one sprite.
  2. The number of colors in the palette was limited to 16.
  3. After any change, the file was pinched by pngcrush with the -brute option to ensure the best compression.

A small update: Today, due to numerous requests, we fixed the display of the mini-game in the opera, although this is not in the requirements of the competition. Patch on the way to 10K Apart.

Thank you very much for your attention, we will be very happy if you rate our game on the contest page .
PS We press further, continued.
PPS Contest is over, thank you all!

Source: https://habr.com/ru/post/102153/

All Articles