I made 7 1k javascript demos in 2 weeks for JS1k!

For the past month I have been doing a deep dive into making tiny javascript programs. It started when I learned the JS1k (JavasScript 1 kilobyte) competition was underway, with the deadline rapidly approaching. I had programmed only a small amount of javascript before, but always wanted to learn more. It’s such a fun language, fast, simple, accessible, and it runs in any modern web browser. Byte for byte I don’t think there’s a better language for “code golf”.

So, I took the plunge and spent 2 weeks working on JS1k entries all day, every day. I learned a lot, had some fun, and made 7 new javascript demos. Read on for an epic postmortem with technical notes!

Links to My JS1k Entries

Code

You can see code for all my entries along with the original source on their JS1k pages. But just to properly appreciate how crazy small 1 kilobyte is, I want to show you the final code for “Queen’s Gambit” in it’s entirety…

// "Queen's Gambit" by Frank Force 2019 (Official Js1k Entry)
for(_='Math.RRabs(QF(P+!P3)O(b-2?NP2)Lor(K-g)J-f)H&&GG(=0n[0>){};onkey?-+=",c.fill),=d.client\\u2650,fKi of p)i.=function(dthis))==b	,f,g,.5:!b|3==b?.8:1,a.widthd.keyCode%37]=a.heightL4:4p.push(new C(,/2,/2,Cd,ke,b.cif(eh2<b,c.font=2*e+"px aStyle=b+41<b?"hsl("+80*N4+b:l/9)+",80%,50%)":"#FFF":"#0005TextNb+4?"740b665be fcd"[3+b]:"588588":m,f-e,g+e/2fd*=f+d>|f+dgk*=g+k>|g+kb&-4<b|1	e*=13<e1>b?.9:.99:0b||(d2]-0],k3]-1],0==l%5G(rH/9,(vJ/918,-1);b(f,g,e,b,)}.ae.bt,u,x,I,yeG0<b&-3<I&1>IGl-I!I&50<l&!Pw)3	dt-f9:9,ku-g9:94	40<++eG0e=25,3,k=(d=L?0:)?0:5	P9)||(tH/8(uJ/8051,d=,k=,QtH<e+x&QuJ<e+x02*e,-3,.a(y.a()}setInterval(Ec();0==++l%50&9<wG--w;9<m--m,h);if(!h)fKi=25*++m,w=50-2*m,p=[],-425,l;--i;)PP25+P9LOO+2/9,-2;h},16);Freturn RfloK1E6*Rrandom(%ddown2up0onmousemoverX;vYp=[],n=[0],m=l=r=v=h=w;';G=/[^ -FIMS-}]/.exec(_);)with(_.split(G))_=join(shift());eval(_)

Post Mortem

The first thing I did was just play JS1k entries from previous years to get a feel for scope size and maybe a few ideas. I am mostly interested in games, here are some of my favorites…

I want to send my gratitude to everyone who shared the original source code for their entries. I learned a ton and would not have been able to make any of this stuff without that help. Because of that I chose to share the original code for all of my entries which you can see if you click “demo details” on any of my Js1k entries.

I’m going talk a little about each one in the order they were made. I used the same tools for all of them: Brackets as my editor, TortoiseSVN for version control, and debugged in Chrome.

Tiny Ski

The first game is a port of a tiny C++ game I recently released. I wanted to start as simple as possible, so it only uses html text rendering by setting innerHTML. The code mostly consists of an update loop, a reset function, and a track generating function. The track is stored in a ring buffer and redrawn each frame. The player is animated by drawing “. .” over itself in the buffer to create the trail. It also keeps the current high score and will show a marker on the track at that distance.

This was my first experience with figuring out a javascript minification pipeline. Minifing javascript code is not as straightforward as it sounds. You may think it’s just about writing smaller code, but there’s more to it because of the way JSCrush works. Writing code that crushes well is tricky, because longer code can end up shrinking smaller if it contains repeats. In fact that’s exactly what you need to do to squeeze code as much as possible.

I used pretty much the same procedure for all of my entries. First I run my original source through Closure Compiler in advanced mode. This does a basic pass on shrinking the code, white space, short variable names, stripping comments, and trivial optimizations. This allows the original source code to stay clean and well documented.

