3

今天看到一个网站。一些开发人员面临的挑战是在满足特定要求的情况下制作最小的游戏,他们发布了令人难以置信的 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 = 150n = 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 = 网格中心)”,这种一数表示位置如何工作?

3) 这到底是什么意思?

这是我最头疼的部分,因为作者把它包装得太巧妙了。

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

我把它拆开,并认为它实际上是 z[]^=1 以便检查后面的? :运算符的条件。但首先:

做什么^=1

有人对该项目发表了评论,并表示可以将其替换为--. 我认为它是按位 AND 运算符,但它与 有什么关系--

接下来:

他们如何[e.which&3]与预设数组一起使用来过滤击键“i”、“j”、“k”、“l”太有效?

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

这些都是我要问的。简短而复杂的代码真的让我很兴奋,我非常感谢任何有助于进一步理解代码的帮助。

4

3 回答 3

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 会使您向前移动一整行,因此类似于向下移动一次。

你的问题,一一解答:

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

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

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

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

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

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

3) 这到底是什么意思?

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

context[x] ^= 1.

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

一,他们想分配一些价值来标记他们已经通过这里。context[x] 通常是undefined, undefined ^ 1 === 1,所以他们将一个分配给他们通过的位置。

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

由于他们使用的检查类似于expr & expr & expr,因此产生真/假或 1/0 值的表达式将像以前一样工作expr && expr && exprtruefalse转换为数字10在使用时&

他们如何使用 [e.which&3] 和预设数组来过滤击键“i”、“j”、“k”、“l”太有效?

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

于 2013-08-18T11:44:04.440 回答
1

关于

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

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

也可以看看

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

于 2013-08-18T11:12:59.873 回答
1

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

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

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

您在第 (2) 点中说过,z.fillRect(s = 0, 0, n = 150, x = 11325);可以改写为z.fillRect(0, 0, 150, 11325);,但这在函数调用以fillRect()相同方式工作的情况下才是正确的。这种变化会破坏整个程序,因为需要赋值语句来创建和设置n全局x变量。

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

在那种情况下,fillRect()n变量x之前没有被声明过,所以给它们赋值会将它们创建为全局变量。

3) 这到底是什么意思?&(z[x+=[1,-n,-1,n][e.which&3]]^=1)?

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

他们如何[e.which&3]与预设数组一起使用来过滤击键“i”、“j”、“k”、“l”太有效?

“i”、“j”、“k”和“l”键是游戏中上/左/下/右控制的不错选择,因为它们在标准键盘上的物理布局对应于上/左/下/对,但也因为这些字母在字母表中彼此相邻,所以它们也有连续的键码:73, 74, 75, 76. 因此,当您获取这些键码并进行按位与运算时,&3您会得到这些值1, 2, 3, 0,然后这些值将作为[1,-n,-1,n]数组中的适当索引。

^=1 有什么作用?...我认为它是按位 AND 运算符...

不是按位与运算符。^是按位异或,^=是按位异或赋值运算符。即,x ^= 1等价于x = x ^ 1

于 2013-08-18T11:46:08.043 回答