Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Heavily packed javascript, multiple questions from 219-byte (canvas, bitwise,...)

I came across a website today. It was a challenge of some developers about making the smallest game possible with certain requirements, and they released the final code of unbelievable size of 219 bytes and runnable on Chrome 17 (one of the requirements). I tried to explore the code (with the explanation provided from the site) and do researches on codes I didn't comprehend. However, the code was packed too heavily to my knowledge, so I am seeking for some helps.

Here is the code, after beautified:

<body id=b onkeyup=e=event onload=
    z=c.getContext('2d');
    z.fillRect(s=0,0,n=150,x=11325);
    setInterval("
        0<x%n
        &x<n*n
        &(z[x+=[1,-n,-1,n][e.which&3]]^=1)?
        z.clearRect(x%n,x/n,1,1,s++)
        :b.innerHTML='GameOver:'+s
    ",9)>
<canvas id=c>

The game is named "Tron", and just like classic snake game (without apple).

Anyway, here are my questions:

1) How can they select element with id 'c' without using getElementById() rather than a simple c

Look at the bottom of the code, there is a canvas with <canvas id=c>. I understand that advanced browsers will automatically fix the quotes "" for the id="c". However, when a function is called in onload event, it assigns z = c.getContent('2d'); and directly refers to the canvas without even applying anything such as document.getElementById(). Also the same when they referred to <body id=b>. How is that possible? Under which circumstances am I able to do similarly?

2) Will replacing parameters of a functions by quick assigning variables affect the function at all? If it does, how will it be calculated?

Particularly, take a look of the third line:

z.fillRect(s = 0, 0, n = 150, x = 11325);

I understand to the most basic level, fillRect() requires 4 parameters fillRect(x-cor, y-cor, width, height). However, the code above produces a rectangle 150x150. First of all, I read the description and they claimed that by default, the code would produce a rectangle 300x150, so I assumed that the assigning short functions would actually assign nothing to the parameters of the parent function. I did a test, and replaced n = 150 by n = 200. Weirdly enough, it produces a rectangle 200x150, so once again I agree that n = 150 did assign 150 to that slot. Hence, the code above can be written as:

z.fillRect(0, 0, 150, 11325);

However, another problem comes. Why isn't its height 11325px but 150px instead? I thought it was reset to default because 11325 excessed the handling of browsers, so I changed it to 500 instead; and it produced the same problem.

So generally, when you do short assigning inside a function (for instance: someCustomFunction.call(s = 1)), what really happens there? If nothing happens, why did the rectangle in the example above changed its size when replacing n = 200 but not when x = 200?

Additional question: this question is not the question I am really into, because it is too personal, but I would love to know. The source states that "x is going to be the tron's position, and is set to 11325 (11325 = 75 x 75 + 75 = center of the grid)", how does this kind of one-number-represents-position work?

3) What in the world does it mean?

This is the most headache part to me, because the author packed it too smartly.

&(z[x+=[1,-n,-1,n][e.which&3]]^=1)?

I broke it up, and figured that it was actually z[]^=1 in order to check the condition for the later ? : operators. But first:

What does ^=1 do?

Some people commented on the project and said it could be replaced by --. I think of it as a bitwise AND operator, but what does it have to do with --?

And next:

How did they use [e.which&3] together with the preset array to filter keystroke "i", "j", "k", "l" too effectively?

I notice the array has the length of 4, which is the length of the keys needs filtering. Also, pressing another key rather than "i", "j", "k", "l" also works. It leads me to believe that the &3 does something in filtering the 4 keys, but I don't know how.

Those are all I have to ask. The short but complicated code really excites me, and I really appreciate any help in understanding the code further.

like image 826
cuzmAZN Avatar asked Mar 27 '26 21:03

cuzmAZN


2 Answers

Sidenote: I didn't actually look at the website, so if I'm covering anything they already have, I'm sorry. I realised there was a link to it halfway through my post.

Here is the code, unminified, with some adjustments.

HTML:

<body id=b>
    <canvas id=c></canvas>
</body>

Javascript:

document.body.onkeyup = handleOnKeyUp;
document.body.onload = handleOnLoad;

