alexkras.com

  • Home
  • Top Posts
  • Resume
  • Projects
  • YouTube
  • Boots to Bytes
  • About
  • Contact

Reverse Engineering One Line of JavaScript

July 13, 2017 by Alex Kras 26 Comments

Few months ago I saw an email asking if someone could unwrap this one line of JavaScript.

<pre id=p><script>n=setInterval("for(n+=7,i=k,P='p.\\n';i-=1/k;P+=P[i%2?(i%2*j-j+n/k^j)&1:2])j=k/i;p.innerHTML=P",k=64)</script>

This line will render just like the image below. You can see it in your browser here. It was created by Mathieu ‘p01’ Henri, author of www.p01.org where you can find this and many other cool demos.

Challenge accepted!

Reverse Engineering One Line of JavaScript
#javascript https://t.co/a1JheNcJSq

— Alex Kras (@akras14) July 13, 2017

Part 1. Making the code readable

First things first, I kept HTML in the HTML and moved JavaScript into a code.js file. I also wrapped p in the id="p" in quotes.

index.html

<script src="code.js"></script>
<pre id="p"></pre>

I noticed that there was a variable k, which was just a constant, so I moved it out of the line, and renamed it as delay.

code.js

var delay = 64;
var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var n = setInterval(draw, delay);

Next, the var draw was just a string, that was being executed as eval within setInterval, since setInterval can accept a function or a string to be evaluated. I moved it to an actual function. I kept the old line there, though, for reference.

Another thing that I noticed was that element p was actually referring to the DOM element with id p that was declared in HTML, the one I recently wrapped in quotes. Turns out elements can be referenced by their id name from JavaScript, as long as the id name is made up of alpha-numeric characters only. I added the document.getElementById("p") to make it more intuitive.

var delay = 64;
var p = document.getElementById("p"); // < --------------
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var draw = function() {
    for (n += 7, i = delay, P = 'p.\n'; i -= 1 / delay; P += P[i % 2 ? (i % 2 * j - j + n / delay ^ j) & 1 : 2]) {
        j = delay / i; p.innerHTML = P;
    }
};
var n = setInterval(draw, delay);

Next, I declare the variables i, p, and j, and moved them to the top of the function.

var delay = 64;
var p = document.getElementById("p");
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var draw = function() {
    var i = delay; // < ---------------
    var P ='p.\n';
    var j;
    for (n += 7; i > 0 ;P += P[i % 2 ? (i % 2 * j - j + n / delay ^ j) & 1 : 2]) {
        j = delay / i; p.innerHTML = P;
        i -= 1 / delay;
    }
};
var n = setInterval(draw, delay);

I factored out for loop into a while loop. Keeping only the CHECK_EVERY_LOOP part of for‘s 3 parts: (RUNS_ONCE_ON_INIT; CHECK_EVERY_LOOP; DO_EVERY_LOOP), and moving everything else in or outside of the body of the loop.

var delay = 64;
var p = document.getElementById("p");
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var draw = function() {
    var i = delay;
    var P ='p.\n';
    var j;
    n += 7;
    while (i > 0) { // <----------------------
        //Update HTML
        p.innerHTML = P;

        j = delay / i;
        i -= 1 / delay;
        P += P[i % 2 ? (i % 2 * j - j + n / delay ^ j) & 1 : 2];
    }
};
var n = setInterval(draw, delay);

I unroll ternary operator ( condition ? do if true : do if false) in P += P[i % 2 ? (i % 2 * j - j + n / delay ^ j) & 1 : 2];.

i%2 was checking if i was even or odd. If i was even, it was just returning 2. If i was odd, it was returning the magic value of (i % 2 * j - j + n / delay ^ j) & 1; (more on that in a bit).

Finally that index was used to offset into string P, so it became P += P[index];.

var delay = 64;
var p = document.getElementById("p");
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var draw = function() {
    var i = delay;
    var P ='p.\n';
    var j;
    n += 7;
    while (i > 0) {
        //Update HTML
        p.innerHTML = P;

        j = delay / i;
        i -= 1 / delay;

        let index;
        let iIsOdd = (i % 2 != 0); // <---------------

        if (iIsOdd) { // <---------------
            index = (i % 2 * j - j + n / delay ^ j) & 1;
        } else {
            index = 2;
        }

        P += P[index];
    }
};
var n = setInterval(draw, delay);

I factored out & 1; in index = (i % 2 * j - j + n / delay ^ j) & 1 into another if statement.

