Skip to content

Instantly share code, notes, and snippets.

@subzey
Last active August 29, 2015 13:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save subzey/10020935 to your computer and use it in GitHub Desktop.
Save subzey/10020935 to your computer and use it in GitHub Desktop.
Requiem for a Dragon

Requiem for a Dragon

This is post-mortem for my first JS1K entry, Breath.

Complete, almost per-line, explaination was subitted with uncompressed de-optimized source code. So it's not so "techy", this is something I would share with other coders after few beers in a bar. No code, just thoughts.

The Idea

The most important and hard part for me. At first it was like this: "Idea… The idea… Hmm… Dragons… I need idea".

The first thing came in mind was Dragon curves. But someone would submit a dragon curve demo, and my implementation would be just another one. Nope, that's not it.

Other idea was a dragon flying over some terrain. But it would be too hard to make a more-or-less realistic dragon over more-or-less realistic terrain. As for me, the demo where you have to explain that this cube is a dragon, is a bad demo.

Several days passed. Bad thoughts came: "After all, dragons/map theme is not mandatory. What if I try…"

And then - Bang! I could make some flames and 1024 bytes would be more than enough.

The Flames

I made a numerous flame-like effects before in - ahem - WinAMP AVS and played a lot with matrix convolutions in Photoshop/GIMP. Well, it turns out that knowledge achieved in useless toys can be handy.

The principle of matrix convolution as that for each pixel you take some part of neighbour pixels and this will result a new value for this pixel. And if you take more "value" from the left pixel, overall picture will be sliding right.

Okay, there will be some "ignition" pixels that have maximum value at each frame, and other pixels that will create the fading out "body" of flame.

If static translation matrix is applied, the effect is quite boring, just a sliding thingie. If static blur matrix is applied, it would be just a radial gradient. If both, it looks better, like jet. But the jet is not a flames yet!

As these calculations are held in JavaScript, we don't need to use one matrix for all pixels. We could take more from top pixel at the left side of screen and more from the bottom pixes at the right one. It creates the parabolic-like trajectory that resembles convection (hot air comes up). Also, closer to the right flames moves slower, kinda spread in 3d space.

Now we're just mapping values to colors, dark red to bright yellow, and draw it on canvas each frame. It is made by filling ImageData object in a loop and the pasting it into canvas, nothing too interesting here. Except maybe one tiny trick: getImageData() is shorter than createImageData().

The most difficult part is a divisor. If it's too big, flames becomes too short. If it's too small, all screen becomes yellow. But there's a quick solution to this issue: tweak it manually until you like what happens on the screen. After all, large part of fluid dynamics is empiric.

There's another problem: as values array is one-dimensional, flames wraps the screen. And again, the yellow screen. First, I tried to zero all the values at the right edge of the screen, it looked awful. But then beforementioned solution, tweaking the coefficient, helped me to prevent yellow screens: wrapped flames became reddish glow at the bottom of the screen. It looked even better.

But the overall picture is still dull. Another step is adding random value to divisor. And... well, I didn't expect that. The flames instantly became much more fuzzy and live.

Small addition: blue component is unused. We can add vertical gradient almost for free, so why should we avoid it? Everyone loves gradients.

After minifying we get about 400 bytes. We have plenty of space!

The Dragon

Okay. There should be a dragon that emits fire. Sure, real looking, texturized dragon is great, but I wasn't sure I could fit something like this into remaining bytes. So line art was my pick.

But line art requires high resolution. And one cannot just resize canvas with flames as all the hand picked coeeficients and wrapping effects will be lost. There's a solution: create another canvas. Or cloneNode() existing that already have proper CSS size and set position: absolute.

Later I discovered that JS1K shim was changed after I submitted the and code and now it reacts to the resize event, so this composition is broken. Unfortunately, I've noticed it after submissions were closed and I couldn't fix anything. So just don't resize the demo.

I worked a lot with Bezier curves and decided to use it. I draw the dragon outline on paper and then used a lot of bezierCurveTo's to draw it on screen. It looked awful. I've tossed out my paper sketch and started from scratch.

It was a long and meditational process of bezierCurveTo'ing. The result was a blunt headed snake with schematic wings.

Now it's compositioning time. Dragon canvas is placed over flame canvas, just like in oldschool PC demos, with mouth over "ignition" pixels. Unfortunatly, it means I cannot crop the dragon, only stretch it. But I hope it looks not so ugly at extreme height/width ratios.

Now another stage. When you hold mouse button, fire is emitted and mouth becomes yellow. That simple change turned demo in interactive demo. Almost a game (or rather a toy) when user can control dragon. Another small battle for size and there's few more bytes left. I decided to waste it on thing that all the modern games has: the onscreen hint is shown after several idle seconds that notifies user he could press left mouse button and something more interesting than just occasional small bursts of flame will happen.

1032 bytes. Damn. Half of a day this background task ran in brain and then sudden inspiration: make it with 2 Bezier curves insead of 3. Can't wait to get to my PC and code it. 1024 bytes!

The code is done. It's something about 03:30 AM and I'm submitting a code. One more cigarette while staring at the screen and then sleep.

The Pack

But it's not the end. The lesson I took is 1K entries are completely different from 140b ones. It turned out that JSCrush and RegPack works awesomely. Better than I can achieve by manual golfing.

It was a long battle with entropy for each byte and I've managed to add nostrils and teeth to dragon and even make a small facelifting so it looks more like dragon and less like a dinosaur.

Creating JSCrush/RegPack compatible code is highly covered by other authors and I won't explain it in the details. My advice in the nutshell: repeat yourself!

There's one trick however that you won't notice reading uncompressed source. hintCounter = NaN; became h=C. And C is not even defined in source that is eval'd. C was defined by RegPack itself and was its temporary string variable. Which is for sure will result NaN after incrementing. That shameless RegPack (ab)use saved me 2 bytes.

The End

That's it. I've carefully commented the source an resubmitted the code. And forgot about it for almost two months.

For me demo making is a process of constant improvements resulting something that feels worse than it was in the beginning. I almost hated my code, the dragon looked ugly, the flame looked ugly, the whole idea looked dumb. It's a perfectionism, I guess.

But it was fun for sure!

Thanks to JS1K organizer, judges and participants. And you, for reading this non-filtered stream of consciousness.

@xem
Copy link

xem commented Apr 7, 2014

Great lesson!
Maths-based demos are the best, and the NaN trick is good to know ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment