js1k contest – writing a pool game with only 1024 bytes

I was looking for some Javascript contest the other day and found this awesome JS1K contest http://js1k.com/2011-dysentery/. This contest basically challenges people to write cool games with no more than 1024 bytes.

I decided to call in sick and made the day a hacking day for me. And I came up with this pool game in which users have to aim and shoot the cue ball against the walls to hit a red target ball without touching other balls. My entry: http://js1k.com/2011-dysentery/demo/946

I first wrote a full version of the game, and along the way optimizing, and reducing the code to be smaller and smaller, I stripped off ‘nice-to-have’ features without making the game too boring. It took me around 8 hours from sketching out the game on paper to submitting the entry to the contest. The most fun part is when I have to look for ways to optimize the code into a definitely unreadable version.

Original code:

/*
 GAME: Tactical 8-ball Pool
 --------------------------

 Author: Totti Anh Nguyen (http://iamtotti.com)
 Controls:
  - Move mouse to aim.
  - Left mouse click to shoot.
*/

for(p in a) a[p[d = o = V = W = 0]+(p[6]||'')]=a[p];
u=50;		// unit

X=w=c.width=667;
h=c.height=377;

v=w/2;		// half width (center)
P=v-10;
Q=v+10;
G=8.5;
D=70;		// max speed
z='#';

// level : b
M=Math;
p=M.abs;

// Table boundaries
R = 621;
// Left = Top
T = 57;
B = 331;
setInterval(function(){
	// we'll need the context a lot
  with(a){

	A=function(c,x,y,r,_){
      ba();
	  // a is the abbreviated form of arc
      a(x,y,r,0,7,0);

	  (_&&s())||(strokeStyle=fillStyle=z+'900fff000'.substr(c*3,3),f());
    };

	C=function(c,x,y,w,h){fillStyle=z+c;fc(x,y,w,h)};
	Y=function(q,w,e,r,t){ba();m(q,w);l(e,r);l(t,w);s();};

	// draw the pool table
	C('4c1f22',0, 0, w, h);
	C('0e617f',32, 32, 603, 313);

	// Table Inner
	C('07516c',43, 43, 581, 291);

	// Table Shadow
	C(r='139ebd',46, 46, 575, 285);

	lineWidth=30;
	lineJoin="bevel";
	strokeStyle = '#'+r;

	// Draw top-center gate
	Y(P,t=67,v,37,Q);

	// Draw bottom-center gate
	Y(P,306,v,343,Q);

	// Draw top-left gates
	Y(61,64,35,35,t);

	// Draw bottom-left gates
	Y(61,313,35,341,70);

	// Draw top-right gates
	Y(600,62,631,37,607);

	// Draw bottom-right gates
	Y(601,313,631,341,606);

	// Draw holes
	H = 18;

	// Two on the left
	A(2,35,35,H);
	A(2,35,342,H);

	// Two on the right
	A(2,632,35, H);
	A(2,632,342, H);

	// Two in the middle
	O=14.5;
	A(2,v, 28, O);
	A(2,v, 349, O);

	// Draw Score
	font = "90px Arial";
	fillStyle = g = "rgba(0,0,0,0.5)";
	fx(o, v, h/2);

	if(X){
		X = 0;

		// Init E balls
		for (E = [], x=b=0; b < 9 && b < o+2; ++b) {
			x += 60;
			y = M.random()*x + 20;

			y=y>B?B-9:y<T?T+9:y;
			x=x>R?R-9:x<T?T+9:x;

			E.push({x:x,y:y});
		}
	}
	else {
		// Falling to Holes detection
		if ((x < t || x > 611 || (x > P && x < Q+6)) && (y < t || y > 321))
			X = 1,
			d = o = 0;

		// Ball collision detection
		for(i = 0; i < b-1; ++i) {
			r = E[i];
			// (x > r.x - 13) && (x < r.x + 13) && (y > r.y - 13) && (y < r.y + 13)){
			if (p(x-r.x) < 13 && p(y-r.y) < 13){
				o = i ? 0 : o+1;
				X = 1;
				d = 0;
			}
			// Draw E ball
			A(i ? 2 : 0, r.x-6, r.y-6, G);
		}
	}

	// ~~ Math.floor
	d -= M.max(~~(d/10), .2);

	// Draw white ball
	A(1, x-6, y-6, X ? 7 : G);

	// If balls moving
	if (d > 0) {

		// Draw running balls
		if (K)
			x += F ? d : -d,
			y = k * x + n;
		else
			y += F ? d : -d,
			x = (y - n) / k;

		// Check boundaries
		// Bottom
		if (y > B) N(0,B);
		// Top
		else if (y < T) N(0,T);
		// Right
		else if (x > R) N(R);
		// Left
		else if (x < T) N(T);

	}
	else
		// Draw the aiming lines to shot
		if (V > T && V < R &&
			W > T && W < B)
			lineWidth=1,

			// Expected ball
			fillStyle = g,
			A(3, V-6, W-6, G, 1),

			// Direction line
			m(V-6, W-6),
			l(x-6, y-6),
			s();
  }
}, u);

