Пишем бота для браузерной игры agar.io



    Все уже наверно в курсе о такой замечательной веб-игре, как agar.io.
    В очередной раз проиграв в ней более везучему сопернику, я тихо выругался про себя и решил как-то взломать эту игрушку, чтобы получить наконец в ней преимущество! В итоге мне удалось создать себе отряд игровых ботов, которые стремятся найти меня на карте, чтобы влиться в мою игровую клетку.

    Влезаем в клиент игры


    Сначала надо было понять, как все работает.
    Игра написана на javascript и общается с игровым сервером через веб-сокет.

    Основной игровой скрипт лежит в файле main_out.js.
    Код там конечно же обфусцирован и всячески пытается не давать себя запускать откуда не следует:

    if ("agar.io" != h.location.hostname && "localhost" != h.location.hostname && "10.10.2.13" != h.location.hostname) h.location = "http://agar.io/";
    

    Развернув файл в читаемый вид через дебаггер Хрома, встал вопрос: каким образом вклиниться в логику игры?

    Вначале я решил создать локальную копию файлов и соединяться с сервером, отключив в браузере проверку на кроссдомен:

    #файлы игры
    /css/bootstrap.min.css
    /js/jquery.js
    /index.html
    /main_out.js
    /quadtree.js
    
    #запуск Хрома без same origin policy
    chrome.exe --disable-web-security
    

    Это заработало для AJAX запросов игровых регионов, но дальнейшие попытки соединиться по веб-сокету были отклонены. Нужен был другой подход.

    Подменяем файлы по урлу


    Рабочим решением стала загрузка реального игрового клиента, но подмена для браузера нужных файлов на свои. Для этого устанавливаем замечательную программу Fiddler Web Debugger и указываем нужные пути в табе AutoResponder:



    Такой подход очевидно требует держать Fiddler запущенным во время игровой сессии.

    Пытаемся обмануть сервер


    Хочу похвалить авторов игры — на сервер не отправляется ничего такого, что можно было бы поменять в свою пользу (например: свой размер :)). Клиент шлет лишь координаты мыши, куда бы он хотел передвинуть свою клетку и сообщает о желаемых действиях (например: разделиться).

    Сервер в свою очередь не присылает клиенту «лишних» для него данных. Например, когда я увеличил масштаб игровой карты, то сервер все равно присылал лишь то окно объектов, которое я должен был видеть в рамках своей клетки:



    Казалось бы все пути закрыты: сервер не доверяет клиентам никакой важной информации и всё просчитывает самостоятельно.

    Но тогда можно обмануть сервер в рамках его правил: создать стаю ботов, которые постоянно будут жертвовать собой, увеличивая мою массу. Но как же ботам находить мою клетку на карте? Выручило само API сервера: если постоянно отправлять ему например координаты (0, 0), то игровая клетка будет всегда следовать в эту часть карты без остановки, пока не достигнет цели. Вместо нулей надо всего лишь отправлять ботам мои текущие координаты и они сами будут приходить ко мне на ужин!

    Пишем ботов в текущем окне


    Код клиента одновременно получает данные и перерисовывает объекты на экране. Можно было бы открыть 20 табов, управляемых ботами и один мой игровой таб. Но тогда надо было бы как-то передавать мои координаты в соседние табы. Плюс рисование каждого таба тормозило бы весь браузер (я пробовал — так и есть). Поэтому было решено создавать новые игровые сессии прямо в текущем табе, но выключить для них связь с отображением:

    //запускаем новые копии игры
    var isBot = true;
    for (i = 0; i < botsCount; i++) {
    	//нужно делать паузу перед новым ботом,
    	//чтобы сервер не отклонил слишком частые соединения
    	setTimeout(function(){
    		game(window, r, isBot, botsUrl, M);
    	}, 500);
    }
    
    //не даем эти копиям рисовать на экране
    function paint() {
    	if(bot) return;
    	//...
    }
    

    Так же нужно было дописать код, чтобы при смерти бота, он автоматически начинал новую сессию.

    Результаты работы


    Боты создаются. И находят меня на карте!



    Однако, все не так радужно.

    Во-первых, сервер раскидывает игроков по игровым комнатам. Поэтому со мной на карту из 50 ботов попадают 2-3. Остальные «играют» в других комнатах, следуя по координатам из соседней Вселенной.

    Во-вторых, ботов может съесть кто-то другой! Поэтому им удается придти ко мне где-то пару раз в минуту.

    И, наконец, в-третьих, боты маленькие. Идя ко мне, они не набирают особой массы. Поэтому, с определенного этапа, их вклад в мою победу становится минимальным.

    Выводы


    Авторам игры удалось создать замечательный сетевой код, который не ломается от банального хакинга. Однако игра не защищена от ботоводства и немного разобравшись в коде клиента, можно создать себе небольшое техническое преимущество перед остальными.

    Если же решить вопрос с тем, чтобы боты подключались на нужную карту, то тогда есть возможность серьезно потеснить своих менее технически-подкованных соперников.

    Я же своей маленькой цели достиг:


    Может быть благодаря ботам, может быть мне повезло самому.

    [ Итоговый код клиента ]

    Спасибо за внимание и удачи в игре!
    Поделиться публикацией
    Похожие публикации
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 35
      –2
      Мне единственному кажется, что в этой игре нельзя выиграть благодаря какому-то плану.
      Рост в размерах происходит только благодаря великому рандому и он глобально не зависит от твоих действий.
        +10
        Зависит. Можно подкрадываться и съедать конкурентов делением. Можно кидать приманки, можно растить зеленые растения, чтобы они делились и разрывали бОльшего конкурента на части…
        Шахматы прям :)
          +5
          Да. На самом деле, успех в игре зависит прежде всего от умений игрока, а уже потом от удачи.

          Для того, чтобы стабильно выбиваться в лидеры нежно освоить несколько приемов:
          1) рассчитывать свою массу и массу противника, чтобы при делении не оказаться в патовой ситуации и не быть съеденным;
          2) разбивать лидеров в с помощью зубастых клеток;
          3) обмениваться массой между поделившимися клетками;
          4) ретироваться делением в случае угрозы со стороны лидеров;
            0
            их можно растить?
              0
              Да, стреляя в них.
                0
                С помощью клавиши W можно кидать массу в зеленое растение — от этого оно распухает и в какой-то момент делится. Таким образом можно делать ловушки для близких к растению соперников, разбивая их на части прилетевшим к ним новым растением.
                  0
                  вот оно как )
            +1
            Круто, я тоже пользовался fiddler'ом, но хотел немного более интеллектуального бота. Поделюсь своей наработкой немного деобфусцированного скипта, правда, одной из предыдущих версий, gist.github.com/abby-sergz/65aad7682de388f14ee2.
              0
              Круто! Спасибо!
              Уже есть достижения в интеллекте бота?
                0
                Достижений пока нет, основная причина — мало времени :)
              0
              Во-первых, сервер раскидывает игроков по игровым комнатам. Поэтому со мной на карту из 50 ботов попадают 2-3. Остальные «играют» в других комнатах, следуя по координатам из соседней Вселенной.

              Можно переконнектиться к нужному серверу, зная его IP: в консоли после выбора региона будет написан ip-адрес, потом можно коннектиться к нему:
              connect("ws://213.219.39.46:443"); //нужный ip сервера
              

              Правда, наверное, нужно будет что-то подправить чтобы это всё работало на одной странице.
                0
                Если я не ошибся, то сервер на одном ip содержит много игровых комнат. Потому что я подставляю один и тот же ip для всех ботов. Но ко мне попадает лишь часть. Другие крутятся с другими игроками.
                В итоге в комнате играет 15-20 игроков, на сервер может и 5000.
                  0
                  Хм, точно. Тестил со вкладками браузера. Если подключаться довольно быстро, то попадаю в одну комнату, если через некоторое время, то уже кидает в другую.
                    0
                    Комнат там действительно довольно много m.agar.io/info
                      0
                      в US-Fremont всего 3 комнаты на сервак)
                +2
                Можно и проще. Перед загрузкой удаляем main_out из страницы и подгружаем свою копию. Пример для GM или аналогов — pastebin.com/7ZhB5cVD
                  0
                  Спасибо!
                    0
                    К сожалению работает не для всех браузеров.
                    «beforescriptexecute» был убран из Chrome.
                    0
                    (del)
                      –3
                      В треде на reddit уже всё давно обсудили, и даже разработчик ответил: www.reddit.com/r/Agario/comments/34z3zp

                      Да и на youtube, к слову, такие же боты-кормильцы 8 дней как выложены: www.youtube.com/watch?v=XyLWCdnff2A
                      +4
                      Я пошел по другому пути. Ботов не стал делать, а расковырял оригинальную js и сделал пачку улучшений код на githab
                        +1
                        Круто, еще бы границы поля сделать видимыми и флаги вернуть)
                          0
                          Ага про поля как доберусь, сделаю. А флаги стран работают даже с «Enemy Types Hack», сейчас проверил.
                        +13
                        Я пошёл дальше и вообще исключил игрока оставив лишь ботов, которые кооперируются для выживания.

                        Свой код я инъектирую с помощью отладчика. Достаточно остановить его в любой внутренней функции и мы имеем полный доступ ко внутренним переменным. Так что можно через консоль создать замыкание, вызывающееся по таймеру, и можно творить что угодно.

                        Инфа о своих клетках находится в хэше «m». Инфа обо всех остальных клетках лежит в словаре «v». Этого уже достаточно для игры. Пробегаемся в цикле по своим клеткам и по чужим и складываем вычисленные для пар «я — не я» ускорения. Потом дёргаем onmousemove, чтобы сообщить движку в какую сторону мы хотим ускориться.

                        Все чужие клетки делятся на следующие группы:

                        1. Вирусы — зелёные пассивные клетки, которые могут взрывать остальных. У них стоит флаг isVirus. От них отталкиваемся по обратно квадратичному закону.

                        2. Друзья — дружеские боты, действующие всоседних вкладках. Их определяем по префиксу в имени. К ним притягиваемся с константной силой. Это позволяет им находить друг друга даже если отреспаунились в разных конца карты.

                        3. Враги — клетки, которые больше максимального размера, что мы можем съесть. От них отталкиваемся как отвирусов, но с другим коэффициентом.

                        4. Еда — все остальные клетки, существенно меньше нашей. К ним притягиваемся по обратно квадратичному закону.

                        Кроме того, чтобы бота не зажимали к стенке, добавляем отталкивание от них в перпендирулярних к ним направляниях по тому же закону обратных квадратов.

                        Итого это позволяет убегать от группы врагов поедая попадающуюся по пути еду и сливаться с друзьями образуя более крупную особь. Однако люди всё ещё могут хитростью зажать нашего бота в тиски или порвать его простреливая через вирус.

                        Чтобы отреспауниться, достаточно вызвать функцию setNick(string). Я её вызываю просто всегда при вызове моего замыкания.

                        Фото рекорда:
                        image

                        Видео с четырьмя ботами:


                        Код бота без констант - подберите свои значения :-)
                        window.canvas = document.getElementById('canvas')
                        
                        if( window.botovod ) clearInterval( botovod )
                        
                        window.botovod = setInterval(function () {
                            setNick(nname)
                        
                            var mys = m
                        
                            var others = Object.keys(v).map(function (k) {
                                return v[k]
                            })
                        
                            var dist = function (one, two) {
                                return Math.sqrt( Math.pow(one.x - two.x, 2) + Math.pow(one.y - two.y, 2) )
                            }
                        
                            var aX = 0
                            var aY = 0
                        
                            mys.forEach( function(my) {
                                others.forEach(function (o) {
                                    if( my === o ) return
                                    var od = dist(my, o)
                                    if (o.isVirus) { // virus
                                        var f = - localStorage.antivir / Math.pow(od, 2)
                                    } else if (o.name.indexOf(pfix) === 0) { // friend
                                        var fac = my.size / o.size
                                        if( fac > 1 ) fac = 1/fac
                                        if (fac < 0.9) {
                                            var f = localStorage.friendly
                                        } else {
                                            var f = 0
                                        }
                                    } else if ( ( my.size / o.size ) > 1.2) { // food
                                        var f = o.size * localStorage.hunger/ Math.pow(od, 2)
                                    } else { // enemy
                                        var f = - o.size * localStorage.danger / Math.pow(od, 2)
                                    }
                                    if (isNaN(f)) return
                                    aX += (o.x - my.x) * f / od
                                    aY += (o.y - my.y) * f / od
                                })
                        
                                aX += localStorage.bords / my.x
                                aX += -localStorage.bords / Math.abs(my.x - 12000)
                                aY += localStorage.bords / my.y
                                aY += -localStorage.bords / Math.abs(my.y - 12000)
                        
                            })
                        
                            var a = Math.sqrt( Math.pow( aX , 2 ) + Math.pow( aY , 2 ) ) || 1
                        
                            var offX = aX * 100 / a
                            var offY = aY * 100 / a
                        
                            var mX = Math.abs(canvas.clientWidth / 2 + offX)
                            var mY = Math.abs(canvas.clientHeight / 2 + offY)
                        
                            canvas.onmousemove({ clientX: mX, clientY: mY })
                        
                        }, 50)
                        

                          +3
                          Ну ты сотона
                            0
                            Великолепно!
                            На сколько сложным будет добавление возможности делиться пополам, чтобы съесть соперника?
                            или это слишком рискованно?
                              0
                              Да не очень сложно, правда для этого придётся уже учитывать не только координаты как сейчас, а ещё и скорости. В любом случае, деление — довольно рискованная операция, так как нельзя управлять каждой своей частью независимо.
                            +2
                            Спасибо, за статью, но читерство — это плохо.
                              –1
                              Я допускаю, что данное решение может привести в лидерству с куда большей вероятностью, чем это можно сделать самому.
                              Но самый главный вопрос возникающий при борьбе за бОльшее количество своих ботов в комнате — А смысл сего действа?
                              В контексте того, что если вы наводните комнату ботами, которые вас накормят до первого места, то места для реальных игроков не останется.
                              В итоге вы первый среди ботов. Нет конкуренции, нет куража, нет азарта и веселья от ников игроков.

                              А смысл тогда играть то? Променять веселье на ботов.
                                +2
                                Ботов же можно потом убрать и вместо них придут игроки.
                                Можно делать одного бота — это тоже интересно.
                                  +4
                                  Разработка бота сама по себе интереснее любой игры.
                                  0
                                  Видимо, что-то изменили. Сейчас не работает. При загрузке — пустое поле.
                                    +1
                                    Сейчас стало популярным играть на пару в комнате

                                    Когда оба более менее станут большими, начинают работать вместе по следующему алгоритму
                                    Как только один из них видит потенциального соперника, которого можно съесть, поделившись пополам, он это делает сразу и оставшуюся часть намеренно отдает напарнику съесть. Затем напарник с помощью «w» выравнивает массы друг друга.

                                    Если вот эту стратегию попробовать сделать с ботами, было бы просто замечательно
                                    Хотя бы сделать так, чтобы бот понимал, что нужно съесть маленькую часть от твоего куска и затем отдать это кол-во обратно, пусть даже не прыгает, просто находится рядом всё это время
                                    Кстати, со временем масса уменьшается на какое-то количество, так что нужно постоянно что-то есть
                                      0
                                      Добрый вечер, подскажите а какие должны быть настройки Fiddler что бы использовать Ваш скрипт? никак не удается его заставить работать
                                        0
                                        Код main_out изменился, и метод из поста теперь не работает.

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

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