This is the first in a series of posts where I will analyze a dweet’s code, explain how it works, and why it was done that way. A dweet is a 140 character JavaScript program published on Dwitter. The goal is to write small compact code that does something interesting. This ends up being much different from typical programming and creating quality dweets is something of an art.
I’ve published several hundred dweets but this is one of my favorites. A simple yet beautiful program that creates a winding spiral with the text of it’s own code.
Here’s the actual dweet we will be talking about today running live. It may look like a blank canvas at first because it draws a bit slowly, taking about a minute to fully complete. Slow rendering is technique that can save space by using time in lieu of for loops. So in this case the slow rendering is caused by a trick to save some code space and not necessarily because it’s computationally expensive.
We will start by just formatting the code so it’s easier to read. Here’s a link to open this code in CapJS, my tiny JavaScript editor that works similar to dwitter.
with( x ) beginPath( save( restore( font="9em'" ) ) ), translate( 960, 540 ), rotate( -t ), moveTo( 0, 0 ), clip( arc( 0, 0, 3e3, 1.56, 1.58 ) ), fillText( u, -t*t*12, t*20 )
Now that things are a little more straightforward we can begin to analyze each line.
with( x )
The variable x is the canvas context, created automatically by Dwitter. The with statement allows us to extend the scope of x so that calls to x no longer need to be prefixed with “x.” There is an upfront cost of 7 bytes for “with(x)” and a savings of 2 bytes for each call. The following code must be combined into a single statement either wrapping it in a code block with curly brackets, or using commas, as it is done here.
beginPath( save( restore( font="9em'" ) ) ),
Now that we are scoped to x, these calls don’t need the “x.” prefix even though they will be using the canvas context. The first odd thing you may notice is that these functions are passed as nested parameters, but none of these parameters are actually used! This is a common code golfing trick to save the need for extra commas. For normal coding this would be considered bad practice to say the least, but for tiny coding it’s exactly what you want. The thing to remember is the code is executed from the inside out, or in this case left to right.
The font is set to “9em'”. Why? Because that is the largest font using the shortest string. The number 9 frequently shows up in golfed code because it’s the largest possible single digit number. The font size unit “em” is the largest unit used to define font size, much larger then “px” which maps directly to pixel size. You may not have noticed the single quote inside the font definition but without that it wouldn’t work. That single quote is necessary to register as an empty font name and make the entire font statement valid.
After the font is set we call restore then save. This has the effect of saving the context state at the beginning of the function and restoring it at the end. There will be an extra call to restore initially but it doesn’t hurt anything. We need the save and restore methods because there is no other way to clear the clipping region. It also allows us undo other the transformation functions. Finally we call beginPath to start a fresh path.
translate( 960, 540 ),
Center our drawing on the canvas provided by dwitter which is 1920×1080. This is necessary because the spiral will rotate around the center.
rotate( -t ),
Dwitter passes in the current time as t which increments by 1/60. Each frame we will rotate the canvas that tiny bit, which amounts to around 1 degree per frame.
moveTo( 0, 0 ),
The goal is going to be to create a wedge path originating from the center of the canvas which is now at 0,0 because we already centered it. So we just need to set the first vertex to the origin.
clip( arc( 0, 0, 3e3, 1.56, 1.58 ) ),
The arc method is used here to create a thin wedge, starting at the origin with a radius of 3e3 which is large enough to cover the whole canvas. Scientific notation is often used to write large values in golfed code. The values 1.56 and 1.58 were chosen because a downward angle corespondents to PI/2 which is approximately 1.57. Using values to either side creates a wedge of thickness .02 radians angled straight down.
Remember from earlier that the canvas is rotated 1/60 radians per frame. The .02 radian angle also needs to be slightly larger then the 1/60 (.0167) radian rotation to prevent gaps. There is a small amount of overlap but not enough to mess up the image. After creating the path we just need to call clip to set the clip region.
fillText( u, -t*t*12, t*20 )
We’ve already rotated the canvas and set up the clip region so all that’s left is to actually draw something. The u here refers to the code for the function itself. Unlike most languages, quines are trivially easy to write in JavaScript. The text of the code is really just a something for us to print and can be replaced with any string. The Y coordinate is easy to explain, as the spiral is rotating by t it also extends linearly outwards by a multiple of t.
The X coordinate is a little trickier. If you can imagine our downward pointing clipping wedge, the text needs to slide to the left each frame to sweep through the full string. To do this we multiply by -t. We must multiply a second time by t to counter the rotation that’s also happening. Finally we apply a scale of 12 to stretch the text so it is approximately normal width.
I created an animation to help show how the code works. Here you can see the clipping region in red and the unclipped text in blue.
That’s pretty much all there is to it! I’d like to do this kind of code dissection more often so let me know what you think. I hope you learned something new, thanks for reading!
Leave A Comment