Show HN: Untrusted, a JavaScript adventure game you play by modifying its source
alex.nisnevich.com
alex.nisnevich.com
Thank you all so much for all of your feedback! I never thought this game would become so popular.
It seems that our server is more or less overloaded right now, so AJAX requests for new levels are sometimes failing. This appears to be the cause of the bugs that some of you have experienced where levels load incorrectly or are overwritten by previous levels. Sorry about that. :-/ We will work on making the game more robust in the case of failures like this.
If you want to run the game locally, you can clone it from https://github.com/AlexNisnevich/untrusted and follow the instructions there.
Sorry about that. That's what you get for making something really interesting and posting about it on hacker news.
GET http://alex.nisnevich.com/untrusted/levels/10_ambush.jsx 503 (Service Unavailable) jquery.min.js:6
GET http://alex.nisnevich.com/untrusted/levels/11_robot.jsx 503 (Service Unavailable) jquery.min.js:6
GET http://alex.nisnevich.com/untrusted/levels/12_robotNav.jsx 503 (Service Unavailable) jquery.min.js:6
GET http://alex.nisnevich.com/untrusted/levels/13_robotMaze.jsx 503 (Service Unavailable) jquery.min.js:6
GET http://alex.nisnevich.com/untrusted/levels/14_crispsContest.jsx 503 (Service Unavailable) jquery.min.js:6
GET http://alex.nisnevich.com/untrusted/levels/15_exceptionalCrossing.jsx 503 (Service Unavailable) jquery.min.js:6
GET http://alex.nisnevich.com/untrusted/levels/16_lasers.jsx 503 (Service Unavailable) jquery.min.js:6
GET http://alex.nisnevich.com/untrusted/levels/17_pointers.jsx 503 (Service Unavailable) jquery.min.js:6
GET http://alex.nisnevich.com/untrusted/levels/19_documentObjectMadness.jsx net::ERR_CONNECTION_REFUSED jquery.min.js:6
GET http://alex.nisnevich.com/untrusted/levels/20_bossFight.jsx net::ERR_CONNECTION_REFUSED jquery.min.js:6
GET http://alex.nisnevich.com/untrusted/levels/21_endOfTheLine.jsx net::ERR_CONNECTION_REFUSEDI've set up a mirror at: http://alexnisnevich.github.io/untrusted/
We're currently working on handling the AJAX responses more robustly. The level counter should no longer increment on a failed request.
Could you possibly help us by putting in a ruler that marks the map coordinates? Yes, I can read the code to find where things are (or count), but there were a few times where it was somewhat inconvenient.
map.placeObject(map.getWidth()/2, 5, 'block');
//map.placeObject(map.getWidth()/2, 1, 'block');
var player = map.getPlayer();
map.defineObject('missile', {
'type': 'dynamic',
'symbol': '^',
'color': 'blue',
'interval': 100,
'projectile': true,
'behavior': function (me) {
me.move('up');
}
});
function jericho()
{
var i;
for (i = 3; i < map.getWidth() - 3; i++)
{
map.placeObject(i, map.getHeight() - 1, 'missile');
}
}
player.setPhoneCallback(function()
{
jericho();
}); map.overrideKey('up', function() {
if (map.countObjects('boss') > 0) {
for (var x = 0; x < map.getWidth(); x++) {
map.placeObject(x, map.getHeight()-5, 'missile');
}
} else {
map.overrideKey('up', null);
}
});:-)
//Math.random = function(){ return false;};
/* my first Idea was to try and redefine Math.random to always return <0.3, but for some reason even though this did effect the dropping of bullets, it didn't stop them completely (if someone could explain this I would really appreciate it*/
map.defineObject('MagicBlock', {
'type' : 'static',
'symbol' : '$',
'color' : 'magenta',
'impassable' : function() { return map.countObjects('boss') >0;}
});
//place a row of MagicBlock's aboove the player's starting position
for(var i =0; i < map.getWidth(); i++)
map.placeObject(i, map.getHeight()-5, 'MagicBlock');
map.defineObject('missile', {
'type' : 'dynamic',
'symbol' : '^',
'interval' : 100,
'projectile': true,
'behavior' : function(me) { me.move('up');},
'color' : 'lime'
});
player = map.getPlayer();
function volley(){
for(var i = 0; i < map.getWidth(); i++)
map.placeObject(i, map.getHeight()-6, 'missile');
map.placeObject(i, map.getHeight()-7, 'missile');
}
map.getPlayer().setPhoneCallback(function(){
volley();
});for(ii=0;ii<23;ii++){ map.getDynamicObjects()[ii].direction='down'; }
f=function(){ map.placeObject(10, 18, 'bullet'); } map.getPlayer().setPhoneCallback(f);
startLevel["constructor"]("m",
"console.log(m);" +
"var old = m.placeObject;\n" +
"m.placeObject = function(x,y,t) { \n" +
"console.log(t);\n" +
"if (t === 'exit' || t === 'computer') {\n" +
"console.log('adding' + t);\n" +
"return old['ca' + 'll'](m, x,y,t) }};")(map);
EDIT: I wonder if you could wrap the user's code in, e.g. https://code.google.com/p/es-lab/wiki/SecureEcmaScript, and use this to gamify finding bugs in that sandbox :-)similarly,
var oldPlaceObject = map.placeObject;
var newPlaceObject = function(x, y, type) {
if (type !== 'block') {
map.placeObject = oldPlaceObject;
map.placeObject(x, y, type);
map.placeObject = newPlaceObject;
}
};
map.placeObject = newPlaceObject;
was my solution to multiple levels ;).My biggest critique so far is that it's extremely hard for me to tell the difference between #311 and #000 for the `.disabled` lines (I'm mildly red-green colorblind). Changing it to #711 fixed it for me, but I can imagine it would be impossible to even see #F11 for certain colorblind individuals. Perhaps some other kind of indication that a line is editable, or a more drastic #FFF/#000 distinction?
Edit: I think I solved lvl two to four all the same way. Not sure if that's intended. But I also don't want to spoil it for others.
Edit 2: This worked again at lvl 6, so I'll assume that's a bug. Click this pastebin for spoilers: http://pastebin.com/yfhDhE7P
Also, is there a good way to completly prevent tampering with functions, or is this just going to be an arms race?
I mean if you want to cheat you always can. But that's not the point. Right now you can cheat using official apis.
Just use map._blah() for internal use
By the way: Same trick works for keys.
Let's hope for an arms race - it will be far more fun and educating at the same time.
Yes, it's too easy that way:
map.pO = map.placeObject;
map.placeObject = function(x, y, w){
return map.pO(x, y, w=='block'?'empty':w);
}
Does not even need call. But while you cannot protect from `Function['p'+'rototype']['c'+'all']`, I think using `defineProperty` to stop mucking with the code would go a long way into preventing blatant cheating at little cost. That or enclosed reference funcs.Btw, you trust the level increment counter before succeeding in loading the level. Right now calls are 503ing, so basically I'm [gisting bogus solutions](https://gist.github.com/anonymous/f1b06d63848d6d013e26) but it's also saving those bogus level ups in localstorage...
maze.create = function() {};
var tmp = map.placeObject;
map.placeObject = function(x, y, t) {
if(t == 'exit') tmp(x,y,t);
};map.placeObject(7,4,'exit');var a = {map: {placeObject:function(){}}};a.
if (false) // <- inserted by me
for (var ...) { // <- existing code
...code...
}
Too bad, because extra `for`s could be fun...PS. I'm really enjoying all the creative solutions you guys are coming up with!
The main site is overloaded, but I've set up a mirror at: http://alexnisnevich.github.io/untrusted/
All level-loading issues should be resolved on it, since they were tied to AJAX failures. Let me know if you experience any problems on the mirror site.
Here's making the boss kill itself
I also solved the DOM level without editing anything, I just pressed some keys and it transported me to the next level before I even had an idea what was going on.
(im playing on Firefox / Linux, in case that makes a difference)
Oh and thanks for this game, it's EXTREMELY COOL and I had a lot of fun playing it all the way to the credits screen (and I'm going to go back now to see if that's really the last level or not ...)
http://home.iitb.ac.in/~pritambaral/level6.mp4 (16K)
http://home.iitb.ac.in/~pritambaral/level6.webm (31K)
http://home.iitb.ac.in/~pritambaral/level6.gif (161K)
EDIT: Seems to have been fixed. Leaving comment for archival purposes?
Also, is it possible for counter drones to kill the red drone? Mine wouldn't, all they do is block him.
I have 0 knowledge about writing all kinds of code. This is perfect to start with. Specially thanks to the API part.
My solution to level 14 [1] involved a state variable on the me object; could that have broken something?
Try going to level 15 and resetting the level (Ctrl-4). Does that help at all?
(Update: Each time I enter a level, it loads as level 13, and I have to reset it to get the correct map and code -- just a heads-up in case it helps with debugging. Chrome 33.something, Windows 7 x64.)
Love that you're auto-gisting solutions. That was clever - I presume you are browsing through searching for the common description tag? I also like the API popup, although I didn't see it until I got stuck on the (uneditable) level 7. I verified this because $('.editableLines') == [] in console! Perhaps this is a very fancy meta-game that you can only win with a pull request? :)
Does your level 7 not look like http://i.imgur.com/AFTxPWC.png ?
Hint: Phone functions
canvasContext.beginPath() Begins drawing a new shape. canvasContext.beginPath(x, y) Sets the end coordinates of a line.
this second beginPath should be lineTo, took a minute to debug in level 16 :)
Perhaps consider a level select, since it's able to remember your solutions?
I don't see anywhere to edit on level 21, and level select says there's one more...
EDIT: OK they load now, servers must have been busy.
EDIT2: Credits load on the main site but not on the mirror.
With only 75, the level is easily solved via https://en.wikipedia.org/wiki/Big_sky_theory
http://qiao.github.io/PathFinding.js/visual/
You'd just need to recalc the pathfinder each time.
The robot follows my player, unless it's green, in which case it goes 'down' (cause it wont pick up the key under remote control for some reason). I'm saving state in the color of the player.
The robot goes down unless its x-coordinate is between 20 and 33, in which case it goes up. Kinda cheesy, but it works...
Variable to control the direction the robot moves, changed with the function phone.
var r = Math.random();
me.move(
r < 0.4 ? 'right' :
r<0.6 ? 'up' :
r < 0.8 ? 'down' :
'left'
);
Works eventually :)For level 13 I just used:
me.move(Math.random() > 0.5 ? 'right' : 'down');
And regenerated the map until I got to one that could be solved by moving only right and down.Did the quick ^W at work :|
I broke my game by reassigning the getWidth method to return an invalid value:
map.getWidth = function() { return 1; }
The approach worked with a valid value: ( map.getWidth = function() { return 6; } ) and I beat the level, but I had to replay the previous 3 levels.
Some suggestions (Again, we loved the game!) - Provide some interface to conveniently index all the solution Gists. - Provide some way to quickly skip to last level played.
Other thoughts: - Enjoyed the look, feel, and user interaction. - The music was great. - I will keep playing this game. :)
---
Edit: it saves the game state in localstorage; kudos!
if ( ! ( player.cc && player.cc.length ) ) {
player.cc = ['#f00', '#ff0', '#0f0'];
}
player.setColor(player.cc.shift());
It says it's loading the next level, but the code and gamefield remain stuck on level 7 until you push it to level 12.No clue why that's happening, but we'll continue to investigate.
We're still not sure why it happens or when (it seems to only affect a few people), but we'll try to figure it out.
There aren't any errors you can see in the JavaScript console, are there?
I'm also encountering a bunch of exceptions during game execution instead of at build, which are making things rather hard to beat. I'd list them but they vanish faster than I can read them.
What was happening was that the highlighting of you and the boss were not showing. So just the page without anything interactive.
not sure if you would want to disallow this or not, but i find it allows for some interesting hacky solutions.
One thing it drives home for me is just how distracting music is when I'm trying to code. It pulled up that first listing and I literally could not parse it until I muted the audio. Then it immediately turned back into code.
It's like music just turns off the switch that connects the eyeballs to the brain. Amazing.
Anyhoo, nice game, it doesn't matter how you solve it, as it still proves that eval is evil ;)
Great game!
https://gist.github.com/anonymous/dacd3f1de73a59b5983e
Is this cheating?
Solved it now by changing greenKey to theAlgorithm
d up the function p 1, found:oad the level
Wone! number of exits ...Another weird thing I noticed was that next to the computer and telephone in the inventory, two letters "k" appeared. I'm using Firefox 28 on Windows 7.
Here's the solution I used for colors.js https://gist.github.com/anonymous/ab5cd3b393c290fbf8c1
Does resetting these levels (Ctrl-4) help at all?
Anyway, really nice work with the game!
Loaded the site, popped open the Chrome Dev Tools, and was thoroughly disappointed.
Last weekend I hacked around in the source of various online games, I anticipated this to be a similar experience but was disappointed to find that we weren't supposed to actually change the game's source.
map.validateExactlyXManyObjects = function() {};
lets you put an exit tile next to your character on every levelOtherwise a very cool game!
Try restarting the game by running localStorage.clear() in the JavaScript console, then refreshing the page. Does that fix it?
I suspect that the GP ran into the same problem.
Try restarting the game by running localStorage.clear() in the JavaScript console, then refreshing the page. Does that fix it?
Okay, I think I've replicated it. I just overrode onCollision with empty braces, so the final definition looked like this:
map.defineObject('attackDrone', {
'type': 'dynamic',
'symbol': 'd',
'color': 'red',
'onCollision': function (player) {
player.killedBy('an attack drone');
},
'behavior': function (me) {
},
'onCollision': function (player) {
}
});
Note that you're closing the game's braces, inserting another definition, then re-opening braces to match the game's closing ones.Can anyone help?
player.killedBy(function(){throw new Exception();});
...but then that just means I get killed by "function(){throw new Exception();}".Edit: never mind, got it. Overriding onCollision helps. :)
Additionally it doesn't support injection there, so while "player.removeItem('greenKey'); player.additem('greenKey');" should work, it says "TypeError: undefined is not a function".
player.removeItem('greenKey'); return 0;} if(1==1){('');
Full link: https://gist.github.com/anonymous/c78b439d8f2369e03f68
Basically, this let's you delete the green item once. After that, it makes sure that return true is never reached (because of the 1==1), so next passages through green are allowed. Kinda liked the sneaky (''); myself :P
map.getPlayer().killedBy = function() {};
Drone just follows you around like harmless puppy.
(Being obtuse is difficult...)
moves = [['down', 5], ['right', 20], ['up', 1] /* and so on */] function (me) {}
for the red and yellow drones and function (me) {
var r = 'right:'+me.canMove('right');
var l = 'left: '+me.canMove('left')
alert(r+'; '+l);
}
for the green drones. But I always get 'right:true; left: true'. WTFFFFFF? :-(
Please please fix that, please!!!