Create a gist now

Instantly share code, notes, and snippets.

Fuse - JS1K
  ____ __ __ ____ ____
/  __|  |  | ___/ ___| 
|  __|  |  |__  | __) 
|_|  `_____|____/____/  JS1K

FUSE

Spread the love by absorbing all the positive energy around you.

In this simple game you control an energy ball through seven levels of love.

The goal is to fuse with the warm glowing blobs. But beware: Others might grow to big and turn into negative energy.

Demo

Play it here: http://js1k.com/2012-love/demo/1254

Instructions

  • click to accelerate movement
  • collect warm (smaller) blobs
  • avoid negative (bigger) energy
  • fuse into a single giant ball
  • master all seven levels

Features

  • seven stages from easy to hard
  • simple and unique game play
  • smooth graphics, fonts and transitions

About

Created by Martin Kleppe for the JS1K 2012 competition. The object of this competition is to create a cool JavaScript "application" no larger than 1024 bytes. Feel free to follow me on Twitter (@aemkei).

Source

The final script: ./crushed.js

_="for(i in a)a�[3]+i[6]]=a�ZjL=�S=c.height=c.wid�=60��n=�f=j[],k'WiDa7LLB`@`^?
1WA@YAh8;rL1YKCEAxUCE?+AJuA{AI1W7@5C+K_CBmi=}>1MLB8UPMAV7CHFUUu9?1WKFu/i
UI1uM?VKCABTAW-DWA1V_C2A9iCmAuiErAx7?rA<7?'�plit(1)[L] i�i<zleng�;)f�zcharCo
deAt(i�)-32 i�f�ZP=B[0])B.push({x�*6�y�*6�s�*1�X�-3,Y�-3})},setInterval(�if(!B){k(i-
27�/3�$2��,�font='3em Impact',�'+�O%360�'+(e-�)+'�,�lx(L<8?L?'ιQvQι '+L:'  fυsQ':'τ
hQ eηd',24�i-=L<8),e<0&&n();return}$4�� f=i�b=B��Z)wi�(b){x<0^x>SX�y<0^y>SY�x�X,y�Y
e�l=B[e�Z)l!=b&s>0&#>00|Ma��qrt(G*G+N*N)-s-#)<0#>s?(#��,s-=�):(s��,#-=�));s>0�k�aR(x
,y,�x,y,s),c=s>P�?200:340�4�'�1+'�)�5�.1*Ma��in(�32��1�)�97��')�,f�)}0>P�?(j��:f==1L�,j��},3�,onmo
usedown=�P.X�(P.x�X)/O�P.Y�(P.y�Y)/O0}�,�'+c�����,zaddColorStop(.�,�lc(��S,S)�:f��]�function(
e){�0,�0%, ;for(��fillStylk&&(�.s��i=32�new Date/�+',O�5��+(b==P)*�+=�=0;�-l.�-zpage�++�0)
�*=-1),�'hsla(�[i�th�)'�a.�.3#l�$����.G(x�x)N(y�y)O10QεZ];jB=ke=ze.";for(Y=0;$="zkjZQO
NG$#������������������� ��������"[Y++];)with(_.split($))_=join(pop());eval(_)

Code Minifications

Last year I gave some talks about JS Golfing Lessons and Binary Love with many tricks how to save bytes in JavaScript.

But for this project I skipped most of the rules and did some code optimization on a "meta" level.

To dramatically reduce the file size, I've used JSCrunsh where the main rule is: Repeat yourself! That genius tool replaces multiple occourences in your code with a singe character, so no need to assign Math to a variable and reuse this across the project.

You'll see eg. f[i++], 240 or function(e){ many times around the code. And even if there is no argument "e" for the function statement, let's add it, because it will be swapped with the same placeholder as all other functions.

To keep track of my manual optimisations I created a little Node.js script that can be executed from the command line:

$ node zip.js

The longest statements were the method calls to the canvas object. To shrink them down to a two letter shortcut I used a modified version of Marijn Haverbeke's mechanized abbreviation:

for(i in a)a[i[3]+i[6]]=a[i]

Another heavy part was the level data. I had to store the position, size and movement for each blob in each level. Initially it wasted about 300 characters but I managed to write a simple tool that encoded the data into a custom format where two integers are replaces by a single character. The decoder itself is only about 50 bytes:

f="",e="WiDa7LLB`@`^?1WA@YAh8;rL".split(1)[L]
for(i=0;i<e.length;)f+=e.charCodeAt(i++)-32

More Stuff

See the list of my entries for 140byt.es including Binary Tetris, the JS Community Logo, Game of Life, Rubik's Cube Solver and Minesweeper.

Acknowledgement

Thanks to the 140byt.es community and it's Wiki, Uglify.js for saving the first bytes, Marijn Haverbeke for his canvas shortcut tricks, Aivo Paas for his insane JSCrush Tool, my company Ubilabs for the great feedback, JSBin for the hacking environment and Astrid for her patience.

_="for(i in a)a[3]+i[6]]=aZjL=S=c.height=c.wid=60n=f=j[],k'WiDa7LLB`@`^?1WA@YAh8;rL1YKCEAxUCE?+AJuA{AI1W7@5C+K_CBmi=}>1MLB8UPMAV7CHFUUu9?1WKFu/iUI1uM?VKCABTAW-DWA1V_C2A9iCmAuiErAx7?rA<7?'plit(1)[L] ii<zleng;)fzcharCodeAt(i)-32 ifZP=B[0])B.push({x*6y*6s*1X-3,Y-3})},setInterval(if(!B){k(i-27/3$2,font='3em Impact','+O%360'+(e-)+',lx(L<8?L?'ιQvQι '+L:' fυsQ':'τhQ eηd',24i-=L<8),e<0&&n();return}$4 f=ib=BZ)wi(b){x<0^x>S Xy<0^y>S YxX,yY el=B[eZ)l!=b&s>0&#>0 0|Maqrt(G*G+N*N)-s-#)<0 #>s?(#,s-=):(s,#-=));s>0 kaR(x,y,x,y,s),c=s>P?200:3404'1+')5.1*Main(321)97'),f)}0>P?(j:f==1 L,j},3,onmousedown=P.X(P.xX)/OP.Y(P.yY)/O0},'+c ,zaddColorStop(.,lc(S,S):f]function(e){0,0%, ;for( fillStylk &&(.si=32new Date/+',O5+(b==P)*+==0;-l.-zpage++0)*=-1),'hsla(th)'a..3#l$.G(xx)N(yy)O10QεZ];jB=ke=ze.";for(Y=0;$="zkjZQONG$# "[Y++];)with(_.split($))_=join(pop());eval(_)
/*
____ __ __ ____ ____
/ __| | | ___/ ___|
| __| | |__ | __)
|_| `_____|____/____/
FUSE - by @aemkei
Spread the love by absorbing all the positive energy around you.
In this simple game you control an energy ball through seven levels of love.
The goal is to fuse with the warm glowing blobs.
But beware: Others might grow to big and turn into negative energy.
Instructions
* click to accelerate movement
* collect warm (smaller) blobs
* avoid negative (bigger) energy
* fuse into a single giant ball
* master all seven levels
Features
* seven stages from easy to hard
* simple and unique game play
* smooth graphics, fonts and transitions
More info: http://j.mp/1kfuse
*/
/*
* CANVAS MAPPING
* Reassign canvas methods to shortcut.
*/
for (
i in a // find all properties in canvas
) a[
i[3] + i[6] // create shortcut from 4. and 7. char
// "fillRect" => "lc"
// "fillText" => "lx"
// "createRadialGradient" => "aR"
] = a[i] // assign original property to shortcut
/*
* INITIAL SETUP
* Create the canvas and setup game specific variables.
*/
B = L = 0 // clear balls (B) and set level (L)
S = // set screen size to 600 pixel
c.height =
c.width =
600
i = 320 // set initial text position
/*
* NEW LEVEL
* Update the level based on encoded data.
*/
n = function(e) { // next level
// "e" is a placeholder here
f = // "f" is an empty string
B = [] // "B" is the array with all balls
e = // the final level data
"WiDa7LLB`@`^?1WA@YAh8;rL1YKCEAxUCE?+AJuA{AI1W7@5C+K_CBmi=}>1MLB8UPMAV7CHFUUu9?1WKFu/iUI1uM?VKCABTAW-DWA1V_C2A9iCmAuiErAx7?rA<7?"
// encoded level per line
// "1" is uses as a delimiter
// a char = two numberic values
.split(1)[ // convert string to array
L // get current level as string
]
for (
i = 0; // analyse level data
i < e.length;
) f += e.charCodeAt( // put numeric data into string
i++ // convert char into charcode
) - 32 // shift down by 32 to get zeros
for (
i = 0; // analyse numeric level data
f[i]; // values range from 0 to 9
P = B[0] // assign players ball ("P")
) B.push({ // store ball data
x: f[i++] * 60, // x position
y: f[i++] * 60, // y position
s: f[i++] * 10, // ball size
X: f[i++] - 3, // x movement
Y: f[i++] - 3 // y movement
})
}
/*
* MAIN LOOP
* Update the stage in an interval.
*/
setInterval(function(e) { // "e" is used as a placeholder
if (!B) { // if there are no balls ...
e = ( // set alpha value for text
i - 270
) / 30
a.fillStyle = 'hsla(0,0%,0%,.2)' // fadein black stage
a.lc(0, 0, S, S) // "lc" -> fillRect
a.font = '3em Impact' // set font and text color
a.fillStyle = 'hsla(' + new Date/10%360 +',100%,50%,' + (e-.3) + ')'
a.lx( // "lx" -> fillText
L < 8 ?
L ? 'ιεvει ' + L : ' fυsε' : // show level ...
'τhε eηd', // or love if game is over
240,
i-= ( // move up text
L < 8 // .. only if we have an level
)
)
e < 0 && n(); // show next level if text was hidden
return // cancel all other actions
}
a.fillStyle = 'hsla(0,0%,0%,.4)' // fadein black stage
a.lc(0, 0, S, S) // "lc" -> fillRect
for (
f = i = 0; // "f" counts the visible balls
b = B[i++]; // get a ball as long as there is one
) {
with (b){ // remember ball
x < 0 ^ x > S && (X *= -1) // switch x or y direction
y < 0 ^ y > S && (Y *= -1) // ... if ball is out of bounds
x += X // set new position
y += Y // based on current speed
for (
e = 0;
l = B[e++]; // compare with other balls
) l != b & // if they are not the same
s > 0 & l.s > 0 && ( // ... and if they both have a size
0 | Math.sqrt( // convert to integer
(x - l.x) * (x - l.x) + // get distance
(y - l.y) * (y - l.y)
) - s - l.s // ... and subtract the sizes
) < 0 && ( // so if they touch ...
l.s > s ? ( // shrink the smaller
l.s += .3,
s -= .3
) : ( // inflate the bigger
s += .3,
l.s -= .3
)
)
s > 0 && ( // if the ball has a size
a.fillStyle = e = a.aR( // prepare a new radial gradient
x, y, 0, // at the ball's position
x, y, s // with it's size
),
c = s > P.s ? 200 : 340, // make it blue if creater than player
// and add some fancy color stops
e.addColorStop( // fill players ball
.4,
'hsla(' + c + ',100%,50%,' + (b==P)*1 + ')'
),
e.addColorStop( // pulsate players ball
.5 + ((b==P) * .1 * Math.sin( new Date / 320 )),
'hsla(' + c + ',100%,50%,1)'
),
e.addColorStop(
.97, // fade ball to edge
'hsla(' + c + ',100%,50%,0)'
),
a.lc(0, 0, S, S), // call "fillRect" to draw the ball
f++ // increase count for visible balls
)
}
}
0 > P.s ? ( // lose, if players ball if empty
B = 0, // remove all balls
i = 320 // and move text to bottom
) : f == 1 && ( // win, if only one ball is left
L++, // increase level
B = 0, // and reset balls and text position
i = 320
)
}, 30)
/*
* MOUSE INTERACTION
* Move ball based on clicked position.
*/
onmousedown = function(e) { // watch mouse clicks
P.X += (P.x - e.pageX) / 100, // speed up ball
P.Y += (P.y - e.pageY) / 100
}
<!doctype html>
<html>
<head>
<title>JS1k, 1k demo submission [ID]</title>
<meta charset="utf-8" />
</head>
<body>
<canvas id="c"></canvas>
<div id="out"></div>
<script>
var b = document.body;
var c = document.getElementsByTagName('canvas')[0];
var a = c.getContext('2d');
document.body.clientWidth;
</script>
<script src="crushed.js"></script>
</body>
</html>
// $ node zip.js
fs = require("fs")
uglify = require("uglify-js")
function crush(a){
Q=[];for(i=127;--i;i-10&&i-13&&i-34&&i-39&&i-92&&Q.push(
String.fromCharCode(i)));i=a=a.replace(/([\r\n]|^)\s*\/\/.*|[\r\n]+\s*/g,"")
.replace(/\\/g,"\\\\"),B=a.length/2,m="";for(S=encodeURI(i)
.replace(/%../g,"i").length;;m=c+m){for(c=0,i=122;!c&&--i;
!~a.indexOf(Q[i])&&(c=Q[i]));if(!c)break;for(o={},M=N=e=Z=t=0;++t<=B;)
for(i=0;++i<a.length-t;)if(!o[x=a.substr(j=i,t)]&&~(j=a.indexOf(x,j+t)))
for(Z=t,o[x]=1;~j;o[x]++)j=a.indexOf(x,j+t);B=Z;for(i in o){j=encodeURI(i)
.replace(/%../g,"i").length;if(j=(R=o[i])*j-R-j-1)if(j>M||j==M&&R>N)M=j,N=R,
e=i}if(!e)break;a=a.split(e).join(c)+c+e}return c=a.split('"').length<a.
split("'").length?(B='"',/"/g):(B="'",/'/g),"_="+B+a.replace(c,"\\"+B)+B+
";for(Y=0;$="+B+m+B+"[Y++];)with(_.split($))_=join(pop());eval(_)"
}
raw = "(function(){"+fs.readFileSync("./original.js").toString()+"})()"
ugly = uglify(raw, {mangle_options:{ except: ["e"]}}).slice(12,-4)
crushed = crush(ugly.replace(/"/g,"'"))
fs.writeFileSync("./crushed.js", crushed)
function bytes(s){
return encodeURI(s).replace(/%../g, 'i').length + " b"
}
console.log("Raw: "+ bytes(raw))
console.log("Crush: "+ bytes(crushed))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment