Приключенческая игра, в которую играют путем изменения её Javascript-кода

    Удивлен, что мимо Хабра прошла очаровательная приключенческая javascript-игрушка Untrusted.



    Надо помочь герою преодолеть более 20 уровней, в процессе прохождения которых мы встретим боевых дронов, реки и лабиринты, ключи и замки, звонки «оператору Матрицы» и многое другое… К счастью, благодаря взломанному компьютеру у главного героя есть доступ к коду игры! И если на первых уровнях мы просто изменяем на ходу реальность, то в конце нам придется запускать в нее свои js-объекты, помогающие атаковать мега-босса.

    Одно жаль — уровней мало. Бонус: милая музыка + хорошие комментарии в коде. Приятного вечера!
    Поделиться публикацией
    Комментарии 114
      +2
      map.placeObject(7, 5, 'block');
      чёрт, я жив!
        +2
        Напомнила Ruby Warrior [веб|консоль] — похожая для рубистов. О, эти игры в которых нельзя в тупую клацать — побольше бы таких.
          0
          а есть еще такие по руби?
          затяунло, но она кривовата — курсор в сафари там криво встает не по положению букв и выделение криво работает, после 4-го уровня сложные конструкции уже очень напрягает писать.
            0
            Какая отличная штука! Кстати, не совсем по теме, но очень советую игровой момент с Руби и rubykoans.com
              0
              Гораздо удобнее получается делать это в консоли, используя любимый редактор кода. А еще там есть продолжение приключений воина, в двух измерениях.
            0
            Ну все, пипец работе…
              +2
              Про 11 уровень (Робот и красный ключ)-
              Много нас таких извращенцев?
              if(Math.random()>0.15){
              me.move('right');} else {me.move('down');}

                +1
                Я извратился по другому — сторону, в которую идти, робот определял в зависимости от положения игрока относительно стартовой точки игрока.
                  +1
                  Тоже так поступил. Получился аналог пульта управления :)
                  Скрытый текст
                  if (player.atLocation(0, map.getHeight() — 1)) {
                  me.move(«down»);
                  } else if (player.atLocation(0, map.getHeight() — 2)) {
                  me.move(«up»);
                  } else if (player.atLocation(1, map.getHeight() — 1)) {
                  me.move(«right»);
                  }
                    0
                    Для 11го делал проще:
                    Решение
                    if(me.canMove('down'))
                       me.move('down');
                    else if(me.canMove('right'))
                       me.move('right');
                    



                    А на 12 добавил счетчик :)

                    Решение
                    map.i = map.i || 0
                    
                    if(map.i < 5)
                        me.move('down');
                    else if(map.i < 30)
                        me.move('right');
                    else if(map.i < 35)
                        me.move('up');
                    else {
                        if (me.canMove('right'))
                            me.move('right');
                        else
                            me.move('down');	
                    }
                    map.i++;
                    



                    А на 13ом уже сделал аналогичное решение с ручным управлением
                    0
                    Я просто определял направление перемещения в зависимости от цвета игрока…
                  +9
                  чит
                  переопределив функцию валидации структуры уровня, можно поставить в удобное место дополнительный «выход» и воспользоваться им.

                  this ['v' + 'alidateLevel'] = function() { return true; } 
                  map.placeObject(1, 1, 'exit'); 
                   

                    0
                    Скриншот напомнил игру KLAD, в которую я играл ещё на ПК «Корвет».
                      0
                      А мне – «Лестницу» на «Радио-86РК».
                      0
                      То что можно создавать второй выход это баг или так должно быть?
                        0
                        Так должно быть.
                          0
                          Местами.
                          +5
                          Игра классная =)

                          Еще немного читов
                          Автор игры поставил кучу валидаций введенного кода на всякие разные плохие слова типа eval, на использование символа подчеркивания (с него начинаются запрещенные игроку методы map) и т.д., но забыл запретить итерацию по объекту. В итоге можно вытащить в паблик всякие прикольные методы типа removeItemFromMap. Например так:

                              var allmembers = "";
                              var underscore = null;
                              
                              for (var n in map)
                              {
                                  if (!underscore) // первый метод приватный
                                      underscore = n.slice(0, 1);
                                  allmembers += n + "; ";
                              }
                              
                              for (var n in map)
                              {
                                  if (n.slice(0, 1) == underscore)
                                  	map[n.slice(1)] = map[n];
                              }
                          


                          Еще автор хотел заблокировать eval, setTimeout и т.п., но почему-то отдает window в качестве this. В итоге делаем, что хотим:

                          
                          this ['docu' + 'ment'].GlobalMapReference = map;
                          var st = this ['set' + 'Timeout'];
                          st ("alert(docu" + "ment.GlobalMapReference.getPlayer);", 1000);
                          


                            +1
                            Так ведь это не его фейл, это интересное прохождение! Побольше бы таких.

                            Кстати, API для конечных уровней довольно аккуратное, правда?
                              0
                              В целом да, но с такими читами все становится очень просто. Исключение — уровни, где редактировать можно только одну константу.
                              +1
                              Волшебный ластик
                              (new Function('map',
                                  'map.erase = map[String.from'+'CharCode(95)+\'removeItemFromMap\'];'))(map);
                              map.erase(x,y,'wall')
                              

                                0
                                Медалька «Нео» почётно заслужена! :)
                                0
                                Интересно, а сохраниться можно? А то сегодня пройти не успел, а потом с нуля писать не интересно будет. (
                                  +2
                                  Вроде она сохраняется в localstorage сама.
                                  +3
                                  Хохотал, убивая босса самонаводящимися ракетами :-)
                                  • НЛО прилетело и опубликовало эту надпись здесь
                                      +1
                                      Спавн был честный? Или через setInterval?
                                        +2
                                        Спавн был честный? Или через setInterval?

                                        Телефоном же ж можно спавнить)
                                          0
                                          До телефона еще добежать надо =)
                                            +2
                                            ;)
                                            Скрытый текст
                                            Один блок дополнительный можно поставить посреди карты
                                              +2
                                              Или
                                              можно много деревьев насадить
                                                +1
                                                Кстати, из этого получается отличное нестандартное решение 18го уровня :3
                                              +5
                                              Пфф. Переопределите Math.random и босс не будет стрелять )
                                                +2
                                                А я один...
                                                … создавал новый тип блока (главное, не динамический), построил себе «козырек» и добежал до телефона. Там уже создаем что хотим.
                                                  0
                                                  Хе-хе
                                                  А еще на уровне нет проверки на количество item-ов...
                                                    0
                                                    Можно было не создавать свои блоки, а использовать деревья.
                                              0
                                              Можно переопределить кнопку (например, left) и спавнить сколько угодно
                                                0
                                                Я, каюсь, вообще не парился и спавнил пули в углу по таймеру. Получалась хорошая такая очередь в 5 рядов. Оставалось только проверку поставить, чтобы оно не пуляло, когда боссы кончатся.
                                                  0
                                                  Там же нельзя ставить таймер честным способом :)
                                                    0
                                                    А я не говорил, что я честный =)
                                                    Скрытый текст
                                                    var st = this ['set' + 'Interval'];
                                                    st(myFunction, 100);

                                            +4
                                            А я добавил к самонаводящимся ракетам еще и разделяющиеся боеголовки :)
                                            Вот так:
                                              0
                                              *___* как так?
                                                0
                                                WTF?! Разве осколки не должны самоуничтожаться при контакте с боссом?..
                                                  +1
                                                  Никто не запрещает создавать объекты в onDestroy ;)
                                                    0
                                                    Хех. А я уже добился очень похожего эффекта при помощи
                                                    target = me.findNearest('boss');
                                                    if ( target ) {
                                                        /*лететь в сторону таргета*/
                                                        if ( Math.abs(target['x']-me.getX()) + Math.abs(target['y']-me.getY()) == 1 )
                                                            map.placeObject(target['x'],target['y'],'rocket');
                                                    } else {
                                                        me.move('down');
                                                    }
                                                    

                                                    В результате в босса врезается две ракеты, одна из которых уничтожается, а вторая — занимает его клетку, чтобы на следующем шаге сокрушить следующего босса и так далее.
                                                +1
                                                Аналогично, но я поступил более нечестно по отношению к боссу. А зачем вообще тут телефон? =)
                                                Принцип
                                                Рядом с собой делаю невидимую линию с триггером на прохождение через нее. Триггер спавнит 2 линии по 12 ракет летящих от x = 0 вправо по линиям где летают боссы. Босс сдыхает с 1 пайки.
                                                gist.github.com/anonymous/10977969
                                                  +3
                                                  В первый раз убил его так же, но потом захотелось придумать что-нибудь пооригинальнее =)
                                                  Вот что вышло


                                                  Кто придумает что-нибудь более интересное?

                                                  PS. Читы не использовал
                                                +3
                                                хехе, еще на 11-м уровне сделал зеркалирование робота относительно положения игрока в нижней части. Как знал что пригодилось без изменений для 12 и 13-го уровня :)

                                                код
                                                var player = map.getPlayer();
                                                            
                                                var dir = 'none';
                                                            var y = player.getY();
                                                            y = map.getHeight() - y;
                                                            var x = player.getX();
                                                            if (me.getX() != x)
                                                            {
                                                            	if (me.getX() > x)
                                                                {
                                                                	dir = 'left';
                                                                }
                                                                else
                                                                {
                                                                	dir = 'right';
                                                                }
                                                            }
                                                            else if (me.getY() != y)
                                                            {
                                                            	if (me.getY() > y)
                                                                {
                                                                	dir = 'up';
                                                                }
                                                                else
                                                                {
                                                                	dir = 'down';
                                                                }
                                                            }
                                                            me.move(dir);
                                                
                                                  0
                                                  Отличная идея. А я изучал алгоритмы поиска пути и потом час с лишним это отлаживал. Зато хоть что-то новое узнал… Простое решение всегда ближе, чем кажется :)
                                                    0
                                                    Сложно как-то =) Просто указываем текущий дирекшн.

                                                    me.d||player.setPhoneCallback(function(){me.d = me.d==4?1:me.d+1;});
                                                    me.d = me.d||1;
                                                    switch(me.d){
                                                    case 1:me.move('up');break;
                                                    case 2:me.move('down');break;
                                                    case 3:me.move('right');break;
                                                    case 4:me.move('left');break;
                                                    }
                                                    
                                                      0
                                                      Сделал точно так же. Только еще игрока красил, чтобы видеть текущее направление.
                                                    +4
                                                    ну и зачем было перед сном такое вылаживать? ты не прав. это надо было хотя бы в пятницу…
                                                      –1
                                                      Вот и вышел 4й сезон «Обмани меня, если сможешь». Сидишь и придумываешь читы…
                                                        +2
                                                        «Приключенческая игра»??? Ну рогалик же это, ну!

                                                        Отличная игра!
                                                        Удивился, увидев пост.
                                                          +1
                                                          Характерными особенностями roguelike являются генерируемые случайным образом уровни
                                                          (с) Википедия про рогалики

                                                          Я так понимаю, тут только 20 уровней?
                                                            0
                                                            Я так понимаю, тут только 20 уровней?

                                                            По крайней мере 21.
                                                            +2
                                                            Я бы сказал, что тут скорее реверанс в сторону rogue-like (text-mode, @ и проч)… Но уж точно не -like.
                                                            0
                                                            Вспомнилась эта codecombat.com
                                                              0
                                                              Великолепно! Жаль на английском, я бы может сына на такие игры подсадил…
                                                              прошел без читов
                                                                +3
                                                                Форкаете и переводите :)
                                                                +2
                                                                Игра понравилась, а 21 уровень вообще проходим или он просто оставлен как заглушка?
                                                                  +2
                                                                  А, уже сам всё понял, перешёл на 22.
                                                                    0
                                                                    Правда?)
                                                                      +1
                                                                      Да, правда. Хитро. Случайно нашёл, изучая интерфейс игры.
                                                                      Только после 21-ого у меня перешло не на 22-й, а на конец игры )
                                                                  +1
                                                                  Вообще офигительная игра!..
                                                                    +1
                                                                    Еще бы топ самых красивых решений для каждого уровня сделал кто-нибудь. После некоторых уровней очень хотелось увидеть, как их проходят нормальные люди (без «читов»).

                                                                    Например, lvl15 как по-умному проходить? Там, где можно править только параметры player.killedBy(). Я включал режим Иисуса через попытку клонирования:
                                                                    player.killedBy(map.placePlayer(3, 3));//);

                                                                    валидатор не позволял мне утонуть. Но, на мой взгляд, это костыль, и должно же быть что-то еще.
                                                                      0
                                                                      Не знаю, как по-умному, но я туда просто написал несуществующую переменную exit (player.killedBy(exit);). Таким образом, он просто матюгается на неизвестный тип, и ты не тонешь
                                                                        0
                                                                        Ну, аналогичное решение по сути. Интересно наличие чего-то принципиально иного.
                                                                          +3
                                                                              map.defineObject('water', {
                                                                                  'symbol': '░',
                                                                                  'color': '#44f',
                                                                                  'onCollision': function (player) {
                                                                                      player.killedBy();},'onCollision':function(){//);
                                                                                  }
                                                                              });
                                                                          
                                                                          


                                                                          а, ниже уже написали этот вариант
                                                                        0
                                                                        Вообще говоря в четвертом уровне написана подсказка, которая пригодилась только в этом. Ищите :)
                                                                          0
                                                                          Ну почему же, на самом четвёртом уровне эта подсказка очень помогает.
                                                                            0
                                                                            По моему, на четвертом уровне другое решение и придумать то сложно. Это самое очевидное.
                                                                              0
                                                                              Возможно; просто мне решение в голову пришло как раз-таки сразу после прочтения названия уровня.)
                                                                          0
                                                                          Дык, там вроде бы в том и задумка, чтоб exception выкинуть в процессе убиения себя. И комментарий как раз есть про выбор способа умереть.
                                                                            0
                                                                            И название уровня…
                                                                            +3
                                                                            Принципиально другое решение — переопределить onCollision, добавив его ещё раз.
                                                                            Как-то так
                                                                            'onCollision': function (player) {
                                                                            player.killedBy('whatever');},'onCollision': function (){ ('whatever');
                                                                            }


                                                                            Но я решал тоже через exception, так проще.
                                                                              0
                                                                              Думаю, этот вариант тоже подходит под определение «принципиально иное». =)
                                                                              Получается смешно
                                                                              map.defineObject('water', {
                                                                                  'symbol': '░',
                                                                                  'color': '#44f',
                                                                                  'onCollision': function (player) {
                                                                                      player.killedBy('');},type:'item'});(function(){if(1){void(0);
                                                                                  }
                                                                              });
                                                                              


                                                                              А вот про эксепшены я почему-то не подумал, несмотря на название.
                                                                                0
                                                                                Самое простое решение
                                                                                player.killedBy(none);
                                                                                

                                                                                  0
                                                                                  Кстати, босса можно убить без использования телефона. Вообще, это можно сделать одной кнопкой. И без переопределения чего-то существующего. Кто еще догадается?
                                                                                    +2
                                                                                    Я рисовал линию в точке старта, триггер коллизии с линией спавнил пули и тупо закидывал босса ими
                                                                                    Скрытый текст
                                                                                        map.defineObject('arrow', {
                                                                                            'type': 'dynamic',
                                                                                            'symbol': '^',
                                                                                            'color': 'green',
                                                                                            'interval': 100,
                                                                                            'projectile': true,
                                                                                            'behavior': function (me) {
                                                                                                me.move('up');
                                                                                            }
                                                                                        });
                                                                                    
                                                                                        function testFunc() {
                                                                                          map.placeObject( 31, 21, 'arrow' );
                                                                                        }
                                                                                    
                                                                                        //press UP/Down at start location to FIRE
                                                                                        map.createLine([10, 0], [10, 2000], function(){
                                                                                          testFunc();
                                                                                        });
                                                                                    
                                                                                    

                                                                                      0
                                                                                      Да, как много решений узнаешь. Про линии я не подумал, т.к. не запомнил их из-за того, что квесты с ними были
                                                                                      слишком легкими
                                                                                      Например, в уровне с лазерами достаточно переопределить функцию
                                                                                      function getRandomInt() {return 0;}
                                                                                      


                                                                                      Но вобще я имел ввиду
                                                                                      породить рядом с собой элемент инвенторя, при подборе которого спавнить ракетный удар:
                                                                                      gist.github.com/anonymous/efe382b17347dc4bde55
                                                                                    +1
                                                                                    не зря же в заголовке написано exceptional =)
                                                                                    Как-то так
                                                                                    function () {throw 'Режим Иисуса!'}

                                                                                    +4
                                                                                    Мне понравилось все: задумка, дизайн, интерфейс, музыка. Вот бы побольше таких игр.
                                                                                    Кстати, кто как 9 уровень прошел?
                                                                                    Мое решение кажется мне немного читерским
                                                                                    т.к. я тупо проложила мост.
                                                                                    map.defineObject('bridge', {
                                                                                            'type': 'dynamic',
                                                                                            'symbol': '▓',
                                                                                            'color': '#420',
                                                                                            'transport': true, 
                                                                                        });
                                                                                    for (var i = 5; i < 15; i++) 
                                                                                       map.placeObject(20, i, 'bridge');
                                                                                    

                                                                                      +2
                                                                                      На 9 уровне можно...
                                                                                      … телефон заюзать. Встаёшь на плот — и направляешь в обратную сторону.
                                                                                        +1
                                                                                        По-моему, это референсное решение. Так неинтересно :)
                                                                                        +2
                                                                                        Забавное решение, но до него нужно ещё додуматься!
                                                                                        Получив телефон (а с ним и колбэки), сразу начинаешь думать, куда бы их применить.

                                                                                        А решение-то вот, на поверхности…
                                                                                          +3
                                                                                          Точно так же, но я не парился с вычислением координат, а замостил всю реку :)
                                                                                          0
                                                                                          А что насчет 14го, кто как прошел? Я отдал-таки зеленый ключ в итоге, но может есть более интересное решение?

                                                                                          Решение
                                                                                          'greenKey');}else {return false;} if(0) {//
                                                                                            +1
                                                                                            Скрытый текст
                                                                                            Заменил на «blueKey», без хитростей, а потом пошёл по такому пути:

                                                                                              +2
                                                                                              Скрытый текст
                                                                                              Я «отдавал» block.
                                                                                              +3
                                                                                              В точности моё решение!

                                                                                              А можно смешнее (коллега сделал так):
                                                                                              Скрытый текст
                                                                                              player.removeItem('greenKey');map.placeObject(24,12,'greenKey');
                                                                                                +3
                                                                                                Я желтый ключ в нужном месте создавал

                                                                                                  +1
                                                                                                  А вот это неожиданный ход, даже не думал о таком!
                                                                                                  +1
                                                                                                  Отдавать можно любой предмет, известный игре, при этом не важно, есть ли он у игрока. Я отдавал theAlgorithm.
                                                                                                  +1
                                                                                                  А вот мне интересно, каким образом вообще была сделана возможность запрещать редактировать часть текста? Ведь это контрол браузера, как я понимаю. Конечно, редактор CodeMirror что-то на него навесил, но ведь не мог же он создать редактор целиком! Он явно нативный. Кроме того, это все работает кроссбраузерно! Кто-нибудь может объяснить, как делается эта магия?
                                                                                                    0
                                                                                                    Откройте раздел меню «scripts», там весь код. В самом начале файла codeEditor.js:
                                                                                                    'begin_line':'#BEGIN_EDITABLE#', 'end_line':'#END_EDITABLE#',
                                                                                                      0
                                                                                                      Это-то понятно, что где-то есть разметка, что можно редактировать, а что нельзя. Меня интересует, как вообще такое возможно? API контролов редактирования текста не позволяет такое делать даже на декстопе, что уж говорить о вебе? Как CodeMirror этого добился? Есть что почитать по теме (кроме кода CodeMirror)?
                                                                                                        0
                                                                                                        Например, когда изменяешь текст (удаляешь букву) — ставить эту букву на место и контрол в предыдущю позицию.
                                                                                                    +1
                                                                                                    Прошел игру. Влюбился. Просто офигенно. Ребята, а есть еще подобное?
                                                                                                      +1
                                                                                                      Ну, например, если «программировать» паяльником, то есть конструктор. Вообще, от этих ребят есть много занятных игр.
                                                                                                      +1
                                                                                                      19 уровень я даже осознать не успел. Поклацал куда-то и оказался на следующем уровне.
                                                                                                        0
                                                                                                        Аналогично, просто все время жал вверх, пытаясь понять, что происходит, и неожиданно прошел. Потом уже почитал код — кажется надо совместить две собаки.
                                                                                                        0
                                                                                                        А на 21 ( endOfTheLine.js ) что делать?
                                                                                                          +4
                                                                                                          Не стану портить удовольствие, поэтому лишь подскажу, что копать надо в сторону меню (^0). После осознания обнаруженного лично я пожалел, что не видел этого в процессе прохождения, появилось даже желание пройти заново.

                                                                                                          Помнится, в интервью с Сидом Мейером было такое понятие, как переигрываемость. Как ни странно, у Untrusted этот показатель куда выше, чем у большинства современных игровых поделок.
                                                                                                            0
                                                                                                            Ой, упустил! Спасибо, буду копать дальше!
                                                                                                              0
                                                                                                              И ещё раз спасибо, однозначно минимальность подсказки оставила место для удовольствия! Если б мог, плюсанул везде! Пойду переигрывать!
                                                                                                                0
                                                                                                                А я тоже пожалел и проверил. При игре с чистого листа этой возможности нет до самого конца.
                                                                                                                  0
                                                                                                                  Видимо в этом суть обладания алгоритмом.
                                                                                                                    0
                                                                                                                    Можно просто перепроходить уровни, не сбрасывая игру.

                                                                                                                    Или выйти на другой масштаб и работать с форком, снаружи «матрицы».
                                                                                                                +1
                                                                                                                Большое спасибо за ссылку на сию замечательную игру! Надеюсь проект будет развиваться и в один прекрасный день мы увидим там нелинейность, побочные миссии, красивый сюжет и конечно-же больше уровней!
                                                                                                                Спойлер
                                                                                                                  0
                                                                                                                  [missing]
                                                                                                                  0
                                                                                                                  Игра действительно прекрасна! Спасибо за два часа чудного времяпрепровождения.

                                                                                                                  Только вот босс слабоват :(
                                                                                                                  map.defineObject
                                                                                                                  (
                                                                                                                      'rocket', 
                                                                                                                      {
                                                                                                                          'type': 'dynamic',
                                                                                                                          'symbol': '>',
                                                                                                                          'color': '#bfbf00',
                                                                                                                          'interval': 100,
                                                                                                                          'projectile': true,
                                                                                                                          'behavior': function(me) 
                                                                                                                          {
                                                                                                                              me.move('right');
                                                                                                                          },
                                                                                                                          'onDestroy': function(me) 
                                                                                                                          {
                                                                                                                              var x = me.getX();
                                                                                                                              var y = me.getY();
                                                                                                                                  
                                                                                                                              if(x < map.getWidth())
                                                                                                                                  map.placeObject(x + 1, y, 'rocket');	
                                                                                                                          }
                                                                                                                      }
                                                                                                                  );
                                                                                                                      
                                                                                                                  map.placeObject(0, 5, 'rocket');
                                                                                                                  map.placeObject(0, 6, 'rocket');
                                                                                                                  
                                                                                                                  this ['v' + 'alidateLevel'] = function(){return true}; 
                                                                                                                  



                                                                                                                  Давненько хотел сделать нечто похожее (в плане игровой процесс = программирование), но почему то прицепился к десктопному приложению с мини интерпретатором. А ведь, оказывается, можно велосипедов не изобретать и использовать JS… надо это хорошенько обдумать.
                                                                                                                    0
                                                                                                                    Вот бы было и на питоне

                                                                                                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                                                                                    Самое читаемое