Defend your heart against waves of skulls by building a maze of towers but without blocking the way completely. Regular towers cost $2 and attack one enemy at a time but you can buy two upgrades to double their power ($3) and hit all enemies near by ($6). Letting eight skulls into your heart ends the game.
Source: “readable”, minified, compressed.
The demo code is minified in two steps. First, one script replaces variable names longer than one byte and removes whitespace and comments. Then, a packing script replaces commonly used substrings with characters that don't appear in the minified program. The dictionary and code required to unpack the source is included in the final demo. The final version was created with First Crush but I used my own script for testing that displays more information about what's been replaced and I added one additional optimization from JSCrush by hand.
The path that the enemies take is determined by the placement of the towers and has to be calculated again each time a tower is added or removed. The genMap function implements a simplified version of Dijkstra's algorithm. Starting from the heart, it does a breadth-first search for the shortest way to every square on the board. The resulting directions for the creeps are stored in dir. Each element in this array points to the next square on the shortest way to the heart (e.g. {x:0,y:1} for “walk east”).
For every enemy, the game remembers only the position on the board. To animate the movement, the direction is added to the position multiplied by the current animation step. After 32 steps, the enemy is moved to the next square. That's why an enemy jumps if you put a tower directly in front of it. Its position on the board doesn't change but it has to walk a different path.
In every step, a list is generated for each tower of enemies that are within a Euclidean distance of 2 squares or less. Regular towers shoot only at the first in the list while upgraded towers hit all.
The size of the minified demo is 1566 bytes that's 542 more than in the final version which also includes the code for unpacking. To optimize the demo for packing requires to things: reusing as many strings as possible while using as few characters as possible. For instance, shift is both the Array function and part of shiftKey. Using the shift key for selling towers was cheap because I already used shift/pop for my search queue. Here are some more examples for packer-friendly programmingâ„˘.
Even with a great packer you still have to use Javascript in creative and rule-bending ways. Here are some of my favorite insanities.
if(arr.length==0) doStuff() ⇒ if(!(''+arr)) doStuff() ⇒ ''+arr || doStuff()Depending on the contents of the array checking for the first array element is also possible.
if(x >= 0 && x < 16 && y >= 0 && y < 16) doStuff()Because 16 is a power of 2, we can do some bit shifting and reduce the test for the upper bound to “is there a 1 left of the 4 lowest bits?”.
if(x >= 0 && !(x>>4) && y >= 0 && !(y>>4)) doStuff()Fortunately, the same test works for the lower bound because negative numbers are stored in two's complement which means that we can ditch the original lower bound check.
if(!(x>>4) && !(y>>4)) doStuff() ⇒ if(!(x>>4 || y>>4)) doStuff()What we are now asking is “is there a 1 left of the 4 lowest bits in x or y?“ which can be replaced by “is there a 1 left of the 4 lowest bits in x | y?“.
if(!((x|y)>>4)) doStuff() ⇒ (x|y)>>4 || doStuff()
for(i=2;2+i--;) x = i%2, y = (i+1)%2;However, y can be replaced by -y unless you care for the order in which the squares are iterated. Again knowing the two's complement helps reducing the code further.
-(i+1)%2 ⇒ (-i-1)%2 ⇒ ~i%2
f = PROGRAM; for(i=0;g=CHARS[i++];) e = f.split(g), f = e.join(e.pop()); eval(f)I changed it to iterate directly over the properties of the string, which are the indexes of the letters. Fortunately, it works like iterating an array with the indexes in order and the string methods ignored.
f = PROGRAM; for(i in g=CHARS) e = f.split(g[i]), f = e.join(e.pop()); eval(f)The improvement is already included in the current version of First Crush. I also added added an optimization from Aivo Paas' JSCrush to get rid of one more byte.
f = PROGRAM; for(i in g=CHARS) with(f.split(g[i])) f = join(pop()); eval(f)