今天早上刷微博的时候看到 @fakefish 分享了一个游戏微博,游戏的名字叫做《Untrusted》,通过修改JS代码来通关的游戏,作者把游戏代码托管在了Github上,游戏地址在 http://alexnisnevich.github.i...

Level 1

这关简单,移动玩家对象@先拾取⌘后发现你可以操作关卡代码了,把生成#号栅栏的代码删除掉,然后移动到出口就好了。

Level 2

这关看着挺吓人的,路都被#号给各种拦着了,但是其实读一下代码发现也就那么回事。13行的new ROT.Map.DividedMaze(map.getWidth(), map.getHeight())负责根据地图大小生成迷宫,30行到33行在出口的四个方向生成了#号阻拦我们。看着其实挺恐怖的,但是其实我们只要开辟一个新思路不移动@对象到出口而是把出口移动到对象边上就好了。

当然没办法移动现有的这个出口了,我就尝试着再新建了一个出口在@的旁边。map.placeObject(7,6,'exit');,一次性成功!

Level 3

这一关#栅栏把@和出口给隔开来了,首先想到的是把生成#栅栏的代码删除掉。但是很不幸的是过关验证函数validateLeve()上清楚的写着一定要有一定数量的栅栏才行。所以我们转变思路,用栅栏把@和出口都包括进去就好了。为了方便我就直接生成在了边缘了。

for (y = 0; y <= map.getHeight(); y++) {
    map.placeObject(0, y, 'block');
    map.placeObject(map.getWidth(), y, 'block');
}

for (x = 0; x <= map.getWidth(); x++) {
    map.placeObject(x, 0, 'block');
    map.placeObject(x, map.getHeight()-1, 'block');
}

Level 4

这一关和上一关的感觉是一样的,应该可以抄袭上一关的代码。不过你仔细读代码的话会发现比上一关少了过关验证函数。所以我这里就取巧用了第二关的方法,用 map.placeObject(map.getWidth() - 5, map.getHeight() - 5, 'exit');在@对象旁边新建了一个出口。

Level 5

初看血红的图像可能还觉得兴奋,觉得直接移动过去就好了。不过仔细一看代码你就会发现不是那么回事。代码随机在地图上生成了75个看不见的mine对象,不能触碰到它,否则就Game Over。

要越过无形的东西只要让它现行知道它的方位避开它就好了,所以我们可以用map.setSquareColor(x, y, '#000')给他们都附上一个颜色。

Level 6

这一关就比较高级了,有一个攻击者来守护出口,并且会跟随你的步伐靠近你并杀掉你。我的第一想法比较简单,就是建立一个横向屏障让其无法靠近我,不过为了能让自己到达出口位置,我设置了一个空的出口。

for(var x = 0; x < map.getWidth()-3; x++) {
    map.placeObject(x, 11, 'block')
}

Level 7

这一关有一个电话符号,吃了它就可以在移动过程中执行回调函数。在过关路上有不同颜色的障碍物,如果@对象颜色和障碍物的颜色不一样就不允许通过。结合以上两个消息,得到的解决办法是通过一次障碍物前用电话回调函数进行一次“变装”就好了,为了方便我直接指定了在障碍物的前一个位置进行“换装“。

    if(player.atLocation(24, 12) || player.atLocation(33,12))
        player.setColor('#f00');
    if(player.atLocation(27,12) || player.atLocation(36,12))
        player.setColor('#ff0');
    if(player.atLocation(30,12))
        player.setColor('#0f0');

Level 8

到这里就成功进入第二章了!这一关出现了一片一片的绿森林,不幸的是路被他们给挡住了。可供我们修改的代码也非常有限,仅仅只能修改101行的几个字符。可以看到functionList是一个函数组成的数组,看代码的意思应该是让我们给电话回调函数指定一个functionList里面设置好的函数。

理解一下后我们就能想到利用电话回调函数执行重新生成森林的代码generateForest,这样每次@对象旁边的道路就会“开辟”出来。

Level 9

哈哈这关做的很漂亮,乘船过河的说。这里我们发现可以自定义对象,raft对象的transport参数提醒了我们可以通过设置这个参数让@对象穿过自己。所以我另辟蹊径,自己创建了一个可以通过的通道。

map.defineObject('bridge', {
    'type': 'dynamic',
    'symbol': 'H',
    'color': '#420',
    'transport': true,
    'behavior': function () {}
});
for (var y = 5; y < 15; y++) {
    map.placeObject(1, y, 'bridge');
}

Level 10

这一关有各种攻击者挡住你的去路。因为可以自定义各个攻击者的行为函数,所以我这里的想法是让攻击者自动让出一个通道出来。