After running through closure there are several things I did manually because Closure doesn’t handle it properly. Any variables that were initialized to the same value can be combined, (ex a=b=0). For loops can usually can be replaced with for(X of A) style loops. I modified all the function signatures to be the same when possible, adding unused “dummy” variables if necessary, so all the functions are crushed together.

Finally I run that code through RegPack, which uses JsCrush compression to shrink the code further, usually saving around 30%. It takes repeated attempts of making small changes and rearranging the code to get it under 1k. There are tools within RegPack to help understand what’s happening. I tried to exploit the compression more in some of my later demos. Nikhil wrote a more in depth explanation of how it works if you are interested.

The Digit Dilemma

My second game is more of a meaty offering, and almost entirely encapsulated in a single function. The bulk of the code is devoted to generating solvable puzzles. It works by starting with a random “solved” puzzle and runs in reverse, kind of like how you would scramble a Rubik’s Cube. Of course it’s a bit trickier because unlike a Rubik’s cube, this game doesn’t work the same way in reverse. There is also a special function C(x,y) that uses a fill routine to check if the player can get from one place to another. This is necessary because puzzles can get crowded on higher levels, cutting off player movement, and it is crucial that a player is never presented with an unsolvable puzzle.

The idea for this game is completely original so I didn’t know if the puzzles would be easy, hard, or even fun at all. After I coded it up and started playing, I was amazed at the depth in strategy necessary for solving some of these puzzles. I came across some that I couldn’t figure out and I wasn’t even sure it they were solvable at all. I ended up writing a special debug tool to solve puzzles for me. This was the only way I could guarantee that really hard puzzles were solvable.

Eventually I was able to get the base code small enough that I could include some extra features. The coolest bonus feature is the ability to rewind time, aka undo. I had been playing Baba Is You so that mechanic was fresh in my mind. In this game it’s kind of essential to prevent player frustration. The undo code is rather simple, just a array values representing the game state. I also exploited the JsCrush compression by reusing the string “rewind[rewind.length-1][i++];” 5 times.