function handleOnKeyUp(event) {
    e = event;
}
function handleOnLoad(event) {
    score = 0, n = 150, x = 11325;
    context = c.getContext('2d');
    context.fillRect(0, 0, n, n);
    end = setInterval(function () {
        if (typeof e === "undefined") return; // This isn't part of the original code - removes errors before you press a button
        // Map key that was pressed to a "direction"
        // l(76) i (73) j(74) k(75).
        // If you AND those values with 3, you'd get 
        // l(0), i(1), j(2), k(3)
        var oneDimDirs = [1, -n, -1, n];
        var dirChoice = oneDimDirs[e.which & 3];

        // Now add the chosen "direction" to our position
        x += dirChoice;
        if (x % n <= 0 || n * n <= x || // out of bounds
            !(context[x] ^= 1) // already passed through here
           ) {
            b.innerHTML = "GameOver:" + score;
            clearInterval(end);
        }
        else {
            score++;
            context.clearRect(x % n,x / n, 1 , 1)
        }
    }, 9);
}

Generally, the code makes heavy use of a couple of hacks:

  1. It makes heavy use of global variables and the fact that assigning to an undefined variable creates a global.
  2. It also makes use of irrelevant function parameters to set or modify variables(this shortens the code length)
  3. The code uses a one-dimensional representation of a two-dimensional space. Instead of moving you in two directions, x and y, it "flattens" the representation of the two-dimensional space(the gameboard) into a one-dimensional array. x represents your position on the overall game board. If you add 1 to x, you will move along the current "line"(until you get to its end). Adding n moves you a whole line forward, so it's similar to moving once down.

Your questions, one by one:

1) How can they select element with id 'c' without using getElementById() rather than a simple c

Kooilnc's answer already covers this. This is called "named access":

http://www.whatwg.org/specs/web-apps/current-work/#named-access-on-the-window-object

2) Will replacing parameters of a functions by quick assigning variables affect the function at all? If it does, how will it be calculated?

You misunderstood what is happening here. First, functions accept values as their parameters, and expressions produce values when they are evaluated. An assignment expression, n = 150, produces the value 150 when evaluated. As a side effect, it also assigns that value to the variable n. So calling a function func(n = 150) is almost equivalent to func(150), with the exception of that side effect. That side effect is used in the code to save space, instead of having to assign the variables on separate lines.

Now, for the canvas WTF - As far as I can tell, a canvas element's default width and height happen to be 300px and 150px. You cannot draw past those limits, so trying to execute z.fillRect(0, 0, 150, 11325); will not draw past the 150 height limit. The code authors use the fact that 11325 is bigger than 150 and is thus safe to pass as a parameter(the rectangle will still be drawn as 150x150). 11325 happens to be the one-dimensional coordinate of the starting position.

3) What in the world does it mean?

I hope I mostly answered it within the code. The inner part is unpacked and commented, which only leaves this part unexplained:

context[x] ^= 1.

(Note, ^ === XOR) The idea is that the authors are using the context as an array to store which positions they've already visited. There are three reasons for doing it this way:

One, they want to assign some value to mark that they've passed through here. context[x] is usually undefined, undefined ^ 1 === 1, so they're assigning ones to the positions they pass through.

Next, they want to be able to check if they've passed through there. Coincidentally, 1 ^ 1 === 0, which makes it easy to check. Note what I mentioned about assignment expressions - the value of this assignment expression will be either 1, if the position has not been visited before, or 0, if it has. This allows the authors to use it as a check.

Since the check they use is something like expr & expr & expr, expressions which yield true/false or 1/0 values will work the same way as if it was expr && expr && expr(true and false are converted to the numbers 1 and 0 when used in &)

How did they use [e.which&3] together with the preset array to filter keystroke "i", "j", "k", "l" too effectively?

I hope I answered this sufficiently with comments in the code. Basically, using the onkeyup handler, they store the event which has the key that was pressed. On the next interval tick, they check e.which to determine which direction to go in.

like image 78
Sacho Avatar answered Mar 29 '26 10:03

Sacho


About

1) How can they select element with id 'c' without using getElementById() rather than a simple c

In most browsers you can access an element using its ID as a variable in the global namespace (i.e. window). In other words, to retrieve <div id="c"> you can also use c, or window.c.

See also

The other questions I leave to smarter people ;)

like image 22
KooiInc Avatar answered Mar 29 '26 09:03

KooiInc



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!