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 6 main stages I went through to make it much shorter. This should give a good overview of the main techniques used to code golf in JavaScript.
Here's the code I started with. It's using the canvas to draw a moving square. 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?
Let's start with some basic techniques to shorten the code:
let
is not mandatory in JavaScript, so we remove it.canvas
becomes c
.loop()
function, we replace c.width
and c.height
by their respective values: 100
and 100
.if-else
shorter with a ternary operator.Removing spaces, semicolons, and new lines will be done near the end to keep the code more readable for now.
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.
After some research 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's not necessary to use this function at all, because JavaScript automatically creates variables for all 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 powerful 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.
If we use our variables in smarter ways, it's possible to save a few characters. Here are some ideas:
setInterval()
is called, it returns the value 1
. We can take advantage of that to directly initialize the variable x
like this: x = setInterval(...)
. c.width = c.height = 100
.t = c.getContext('2d')
is only used once for t.fillRect(...)
. This means we don't need t
if we write c.getContext('2d').fillRect(...)
.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:
20
5 times in our code. We can avoid this repetition by storing 20
in a new variable called k
.100
that is used 2 times, and store it in w
.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.
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.
What we have so far is quite compact, yet there are some unnecessary characters in it. Removing them won't change anything, except making our code shorter. So we should delete:
x=setInterval(()=>{c.getContext('2d').fillRect(x=x>(c.width=c.height=100)?-k:x+2,k,k,k)},k=20)
Character count: from 126 to 94.
There are 3 remaining tricks!
First, if we read closely the documentation for setIterval()
, we will discover that the callback parameter can either be a function or a string. So we replace ()=>{...}
by "..."
to win 4 chars.
Second, we should take advantage of the "tagged template" ES6 feature by changing c.getContext('2d')
into c.getContext`2d`
. That's shorter by 2 chars.
And third, it's possible to change the number 100
into 99
to save one char.
Here 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 94 to 87.
The original code had 344 characters. Now it's about 4 times shorter for the same result!
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 117 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.
Going from 344 characters to just 87 was a fascinating journey. Multiple times I thought "there's no way to be shorter", but I was always wrong. And there may be ways to make the code even smaller.
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.