Приключенческая игра, в которую играют путем изменения её 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
                      То что можно создавать второй выход это баг или так должно быть?
                    • +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 выкинуть в процессе убиения себя. И комментарий как раз есть про выбор способа умереть.
                                                                    • +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
                                                                                                          Игра действительно прекрасна! Спасибо за два часа чудного времяпрепровождения.

                                                                                                          Только вот босс слабоват :(
                                                                                                          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
                                                                                                            Вот бы было и на питоне

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