lesscake.com

The subtle art of code golf in Javascript

The goal of "code golf" is to reduce the size of a program as much as possible. It's a fascinating practice that I knew very little about. To change that, I recently challenged myself to code golf a simple Javascript program.

In this article I'm sharing the program I wrote, and the 5 main stages I went through to make it much shorter. This should give an overview of the main techniques used to code golf in Javascript.

The starting point

The code I made for this challenge is using the canvas to draw a black square, and make it move from left to right. That's not very impressive, but that's enough for our purpose.

// Retrieve the canvas and its context
let canvas = document.getElementById('c');
let ctx = canvas.getContext('2d');

// Set the canvas size
canvas.width = 100;
canvas.height = 100;

// Variable to store the x position of the square
let x = 0;

// Main function that draws the square and moves it
loop();

function loop() {
  // Clear the canvas, so we can draw on it later 
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // Change the position of the square
  if (x > canvas.width) {
    // If it's gone too far, move it to the far left
    x = -20;
  } else {
    // Otherwise, move it slightly to the right
    x = x + 2;
  }

  // Draw the black square at the position x
  ctx.fillRect(x, 20, 20, 20);

  // Call the loop function about 60 times per second
  requestAnimationFrame(loop);
}

Without comments, that's 344 characters. How short do you think it can become?

Stage 1: the basics

Let's start with some basic techniques to shorten the code:

Removing spaces, semicolons, and new lines will be done at the very end to keep the code readable in this article.

With all these changes, the program looks like this.

c = document.getElementById('c');
t = c.getContext('2d');
c.width = 100;
c.height = 100;
x = 0;

loop();

function loop() {
  t.clearRect(0, 0, 100, 100);
  x = (x > 100) ? -20 : x + 2;
  t.fillRect(x, 20, 20, 20);
  requestAnimationFrame(loop);
}

Character count: from 344 to 247.

Stage 2: DOM and canvas tricks

After some googling I discovered 3 tricks related to the DOM and the canvas that will make the code significantly shorter.

The first one is about getElementById(). It turns out it is not necessary to use this function at all, because Javascript automatically creates variables for each HTML elements with an id. So to access the canvas that has id="c", we can directly use the variable c.

The second trick is about requestAnimationFrame(). This is a great function to refresh the canvas about 60 times per second, but it's also quite long to type. Instead we should call setInterval() that works pretty much the same, and is a lot shorter.

The last trick is about clearRect(). There is a way to clear the canvas without clearRec(): by setting the size of the canvas. So if we put c.width = 100 directly inside the loop() function, the canvas will be cleared at every frame.

Here's the new code with the 3 tricks in place.

t = c.getContext('2d');
x = 0;

setInterval(loop, 20);

function loop() {
  c.width = 100;
  c.height = 100;
  x = (x > 100) ? -20 : x + 2;
  t.fillRect(x, 20, 20, 20);
}

And now that we are using setInterval(), let's replace loop() by an anonymous function.

t = c.getContext('2d');
x = 0;

setInterval(() => {
  c.width = 100;
  c.height = 100;
  x = (x > 100) ? -20 : x + 2;
  t.fillRect(x, 20, 20, 20);
}, 20);

Character count: from 247 to 154.

Stage 3: smarter variables

If we use our variables in smarter ways, it's possible save a few characters. Here are some ideas:

x = setInterval(() => {
  c.width = c.height = 100;
  x = (x > 100) ? -20 : x + 2;
  c.getContext('2d').fillRect(x, 20, 20, 20);
}, 20);

And there are 2 more things we should do:

These 2 changes are actually making the code slightly longer. But soon, when we will remove spaces and semicolons, these modifications will become worthwhile.

k = 20;
x = setInterval(() => {
  w = c.width = c.height = 100;
  x = (x > w) ? -k : x + 2;
  c.getContext('2d').fillRect(x, k, k, k);
}, k);

Character count: from 154 to 141.

Stage 4: merge lines

When a variable is assigned in Javascript, the value of the variable itself is returned. For example, when doing k = 20 we are setting k to 20 and we are also returning the value 20. We can use this fact to combine two lines into one, like this.

// Before
k = 20;
x = setInterval(/* ... */, k);

// After
x = setInterval(/* ... */, k = 20);

With the same idea, we merge these two lines, and remove the variable w.

// Before
w = c.width = c.height = 100;
x = (x > w) ? -k : x + 2;

// After
x = (x > (c.width = c.height = 100)) ? -k : x + 2;

And also these two lines.

// Before
x = (x > (c.width = c.height = 100)) ? -k : x + 2;
c.getContext('2d').fillRect(x, k, k, k);

// After
c.getContext('2d').fillRect(x = (x > (c.width = c.height = 100)) ? -k : x + 2, k, k, k);

Now our anonymous function contains a single line of code.

x = setInterval(() => {
  c.getContext('2d').fillRect(x = (x > (c.width = c.height = 100)) ? -k : x + 2, k ,k, k);
}, k = 20);

Character count: from 141 to 126.

Stage 5: last tricks

If we read closely the setIterval() documentation, we will discover that the first parameter can either be a function or a string. Meaning it's possible to write this.

x = setInterval(
  "c.getContext('2d').fillRect(x = (x > (c.width = c.height = 100)) ? -k : x + 2, k ,k, k);", 
  k = 20
);

It looks weird, but it works fine. And last but not least, we should:

And this is the final result:

x=setInterval("c.getContext('2d').fillRect(x=x>(c.width=c.height=99)?-k:x+2,k,k,k)",k=20)

Character count: from 126 to 89.

The original code had 344 characters. Now it's almost 4 times smaller for the same result!

Bonus

We only talked about the Javascript so far, but what about the HTML?

Here's the code for both the HTML and the Javascript, in only 119 chars. I added some line breaks below only for legibility.

<canvas id=c>
<script>
  x=setInterval("c.getContext('2d').fillRect(x=x>(c.width=c.height=99)?-k:x+2,k,k,k)",k=20)
</script>

If you save this tiny program in an HTML file and open it in a browser, you will see this result.

Conclusion

Going from 344 characters to just 89 was a fascinating journey. Multiple times I though "there's no way to be shorter", but I was always wrong. And I'm sure it's possible to make the code even smaller. The full source code of this project is available on GitHub.

It took me about a week of work and the help of a friend to get to the last solution. I learned a lot about Javascript with this challenge, and I hope you enjoyed reading about it!

If you have any questions or feedback: thomas@lesscake.com :-)