

今天早上刷微博的时候看到 @fakefish 分享了一个[游戏微博][1]，游戏的名字叫做《Untrusted》，通过修改JS代码来通关的游戏，作者把游戏代码托管在了[Github][2]上，游戏地址在 http://alexnisnevich.github.io/untrusted/ 。

### Level 1

这关简单，移动玩家对象@先拾取⌘然后移动到出口就好了。

### Level 2

这关看着挺吓人的，路都被#号给各种拦着了，但是其实读一下代码发现也就那么回事。13行的`new ROT.Map.DividedMaze(map.getWidth(), map.getHeight())`负责根据地图大小生成迷宫，30行到33行在出口的四个方向生成了#号阻拦我们。看着其实挺恐怖的，但是其实我们只要开辟一个新思路不移动@对象到出口而是把出口移动到对象边上就好了。

当然没办法移动现有的这个出口了，我就尝试着再新建了一个出口在@的旁边。`map.placeObject(7,6,'exit');`，一次性成功！

### Level 3

这一关#栅栏把@和出口给隔开来了，首先想到的是把生成#栅栏的代码删除掉。但是很不幸的是过关验证函数`validateLeve()`上清楚的写着一定要有一定数量的栅栏才行。所以我们转变思路，用栅栏把@和出口都包括进去就好了。为了方便我就直接生成在了边缘了。

```js
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');`在@对象旁边新建了一个出口。

<!-- m o r e -->

### Level 5

初看血红的图像可能还觉得兴奋，觉得直接移动过去就好了。不过仔细一看代码你就会发现不是那么回事。代码随机在地图上生成了75个看不见的`mine`对象，不能触碰到它，否则就Game Over。

要越过无形的东西只要让它现行知道它的方位避开它就好了，所以我们可以用`map.setSquareColor(x, y, '#000')`给他们都附上一个颜色。

### Level 6

这一关就比较高级了，有一个攻击者来守护出口，并且会跟随你的步伐靠近你并杀掉你。我的第一想法比较简单，就是建议一个横向屏障让其无法靠近我，不过为了能让自己到达出口位置，我设置了一个空的出口。

```js
for(var x = 0; x < map.getWidth()-3; x++) {
    map.placeObject(x, 11, 'block')
}
```

### Level 7

这一关有一个电话符号，吃了它就可以在移动过程中执行回调函数。在过关路上有不同颜色的障碍物，如果@对象颜色和障碍物的颜色不一样就不允许通过。结合以上两个消息，得到的解决办法是通过一次障碍物前用电话回调函数进行一次“变装”就好了，为了方便我直接指定了在障碍物的前一个位置进行“换装“。

```js
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`里面设置好的函数。

理解一下后我们就能想到利用电话回调函数执行重新生成森林的代码，这样每次@对象旁边的道路就会“开辟”出来。

### Level 9

哈哈这关做的很漂亮，乘船过河的说。这里我们发现可以自定义对象，`raft`对象的`transport`参数提醒了我们可以通过设置这个参数让@对象穿过自己。所以我另辟蹊径，自己创建了一个可以通过的通道。

```js
map.defineObject('boat', {
    'type': 'dynamic',
    'symbol': '▓',
    'color': '#420',
    'transport': true, // (prevents player from drowning in water)
    'behavior': function () {}
});
for (var y = 5; y < 15; y++) {
    map.placeObject(1, y, 'boat');
}
```

### Level 10

这一关有各种攻击者挡住你的去路。因为可以自定义各个攻击者的行为函数，所以我这里的想法是让攻击者自动让出一个通道出来。

```js
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在能右的时候往右走不能右走的时候往下走就好了，然后@在门口静候机器人送钥匙过来就好啦。

```js
me.canMove('right')?me.move('right'):me.move('down')
```

### Level 12

哈哈，和上关以一样，不过高级了一点增加了两个阻碍物。解法是一样的，看个人的控制情况了，我的想法比较简单，就是左边的话是能下就下不能下就右，右边就是能上就上，不能上就右。

```js
 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关的解决办法收录][3]。

```
这关自己的代码太dirty了就不放上来了
```

### Level 14

这一关很简单，就是用同颜色的钥匙开同颜色的锁，然后最终拿到A并过关的意思。可以操作的地方不多，一看就是让我们设置当开绿锁的时候我们应该把哪把钥匙献上。要么是`redKey`要么是`blueKey`，`greenKey`自己肯定是不可能了。通过实验你会发现答案是`blueKey`。

