Пользователь
0,0
рейтинг
15 апреля 2014 в 20:48

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

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



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

Одно жаль — уровней мало. Бонус: милая музыка + хорошие комментарии в коде. Приятного вечера!
Вадим Колонцов @vk2
карма
98,7
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (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. Читы не использовал
      • 0
        Красавчик!
  • +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
    Вот бы было и на питоне

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