map.defineObject('attackDrone', {
    'type': 'dynamic',
    'symbol': 'd',
    'color': 'red',
    'onCollision': function (player) {
        player.killedBy('an attack drone');
    },
    'behavior': function (me) {
       if(me.getY() == 12) me.move('left')
       if(me.getY() == 11) me.move('down')
    }
});

map.defineObject('reinforcementDrone', {
    'type': 'dynamic',
    'symbol': 'd',
    'color': 'yellow',
    'onCollision': function (player) {
        player.killedBy('a reinforcement drone');
    },
    'behavior': function (me) {
        me.move('down');
    }
});

map.defineObject('defenseDrone', {
    'type': 'dynamic',
    'symbol': 'd',
    'color': 'green',
    'onCollision': function (player) {
        player.killedBy('a defense drone');
    },
    'behavior': function (me) {
       if(me.getX() == map.getWidth() - 10) return false;
       if(me.getY() == 12) me.move('right')
       if(me.getY() == 11) me.move('down')
    }
});

Level 11

同样是控制对象的运动行为,需要良好的控制R机器人拿到K钥匙并交给@后才能顺利通关。这里比较简单,只要让R在能右的时候往右走不能右走的时候往下走就好了,然后@在门口静候机器人送钥匙过来就好啦。

        me.canMove('right')?me.move('right'):me.move('down')

Level 12

哈哈,和上关以一样,不过高级了一点增加了两个阻碍物。解法是一样的,看个人的控制情况了,我的想法比较简单,就是左边的话是能下就下不能下就右,右边就是能上就上,不能上就右。

 if(me.getX() < map.getWidth() / 2 || me.getX() == map.getWidth() - 2)
             me.canMove('down') ? me.move('down') : me.move('right')
         else
             me.canMove('up') ? me.move('up') : me.move('right')

Level 13

这一关在上一关的基础上又更上一层楼,出了个迷宫。开始我绞尽脑汁想怎么让机器人自动出来的算法,不过想来我是实在没有那个本事了。后来突发奇想,既然之前的关卡中有攻击者对象根据@对象的操作做出反应,那么我也可以通过@对象来控制机器人咯?所以我比较简单的设置了一个上下左右四个位置,只要@在这个位置上就做出对应的操作。不过作者的Github项目里面收录了各种机器人自动和人工控制的算法,比我这个好用多了,大家可以去欣赏一下:官方第13关的解决办法收录

这关自己的代码太dirty了就不放上来了

Level 14

这一关很简单,就是用同颜色的钥匙开同颜色的锁,然后最终拿到A并过关的意思。可以操作的地方不多,一看就是让我们设置当开绿锁的时候我们应该把哪把钥匙献上。要么是redKey要么是blueKeygreenKey自己肯定是不可能了。通过实验你会发现答案是blueKey

具体的原因应该是修改这个值之后如果你开绿锁的时候有这个东西就会丢,如果没有就直接溢出错误了,跳过了丢的动作。所以 @nightire 棋高一着的填写了theAlgorithm也是可以的,这样一把绿钥匙都不用丢了。而红钥匙之所以不适合的原因是因为整个运动过程中会比蓝钥匙多扔一个蓝钥匙,而下面获取Algorithm的过程中需要一个蓝钥匙去解锁蓝锁,然后就么有然后了。

Level 15

这一关我也想了很久,同样是过河,但是比之前那关少了一条船,最重要的是可以修改代码的地方不多。我想了一下觉得既然在行为中player被killed了那我就可以再新建一个player了,然后我填写了map.placePlayer(map.getWidth()-2,map.getHeight())这个代码后成功了,但是并不是我想的一样。实际上这里是利用"产生异常会中断函数执行"的特性来打断死亡函数的执行。所以可以在括号内一个未定义的函数来打断,例如gogogo();,我填写的placePlayer()在已经有一个player的情况也会抛出异常所以也可以过关。(原理这块感谢 @DanoR 的补充)

Level 16

这关是随机新建了25个无形的墙壁(你还不能删除这些墙壁,因为validateLeve()函数有验证),每个墙壁有一个颜色如果@对象的颜色和墙壁颜色不同的话就会被撞,相同就会通过。