### Level 15

这一关我也想了很久，同样是过河，但是比之前那关少了一条船，最重要的是可以修改代码的地方不多。我想了一下觉得既然在行为中player被killed了那我就可以再新建一个player了，然后我填写了`map.placePlayer(map.getWidth()-2,map.getHeight())`这个代码后成功了，但是并不是我想的一样。大概是因为新建了Player然后直接跳到了Player的判定而跳过了Kill的操作了。

### Level 16
这关是随机新建了25个无形的墙壁（你还不能删除这些墙壁，因为`validateLeve()`函数有验证），每个墙壁有一个颜色如果@对象的颜色和墙壁颜色不同的话就会被撞，相同就会通过。

由于作者非常好像的给出了一部分代码让我们通过`canvas`将无形的墙壁变的有形，大大的降低了问题的难度。所以我们只要让无形的颜色变成有形的颜色，在通过墙壁之前“换装”成相应的颜色就好了。换装同样是用电话回调函数，不过因为不知道怎么获取最近的墙壁的颜色，我选择的是在墙壁的颜色（总共3个）随机，最多只要点两下就会粗来正确的颜色了。

```js
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))]);
})
```

### Level 17

这关大概的意思是每个紫色的出口都是传送门，但是你不知道你是传送到下一个传送门还是荆棘中。所以我们的目标是让传送到传送门的传送门给我们现实出来，这里我利用上一关刚学会的`canvas`做标记操作。

```js
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

这一关是让我们编写一个`jump()`回调函数让@跳过"悬崖"抵达出口。不过我们如果真的被`jump`这个词给迷惑住了的话可能真的会想一会儿。我看了一下电话回调函数中执行`jump()`函数的条件是当@对象下面不是空，那就是立即可以执行了（初始状态下面是#号不是空）。所以我直接在悬崖上架了一座桥让@“跳”过去。

```js
function jump() {
    	for(x = Math.floor(w/2)-5; x<Math.floor(w/2)+5; x++) {
        	map.placeObject(x, Math.floor(h/2), 'block');
        }
}
```
### Level 19

这关没怎么理解真谛，大概的意思是让红色和绿色的@碰到一块吧，反正我随便左右上下左右上下的按了一通就过去了。不过有大牛看出了前因后果，我摘抄一下：

> 19 巨坑爹的一关 我居然认真的读了该主页 并且认为一个玩TCS 的副教授人搞人机交互 以及 UI 毫无不妥。毕竟高德纳不也写了LATEX嘛。。 然后看到它把Lorem 放上去还觉得很有艺术性。。 然后觉得这哥们的姓很有特色 居然叫Eval 天生搞计算机的命啊。。。 然后我就去搜了一下 没找到 paper 才意识到被蒙了。 。。。。。。 忒缺德了。 欺负老实人啊。。。。
>
> 总之。。 这是个抓虫子 游戏 网页本质上是一个 递归组合 也就是 盒子里面套盒子 按上会走到 外面的盒子 按下 会进入里面的盒子 按左右会 走到 并列的盒子 操纵绿色符号追击红色符号 策略就是 首先走到根盒子 然后看红色在哪个盒子 就进入哪个盒子 尽可能地 在它外面的盒子 然后 慢慢接近它 很容易就抓到了。。

### Level 20

这一关是天降毒雨，我们必须顶着毒雨和上面的BOSS作斗争，消灭所有的BOSS之后拿到A之后才能通关。这关我想了很久，然后在我翻API的时候突然发现有`map.overrideKey`这个函数，可以复写一个方向键的回调函数，解决了我想了半天没办法触发的问题。然后我们只要做向上发射的子弹去消灭BOSS就好了。这里因为我们要往右上下移动，所以我选择复写了左方向键。

```js
 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文件夹，就是游戏的源码了，而且可以修改的说。进`Object.js`文件修改`exit`对象的行为判断函数，把`if(!map.finalLevel){}`去掉就好了。

### Level 22

这一关是作者的谢幕，至此全部通关。


  [1]: http://weibo.com/1779194137/AFsTDsLRr
  [2]: https://github.com/AlexNisnevich/untrusted
  [3]: https://github.com/AlexNisnevich/untrusted/blob/master/solutions/13_robot_maze.md
