JS1K X part 2: Nyan Cat 2.0

My second entry for JS1K was an animation: Nyan Cat 2.0.

Why?

So, if I already made one back in 2014, and even wrote a blog post about it, why would I do it again? Well, for me it was really frustrating not to be able to find a way add the twinkling stars in the background.

Comparing Nyan Cat GIF and JS

It took me quite a lot of time to get that starless version down to 1Kb back then, so I just gave up, but from time to time I still thought about it… until one day I had an Eureka moment.

A more efficient encoding

As I described in the 2014 Nyan Cat entry blog post, there sprites were compressed using a run-length encoding system, so a string like AABCCC (which in that case would be “2 cells of the first color, 1 of the second, and 3 of the third”) was compressed to A3BC4.

In this particular animation, the different parts have rather small amount of colors; for instance, the cat’s head only has 6 different colors, while the legs and tail have 3, and this characteristic allows a quite interesting trick: encoding the color and the amount on a single character.

So, let’s say that our basic colors are ABCDEF; that means that we want to paint a single cell with each of those colors; then, the next 6 characters, GHIJKL, are used to represent two cells; then, the next 6 (MNOPQR) three cells, and so on. So, if we had AABCCC, it would be compressed to GBO, 2 bytes smaller. Success!

Not only did it make the data more compact (around 80 bytes), but also the new sprite drawing function ended up being 40 bytes smaller. So now I had some wiggle room in order to add those stars.

What else?

In the previous Nyan Cat blog post I predicted that the stars would take somewhere between 200 and 400 bytes… in the end it was 234 bytes before compression, so quite close.

So, I had to roll up my sleeves and find some additional optimizations; an easy one was to change the cat body, as it was using some custom rectangle fillings in order to make it a perfect copy of the one in the original GIF animation; instead, I made a very close aproximation using the same sprite painter function that it’s used everywhere else, while the other ones mostly involved renaming things around in order to increase RegPack’s efficiency.

Commented source code

b=0;

// Color picker
P=(z,x,d,w,s)=>{
  c.fillStyle="#"+"036#000#999#f99#fff#000#fc9#f9f#f39#f00#f90#ff0#3f0#09f#63f".split("#")[z]
};

// Sprite painter
S=(z,x,d,w,s)=>{
  for(i=0,g="";z[i];i++)
    o=z.charCodeAt(i)-36,g+=Array(2+o/5|0).join(o%5);
  z=g.split("");
  for(i=0,g="";z[i];i++)(v=+z[i])&&(P(v+s),c.fillRect(x+i%w*6,6*(i/w|0)+d,6,6))
};

// Function that paints the star
E=(z,x,d,w,s)=>{
  S("~8%#t%8%$%8%#Q%=%3*$*3%=%#.%=%Q*$%$*Q%=%#.%3%.%G%8%G%.%3%#.%t%8%t%".split("#")[(z+b)%12%6],(w+b)%12*-38+x+224+146,146+d+86,7,3)
};

setInterval((z,x,d,w,s)=>{
  // Paint rainbow
  for(i=0,g=34,P(0),c.fillRect(0,0,999,999),d=2&b;g<999;g+=46,d=!d)
    for(i=0,g=g;6>i;i++)
	  P(9+i),
	  c.fillRect(-(2&b)*3-g+146,146+d+18*i+4*d-3,46,18);
  f=b%6,
 
  // Paint the stars
  E(3,0,-202,0),
  E(1,6,-129,10),
  E(1,0,-243,4),
  E(2,19,127,5),
  E(4,6,0,7),
  E(13-2*b,27,70,0),

  d=7*(f>1),

  // Paint the cat body parts
  S("4)%+*$*+*$*+%)*+./8%#B*.%+%)%+/$%5)*+3*#~3%)>5%0*$4#o*)*+$%5%+4+%.*#G4)%095%)4&8*#$*.%+/$%5)*+3*".split("#")[f],-36-6*(f==4)+146,146+d+33,1*(f==4)+6,0),
  S("L9$%:$%+*)4#o%5)%+%./#t%0.%+%./#o%5)%+%./#)9$C04+%./#)9$%:%04+%./".split("#")[f],-18+146,146+d+81,7,0),
  S(")%+%8%+%$%+%3/=/)/#)%+%8%+%$%+%./=/)/#.%+%8%+%$%+%./=/)/#)%+%8%+%$%+%./=/)/#%+%8%+%$%+%./=/)/#%+%8%+%$%+%./=/)/".split("#")[f],11+146,146+d+99,21,0),
  S(")u.%v%$%0c0*+@(,(;+*&,(h&*&h(,&*&w&*&@(T&*&w&*&1(m%&E(Y%&'(w%&|'%&;(c%+'(@(O%0c0/v%.u)",+146,146+d+-9,21,4),
  S(")*G*.%+%=%+%)%0%3%0%)%545%)%]%$%g*0(%:(%+*0*0%&*+*&,N,*&,&%+%+%&,%$%0C+%.%S%8R",66-6*(f>3||!f)+146,146+d+21-7*(f==5),16,0),b=(1+b)%12
},70)

Game Over

Sadly, JS1K is now over. This competition was great to motivate myself to create these tiny games and demos, and I was able to work on most of the ideas I had… well, at some point I wanted to build a shoot’em up game, but other than that, I’m satisfied with the work I created. I participated for the challenge and the learning, and certainly entertained me; gave me a excuse to create a plasma effect, a 3D renderer, cat animations, and even some games.

What now? Well, I’ve been thinking about participating in JS13K, but that may be a major time commitment, considering how long it took me to complete some of the single-kilobyte entries… Still, would be nice to do it at least once. Maybe 2020!!