由于作者非常好心的给出了一部分代码让我们通过canvas将无形的墙壁变的有形,大大的降低了问题的难度。所以我们只要让无形的颜色变成有形的颜色,在通过墙壁之前“换装”成相应的颜色就好了。换装同样是用电话回调函数,不过因为不知道怎么获取最近的墙壁的颜色,我选择的是在墙壁的颜色(总共3个)随机,最多只要点两下就会粗来正确的颜色了。

   function createLaser(centerX, centerY, angleInDegrees, length, color) {
        var angleInRadians = angleInDegrees * Math.PI / 180;

        var x1 = centerX - Math.cos(angleInRadians) * length / 2;
        var y1 = centerY + Math.sin(angleInRadians) * length / 2;
        var x2 = centerX + Math.cos(angleInRadians) * length / 2;
        var y2 = centerY - Math.sin(angleInRadians) * length / 2;

        // map.createLine() creates a line with an effect when
        // the player moves over it, but doesn't display it
        map.createLine([x1, y1], [x2, y2], function (player) {
            if (player.getColor() != color) {
                player.killedBy('a ' + color + ' laser');
            }
        });

        // using canvas to draw the line
        var ctx = map.getCanvasContext();
        ctx.beginPath();
        ctx.strokeStyle = color;
        ctx.lineWidth = 5;
        ctx.moveTo(x1, y1);
        ctx.lineTo(x2, y2);
        ctx.stroke();



    }

    player.setPhoneCallback(function(){
        var colors = ['red', 'yellow', 'teal'];
        colors.filter(function(color){
            return player.getColor() != color;
        });
        player.setColor(colors[parseInt(Math.random()*(colors.length-1))]);
    })

其它解:

  • 27楼评论的 ts尼 利用了函作用域内定义提前的到底把关键函数getRandomInt()复写导致长条方块生成位置固定的方法非常的赞。

Level 17

这关大概的意思是每个紫色的出口都是传送门,但是你不知道你是传送到下一个传送门还是荆棘中。所以我们的目标是让传送到传送门的传送门给我们显示出来,这里我利用上一关刚学会的canvas做标记操作。

    if(t1.getType() == 'teleporter' && t2.getType() == 'teleporter') {
        var t1p = map.getCanvasCoords(t1);
        canvas.strokeStyle = 'green';
        canvas.moveTo(t1p.x, t1p.y);
        canvas.lineTo(t1p.x+5,t1p.y+5);
        canvas.stroke();
    }

Level 18

这一关因为作者的更新,增加了一个页面中只能有520个#号存在导致了之前创建#的办法失效了。不过不能新建#号咱还不能建自定义的符号么,恩哼,同样是桥,#号是桥,■就不是桥了么。

    function jump() {
      map.defineObject('bridge', {
          'type': 'item',
          'symbol': '■',
          'color': 'yellow'
      });

         for(x = Math.floor(w/2)-5; x<Math.floor(w/2)+5; x++) {
            map.placeObject(x, Math.floor(h/2), 'bridge');
        }
    }

不过楼下 @vISvLee 君根据17关“虫洞理论“通过自定义两个时空隧道连接亮点看起来也非常的炫酷,大家不妨试一试。总之来说大家就是不要被jump这个词给局限住了。

Level 19

这关没怎么理解真谛,大概的意思是让红色和绿色的@碰到一块吧,反正我随便左右上下左右上下的按了一通就过去了。不过有大牛看出了前因后果,我摘抄一下:

Level 20

这一关是天降毒雨,我们必须顶着毒雨和上面的BOSS作斗争,消灭所有的BOSS之后拿到A之后才能通关。这关我想了很久,然后在我翻API的时候突然发现有map.overrideKey这个函数,可以复写一个方向键的回调函数,解决了我想了半天没办法触发的问题。然后我们只要做向上发射的子弹去消灭BOSS就好了。这里因为我们要往右上下移动,所以我选择复写了左方向键。

 map.defineObject('arrow', {
        'type': 'dynamic',
        'symbol': '↑',
        'color': 'green',
        'interval': 100,
        'projectile': true,
        'behavior': function (me) {
            me.move('up');
        }
    });

    function shoot() {
        for (x = 0; x < map.getWidth(); x++) {
            map.placeObject(x,12,'arrow');
        }
    }

    map.overrideKey('left', shoot);

}

Level 21

这一关是耗费我最久的一关了,什么阻碍都没有,然后你也不可以操作代码,但是就是没法过关。看代码的原因应该是map.finalLevel这个值变成True了表示最后一关,所以就没办法再下一关了。

最后搜索了一下发现原来Menu界面下可以查看scripts文件夹,因为之前正好有看过游戏的Github地址,所以知道这应该就是游戏的源码了,而且发现有几个文件是呈黑色的,似乎可以修改的说。进Object.js文件修改exit对象的行为判断函数,把if(!map.finalLevel){}去掉就好了。

Level 22

这一关是作者的谢幕,至此全部通关。

03-05 22:38