*blog... kind of... *rss 



Javascript size coding challenges
Last friday MIX Online and An Event Apart launched an unusual Javascript contest. 10K Apart pushes the developer to reduce their code so it fits in 10,000 bytes. This sounds like a nice challenge, but strangely they allow specific external libraries, which, in my opinion, over complicate things.

However, just to see what could be done, I quickly checked the amount of bytes that I would need to have a simple and easy to use 3d engine. That turned out to be about 1,000 bytes. Which left me with 9,000 bytes. Now, this may sound great, but it's kind of discouraging. Now I understand why the 64 kbytes coders always say that 64 kbytes are way harder to do than 4 kbytes – the number of possibilities is lower. Anyway, if I come up with a good idea I may try doing something with that for the contest.

Today I found out about JS1k. Which seemed to be Peter van der Zee's response to the former contest (no external libs, just 1,024 bytes of plain javascript) and it also had a few submissions.

Once I got the 3d engine working I got a bit code-addicted and ended up doing a plasma in 3D in 1,464 bytes, it was nice looking already, so it was a matter of reducing those extra 440 bytes. After learning some tricks and testing things here and there, it got reduced to 996 bytes. Here it's the result:



Looking forward to see what p01 has to "say" of all this... :)

EDIT: After reading Diego's post and finding interesting to see which tricks he used I thought I should also share the non-obfuscated code of my entry.

( function () {

	var res = 25, res3 = res * res * res,
	i = 0, x = 0, y = 0, z = 0, s, size, sizeHalf,
	vx, vy, vz, rsx, rcx, rsy, rcy, rsz, rcz,
	xy, xz, yx, yz, zx, zy,
	cx = 0, cy = 0, cz = 1, rx = 1, ry = 1, rz = 0,
	t, t1, t2, t3,
	sin = Math.sin, cos = Math.cos, pi = Math.PI * 3,
	mouseX = 0, mouseY = 0, color,
	doc = document, body = doc.body,
	canvas, context, mesh = [],
	width = innerWidth,
	height = innerHeight,
	widthHalf = width / 2,
	heightHalf = height / 2;

	body.style.margin = '0px';
	body.style.overflow = 'hidden';

	canvas = doc.body.children[0];
	canvas.width = width;
	canvas.height = height;

	context = canvas.getContext( '2d' );
	context.translate( widthHalf, heightHalf );

	doc.onmousemove = function ( event ) {

		mouseX = ( event.clientX - widthHalf ) / 1000;
		mouseY = ( event.clientY + heightHalf ) / 1000;

	};

	while ( i++ < res3 ) {

		mesh.push( x / res - 0.5 );
		mesh.push( y / res - 0.5 );
		mesh.push( z / res - 0.5 );

		z = i % res;
		y = !z ? ++y %res : y;
		x = !z && !y ? ++x : x;

	}

	setInterval( function () {

		context.clearRect( - widthHalf, - heightHalf, width, height );

		cx += ( mouseX - cx ) / 10;
		cz += ( mouseY - cz ) / 10;

		t = new Date().getTime();
		t1 = sin( t / 20000 ) * pi;
		t2 = sin( t / 10000 ) * pi;
		t3 = sin( t / 15000 ) * pi;

		rx = t / 10000;

		rsx = sin( rx ); rcx = cos( rx );
		rsy = sin( ry ); rcy = cos( ry );
		rsz = sin( rz ); rcz = cos( rz );

		i = 0;

		while ( ( i += 3 ) < res3 * 3 ) {

			x = mesh[ i ];
			y = mesh[ i + 1 ];
			z = mesh[ i + 2 ];
			s = sin( t1 + x * t1 ) + sin( t2 + y * t2 ) + sin( t3 + z * t3 );

			if ( s >= 0 ) {

				xy = rcx * y - rsx * z;
				xz = rsx * y + rcx * z;

				yz = rcy * xz - rsy * x;
				yx = rsy * xz + rcy * x;

				zx = rcz * yx - rsz * xy;
				zy = rsz * yx + rcz * xy;

				vx = zx - cx;
				vy = zy - cy;
				vz = yz + cz;

				if ( vz > 0 ) {

					color = ( 64 / vz ) >> 0;
					context.fillStyle = 'rgb('+ ( color - 16 ) + ','+ ( color * 2 - 128 ) + ','+ ( color + 64 ) + ')';

					size = s * 30 / vz;
					sizeHalf = size / 2;

					context.fillRect( ( vx / vz ) * widthHalf - sizeHalf, ( vy / vz ) * widthHalf - sizeHalf, size, size );

				}

			}
		}

	}, 16 );

} )();

