Deep Space Defense
A JS1K entry.
Position your destructo-bots so that none of the invaders reach the
right side of the screen.
z, x, and c keys select a bot, clicking moves your bot.
Now and then, a 'big' invader will be spawned, which doesn't die from
just one shot, but has to be lasered for about a second. Make your bot
move along with it, or surround it by more than one bot, to kill it.
The code looks like this (every byte has a reason):
c=document.body.children[0];w=c.width=800;h=c.height=300;function
S(i,x,y){for(r=0;r<10;r++)for(k=0;k<7;k++)
' p $8\0>>IIw6>>>6k'
.charCodeAt(i*10+r)&1<<k&&C.F(x+r*2-4,y+k*2-7,2,2)}M=Math;R=M.random;O='';e=[];
t=[];l=1;X=[];for(a=K=i=Q=0;i<99;i++)X[i]=[R()*w,R()*h],i<3&&(t[i]={X:400,Y:i*99
+52});setInterval("with(C=c.getContext('2d')){C.F=fillRect;lineWidth=3;
W=strokeStyle='#fff';J=++Q%25;R()<(K+60)/2e3&&e.push({x:0,y:R()*280+9|0,J:J?1:30
,s:J?Q%4?Q%2:2:3});C[L='fillStyle']='#000';F(0,0,w,h);C[L]=W;for(i in X)F((X[i]
[0]+Q)%w,X[i][1],1+i%2,2);fillText('Score: '+K+O,4,12);for(i in t)with(t[i]){a&&
(X+=X-a<-1?2:X-a>1?-2:0,Y+=Y-b<-1?2:Y-b>1?-2:0);C[L]=i^l?'#bbf':'#ff7';S(4,X,Y);
for(j in e)with(e[j]){+i||(x+=s^2?3:5,C[L]='#fd7',S(s,x,y));x>w&&(O=' GAME OVER'
,e.splice(j,1))||M.abs(X-x)<28&&M.abs(Y-y)<28&&(beginPath(),moveTo(x,y),lineTo(X,
Y),stroke(),--J||(e.splice(j,1),O||K++))}}}",50);onclick=function(e){t[l].a=
e.pageX;t[l].b=e.pageY};onkeydown=function(e){u=e.keyCode;l=u^90?u^67?1:2:0}
And a more readable version, with comments explaining what is going on:
// All compression was done manually, this is a rewrite that gives the
// variables sane names, adds whitespace, and explains some of the tricks.
// .getElementById('c') would have been the sane, safe way to get the
// canvas element, but this is sligtly shorter.
canvas = document.body.children[0];
// Resize the canvas, storing width and height for later use.
width = canvas.width = 800;
height = canvas.height = 300;
// Renders ship type i at x,y
function ship(i, x, y) {
// Loop over the pixels in the image.
for (px = 0; px < 10; px++)
for (py = 0; py < 7; py++)
// This is a string containing 5 10x7 pixel graphics. One
// character encodes a single 7-pixel column. This always leaves
// bit 8 at 0, so that each character stays in a single UTF-8 byte.
' p $8\0>>IIw6>>>6k'
// Here we extract the bit we need for the current pixel.
.charCodeAt(i * 10 + px) & 1<<py
// And if it is non-zero, we go ahead and draw it, subtracting a
// few units of the coords in order to center the image on the
// given x,y. (context.F is context.fillRect)
&& context.F(x + px * 2 - 4 , y + py * 2 - 7, 2, 2)
}
// Some short-hands for Math and Math.random.
M=Math;R=M.random;
// This string keeps track of game-over status. It is set to a
// non-empty value when an invader reaches the other side.
gameOver='';
// Arrays of invaders, bots, and stars.
invaders=[];bots=[];stars=[];
// Currently selected bot.
cur=1;
// Initialize the stars and bots.
// a, score, and time are initialized here because it saves a few
// bytes compared to regular, sane initialization.
// a is a screwy global that is needed later on, time is used to
// scroll the starfield and randomize spawned invaders.
for (i=a=score=time=0; i < 99; i++)
// Use an array to store [x,y], since it is slightly smaller than an
// object literal.
stars[i] = [R() * width, R() * height],
// If i<3, also initialize a bot for this i, using i to determine
// the y position
// X and Y are used here, and x and y for invaders, so that both
// can be with-ed in the same scope.
i < 3 && (bots[i] = {X: 400, Y: i*99+52});
// The interval code was a string, but is made into a function here to
// allow newlines to be used inside of it.
setInterval(function() {
with(context = canvas.getContext('2d')) {
// Create a shortcut for fillRect
context.F = fillRect;
// Fatter lines for more awesome laser effects
lineWidth = 3;
// Lasers are the only strokes we use, and they are white. Save
// the color in W for later.
W = strokeStyle = '#fff';
// Update the time, and if it is %25, save that for later.
bigInvader = ++time % 25;
// Magic random formula based on the score, so that the chance
// that this true gets bigger when score is higher.
R() < (score + 60) / 2e3
// If the above is true, spawn an invader. |0 is a way to round the y.
// H is invader health, T is invader type. Note that bigInvader is
// 0 (false) when a big invader is being spawned.
// Regular enemies get 1 health, meaning they die as soon as they
// are shot. 'big' invaders get 30, and have to be lasered for 30
// frames.
// The conditional under T uses more modulo-time magic to
// determine whether this is a fast invader, and if not, whether
// it is a type-0 or type-1 regular invader (those just have
// different graphics). As seen above, one in 25 spawned enemies
// is 'big'. One in 4 is fast.
&& invaders.push({x: 0, y: R() * 280 + 9|0,
H: bigInvader ? 1 : 30,
T: bigInvader ? time%4 ? time%2 : 2 : 3});
// Set fillstyle to black, save fillStyle word for later.
context[L='fillStyle'] = '#000';
// Clear the screen (F is fillRect)
F(0, 0, width, height);
// Back to white (to draw the stars and status text)
context[L] = W;
for (i in stars)
// The + time % width part causes the scrolling.
// The i%2 is used to create bigger and smaller stars.
F((stars[i][0] + time) % width, stars[i][1], 1+i%2, 2);
// Since gameOver is a string, it can just be appended to the text
// here.
fillText('Score: ' + score + gameOver, 4, 12);
// Go over all the bots
for (i in bots)
with (bots[i]) {
// Only try to move the bot if it has its a (goal x) set.
// Otherwise, this will see the global a, which is 0.
a&&
// Unfortunately, assignments always need parentheses when
// put after && or ||.
// Compare current x,y to target x,y. Update if necessary.
(X += X - a < -1 ? 2 : X-a > 1 ? -2 : 0,
Y += Y - b <- 1 ? 2 : Y-b > 1 ? -2 : 0);
// Set the fillStyle based on whether this bot is selected.
// i^cur (exclusive or) returns 0 only if i==l, and it's shorter.
context[L] = i^cur ? '#bbf' : '#ff7';
// Draw the bot (bot graphic lives at position 4 in the bitmap)
ship(4, X, Y);
// Go over all invaders (note that we are still in the bot loop)
for (j in invaders)
with (invaders[j]) {
// If i=='0' (+ converts to integer, || is used as if(!..)
+i ||
// Move invader. Type 2 invaders are faster.
(x += T^2 ? 3 : 5,
// Fill color
context[L] = '#fd7',
// Draw the invader. T corresponds to the bitmap position.
ship(T, x, y));
// If this one has reached the right side of the screen.
x > width &&
// Set the gameOver flag
(gameOver = ' GAME OVER',
// Remove this invader from the array
invaders.splice(j, 1))
// else (not reached right side)
||
// if the distance (on either x or y) between this
// invader and the current bot is less than 28
M.abs(X - x) < 28 && M.abs(Y - y) < 28 &&
// Draw the laser beam
(beginPath(), moveTo(x, y), lineTo(X, Y), stroke(),
// Subtract 1 hit point. If that was the last, remove
// this invader and update the score if not gameOver.
--H || (invaders.splice(j, 1), gameOver || score++))
}
}
}
}, 50); // 20 frames per second
// Set the current bot's target x,y based on the mouse position
onclick = function(event) {
bots[cur].a = event.pageX;
bots[cur].b = event.pageY;
};
// Select a bot based on the cursor code (pressing
// anything other than z or c will select the middle bot)
onkeydown = function(event) {
code = event.keyCode;
cur = code^90 ? code^67 ? 1 : 2 : 0;
}