大量打包javascript,来自219字节的多个问题(画布,按位,...)

时间:2013-08-18 10:29:09

标签: javascript canvas bit-manipulation

我今天遇到website。一些开发人员面临的挑战是如何根据某些要求制作最小的游戏,他们发布了令人难以置信的219字节大小的最终代码,并且可以在Chrome 17上运行(其中一项要求)。我试图探索代码(从网站提供的解释),并研究我不理解的代码。但是,根据我的知识,代码包含太多,所以我正在寻求一些帮助。

以下是美化后的代码:

<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>

游戏被命名为“Tron”,就像经典的蛇游戏(没有苹果)。

无论如何,这是我的问题:

1)如何在不使用getElementById()的情况下选择ID为“c”的元素,而不是简单的c

查看代码的底部,有一个<canvas id=c>的画布。我了解高级浏览器会自动修复""的引号id="c"。但是,当在onload事件中调用函数时,它会分配z = c.getContent('2d');并直接引用画布,甚至不应用document.getElementById()之类的任何内容。当他们提到<body id=b>时也是如此。怎么可能?在哪种情况下,我能够做同样的事情?

2)通过快速分配变量替换函数的参数会影响函数吗?如果确实如此,将如何计算?

特别是,看一下第三行:

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

我理解到最基本的级别,fillRect()需要4个参数fillRect(x-cor, y-cor, width, height)。但是,上面的代码生成一个150x150的矩形。首先,我阅读了描述,他们声称默认情况下,代码会生成一个300x150的矩形,所以我假设分配短函数实际上不会为父函数的参数赋值。我做了一个测试,并将n = 150替换为n = 200。奇怪的是,它产生了一个200x150的矩形,所以我再次同意n = 150确实将150分配给该插槽。因此,上面的代码可以写成:

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

然而,另一个问题来了。为什么它的高度不是11325px而是150px呢?我认为它被重置为默认值,因为11325超出了浏览器的处理范围,因此我将其更改为500;它产生了同样的问题。

一般来说,当你在函数内部进行简短的赋值时(例如:someCustomFunction.call(s = 1)),那里到底发生了什么?如果没有任何反应,为什么上面示例中的矩形在替换n = 200时更改了其大小,而在x = 200时却没有?

补充问题:这个问题不是我真正想要的问题,因为它太个人化了,但我很想知道。消息来源指出“x将成为tron的位置,并设置为11325(11325 = 75 x 75 + 75 =网格的中心)”,这种单数表示位置如何工作?< / em>的

3)这是什么意思?

这对我来说是最头痛的部分,因为作者把它打得太聪明了。

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

我将其分解,并认为实际上是z [] ^ = 1,以便检查后来的? :运算符的条件。但首先:

^=1做什么?

项目中的某些人commented表示可以将其替换为--。我认为它是一个按位AND运算符,但它与--有什么关系?

接下来:

他们是如何使用[e.which&3]和预设数组过度有效地过滤按键“i”,“j”,“k”,“l”的?

我注意到数组的长度为4,这是需要过滤的键的长度。另外,按下另一个键而不是“i”,“j”,“k”,“l”也可以。它让我相信&3在过滤4个键时做了些什么,但我不知道如何。

这些都是我要问的。简短而复杂的代码确实令我兴奋,我非常感谢您在进一步理解代码方面的任何帮助。

3 个答案:

答案 0 :(得分:2)

旁白:我实际上并没有看过这个网站,所以如果我报道他们已有的东西,我很抱歉。我意识到在我的帖子中途有一个链接。

以下是未经通知的代码,并进行了一些调整。

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);
}