Which, after compression ends up like this:

var O=24,d=O*O*O,X=0,U=0,T=0,S=0,W,j,L,o,m,k,b,q,ac,n,ab,l,K,I,r,p,Z,Y,C=0,A=0,w=1,G=1,F=1,E=0,V,P,N,M,u=Math.sin,f=Math.cos,v=Math.PI*3,R=0,Q=0,H,g=document,D=g.body,h,aa,B=[],a=innerWidth,e=innerHeight,J=a/2,c=e/2;D.style.margin="0px";D.style.overflow="hidden";h=g.body.children[0];h.width=a;h.height=e;aa=h.getContext("2d");aa.translate(J,c);g.onmousemove=function(i){R=(i.clientX-J)/1e3;Q=(i.clientY+c)/1e3};while(X++=0){K=q*T-b*S;I=b*T+q*S;p=n*I-ac*U;r=ac*I+n*U;Z=l*r-ab*K;Y=ab*r+l*K;o=Z-C;m=Y-A;k=p+w;if(k>0){H=(64/k)>>0;aa.fillStyle="rgb("+(H-16)+","+(H*2-128)+","+(H+64)+")";j=W*30/k;L=j/2;aa.fillRect((o/k)*J-L,(m/k)*J-L,j,j)}}}},16);
14 comments

Come on, you can compress that even further ;) I do not think YUI does this kind of stuff.

t2 = sin( t / 10000 ) * pi;
t3 = sin( t / 15000 ) * pi;
rx = t / 10000;

Could be rewritten as:

t2 = sin( rx = t / 10000 ) * pi;
t3 = sin( t / 15000 ) * pi;

Basically if you do manual copy propagation and place the assignments in there first use you will always save one byte. Looking at your source: YUI compressor really does not do this. Search for V/1e4 for instance.
Haha! Yeah, I could have done a couple like these, but then the non-obfuscated code would have ended a bit harder play with :S

There are a few things YUI could still do... in fact, that "1e4", that's something I did myself. I think with this kind of optimisations it would have been about 10-20bytes less. Something I would have done if I was at 1034 of course ;)

PS: If you ever feel like doing a compressor for Javacript code...
Why to put all inside a function? (it seems to be removed in the compression, after all...)
Good question! That's because YUI doesn't reduce the name of global variables. I remove the function bit by hand after compression.

Again, it's all for keeping some sanity in the non-obfuscated code :)
How does the Google Clojure compiler perform? I always thought it is superior to YUI compressor.
Hey mr doob,
Any particular reason why you choose 16ms for the set interval value? Could you not save a byte any set it to 9? :)
@Joa: Uh, that goes over my head :S

@Matt: 1000 / 60fps = 16ms. But yeah, I guess I could have used 9 instead. But it goes in the bag of little tweaks that won't really make much of a difference. At least not when you're under 1024 already ;)
Closure compiler usually generates files a little bit smaller but it all depends on the structure of the code. - You can compare both online: http://closure-compiler.appspot.com/ and http://refresh-sf.com/yui/

I've done some other small tweaks that reduce the file size even more: http://gist.github.com/510134 (comments on the source code)

Cheers!
forgot to send this link in case someone else is interested on extra information about JavaScript compression: http://www.alistapart.com/articles/javascript-minification-part-II/
Nice entries sir!

Here's what I have to "say" about all this so far: Quaternion Julia raymarcher with some decent design™ - http://js1k.com/demo/49 ;)
Wow, thanks for finding this. I think I'll actually try to write something. 1k is an awesome challenge.
Howdy, I just copied down a few examples to see how 2D and 3D effects are done with javascript and canvas.

I'm really excited about trying to mashup some of these effects.

My favorite so far is the shiny spheres reforming and rotating about.
depth_of_field and I see an earlier(?) action script version in your examples. Great work, I hope to be able to jury rig something interesting during wait times (remote apis) for users in any web apps I cobble together.
Peter van der Zee created a list with more optimization techniques a couple days after I posted my comments and I forgot to post it here: http://qfox.nl/notes/111
Good work :) I'm trying to come up with ideas for the current JS1K which ends at the end of April.

Thanks for posting the un-minified code - it's interesting to see and learn the tricks which are used for making 3D demos. I particularly like the i+=3 in the while loop :)

Fingers crossed I'll be able to create something half as good as your demo ;)

Will you be taking part in the latest JS1K too?