This one is a clever way of checking if the result in parentheses is odd or even, and returning 0 for even and 1 for odd. & is a bitwise AND operator. Logic for AND is as follows:

  • 1 & 1 = 1
  • 0 & 1 = 0

Hence something & 1 will convert “something” to a binary representation, it will also pad 1 with as many 0 in the front as needed, to match the length of something, and will return just an AND of the last bit. For example, 5 in binary is 101, if we AND it with 1 will get the following:

    101
AND 001
    001

In other words, 5 is odd, and result of 5 & 1 is 1. It’s easy to confirm that this logic holds in a JavaScript console.

0 & 1 // 0 - even return 0
1 & 1 // 1 - odd return 1
2 & 1 // 0 - even return 0
3 & 1 // 1 - odd return 1
4 & 1 // 0 - even return 0
5 & 1 // 1 - odd return 1

Note that I also renamed rest of index to magic, so the code with unrolled &1 will looks as follows.

var delay = 64;
var p = document.getElementById("p");
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var draw = function() {
    var i = delay;
    var P ='p.\n';
    var j;
    n += 7;
    while (i > 0) {
        //Update HTML
        p.innerHTML = P;

        j = delay / i;
        i -= 1 / delay;

        let index;
        let iIsOdd = (i % 2 != 0);

        if (iIsOdd) {
            let magic = (i % 2 * j - j + n / delay ^ j);
            let magicIsOdd = (magic % 2 != 0); // &1 < --------------------------
            if (magicIsOdd) { // &1 <--------------------------
                index = 1;
            } else {
                index = 0;
            }
        } else {
            index = 2;
        }

        P += P[index];
    }
};
var n = setInterval(draw, delay);

Next I unrolled P += P[index]; into a switch statement. By now it’s clear that index can only be one of three values – 0, 1, or 2. It’s also clear that P always gets initialized with the following values – var P ='p.\n';. Where 0 points to p, 1 points to . and 2 points to \n – a new line character.

var delay = 64;
var p = document.getElementById("p");
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var draw = function() {
    var i = delay;
    var P ='p.\n';
    var j;
    n += 7;
    while (i > 0) {
        //Update HTML
        p.innerHTML = P;

        j = delay / i;
        i -= 1 / delay;

        let index;
        let iIsOdd = (i % 2 != 0);

        if (iIsOdd) {
            let magic = (i % 2 * j - j + n / delay ^ j);
            let magicIsOdd = (magic % 2 != 0); // &1
            if (magicIsOdd) { // &1
                index = 1;
            } else {
                index = 0;
            }
        } else {
            index = 2;
        }

        switch (index) { // P += P[index]; <-----------------------
            case 0:
                P += "p"; // aka P[0]
                break;
            case 1:
                P += "."; // aka P[1]
                break;
            case 2:
                P += "\n"; // aka P[2]
        }
    }
};

var n = setInterval(draw, delay);

I cleaned up the var n = setInterval(draw, delay); magic. Set interval returns an integer starting with 1, and increments it by one every time setInterval is called. That integer can be used to clearInterval (cancel it). In our case, setInterval was being called just one time, and n was simply getting set to 1.

I also rename delay to DELAY to remind that it was just a constant.

Last but not least, I put parentheses in i % 2 * j - j + n / DELAY ^ j to point out that ^ bitwise XOR has lower precedence then %, *, -, +, and / operators. In other words all of the computations above will be conducted first, before ^ is evaluated. Resulting in (i % 2 * j - j + n / DELAY) ^ j).

Update: It was pointed out to me, that I mistakenly placed p.innerHTML = P; //Update HTML inside the while loop, so I moved it out.

const DELAY = 64; // approximately 15 frames per second 15 frames per second * 64 seconds = 960 frames
var n = 1;
var p = document.getElementById("p");
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";

/**
 * Draws a picture
 * 128 chars by 32 chars = total 4096 chars
 */
var draw = function() {
    var i = DELAY; // 64
    var P ='p.\n'; // First line, reference for chars to use
    var j;

    n += 7;

    while (i > 0) {

        j = DELAY / i;
        i -= 1 / DELAY;

        let index;
        let iIsOdd = (i % 2 != 0);

        if (iIsOdd) {
            let magic = ((i % 2 * j - j + n / DELAY) ^ j); // < ------------------
            let magicIsOdd = (magic % 2 != 0); // &1
            if (magicIsOdd) { // &1
                index = 1;
            } else {
                index = 0;
            }
        } else {
            index = 2;
        }

        switch (index) { // P += P[index];
            case 0:
                P += "p"; // aka P[0]
                break;
            case 1:
                P += "."; // aka P[1]
                break;
            case 2:
                P += "\n"; // aka P[2]
        }
    }
    //Update HTML
    p.innerHTML = P;
};