通常,代码会大量使用一些黑客:

  1. 它大量使用全局变量,并且分配给未定义的变量创建了一个全局变量。
  2. 它还利用不相关的函数参数来设置或修改变量(这缩短了代码长度)
  3. 代码使用二维空间的一维表示。它不是在x和y两个方向上移动,而是将二维空间(游戏板)的表示“平坦化”为一维数组。 x表示您在整个游戏板上的位置。如果向x添加1,则将沿当前“行”移动(直到结束)。添加n会向前移动整行,所以它类似于向下移动一次。
  4. 您的问题,一个接一个:

    1)如何在不使用getElementById()的情况下选择id为'c'的元素,而不是简单的c

    Kooilnc的答案已经涵盖了这一点。这称为“命名访问”:

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

    2)通过快速分配变量替换函数的参数会影响函数吗?如果确实如此,将如何计算?

    你误解了这里发生的事情。首先,函数接受 values 作为参数,表达式在评估时生成。赋值表达式n = 150在计算时生成值150。作为副作用,它还将该值赋给变量n。所以调用函数func(n = 150)几乎等同于func(150),但副作用除外。该副作用在代码中用于节省空间,而不必在单独的行上分配变量。

    现在,对于画布WTF - 据我所知,canvas元素的默认宽度和高度恰好是300px和150px。你无法超越这些限制,因此尝试执行z.fillRect(0, 0, 150, 11325);将不会超过150高度限制。代码作者使用11325大于150的事实,因此作为参数传递是安全的(矩形仍然被绘制为150x150)。 11325恰好是起始位置的一维坐标。

    3)这是什么意思?

    我希望我在代码中主要回答它。内部部分被解压缩和评论,只留下这部分无法解释:

    context[x] ^= 1.

    (注意,^ === XOR) 这个想法是作者使用上下文作为一个数组来存储他们已经访问过的位置。这样做有三个原因:

    一,他们想要分配一些值来标记他们在这里经过的那些。 context [x]通常是undefinedundefined ^ 1 === 1,所以他们会将它们分配到它们经过的位置。

    接下来,他们希望能够检查他们是否已经通过那里。巧合的是1 ^ 1 === 0,这使得检查变得容易。注意我提到的关于赋值表达式的内容 - 如果之前没有访问过该位置,则此赋值表达式的值将为1,如果有,则为0。这允许作者将其用作支票。

    由于他们使用的检查类似于expr & expr & expr,因此产生true / false或1/0值的表达式的工作方式与expr && expr && exprtrue和{false的工作方式相同在1中使用时,{1}}会转换为数字0&

    他们如何将[e.which&amp; 3]和预设数组一起过滤键击“i”,“j”,“k”,“l”?

    我希望我在代码中的评论中充分回答了这个问题。基本上,使用onkeyup处理程序,它们存储具有按下的键的事件。在下一个间隔时间点,他们会检查e.which以确定进入的方向。

答案 1 :(得分:1)

关于

1)如何在不使用getElementById()的情况下选择ID为“c”的元素,而不是简单的c

在大多数浏览器中,您可以使用其ID作为全局命名空间中的变量(即window)来访问元素。换句话说,要检索<div id="c">,您还可以使用cwindow.c

See also

我留给聪明人的其他问题;)

答案 2 :(得分:1)

  

1)如何在不使用getElementById()的情况下选择id为'c'的元素而不是简单的c

我认为这是IE的旧版本,最初允许通过他们的ID直接访问JS中的DOM元素,但随后还有许多其他浏览器与依赖于此的网站兼容。一般来说,这不是一件好事,因为如果你创建与元素id同名的变量,你就会得到奇怪的错误(对于这个例子而言,这并不重要)。

  

2)通过快速分配变量替换函数的参数会影响函数吗?如果确实如此,它将如何计算?

你在你的观点(2)中说z.fillRect(s = 0, 0, n = 150, x = 11325);可以改为z.fillRect(0, 0, 150, 11325);,但只要函数调用{{}>那就是 {1}}以同样的方式工作。此更改将破坏整个程序,因为需要赋值语句来创建和设置fillRect()n全局变量。

在JS的一般意义上,赋值运算符x不仅将右手操作数的值赋给左边的变量,而且整个表达式给出了右手操作数的值。因此=将变量someFunc(x = 10)设置为x,并将值10传递给10

对于someFunc(),先前未声明fillRect()n变量,因此为它们赋值会将它们创建为全局变量。

  

3)这是什么意思? x

嗯,(显然)有点复杂。要回答您提到的有关其中部分问题的具体问题:

  

他们是如何使用&(z[x+=[1,-n,-1,n][e.which&3]]^=1)?和预设数组过度有效地过滤击键“i”,“j”,“k”,“l”的?

键“i”,“j”,“k”和“l”是游戏中向上/向左/向下/向右控制的不错选择,因为它们在标准键盘上的物理布局对应于向上/向左/ down / right,但也因为这些字母在字母表中彼此相邻,所以它们也有连续的密钥代码:[e.which&3]。因此,当您使用这些密钥代码并使用73, 74, 75, 76执行按位AND时,您将获得值&3,然后将其作为1, 2, 3, 0数组中的适当索引。

  

^ = 1做什么? ......我认为它是一个按位运算符......

不是按位AND运算符。 [1,-n,-1,n]是按位异或,^是按位异或赋值运算符。也就是说,^=相当于x ^= 1