/**
*	Reflect ball against wall
*/
function N(i,j){
	// Update line scope
	// Reflection happens
	// Reset starting position
	Z = i ? (U=I, j = k*i + n, 2*j - J) : (i = (j - n) / k, U = 2*i - I, J);

	S(I = x = i, J = y = j);
}

function S(x, y) {
	// Figure out where to go ?
	k = (y - Z) / (x - U);
	n = y - k*x;
	F = (K = p(U-x) > p(Z-y)) ? U > x : Z > y;
}

onmouseup=function(e){
	if (d <= 0)

		// Power
		d = e.altKey ? D : D/2,

		// Target
		U = e.pageX,
		Z = e.pageY,

		// Compute line scope
		S(I = x, J = y);

}

// Track mouse position when aiming
onmousemove=function(e){
	V = e.pageX;
	W = e.pageY;
}

And this is the final version of 1024 bytes:

for(p in a)a[p[b=d=o=0]+(p[6]||'')]=a[p];w=c.width=550;h=c.height=310;M=Math;p=M.abs;R=522;t=63;T=43;V=W=B=282;setInterval(function(){with(a){A=function(c,y,x,r){ba();c>9?stroke(m(c,x),l(y,r)):(strokeStyle=fillStyle='#'+'9000fff3119b'.substr(c,3),c>5?fc(x,x,y,r):f(a(X=x||X,y,G=r||G,0,7,0)))};A(7,w,0,h);A(9,487,32,246);A(1,u,u,18);A(1,v=w/2);A(1,u,q=516);A(1,v);A(1,28,v,O=15);A(1,B);if(!b)for(E=[],x=0;b<o+2&&b<8;++b)x+=t,y=M.random()*h,y=y>B?B-9:y<T?T:y,E[b]={x:x,y:y};for(i=0;i<b-1;++i)r=E[i],p(x-r.x)<O&&p(y-r.y)<O?(o=i?0:o+1,b=d=0):A(i?0:2,r.y-7,r.x-7,8);(x<t||x>q||(x>268&&x<288))&&(y<t||y>274)&&d>0?(b=d=o=0):y>B?N(0,B):y<T?N(0,T):x>R?N(R):x<T?N(T):0;d-=M.max(~~(d/9),.2);A(4,g=y-7,z=x-7);fx(o,v,t);d>0?(K?(x+=F?d:-d,y=k*x+n):(y+=F?d:-d,x=(y-n)/k)):A(C=V-7,z,D=W-7,g,A(4,D,C))}},u=35);function N(s,e){Z=s?(U=I,e=k*s+n,2*e-J):(s=(e-n)/k,U=2*s-I,J);S(x=s,y=e)}function S(){I=x,J=y,k=(y-Z)/(x-U),n=y-k*x,F=(K=p(U-x)>p(Z-y))?U>x:Z>y}onmousemove=function(j){V=j.pageX;W=j.pageY};onmouseup=function(){d<=0&&S(d=u,U=V,Z=W)}

The entry did not make it to the top 10 list but this is really a good fresh hacking day on Javascript for me. I learned a lot, and I would recommend attend such an event as you never have a chance to hack out such an insane code at work :)

Cheers,

, , , ,