setInterval(draw, 64);

You can see the final result in action here.

Part 2. Understanding what the code does

So what is going on here? Let’s see.

Initial value of i is set to 64 via var i = DELAY;, and it’s decremented by 1/64 (0.015625) on every loop via i -= 1 / DELAY;. The loop goes on until i is no longer greater than 0 while (i > 0) {. Every run of the loop, i is decremented by 1/64th, so it will take 64 loops for i to decrement by 1 (64/64 = 1). In total then i would have to be decremented 64 x 64 = 4096 times, to become below 0.

The image consist of 32 rows, with 128 chars in every row. Conveniently 64 x 64 = 32 x 128 = 4096. i will only be even (not odd let iIsOdd = (i % 2 != 0);) when i is strictly an even number. We’ll get it to happen 32 times, when i is 64, 62, 60 etc. These 32 times, the index will be set to 2 index = 2; and a new line character will be added to the line P += "\n"; // aka P[2]. The rest 127 character per row will either be set to p or ..

But when do we set p and when do we set .?

Well, for starters we know that we will set it to . when magic let magic = ((i % 2 * j - j + n / DELAY) ^ j); is odd, and we’ll set it to p when magic is even.

var P ='p.\n';

...

if (magicIsOdd) { // &1
    index = 1; // second char in P - .
} else {
    index = 0; // first char in P - p
}

But when is magic odd and when is it even? That’s a million dollar question. Before we talk about that though, let’s establish one more thing.

If we remove + n/DELAY from let magic = ((i % 2 * j - j + n / DELAY) ^ j);, we’ll end up with the following static layout, that will not be moving at all.

For now let’s look at the magic with + n/DELAY removed. How do we end up with the pretty picture above?

(i % 2 * j - j) ^ j

Note that for every loop we have:

j = DELAY / i;
i -= 1 / DELAY;

In other words we can express j in terms of the final i as j = DELAY/ (i + 1/DELAY) but since 1/DELAY is such a small number, for illustration purposed we can drop + 1/DELAY and simplify it to j = DELAY/i = 64/i.

Given that we can rewrite (i % 2 * j - j) ^ j as (i % 2 * 64/i - 64/i) ^ 64/i.

Let’s use an online graphing calculator to plot some of these functions.

First of all, let’s plot i%2.

This results in a nice graph, with value of y ranging form 0 to 2.

If we plot 64/i will get a graph that looks like this.

If we plot the full left side, we’ll get a graph that looks kind of like combination of the 2.

Finally, if we plot 2 functions side by side we get the following.

What do all these graphs tell us?

Let’s remind ourselves what question we are trying to answer, and that is how did we ended with a pretty static image like this:

Well, we know that if the magic (i % 2 * j - j) ^ j results in an even number we’ll have to add p and for an odd value number we’ll have to add ..

Let’s zoom in on the first 16 rows of our chart, with i values ranging from 64 to 32.

Bitwise XOR in JavaScript will drop all the values to the right of decimal point, so it’s kind of like taking a Math.floor of a number.

It will return 0 when both bits are 1s or both bits are 0s.

Our j will start as 1 and will slowly make its way to 2, staying right below it, so we can treat it as 1 (Math.floor(1.9999) === 1), and we’ll need another 1 on the left side, to get the result to be 0 (meaning even), giving us a p.

In other words, each diagonal green line represents one row in our chart. Since j is always above 1 but below 2 for the first 16 rows, the only way we can get odd value is if left side of (i % 2 * j - j) ^ j aka i % 2 * i/64 - i/64 aka diagonal green line is also above 1 or below -1.

Here are some output from JavaScript console to drive this point home, 0 or -2 means result is even, 1 means result is odd.

1 ^ 1 // 0 - even p
1.1 ^ 1.1 // 0 - even p
0.9 ^ 1 // 1 - odd .
0 ^ 1 // 1 - odd .
-1 ^ 1 // -2 - even p
-1.1 ^ 1.1 // -2 - even p

If we look at our graph, we’ll see that the right most diagonal line barely goes above 1 or below -1 (few evens – few ps), the next line goes a bit more, and the one after that even more etc. Line number 16 barely stays under 2 and above – 2. After line 16 we can see our static chart shifts a pattern.

After line 16 j crosses the line of 2, and the expected result flips. Now we’ll get an even number when our green diagonal line is above 2, below -2, or within but not equal to 1 and -1. That is why we see two or more groups of ps from line 17 onward.

If you look closely at the bottom few lines of the moving image, you’ll notice that they no longer follow the same pattern, due to wide fluctuation of the graph.

Let’s comeback to + n/DELAY now. In code we can see that n starts out at 8 (1 from setInteval plust 7 on every interval being called). It is then incremented by 7 every time set interval fires.

When n becomes 64, the graph changes to look as follows.

Note that j is still ~1 (~ here means approximately), but now left half of red diagonal line around 62-63 is ~0 and right half around 63-64 is ~1. Since our chars get populated in decreasing order from 64 to 62, we would expect the 63-64 side of the diagonal line (1 ^ 1 = 0 // even) to append bunch of p and left 62-63 side to append bunch of (1^0 = 1 // odd) ., that would be growing left to right as normal English words.

Rendered HTML for that condition will look as follows ( you can hard code value for n in codepen to see for yourself). Which in fact matches our expectations.

At this point number of ps has grown to its constant value. For example, for the first row half of all values will always be even. From now on ps and .s will only shift their position.

To illustrate, when n grows by 7 on the next setInterval, the graph will change slightly.

Note that diagonal line for the first row (around 64 mark) has move roughly 1 small square up. Assuming that 4 larger square represent 128 chars, 1 large square would represent 32 chars, and 1 small square would represent 32/5 = 6.4 chars (an approximation). If we look at the rendered HTML, we’ll see that first row did in fact move right by 7 chars.

One last example. Here is what happens when setInterval gets called 7 more times with n now equal 64+9×7.

For 1st row j is still equal to 1. Now the top half of red diagonal line around 64 is ~2, and the bottom one ~1. This flips the picture, since now
1^2 = 3 // odd - . and 1 ^ 1 = 0 //even - p. So we would expect bunch of dots, followed by ps.

Which renders as such.

The graph continues to loop indefinitely in a similar manner.

I hope this makes sense. I don’t think I would have ever been able to come-up with something like this on my own, but it was fun trying to understand it.

Filed Under: JavaScript

I work for Evernote and we are hiring!

Subscribe to this Blog via Email

New posts only. No other messages will be sent.

You can find me on LinkedIn, GitHub, Twitter or Facebook.

This blog is moving to techtldr.com

Comments

    Leave a Reply to jancvanbruggen Cancel reply

  1. passerby says

    February 15, 2018 at 10:04 am

    Unfortunately, your explanation doesn’t help in understanding the magic. That’s why you don’t think you would have ever been able to do something like this.
    So, just dig deeper. https://www.desmos.com/calculator/gabvherhnz will help you.

    Reply
  2. cosileone says

    July 16, 2017 at 10:59 am

    What graphing software did you use?

    Reply
    • Alex Kras says

      July 16, 2017 at 11:26 am

      I mentioned it in the article, but it’s https://www.desmos.com/calculator

      Reply
  3. Thorsten Frommen says

    July 16, 2017 at 7:05 am

    Nice one, thanks a lot for all your effort!

    However, there is one mistake, which seems to have only little impact on the end result.
    The two lines

    
    j = DELAY / i;
    i -= 1 / DELAY;
    

    actually have to be switched, so we have this:

    
    i -= 1 / DELAY;
    j = DELAY / i;
    

    Initially, the code was a for loop with i -= 1 / DELAY being the, as you described it, CHECK_EVERY_LOOP part. This means, that it will be evaluated before the body of the loop.

    I made a Pen that shows both versions, next to each other. You can easily adapt the REAL_DELAY constant (all the way down, just before the setInterval() call) to make the animation run slower.

    Thanks again,
    Thorsten

    Reply
  4. Tarik says

    July 15, 2017 at 9:20 pm

    Extraordinary!

    Reply
  5. jancvanbruggen says

    July 15, 2017 at 4:10 pm

    Thanks! Mesmerizing and informative πŸ™‚ One tweak though, in your modified index.html the tag should go after the

    <

    pre> tag.

    Reply
    • jancvanbruggen says

      July 15, 2017 at 4:11 pm

      whoops, forgot about code formatting! πŸ˜€

      in your modified index.html the <script> tag should go after the <pre> tag

      Reply
  6. Steven Pemberton says

    July 15, 2017 at 12:31 pm

    Having unrolled
    P += P[index];
    into a switch, thus revealing the role of index, you can now merge the switch back into the if statement above:
    if (iIsOdd) {
    let magic = ((i % 2 * j – j + n / DELAY) ^ j); // < ——————
    let magicIsOdd = (magic % 2 != 0); // &1
    if (magicIsOdd) { // &1
    P += “.”; // aka P[1]
    } else {
    P += “p”; // aka P[0]
    }
    } else {
    P += “\n”; // aka P[2]
    }

    Reply
    • Steven Pemberton says

      July 15, 2017 at 12:32 pm

      Sorry, it stripped my indentation when I posted.

      Reply
  7. quidditywp says

    July 14, 2017 at 7:43 pm

    This is glorious! Thank you, both explainer and original coder!
    I’d suggest tweaking this page’s max-width or the syntax-blocks’ width to ~950px in order to avoid horizontal scrollbars on screens that are big enough? (I think the main .wrap or the .crayon-syntax ?) Thanks again!

    Reply
    • quidditywp says

      July 14, 2017 at 8:01 pm

      Also a suggested tweak to your final output, just more detail in the left: https://codepen.io/anon/pen/MoxPaB?editors=1010

      Reply
  8. cxseven says

    July 13, 2017 at 8:28 pm

    Yes, as iamp01 says, the ^j part makes it a checkerboard rather than just unbroken bars stretching into the horizon. You can see little spots, which become more frequent as you move downward, where the p’s and .’s pass through and get flipped. It looked like a glitch to me at first. It becomes clearer how this is working when you look at the animation without the ^j.

    Reply
  9. Devon says

    July 13, 2017 at 3:09 pm

    Hey FYI your code blocks don’t work at all on mobile, can’t scroll horizontally at all. Android 7 in chrome.

    Reply
    • Alex Kras says

      July 13, 2017 at 3:14 pm

      Could you try clicking into it first please?

      Reply
    • moeabdol says

      July 14, 2017 at 2:51 pm

      Did you try switching it off and then on again!

      Reply
  10. iamp01 says

    July 13, 2017 at 2:03 pm

    Hi! Thanks a ton for this write up!
    It’s not every day that someone dig so deep into my code.

    The part (i%2*j-j)^j is the meat of the raytracer. the i%2*j-j represented the projected x, while j represents the projected y. The x,y coordinates of the intersection point with the plane and the ray. Doing the (x^y)&1 gives the checkboard texture.

    It would be super sweet to point the readers to the original demo or even better to the other 64-1024 bytes Audio Visual demos at http://www.p01.org

    Reply
    • Alex Kras says

      July 13, 2017 at 2:06 pm

      My apologies, I purposely didn’t Google the code to avoid finding a solution, but as result I forgot to try to look for a source link. I’ll add it shortly.

      Reply
    • Alex Kras says

      July 13, 2017 at 2:16 pm

      Added links in the article and both CodePens. Thanks for additional explanation!

      Reply
      • iamp01 says

        July 13, 2017 at 2:43 pm

        Thanks again for doing such a deep write up, without cheating, and for the updates.

        Cheers,

        Reply
  11. Piotr-Aueternum says

    July 13, 2017 at 7:31 am

    Please put innerHTML after loop its too laggy this way.

    Reply
    • Alex Kras says

      July 13, 2017 at 7:43 am

      Thank you, done.

      Reply
  12. tommut says

    July 13, 2017 at 7:20 am

    This was a fascinating read! I didn’t realize how far down the rabbit hole it would go. πŸ™‚
    Well done.

    Reply
    • Alex Kras says

      July 13, 2017 at 7:43 am

      Thank you, me neither πŸ™‚

      Reply
  13. Alex Wilmer (@benevolentNinja) says

    July 13, 2017 at 7:06 am

    Fantastic writeup!

    Reply
  14. losso says

    July 13, 2017 at 5:59 am

    source: http://www.p01.org/128b_raytraced_checkboard/

    Reply
    • Alex Kras says

      July 13, 2017 at 7:44 am

      Thank you, I specifically tried not to Google for it, to avoid clouding my “way” of trying to figure out what is going on there, but it’s nice to have a link to the original source.

      Reply

Copyright © 2021 · eleven40 Pro Theme on Genesis Framework · WordPress · Log in