Another nice feature is the animation system. You may not have noticed, but when you push a piece, it has animated movement, rather then instantly moving to the end point. Also, the player can move while other pieces are moving, so it is very responsive. To eliminate the need for an asynchronous update, one trick is the code “!((x+y+frameCount/4)%2”. If you can imagine the game is on a chessboard, this alternates between moving pieces on white tiles and black spaces.

On the design side I made an interesting choice to have the number pieces “stick” into walls. This is because if the numbers stop at walls, it actually makes the puzzle trivially easy in most cases. That mechanic is further explored by introducing wall pieces as elements of the puzzle to add more variety. Another nice design feature is how the puzzle grows in size and complexity each level, acting as a self tutorial.

I’ve continued working on this game and already released a more complete version with full color, mobile/touch support and a few additional mechanics. You can play it here!

Queen’s Gambit

I think this game is my favorite, to make it work I needed to craft a holistic kind of framework. It consists primarily of an actor function/class/object with an update/render and something I called a cross update, where actors can pass themselves to every other actor. The reason I did it this way is because I noticed when you create a new function in javascript, the parameters persist with that instance of the function. It’s like they are data members but without needing to be qualified with this. The downside is there is no way to access them from outside the function, which is why the cross update is necessary.

Everything in the game, including the background is an actor in this framework. This makes the setup and loop super simple. At the beginning of each round, the actor list is cleared. The background and player actors are added. Then enemies are spawned randomly around the play space. A special object is spawned on top of the player to immediately remove any enemies that spawn too close.

Each frame every actor in the list is updated and rendered. Each object type has some special code, like enemy AI and movement that is related to the chess piece they represent. There’s a simple physics system with velocity, damping and collision that runs for all objects. Here’s a look at the physics update code…

// update position, bounce off walls, and apply damping
x += vx *= x+vx > a.width  | x+vx < 0?-.5:(!type | type == 3)?.8:1;
y += vy *= y+vy > a.height | y+vy < 0?-.5:(!type | type == 3)?.8:1;

The mouse input needed to be simplified to fit everything. So instead of a shoot button, the player is just always shooting. I also removed the normalization calculation typically used for aiming. This has the side effect of tying the speed of projectiles to the distance the mouse is from the player. It’s a little strange at first but kind of adds a fun extra nuance to the controls.

I squeezed in a few nice bonus features for this one. There’s a flower explosion effect when enemies are killed and a special transition effect between levels. The stationary number obstacles cycle through a rainbow of colors and also double as an indicator of the current level. Finally, after beating level 9 the game ends with an awesome kill screen. Here’s a short gif showing off the gameplay. I’ve played a lot of Robotron so I hope this is a worthy tribute to one of my favorite games of all time.

Infinite Yin Yangs

This entry is mostly a minified webgl pixel shader slapped on a single triangle (larger then the canvas) and wrapped in javascript code that I cobbled together from other entries. It’s kind of amazing what can be done in a pixel shader. The pixel shader part of the code can be viewed on Shadertoy.

I’ve been playing around with this infinite zooming mechanism for a while and used it to make a wide large variety of shaders. Unfortunately most of them would not fit into a sub 1k footprint. The recursive yin yang is one of the smaller ones and goes way back to a windows screensaver I made in 2010. I eventually ported it over to work in a pixel shader.

This one was was kind of a pain because the the minification pipeline I was using doesn’t work on shader code, so I had to minify it mostly by hand. It was such a tight fit that I had to move everything into a single function. It was satisfying to see the results though. I’d like to talk more about how these infinite zoom shaders work, maybe in a future post.

In addition to being infinity recursive the yin yangs themselves use a special anti-alialasing solution I came up with, you can see an example of here. It uses smoothstep along with careful math to slightly blur the yin yangs.

ZzFX

The javascript AudioContext framework is very powerful and I was interested in exploring it’s ability to generate waveforms. I ended up making the smallest sound fx generator I possibly could. I tried to package it up as clean as possible and released it under the MIT license. You can get the code on my Github page.

The synth part of ZzFX has only 6 parameters: frequency, length, noise, attack, modulation, and modulation phase. These settings allow for a large variety of sound effects. The synth function can be called directly for full control, but the intended use is to call the seeded generator function which takes a single integer value and uses that as a random seed to generate a sound. This is so sounds can be played with a single tiny function call. For example Z(3) plays a heart beat kind of sound. There is also a small amount of frequency randomness built in, so sound can be played repeatedly without sounding bad.

The bulk of the code for this entry is devoted to the frontend UI for exploring ZzFX sound seeds. Whenever the page is reloaded, it is filled with random sound cards. Each sound has a unique seed and visual look to help users remember their favorites. Sounds can be played simply by clicking on their card. Clicking on the ZzFX logo allows users to copy the seed of the most recently played sound, or change it.

I had a lot of fun setting up the visual aesthetic for this and playing around with unicode characters. There’s also a cool effect on the ZzFX logo and the background changes whenever a sound is played. The style is inspired by a combination of 80’s pop art and Jackson Pollock.

ZzFX Sound Seed Explorer Frontend

At the heart of ZzFX is a very tiny seeded random number generator. This one is based on the Xorshift algorithm. I wouldn’t normally recommend doing it with only 2 shifts, but I ran some tests and it seems plenty “random” enough for my purposes.

RandInt=function(max) // random integer between [0-max-1]
{
randSeed^=randSeed<<3;
randSeed^=randSeed>>2;
return max?randSeed%max:0;
}
//R=m=>(r^=r<<3,r^=r>>2,m?r%m:0) // minified RandInt
Sample output from the random number generator (look random enough?)

Here’s the minified code for just the ZzFX sound generator. You can just paste it directly into your javascript programs to play any ZzFX sound with the z function!

// ZzFX - Minified Code (352 bytes) - MIT License - Copyright (c) 2019 - Frank Force
z=e=>{h=R(5E3);s=r;r=e;R(R());e=(R(Z=1E5)+h)/1E6;l=R(Z);m=R(9);g=R(l);n=R(Z)/1E9;p=R(Z);h=[];for(F=f=0;++F&lt;l;f+=1+R(m))h[F]=Math.cos(f*e*Math.cos(f*n+p))*(F&lt;g?F/g:1-(F-g)/(l-g))
F=X.createBuffer(1,Z,Z);F.getChannelData(0).set(h);h=X.createBufferSource();h.buffer=F;h.connect(X.destination);h.start();r=s}
X=new AudioContext;r=0;R=e=>(r^=r&lt;&lt;3,r^=r>>2,r%e)

One Thousand Free Cells

I actually started by making a version of the card game Speed with an AI opponent. Unfortunately after I wrote out most of the code I realized that it was bigger then I hoped. So I pivoted to rework it into a solitaire game which I knew would be possible.

I was shocked that I couldn’t find a version of Freecell in any of the JS1k entries. There have been several nice solitaire variants in the top 10 previously, but I adamantly believe that Freecell is the best for 2 key reasons: Every card is visible from the start and nearly all games are beatable. It’s easy to learn, quick to play and very satisfying when you win.

The suits don’t matter in Freecell, as long as they are distinguishable. So instead of using the traditional boring suits, I decided to randomly generate them using some of the same tech from ZzFX. To both save space and keep the color scheme consistent I limited the rendering to red, white and black. For the red cards I added a touch of black to help balance them visually.

For final polish I squeezed in Arial fonts so the typography is a bit nicer and added touch of blue to the background. The empty placeholder spots are also randomized in a similar way. Here’s a look at some of the possible random suits.

Example of some randomly generated suits

Secret Bonus Game

You may be wondering, where’s the other game? Well, I was working on my final submission up until the deadline. I thought that I had submitted in time, but apparently not. Maybe I had the time zone confused, I’m not sure what happened. I probably should have submitted it earlier in the day to be safe, but it was rushed and not my best entry so it’s not a big deal.

For this game I played around with ZzFX to generate sound effects that are different every time you play. The graphics are also randomized using unicode characters. I was working down to the wire and had to make some big sacrifices to fit everything so this is really just a proof of concept that I hope to explore more in the future.

  • Rand Space – A shooter with random graphics and sound.

Results

There were so many amazing demos this year, I just want to take a second to congratulate everyone who participated and thank those that played my games! If you get a chance check out some of the other awesome demos, the top 10 are listed on the JS1k website.

It is an honor to have 2 of my games in the top 20 out of a total of 104 entries. I’d be lying if I said I wasn’t a little disappointed that I didn’t make the top 10, but this was my first javascript competition. The important thing is that I’ve learned so much and made so many cool things. I think game jamming helps remind us that it’s not the final results that matter so much as the fun we had along the way.

This JS1k was judged by everyone who participated. We all selected our top 10 games and points were distributed based on that. While I can appreciate the simplicity of this model, it was very difficult picking 10 favorites. After experiencing both, I think I prefer a 1-10 category based rating system like Ludum Dare. One major flaw with this system is it is heavily skewed by how many people play each entry. For example an entry that got 50 votes for 5th place would win over an entry that got 24 votes of first place just because less people knew about it.

Here’s the breakdown of where I placed…

  • 16th The Digit Dilemma (55 points)
  • 18th Queen’s Gambit (50 points)
  • 25th ZzFX (32 points)
  • 31th Tiny Ski (26 points)
  • 41th Infinite Yin Yangs (18 points)
  • 56th One Thousand Free Cells (10 points)

What could I have done better? I’ve already improved in my javascript skills, so I know I can fit a bit more next time. One thing I know I messed up is a bug I accidentally introduced to Queens Gambit when I updated my submission to fix another sprite centering bug. This caused the background to not be centered properly on 16:9 resolution monitors, leaving an ugly gap on the left side. I didn’t notice until after the contest was over because it works fine on my 16:10 monitor! It would have been an easy fix had I noticed it in time.

Blooper Reel

Thanks for reading this! I came across some interesting glitches along the way. I’ll leave you with my favorites…

Accidentally made my yin yangs a bit too recursive
Digit Dilemma Sierpinski puzzle glitch
7 1k #javascript demos in 2 weeks for #JS1k! Click To Tweet
This entry was posted in Game Dev and tagged , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.