What I learnt from my JS1K entry

JS1K logo

I recently endeavoured to take part in the JavaScript challenge website JS1K. Basically submit something that is 1024 bytes or less. Some of the entries are quite astounding. For me it’s rare to have the chance to build a game or animation, something out of my comfort zone. I set about with my own idea.

After having completed it in what I thought was enough characters I was met with dismay when I realised it was 1,900 bytes. Around 900 bytes over the limit.
Needless to say, I was devastated, but before I decided to throw in the towel and mark the competition impossible I thought I’d have a try at optimising my code. Could not hurt to try. Who knows right?

On the first iteration I improved it by 100 bytes. I was relatively pleased. How had I missed this first time I wondered.
On the second iteration another 100 bytes. Again I felt pleased and somewhat confused.
After the third I managed to improve 200 bytes at once. I had now removed a total of 400 of the 900 bytes and was beginning to realise the skill in this competition.
It’s not just the program you write, but also your desire to ITERATE over your own code a dozen times (or more if it takes it) until you have optimised and thought through every single character. So that’s what I did. Unscrupulously going over every line again and again until it was enough, I got to 994 bytes. Removing almost 1000 bytes.
The end result is below:

for(MR=Math.random,MI=(()=>2*Math.PI),cht=innerHeight,cwh=innerWidth,S=(c=>c.stroke()||c),bp=((c,e)=>(c.beginPath(),e&&(c.fillStyle=e),c)),d=3,np=cht*cwh/(100*d),cA=[],crA=["#00f","#6f77ec"],B=((e,r,t,i,l)=>{var y=crA[~~(2*MR())];return()=>{e+=t,r+=i,c=bp(c,y),c.arc(e,r,l,0,MI(),!1),c.fill()}}),C=(()=>{for(requestAnimationFrame(C),c.fillStyle="black",c.fillRect(0,0,cwh,cht),r=150,eR=20,mA=90,c=bp(c,"yellow"),c.arc(cX,cY,r,0,MI(),!1),c.fill(),c.lineWidth=5,c.strokeStyle="black",c=S(c),c=bp(c,"black"),c.arc(eyX,eyY,eR,0,MI(),!1),eT=cX+eX,c.arc(eT,eyY,eR,0,MI(),!1),c.fill(),c=bp(c),c.arc(cX,cY,mA,0,Math.PI,!1),c=S(c),xL=xS+100,yL=yS+100,bX!=xL&&bX++,bY!=yL&&bY++,c=bp(c),c.lineWidth=10,c.strokeStyle="#A00",c.moveTo(xS,yS),c.arc(bX,bY,3,0,MI()),c=S(c),i=1;i<cA.length;i++)cA[i]()}),eX=55,cY=cht/2,cX=cwh/2,eyX=cX-eX,eyY=cY-eX,xS=eyX-50,yS=eyY-50,bX=xS,bY=yS,cA=[],X=(()=>.5*(MR()-.5)),Y=(c=>MR()*(c-2*r)),i=1;i<np;i++)r=3*MR()+1,x=Y(cwh),y=Y(cht),dx=X(),dy=X(),cA.push(B(x,y,dx,dy,r));C();

Hard to read eh, the link to my animation is https://js1k.com/2018-coins/demo/3038.
Lets go over the lessons that I took from this experience.

  1. Pair Uglify minification and human minification
    Using the mangle in Uglify was surprisingly not enough, several variables and functions were not as slim as I felt they could be. I manually made sure I have used single (or double) character names for absolutely everything.
  2. Helpers are all about balance and testing
    I was calling 1 long method name 4 times and another method name twice. I tested and realised the optimal situation was having a helper for the first one and no helper for the other one. Also native functions called a lot can be cached/made a helper e.g MR = Math.random.
  3. Reverse the “abstracting for readability” rule
    I had first created functions for reuse and readability but by reversing it and moving everything into a single function (except 1 which was recursive) I gained a substantial character saving.
  4. Love the comments
    Now that code is becoming a blur of characters its incredibly important to keep a hold of whats happening via comments. What is usually a bad-to-iffy practise is now the only way of keeping a visualisation of what is going on, and applying any necessary fixes. When you come back to this mess of characters they prove most helpful.
  5. ES2015 is your friend
    Features such as Arrow functions can help shave several bytes (ie. ‘function’ vs ‘() =>’)
  6. Use piping and implicit return
    It’s possible (via ES2015) to call a function and return a value all in a single line.
    e.g myFunc=()=> callThis() || ‘return this’.
    As long as callThis() returns falsely, which it can if I am disregarding it’s return.
  7. Globals: my enemies enemy
    I hate to use globals, they are plain wrong and have all sorts of side-effects. But here I realised I didn’t mind if the variables were global and having no var/let/const saved an unexpected amount of space.
  8. Shorthand everything
    Many things can be written in shorthand. A couple I made us of:
    - Hex code 0000FF became 00F
    - Double bitwise of Math.floor(x) became~~(x).
    - Removed the curly brackets on single-line functions. e.g if (x) y++
  9. Repeatedly consider every single line and character of code
    Very general but basically you never know where an improvement will come from, by being disciplined and iterating over your code, wins coming in small batches all add up.
  10. Learn from the community
    JS1K offers the ability for people to submit un-minified source code (with their entry), the ones that do offer you a chance to learn techniques that others have deployed. Some are very insightful and can be useful for this challenge or in general.

To sum up

So I know that the code I normally write is optimised for humans more than machines (most of the time). Many JS runtimes come with optimisations that are so far beyond anything I can add that its often not worth bothering. However with this challenge I found it a real pleasure in having to do it manually myself. Using skills from this areas (as well as Canvas/WebGL API’s) is too rare an occurrence and I look forward to doing it again.