An ode to code golf
@rauchg|April 26, 2020 (5y ago)16,576 views
Even though I don't write code these days, my job involves thinking about and deeply understanding it. Code is, and will be, a large part of everyone's lives for the foreseeable future, and I'm glad I spent much of my life learning its ins and outs.
Outside of work, I've now been a part of a number of podcasts or conversations with strangers that start with the question: how did you learn to code?
And now, I'm slightly embarrassed to admit that I've probably given 10 different answers to the same question.
The main reason is that there wasn't a particular instant, or month, or year, or flow state I got in where I finally learned to code. It was more of a discontinuous process full of ups, downs, impostor syndrome, months of voracious learning, followed by months of entertaining distractions.
However, looking back, there were many decisive contributing factors that clearly helped: exposure to open-source (Linux), being involved in online communities (IRC and forums) and hacking competitions.
#Izhal: the hacker's ladder of success
One day, on one of the forums I frequented, someone shared an online game called "Izhal.com", which I became completely obsessed about. It was similar in spirit as another game called "Slyfx.com", which one could describe as a "interactive class on hacking".
As I mentioned, the journey towards learning to code didn't have a consistent, smooth curve. Looking back, this particular game was one of those things I was fortunate to find along the way which seriously sped things up.
The idea was simple: you start with a simple hacking challenge at Level 1, and your goal is to hack the made-up system you are given to progress onto the subsequent levels. There was a forum associated with it, where you could meet others and get hints. The level you were in was your flair, and there was a public leaderboard of everyone who was playing.
Izhal's step 1's URL was, if I remember correctly, something like http://izhal.com/welcome/1.html
and it said "Welcome to the tournament". The solution to get to level 2 involved inspecting the source of the page and finding a HTML comment like:
<!-- The next step is http://izhal.com/secret/2 -->
This experience was unlike any code tutorial or book I was consuming at the time. It was thrilling, competitive, frustrating, exhilarating. It went from easy (viewing the HTML source) to harder (disassembling binaries and writing key generators).
It taught me skills that are essential to problem solving, rather than coding itself, like reverse engineering a system and unusually effective debugging with very little information. And it did so very quickly.
By now, these tournaments have long been wiped from the internet, and if you google you'll find some that have carried on the torch. I would recommend beginners to play with them, and content creators to experiment with this model for new educational materials.
#The power of JavaScript
Why did I decide to write about Izhal? Yesterday I was catching up with my friend Román Cortes, who for many years was one of the most successful JS1K contestants.
If you are unfamiliar, JS1K was a yearly "code golf" tournament, which is a game with the goal of reducing the amount of code as much as possible ("golfing"). In this case, the constraint was to make the coolest possible demo in 1K (or 1024 characters) of JavaScript.
This article is about learning to code, and I definitely wouldn't start with JS1k. If you are getting started, JS1k is good for setting up the aspirations of what you could do one day, or to marvel at the expressive power of JS. Here's Roman's runner-up entry from 2013:
_="G=[[@o=j=28H,[@-j]];j3@(25+=A8)+i*Bji&32?70:,-40-i]b=a[j]=c.cloneNode(Qob#b3@Ub.getContext('2d'DVA8,e8-x,6 (j<17+7 ,ix/=2;A3_e$J*J*y,.1KDsetIntervalQ5;f=B4+A20%8-8,ia@0 +B830-f*8 ,y=10;f>0&f<9E-10<yV11;-1_(y^-6EOx)^6||i%4)Ex*x+y*y<80||a$Af*50y/f,/fDa5,++j .;G.sortreturn -e[H}DG.map==A)/-W[,=W+X][H;Vo-i*3e3;Q3@YdrawImage(a[~~L2]],-J,L3]-JD!L2]EiYbeginPath(YmoFbezierCurF()})},j=4DjQj?c#c5:4e3;z=VXW*yij|(z+1)%.5<.1E(zZ191+(U527364,x.5+i1.5*e,(59542-yZ5188+(25080*,y-(j&28]j||OU8-i%)<54-BE25,e,|[1K4K,,K,8K][~~(B48+e/3)%1H,45+B])cos(>>j*4&15)Math..StylU'hsla('+[/2+/2,Xj0,(function(b,e){/J+/4,xY +'%',sin()*LHL1]16G.push([XZy=W))--;)601]+')'fillfor(Z12700*4+xPI*),.height=*50/f+o,ij%*#.width=$.Rect(@0,Aj/Bi/D);E&&FveTo(-H0]JK,1Lb[Oabs(Qi=Ue=Vx=WiXYa.Z*(_0<x";for(Y=0;$="_ZYXWVUQOLKJHFEDBA@$# "[Y++];)with(_.split($))_=join(pop());eval(_)
Yes, this is all the code it takes to render the demo belowWhich 7 years later works just fine in any browser and is embeddable inside my Next.js-powered blog post source:
Since JS1k is not returning for 2020, Román shared his new hobby with me: https://code-golf.io, which became the inspiration for this post.
#Code-golf.io
Code-golf.io presents challenges of all levels of difficulty, for several programming languages, whose solution needs to be submitted in the fewest number of characters.
As I played around with it, the first thing that stood out to me was that results were being remotely verified. The game was sound and complete, and there was no easy way of cheating.
Having studied different virtualization and secure code execution techniques over the years, it picked my curiosity.
As I browsed, I noticed there was a "Quine" challenge and I immediately gravitated towards it. If you are not familiar, a Quine is a computer program that prints its own source. One of my favorite examples is CSSCSS by Alex Sexton, which is a CSS Quine <!>, available here for your meta-introspection pleasure: https://rauchg.com/csscss.html.
Initially driven by my fascination to understand how the code sandboxing mechanism worked, I decided to give it a try. In other words, I wanted to understand how the authors of code-golf.io had ensured you couldn't do evil things to their system.
The first question I had in my mind was: "What is this system executing?". Is it invoking a function? Who is invoking it? How?
So I decided to throw an error, knowing that the stack trace that typically displays with them would tell me more about the "breadcrumb" of execution.
At this point I became even more intrigued. Being familiar with Node.js, the stack trace was too short, so Node was ruled out.
In addition to ruling out traditional JS execution environments, it gave me another clue: the code is being read from /tmp/code.js
.
I wanted to learn more about what was there, so I enumerated the global object. I wasn't able to get console.log
to work either, so I re-used my previous inspection mechanism: throw
.
To my delight: globalThis was defined, which surprisingly indicated a very recent JS environment, and the first member I enumerated was called print
. Eureka! That sounds really useful.
At this point, I suddenly found myself again in 2002. I felt that familiar feeling Izhal gave me back in the day, of challenging myself to step through the hidden obstacles, to start from very little and figure things out to beat the game. It felt like learning to code again.
#A blast from the past
Equipped with print
, I was able to enumerate the entire environment[1].
I noticed print was adding a newline character, which meant I probably needed to use write
to solve this puzzle.
The tricky thing about writing a Quine is that it requires that you think recursively. The temptation might be to say: I'll just write the source code as a string!
Notice that code-golf.io was expecting write('write()'))
but we just gave it write()
. No matter what we write in that string, we'll always be one layer removed from success. We'll need to go deeper.
If we look back at the output of our inspection, you'll see another familiar method: read
. That sounds like it could read a file!
I connected the pieces: my first clue (/tmp/code.js
), write
and read
. A beautiful Quine was born:
The program executes, reads its own code, and prints it back. The awesome verification system of code-golf.io compared input and output, and granted my...
😫 Completely unsatisfactory, partial, deflating, half-baked "victory".
See, each code-golf challenge features a leaderboard right next to your input:
My solution used 27 characters, and primo had somehow found a solution with just 24.
Once again, just like back in the day in the programming forums, I was driven to succeed inspired by what other had demonstrably accomplished.
My initial reaction was: how could I possibly make that shorter? That's nuts! There's absolutely nothing non-essential about it.
I abandoned the "read the filesystem" approach altogether, and went off on a disappointing tangent:
I figured: I could define a function that prints its own source code. (In JavaScript, the source of functions is available at runtime for instrospection.)
Unfortunately, it used 30 characters, but I was encouraged by the existence of another path to a solution [2]. I fired up Notion and started documenting my attempts.
Because I had already tried to compress the code a lot (as you can see, it looks quite unintelligible) and the gap between my new approach and the old one was a huge 3 characters, and I needed an extra 3 to match primo's 24, I abandoned this direction.
I went back to my initial approach and I started playing with trying to compress the path:
- Use a relative
code.js
- Use
/tmp/code
without an extension, similar to howrequire
works in Node.js - Use
/tmp/code*
None worked. However, at that point I remembered the introspection above revealed an interesing os
object, which I enumerated:
This looked like the corresponding basic Unix syscalls, so I gave chdir
a try, so that I could change the directory I was in, and I brought back the relative approach:
Even though I was now at a disheartening 39 chars, it offered the understanding that I was inside a regular Unix-like filesystem. I set out to try to understand what was the default working directory of the system. Was it /root
? Was it /
?
At that point, I got one step closer: if the default directory is /
, I can make tmp
relative!?
I got one thrilling character closer to success, by removing that despicable leading /
.
At this point I once again "concluded" I had reached the limits of what was possible, but I was encouraged by Román's and primo's success. I was 2 characters away, where could they possibly come from?
My only thought was that the only pair of possible characters that could disappear were parentheses, which in mathematics and programming are often used redundantly.
I pictured removing them, and at that point was reminded of the tagged template literal feature of JavaScript. Voila!
Templates are a relatively new JavaScript feature, I don't actually ever remember using it, but I had seen it on Twitter many times due to the popularity of its use for CSS-in-JS with Styled Components.
With those two characters gone, I cemented my one and only contribution to the annals of code-golf.io as the permanent 4th 1st-place 😅:
#Optimizing for fun
Throughout that simple game, I had to apply lots of the skills that were essential throughout my life as a programmer: figuring out unfamiliar environments and systems without a manual, applying multiple "search" strategies and selecting the most promising one, iterating and dealing with trial-and-error, and perseverance.
There have never been as many options to learn to code as there are today. From bootcamps, to free youtube videos, to expensive courses and exclusive chatrooms.
Whether it's with code golfing or hacking tournaments, I suggest giving a try to the alternative of learning by having fun, overcoming obstacles and a healthy dose of competition with your friends.
1. ^ In the very minimal list returned, I found Realm
, which provides a clue about how sandboxing might have been implemented by the game.