Написание бота для флэшевой игры


    Зачем?


    О лазерной коррекции зрения я подумывал давно, и вот, наконец, решился на процедуру. После недолгого изучения рынка (живу в Питере) выяснилось, что цены по городу везде примерно одинаковые, и заниматься медицинским туризмом смысла тоже нет (в Мск ненамного дешевле). Впрочем, оказалось, что на операции можно заметно сэкономить, т.к. одна из клиник предоставляет разветвлённую систему скидок на свои услуги.

    Скидки ветеранам и пенсионерам меня, ясное дело, не интересовали. А вот необычной акцией «поиграй во флеш-игру на нашем сайте и конвертируй набранные очки в скидку» я решил воспользоваться. Подкатом описание процесса.

    Вообще идея сперва изумила своей абсурдностью – вроде как считается, что компьютерные игры вредят зрению, и тогда акция похожа на «вычерпай из подвала 10000 вёдер ледяной воды и получи скидку на лечение ревматизма». Сама игра, надо сказать, тоже поразила своей упоротостью – очевидно, что авторы хотели сделать игру без насилия, поэтому легенда гласит: «с помощью технологии LASIK помогите вернуть кротам зрение». Причём, судя по анимации, лечение близорукости производится путём мгновенного испарения пациента.

    Ну да ладно, это лирика. На самом деле я сразу попробовал выбить скидку, однако, весь мой геймерский опыт не помог мне с первого раза получить даже 17%. Сыграв несколько раз, я всё же набрал требуемые 17000 очков, но было ясно, что даже 20000 являются недостижимой планкой, не говоря уже о заветных 25000. Проклятые кроты лезут со всех щелей, но быстро прячутся обратно. При этом за «исцеление» крота даётся 100-200 очков, так что пропускать их нельзя. Не знаю, под силу ли это человеку.

    Решение пришло в голову сразу же – нужно писать бота, который пройдёт игру за меня! Процесс написания бота на C# подкатом.

    Как?


    Концепция

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

    Глаза

    Для начала возьмём OpenCV, чтобы захватывать кадры с экрана и распознавать объекты… СТОП. Мне же не нужно какое-то суперприложение, мне просто нужно получить эту скидку. Стоит ли возиться с OpenCV? Может, проще запустить поток, который в бесконечном цикле будет делать скриншот экрана и просматривать его? Например, так:

    Bitmap bmpScreen = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
    Graphics g = Graphics.FromImage(bmpScreen); 
                
    while (true)
    {
       g.CopyFromScreen(Screen.AllScreens[0].Bounds.X, Screen.AllScreens[0].Bounds.Y, 0, 0,
    bmpScreen.Size, CopyPixelOperation.SourceCopy);
    }
    

    Руки

    А как «лечить» кротов? Очевидно, нужно при запуске найти окно браузера и слать ему сообщение WM_CLICK с нужными параметрами. Впрочем, можно сделать всё проще – физически передвигать курсор на нужное место экрана и эмулировать нажатие клавиш.

    Импортируем соответствующие функции WinAPI

    [DllImport("user32.dll")]
    static extern bool GetCursorPos(ref Point lpPoint);
    [DllImport("user32.dll")]
    static extern bool SetCursorPos(int X, int Y);
    [DllImport("user32.dll")]
    static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, IntPtr dwExtraInfo);
    


    И напишем функцию для клика

                static public void MouseClick()
                {
                    Point ptCoords = new Point();
                    GetCursorPos(ref ptCoords);
    
                    uint x = (uint)ptCoords.X;
                    uint y = (uint)ptCoords.Y;
    
                    System.IntPtr ptr = new IntPtr();
    
                    mouse_event(MOUSEEVENTF_LEFTDOWN, x, y, 0, ptr);
                    mouse_event(MOUSEEVENTF_LEFTUP, x, y, 0, ptr);
                }
    
    


    Теперь, когда все служебные функции есть, осталось написать логику.

    Мозги

    Пройдя вручную несколько раз первый тур, я сделал несколько наблюдений:
    1. Кроты появляются в одних и тех же местах.
    2. Дополнительные цели (робот и НЛО) отпугивают кротов, поэтому их надо «лечить» в первую очередь. Притом появляются они в строго определённых местах.
    3. За ракету «Меди» даётся 500 очков, и зависает она в одном месте


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

    Как хранить координаты целей? Вообще это не так просто. Нужно учитывать разрешение экрана, уровень прокрутки страницы и так далее. Т.е. мало того, что окно игры может иметь разный размер, так оно ещё и может находиться в разных местах экрана. К счастью, я не писал универсальную проходилку игры, мне просто нужно было набрать 25000 очков. Поэтому я решил открывать игру в развёрнутом на весь экран браузере, делать скролл в самый верх страницы и записывать координаты целей как физические координаты пикселей на экране.

    А как записывать координаты? Наиболее удобный для пользователя способ – сделать хоткей, по нажатию которого координаты курсора сохраняются, например, в файл. Тогда при появлении крота нужно навести на него курсор и нажать хоткей. Скажу честно, что сперва я так и сделал. Впоследствии оказалось, что куда как проще наделать скриншотов с кротами, измерить положение каждого крота в графическом редакторе, и эти координаты попросту захардкодить. Получилось что-то вроде этого

    List<Point> m_lpTargets = new List<Point>();
    
    
                m_lpTargets.Add(new Point(557, 623)); //верхняя лунка
                m_lpTargets.Add(new Point(261, 654)); //левая лунка
                m_lpTargets.Add(new Point(352, 486)); //подпрыгивающий слева крот
                m_lpTargets.Add(new Point(450, 500)); //ракета
                m_lpTargets.Add(new Point(592, 698)); //нижняя лунка
                m_lpTargets.Add(new Point(756, 631)); //правая лунка
                m_lpTargets.Add(new Point(373, 514)); //НЛО
                m_lpTargets.Add(new Point(481, 440)); //подпрыгивающий посередине крот
    
    


    Добавим на форму кнопку Aim, которая будет делать эталонный скриншот

            private void btnAim_Click(object sender, EventArgs e)
            {
                m_bmpReference = new Bitmap(Screen.PrimaryScreen.Bounds.Width,
                                                Screen.PrimaryScreen.Bounds.Height);
                Graphics g = Graphics.FromImage(m_bmpReference);
    
                g.CopyFromScreen(Screen.AllScreens[0].Bounds.X,
                         Screen.AllScreens[0].Bounds.Y,
                         0, 0,
                         m_bmpReference.Size,
                         CopyPixelOperation.SourceCopy);
    
            }
    


    Теперь добавляем в главный цикл код нашего бота и запускаем!

                    foreach (Point pt in m_lptTargets)
                    {
                        if (m_bmpReference.GetPixel(pt.X, pt.Y) != bmpScreen.GetPixel(pt.X, pt.Y))
                        {
                            MouseMoveTo(pt, 10, 200);
                            MouseClick();
                        }
                    }
    

    Что получилось?


    Первый результат

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

    Главный рубильник

    Наверное, стоит сделать какой-то мудрый алгоритм, который будет определять, что игра закончилась, а также будет отпускать мышь, когда, например, окно с игрой сворачивается. Но, по счастью, мне нужно просто найти способ останавливать бота по моему желанию, и в этом мне поможет знание горячих клавиш Windows. Я просто добавляю обработчик изменения размеров формы, в котором буду останавливать поток с кодом стрельбы.

            private void Form1_Resize(object sender, EventArgs e)
            {
                if (WindowState == FormWindowState.Minimized)
                {
                    m_objAimingThread.Abort();
                }
            }
    


    Теперь, чтобы остановить бота нужно просто нажать Win+D.

    Первые проблемы

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

    Скажу честно, что над этой проблемой я бился довольно долго. В итоге я пришёл к выводу, что в игре как-то криво реализована отрисовка и учёт кротов. Т.е. если я попадаю по кроту раньше, чем он полностью появился из норы, то очки за него мне начисляются, но при этом он продолжает вылезать. А когда крот помечен как «исцелённый», то повторно получить очки за него не получается. Видно, за состояние крота отвечают несколько переменных, у которых нарушена консистентность. Важно, что эта проблема возникала только с кротами, которые вылезают из четырёх нор – с прыгающими по полю и вылетающими в космос никаких сложностей не было.

    Я рассудил так — раз в ручном режиме такой проблемы не возникает, то нужно просто делать паузу, а может и вообще эмулировать движение мыши до цели.

    После запуска модифицированной программы, которая подводит к кроту указатель за 250 миллисекунд проблема с неисчезающими кротами ушла. Но тут же выявилась другая – подпрыгивающие кроты в нижней части экрана двигаются слишком быстро, чтобы их можно было успеть перевести на него прицел за 250мс. Ведь это из лунок кроты появляются в фиксированных точках, и сидят там некоторое время, а прыгающие кроты проскакивают наши «прицельные точки» очень быстро.

    Ну и наконец, при детальном рассмотрении оказалось, что выпрыгивающие в атмосферу кроты на самом деле имеют разные X-координаты, поэтому простое перечисление их позиций было бы слишком утомительным. Похоже, надо менять концепцию…

    Новая концепция

    Итак, кроты появляются в разных местах, поэтому отслеживать их по координатам невозможно. Неужели придётся делать распознавание образов? Не хотелось бы. Попробую перед этим ещё одну «тупую» реализацию. Что общего у всех кротов? Скафандры и ботинки видны не у всех. Но у всех кротов видна голова в шлеме. К тому же шлем имеет весьма необычный цвет… Что если сканировать всё изображение на предмет наличия этого голубоватого оттенка? Попробуем!

    Color clHelmet = Color.FromArgb(102, 142, 193);
    
                    for (int j = 400; j < 880; j += 1)
                        for (int i = 200; i < 850; i += 1)
                        {
                            if (bmpScreen.GetPixel(i, j) == clHelmet)
                            {
    Point ptTarget = new Point(i, j);
    
                                    MouseMoveTo(ptTarget);
                                    MouseClick();
                                    
                                }
                            }
                        }
    


    На скриншоте я посмотрел координаты рабочей области флешки и цвет шлема. Теперь на своём Core i5 я просто перебираю в цикле все пиксели с помощью супер-тормозного метода GetPixel, и стреляю при совпадении цвета с эталонным. При таком подходе время выполнения одного цикла составляет около 200 миллисекунд, что кажется допустимым значением. Запускаем!

    И новые проблемы


    Всё оказалось не так просто – на шлеме есть не одна точка эталонного цвета, поэтому бот при появлении крота начинает лупить в белый свет как в копеечку, игнорируя других пациентов. Решение нашлось довольно просто – будем сохранять координаты последнего выстрела, и при обработке текущего кадра игнорировать пиксели в окрестности этих координат.

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

    Для начала сократим время обработки кадра. Вообще для этого нужно скопировать изображение в область прямого доступа к памяти, и работать с байтиками напрямую… Но мы просто изменим шаг сканирующих циклов на двойку, тем самым увеличив скорость сканирования картинки в четыре раза. Сильнее увеличивать шаг уже не получится безболезненно – некоторые зоны пикселей будут пропускаться.

    Второй шаг к гарантированному излечению кротов – при обнаружении точки эталонного цвета стрелять не только в неё, а сразу давать очередь в обнаруженную область. Вот код, вводим и запускаем.

                    Point ptLastHit = new Point(0,0);
                   
                    for (int j = 400; j < 880; j += 2)
                        for (int i = 200; i < 850; i += 2)
                        {
                            if (bmpScreen.GetPixel(i, j) == clHelmet)
                            {
                                Point ptTarget = new Point(i, j);
    
                                if (Distance(ptLastHit,ptTarget) > 70)
                                {
                                    ptLastHit = ptTarget;
    
                                    MouseMoveTo(ptTarget);
                                    MouseClick();
                                    
                                    ptTarget.Offset(20, 20);
                                    MouseMoveTo(ptTarget);
                                    MouseClick();
    
                                    ptTarget.Offset(-40, 0);
                                    MouseMoveTo(ptTarget);
                                    MouseClick();                                
                                }
                            }
                        }
    


    Сортировка пациентов

    Теперь всё хорошо – кроты успешно исцеляются, но вернулась проблема с кротами из лунок. Помните, они не исчезали, если стрелять по ним без задержки? Я вернул задержку выстрела в 250мс, но при такой задержке становится невозможным попасть в бегущих кротов… Решение нашлось довольно быстро – забьём в код координаты лунок, и будем давать задержку на стрельбу только в том случае, если цель появляется в этих областях.

    Ещё для увеличения эффективности стрельбы по прыгающим кротам стоит останавливать обработку кадра после поражения первой найденной цели. Поясню – пусть на экране находтся два крота, тогда пока мы стреляем по одному, второй уже успевает немного сместиться, в то время как на старом кадре у нас остались его прежнее изображение. В этот момент мы сталкиваемся с одной из глобальных проблем всех си-подобных языков – невозможность прерывания двух вложенных циклов с помощью команды break. Я в таки случаях совершаю ужасное кармическое преступление, используя оператор goto.

    Наконец, не будем забывать о летающих тарелках, роботах и ракете, которые нужно отслеживать строго в определённых местах. В итоге цикл стал выглядеть так.

                    g.CopyFromScreen(Screen.AllScreens[0].Bounds.X,
                                         Screen.AllScreens[0].Bounds.Y,
                                         0, 0,
                                         bmpScreen.Size,
                                         CopyPixelOperation.SourceCopy);
    
                    foreach (Point pt in m_lptTargets)
                    {
                        if (m_bmpReference.GetPixel(pt.X, pt.Y) != bmpScreen.GetPixel(pt.X, pt.Y))
                        {
                            
                            Point ptMouse = new Point();
    
                            GetCursorPos(ref ptMouse);
    
                            if (ptMouse != pt)
                            {
                                MouseMoveTo(pt);
                                MouseClick();
                            }
                        }
                    }                
    
                    //лунки
                    Rectangle hole1 = new Rectangle(539, 612, 60, 50);
                    Rectangle hole2 = new Rectangle(577, 690, 60, 50);
                    Rectangle hole3 = new Rectangle(738, 621, 60, 50);
                    Rectangle hole4 = new Rectangle(243, 641, 60, 50);
                    //луна
                    Rectangle hole5 = new Rectangle(379, 415, 45, 40);
    
                    int iDelay = 5;
    
                    Color clHelmet = Color.FromArgb(102, 142, 193);
    
                    DateTime tmNow = DateTime.Now;
    
                    Point ptLastHit = new Point(0,0);
                   
                    for (int j = 400; j < 880; j += 2)
                        for (int i = 200; i < 850; i += 2)
                        {
                            if (bmpScreen.GetPixel(i, j) == clHelmet)
                            {
                                Point ptTarget = new Point(i, j);
    
                                if (hole1.Contains(ptTarget) || hole2.Contains(ptTarget) || hole3.Contains(ptTarget) || hole4.Contains(ptTarget) || hole5.Contains(ptTarget))
                                {
                                    iDelay = 200;
                                }
    
                                if (Distance(ptLastHit,ptTarget) > 70)
                                {
                                    ptLastHit = ptTarget;
                                    Thread.Sleep(iDelay);
    
                                    MouseMoveTo(ptTarget);
                                    MouseClick();
                                    
                                    ptTarget.Offset(20, 20);
                                    MouseMoveTo(ptTarget);
                                    MouseClick();
    
                                    ptTarget.Offset(-40, 0);
                                    MouseMoveTo(ptTarget);
                                    MouseClick();
    
                                    goto next;
                                }
                            }
                        }
                next: 
               
                }
    


    Каков итог?


    В общем, эта программа уже успешно набирала 21-22 тысячи очков. Для получения 25 нужно было менять некоторые магические значения типа задержек и координат выстрелов для «очереди». В определённый момент звёзды сложились удачно, и я перевалил заветную отметку в 25000, для этого потребовалась пара десятков запусков.

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

    Я изначально писал плохой с точки зрения архитектуры и функциональности код, стараясь решить конкретную задачу за минимальное время. Эта программа работает только на моей конфигурации монитора и требует довольно много вычислительных ресурсов, но она решает свою задачу, и этого достаточно. Собственно этот пост и задумывался как иллюстрация эффективности подобного подхода, т.к. в прошлом я нередко упускал многие возможности именно из-за «перфекционизма» и желания написать идеальный код, вместо того, чтобы просто сделать вещь, которая решает задачу.

    Мне не совсем непонятно, на что рассчитывали создатели игры, потому что как мне кажется, вручную игру пройти нереально. Возможно, концепция как раз предполагала, что скидки в 25% достойны только красноглазики, которые могут написать бота.

    UPD. В комментариях изложены ещё способы получения скидки. К моему стыду, о самом простом из них я даже не подумал… Получается, пост как иллюстрация того, что не нужно делать сложно, когда можно сделать просто, удался именно благодаря комментаторам. Действительно, бота можно было и не писать… Надеюсь, это послужит уроком не только мне, но и остальным несчастным, страдающим компьютерной болезнью, выявленной и классифицированной полсотни лет назад.

    А что касается мистера Френкеля, который затеял всю эту деятельность, то он начал страдать от компьютерной болезни — о ней сегодня знает каждый, кто работал с компьютерами. Это очень серьезная болезнь, и работать при ней невозможно. Беда с компьютерами состоит в том, что ты с ними играешь. Они так прекрасны, столько возможностей — если четное число, делаешь это, если нечетное, делаешь то, и очень скоро на одной-единственной машине можно делать все более и более изощренные вещи, если только ты достаточно умен.

    Через некоторое время вся система развалилась. Френкель не обращал на нее никакого внимания, он больше никем не руководил. Система действовала очень-очень медленно, а он в это время сидел в комнате, прикидывая, как бы заставить один из табуляторов автоматически печатать арктангенс x. Потом табулятор включался, печатал колонки, потом — бац, бац, бац — вычислял арктангенс автоматически путем интегрирования и составлял всю таблицу за одну операцию.

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

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 75

      +9
      Если целью было просто получить скидку — думаю значительно меньше нервов и времени было бы потрачено декомпиляцией флэшки и последующим эмулированием запросов к серверу (если там таковое вообще было, конечно). В целом, занятная статья.
        +7
        Пожалуй, ещё можно просниффать обращения игры к /game/medigame.php, понять, зачем нужен secret key 719W96lllf7926r791a59W6W5W2d691ed3rfl063 и как он связан с md5. А статья да, годная. Прочитал с удовольствием.
          +2
          А вы оперативно скачали, декомпилировали и расковыряли исходник флэша! Признаюсь, меня эти over6000 строк привели в уныние.
            +3
            Это Вы еще, наверное, не пробовали из байткода флэшки алгоритм разбирать :)
              +8
              не так все страшно)

              сalculate_md5 = function (src) { return(binl2hex(core_md5(str2binl(src), src.length * 8))); };

              secretkey = «719W96lllf7926r791a59W6W5W2d691ed3rfl063»;
              signature = calculate_md5((String(secretkey) + String(fs)) + String(level_data));

            0
            Вот про декомпиляцию я, признаюсь, не подумал. Сперва я решил, что игруля как-то сервером связана, и хотел совсем красиво пройти. Например, эмулировать плавные движения мышью, а не просто переводить курсор в нужную точку. Впрочем, я вообще не уверен, что декомпилировал бы быстрее, т.к. флеш даже не писал никогда, а вот мышкотрепателями занимался одно время.
              +10
              Учитывая, что достаточно распечатать купон, можно вообще обойтись Artmoney. В данном случае работает умножение на 8.
              Делов на пару минут
              imageСодержимое
                +11
                А-а-а-а! Как я мог не подумать про ArtMoney! Компьютерная болезнь, не иначе… Напишу апдейт к статье.
                  +2
                  Неспортивное поведение)
                    0
                    Кстати, а что будет, если указать 205000 очков?
                    Если
                    глянуть в код
                    holder_mc.onEnterFrame = function ()
                    {
                        this._parent.bonus = this._parent.bonus + 25;
                        this._parent.total_score = _root.total_score = _global.scores = _global.scores + 25;
                        this._parent.total_score = _root.total_score;
                        trace(this._parent.discount);
                        if (_root.total_score < 17000) 
                        {
                            trace(this._parent.discount);
                            this._parent.restart_2._visible = true;
                            this._parent.replay._visible = true;
                            this._parent.replay._visible = true;
                            this._parent.replay.total_score = _root.total_score = _global.scores = _global.scores + remainder;
                        }
                        if (_root.total_score >= 17000 && _root.total_score < 20000) 
                        {
                            trace(this._parent.discount);
                            this._parent.discount = "17%";
                        }
                        if (_root.total_score >= 20000 && _root.total_score < 25000) 
                        {
                            trace(this._parent.discount);
                            this._parent.discount = "20%";
                        }
                        if (_root.total_score >= 25000 && _root.total_score < 205000) 
                        {
                            trace(this._parent.discount);
                            this._parent.discount = "25%";
                        }
                        if (this._parent.bonus > _root.bonus) 
                        {
                            _global.printed = true;
                            remainder = _root.bonus - this._parent.bonus;
                            this._parent.total_score = _root.total_score = _global.scores = _global.scores + remainder;
                            restart.enabled = true;
                            this._parent.bonus = _root.bonus;
                            delete this.onEnterFrame;
                        }
                    }
                    , то видно вилку по очкам…
                      +2
                      Получишь небольшую награду за читерство =)
                      15% скидка
                      image
                      0
                      И только дизайнер скажет, что можно было бы на самом купоне заменить цифры на «25%» :)
                    +5
                    Тоже прочел с удовольствием. Главное, чтоб теперь представители клиники эту статью не увидели. А то уберут игру с сайта, чую:)
                      +2
                      Ну, думаю из представителей клиники только IT-шники хабр читают. Соответственно, в худшем для потенциальных клиентов случае может возникнуть соревнование интеллектов — например, программеры сделают шлемы разноцветными. Вообще, думаю, летающие астероиды там сделаны неспроста — поначалу у меня вообще был соблазн просто сравнивать две картинки и стрелять по различиям.
                        +11
                        Думаю, 25% для них — это далеко не предел запаса по наценке. Так что им останется только радоваться такой крутой партизанской рекламе.
                          +2
                          Ага, ниже в каментах уже огласили цены на операцию в Воронеже — разница в два раза…
                        0
                        Узнал свои отклики перфекционизма) В данной ситуации и правда лучше всего было сделать работающий код, выполняющий задачу. И да, спасибо за инфо о OpenCV!
                          +5
                          Сейчас уже поздно, но с утра подтянутся настоящие программисты™ и четвертуют автора за неоптимальный код.
                            +1
                            Четвертовать хочется только за GetPixel. Просто потому что со времён Visual Basic 5 мне на форумах, в чатиках и прочих местах приходится с незавидной регулярностью приходится вещать про возможность загнать изображение в байтмассив, с которым можно работать на порядки быстрее. Это как make install, только хуже.
                              –17
                              Автор ваще отморозок. Это что у нас, проклятые девяностые что ли? Какая венгерская нотация в C# в 1 веке?
                                +4
                                Ну вы и грубиян. И что плохого в венгерской нотации?
                                  –21
                                  Йоу пацан, не парься. Венгерская нотация, в натуре, гнилая тема. Вот тут реально чотко задвинули пацаны, что это дермо мамонта полное фуфло:

                                  «Heresies are, if we know what our variables are, we’ll know their type. If we don’t know what a variable is for, knowing its type won’t help much.»

                                  Если тебе нужен этот отстой, у тебя реально методы длинные, как бабушкин парик, что ты не можешь позырить на тип. Или лабаешь на старперском нотепаде вместо реальной айдии, где можно простым наведением курсора прорюхать тему.
                                    +7
                                    Комментарий как говном на заборе написан. А ведь вы чуть ли не в два раза старше меня. Ну да ладно. К сути вопроса. Остался при своем мнении. Зачем я буду двигать мышкой туда-сюда, чтобы посмотреть на тип, если я могу прочитав название сразу все понять. Даже если метод в 10 строчек, то он может быть частью класса в 100 строчек. И чтобы понять тип свойства вы каждый раз предлагаете мне скроллить вверх и смотреть? По поводу IDE — да, решение. Но попробуйте почитать код на гитхабе не выгружая все в свою любимую IDE.
                                      +1
                                      Про венгерскую нотацию ожидал больше всего комментов: ) Да, привык, отказываться не хочу. Разве что буду делать что-то, где CodeStyle придётся соблюдать какой-то другой.
                                      0
                                      Тру программер с Химмаша )
                                        0
                                        Признаться, господа, меня позабавила ваша фраппированность моей маленькой языковой причудой. Достопочтенный Казмирук, который ранее обратил наше внимание на разницу в возрасте (сколь ни наивно было бы предполагать, что о возрасте можно судить по цифре в le profil), указал, как мне кажется, на корень нашего недопонимания.

                                        Для нас, людей почтенного возраста, этот argot юных вахлаков представляется далеким и ненастоящим, как картинка на стене. Для вас, же, так сказать la nouvelle génération вероятно, подобная речь вызывает яркие воспоминания связанные с настоящим чувственным опытом.

                                        Представьте себе, господа, как мы смотрим под тапёра на веселые ужимки пиратов из новейших произведений синематографа. А теперь вообразите какие чувства бы испытал житель прибрежного поселенья семнадцатого века не раз испытывавший на себе их, хе-хе-хе, проделки." — с этими словами он встал из за стола, промокнул салфеточкой свою бугристую плешь, но будто вспомнив что-то остановился

                                        «А признаться, весьма пикантно было, в сегодняшней утренней корреспонденции целых пять писем со словами „отхабрен“ и „захабрен“, пикантно» — оглядев всех еще раз своими маслянистыми глазками от откланялся и вышел.

                                        PS. А что такое «Химмаш»?
                                          0
                                          Самый известный гоп-район за уралом (Толи в Екатеринбурге, толи в Челябинске). Синоним «Бутово» для Москвы, пожалуй.
                                            +1
                                            Екатеринбург, ага)
                                      +2
                                      На самом деле ничего плохого в ней нет. Дело привычки каждого программиста.

                                      Но соглашение об именовании .net не рекомендует использовать ее.
                                        0
                                        Как это нет? Программисты пишут код не только для себя, но и для других программистов. А если у программиста привычка писать КэмэлКейсом_со_СнейКейсом_ивоттакими_длиннымипеременными? Привычки бывают плохими и не надо их оправдывать.
                                        И если требуется венгерская нотация, то действительно что-то с этим кодом не так, так как она повышает читаемость только спагеттти кода.
                                          +1
                                          Ну вот лично я не вижу трудностей прочитать лишнюю букву перед именем, я такой код пойму и скорее всего смогу продолжить писать код в таком стиле.

                                          А вот именовать переменные без соглашения, как попало, это плохо, это надо искоренять.
                                    0
                                    Это да: ) Когда нужна скорость, приходится с помощью LockBits получать массив значений argb и уже по нему линейно пробегаться. Про это недавно даже пост целый был, вроде. Но тут я как раз из принципа так делать не стал, со словами «и так работает». Просто забыл упомянуть об этом аспекте.
                                +1
                                Спасибо за статью! Ради интереса без напряга прошел игру — после трёх раундов сообщение: «Вы набрали    очков, этого количества не хватает...»; как видите, вместо очков пропуск. Интересно, в автора или других так само?
                                Способ, представленный в статье, очень интересный, однако я бы прежде всего копал в направлении создания запросов на сервер с «выиграшными» данными.
                                  +1
                                  За статью спасибо — очень интересный подход к решению задачи,… хотя создаётся стойкое ощущение, что вся статья — реклама клиники.
                                  Но очень креативная и захватывающая реклама, побольше бы такой! :)

                                  А теперь по существу:
                                  1. Геймер из меня никакой, но с первого раза получил 21063 очка, дающие 20% скидки. Этого уже достаточно, не обязательно гнаться за оставшимися 5%
                                  2. По итогам игра выдаёт только картинку, которую нужно распечатать и всё! Что мешает вооружиться фотошопом и «напечатать» заветные циферки?
                                  3. Как многие уже писали выше — если что-то и делать, то разбирать обмен с сервером, возможно — декомпилировать flash'ку и модицифировать часть, отвечающую за финальный подсчёт баллов. Если задача «получить скидку», то это будет самым простым вариантом.

                                  Ну а самое главное — даже если клиника будет чётко понимать, что купон на скидку «поддельный» (фотошоп, бот, модификация кода игры), то вам об этом никто и никогда не скажет,… ну или похвалят за сообразительность и ничего более.
                                  Вы для них уже готовый к употреблению и проявивший свою заинтересованность клиент. Всё что осталось — подписать договор, провести операцию и расстаться довольными друг другом.

                                  p.s. Ну и цены — от 80 килорублей за два глаза! Иногда задумывался о коррекции,… но ну нафиг, с моим -0.75 проще и дальше использовать при необходимости очки.
                                    +2
                                    Подозрений в рекламе ожидал. Наверное да, идея хорошая — «победи наших программистов — напиши бота!». Хотя, судя по контингенту, который я в ходе трёх посещений наблюдал в приёмной — мало кто из них смог бы хотя бы нарисовать купон в ФШ :) Так что эта идея скорее подошла бы компьютерному магазину (в котором среди покупателей процент гиков всё-таки чуть-чуть побольше).

                                    Насчёт декомпиляции написал выше — с флешем вообще не работал. Так что есть основания полагать, что декомпиляция заняла бы больше времени.

                                    После написания второго камента про то, что выиграть легко я не удержался и попробовал пройти вручную… Невероятно, но я тоже набрал двадцатник. Вот честно, не пойму, почему у меня не получалось это сделать две недели назад. Тут вы правы, оставшиеся 5% мало что решают.

                                    Про фотошоп картинки уже потом подумал, когда держал в руках этот купон, на котором не было никакой защиты. Сперва-то я думал, что есть какой-то учёт выигрышей на сервере. Хотя это крайне маловероятно — не спрашивали же бы они у меня «а когда именно вы выиграли в эту игру?». Вышло так, что, иллюстрируя принцип «как написать код, который просто работает», я забыл о принципе «не нужно писать код, если можно обойтись без него».

                                    Мда, видно, внутреннего программиста в себе подавить не так просто. Остаётся только прорекламировать ещё и замечательную книжку цитатой

                                    А что касается мистера Френкеля, который затеял всю эту деятельность, то он начал страдать от компьютерной болезни — о ней сегодня знает каждый, кто работал с компьютерами. Это очень серьезная болезнь, и работать при ней невозможно. Беда с компьютерами состоит в том, что ты с ними играешь. Они так прекрасны, столько возможностей — если четное число, делаешь это, если нечетное, делаешь то, и очень скоро на одной-единственной машине можно делать все более и более изощренные вещи, если только ты достаточно умен.


                                      0
                                      Хотя, судя по контингенту, который я в ходе трёх посещений наблюдал в приёмной — мало кто из них смог бы хотя бы нарисовать купон в ФШ :)

                                      Ну не скажите.
                                      В приёмной вы видели людей, привлечённых по одним каналам (реклама на телевидение, баннеры на улицах,..). А тут — статья для IT'шников, которая позволит привлечь другую группу людей, которой рекламой по телевизору не заинтересуешь. И среди IT'шников процент «целевой аудитории» (очкариков) достаточно высок. Так что получается отдельный поток клиентов.

                                      После написания второго камента про то, что выиграть легко я не удержался и попробовал пройти вручную… Невероятно, но я тоже набрал двадцатник. Вот честно, не пойму, почему у меня не получалось это сделать две недели назад. Тут вы правы, оставшиеся 5% мало что решают.

                                      Может всё проще — было слишком много жалоб на сложность и игру немного упростили? :)

                                      Мда, видно, внутреннего программиста в себе подавить не так просто. Остаётся только прорекламировать ещё и замечательную книжку цитатой

                                      Как в анекдоте.
                                      «Автомобиль позволяет решить массу проблем — можно и на сервис быстро сгонять, новую резину купить, заехать за запчастями, заправиться… в общем, тех проблем, которых до покупки авто у вас просто и не было :)» ©
                                    +1
                                    Вот только одного не пойму — сложная 4х часовая нейрохирургическая операция на позвоночнике (+7 дней стационара) в хорошей частной клинике практически в центре Москвы стоит ~250 т.р. (и это считается дорого!), а получасовая операция на глазах — почти 100 т.р. (взято с сайта вашей клиники). Странное соотношение.

                                    Правда я бы вам посоветовал ещё раз посмотреть в сторону клиник в Москве — быстрый поиск в Яндексе подсказывает, что вполне можно уложиться в 40-50 т.р. на оба глаза. Это действительно так или я упустил нечто очень важное (к примеру, конкретные технологии)?
                                      0
                                      Про цены разговор будет оффтопом, наверное… Специально ушёл от темы качества и деталей операции в посте. Клиника не моя — выбирал по правилу левой руки. В Мск цены действительно чуть ниже питерских, но ехать смысла нет, тем более что на след. день и через неделю нужно идти на постоперационное обследование. А тут цены средние по Питеру, если верить моему гуглу. Видимо, всё завязано на стоимость эксплуатации оборудования. Технологии действительно разные. Есть Lasik, который старее, дешевле и хуже, а есть Z-Lasik, который новее, дороже и лучше. В чём там техническая разница, из объяснений офтальмолога я не понял.
                                        0
                                        Разница только в том, что меньше светобоязнь и быстрее восстановление в первую неделю после операции, насколько я понял из описания.
                                          +1
                                          На sprosi.d3.ru пару дней назад отвечали на вопросы на эту тему. У самого со зрением проблем нет, но прочитал от корки до корки
                                          +1
                                          В Воронеже у себя 5 лет назад делал за ~30т.р. за оба глаза. Сейчас посмотрел цены в этой клинике — стало даже дешевле. Видимо своего хирурга заимели (раньше раз в месяц приезжал хирург из Москвы).
                                            0
                                            Как самочуствие? Много говорят что в течении нескольких лет после операции коррекции, зрение падает и потом уже не восстановишь.
                                              +1
                                              Не проверял зрение уже давно. К врачу сходить как раз-таки нужно, думаю, 5 лет — достаточный срок, что бы делать какие-либо выводы, тем более, что операцию я делал в 17 (а в этом возрасте не рекомендуется, чем старше, тем лучше, до определённого предела). Так что я попадаю в эту группу риска (если она действительно существует). После посещения врача могу статью запилить, тема то для нас актуальна.

                                              Вывески на улице все могу прочитать, а раньше с -4 по улице ходил как с Adblock.

                                              Левый глаз видит чуть чётче. Целюсь из оружия я им, и он вроде как ведущий, так что это нормально.

                                              В целом просто замечательно — я вижу каждый кирпичик на соседнем доме, каждый листочек на дереве, я узнаю знакомых за сотню метров. И я всё детство не видел звёзд (только самые яркие). А теперь я вижу те, которые некоторые знакомые не видят.

                                              С хирургом во время операции я беседовал и спрашивал у него про повторную операцию. Убеждал меня, что повторная не понадобится, но возможна. После Lasik на роговице остаются рубцы, которые и меняют форму роговицы. Но эти рубцы абсолютно прозрачные. Поэтому повторная операция делается без проблем.

                                                –1
                                                хочется надеяться что это не грамотная реклама)) слишком красиво написано )
                                              0
                                              Действительно, у вас Lasik ровно в два раза дешевле. Можно развивать медицинский туризм в Воронеж. Думаю, жильё на пару дней там недорого можно снять. Хотя ещё через неделю и через месяц придётся ехать на постобследование. Так что идея скорее больше теоретическая, экономии много не выйдет — как раз на то, чтобы компенсировать неудобства и потраченное время. Спасибо за информацию! Похоже, моё изначальное предположение о том, что цена операции складывается в основном из стоимости оборудования, неверно.
                                              –2
                                              Всегда удивлял подход к выбору врачей/стоматологов по принципу цены. Это ж здоровье, нельзя на нем экономить. Лучше искать врача хорошего, ну и хорошо, если цена окажется подъемной.
                                              Лично я вообще крайне скептически отношусь к такого рода операциям в РФ.
                                                +3
                                                Так а по-другому никак… Если начать искать клинику, то найдешь клинику «Гиппократ» с хирургом Ивановым и клинику «Асклепий» с хирургом Петровым. И как их сравнивать? Отзывы на клиники и врачей погуглил — плюс-минус одно и то же. Личные рекомендации тоже не панацея — то, что тёте повезло с операцией, не факт, что повезёт мне. В общем, остаются формальные критерии — цена, географическое расположение, время существования клиники, известность.
                                              +2
                                              Надеюсь вы сделали операцию по коррекции зрения до того, как разместили эту статью :)
                                              А так, спасибо, прочитал с удовольствием)
                                                0
                                                Да, друзья отговаривали публиковать статью до операции: ) В любом случае я хотел сперва проверить, действует ли скидка, чтобы не вводить никого в заблуждение.
                                                +5
                                                Понравилась Ваша статья, пока читал, даже поймал себя на мысли, что тороплюсь: будто чем быстрее прочитаю, тем быстрее главный герой получит свою скидку на операцию. )

                                                После запуска бота мышку невозможно вывести за пределы игрового поля — она будет вечно прыгать по указанным точкам, даже при запуске диспетчера задач. Признаюсь, что я из этой ситуации вышел с помощью перезагрузки…

                                                Теперь, чтобы остановить бота нужно просто нажать Win+D.


                                                Люблю для этой цели использовать Num Lock. Проверить, горит ли он, можно одной строчкой.
                                                  +1
                                                  NumLock это просто, как и всё гениальное — возьму на вооружение! Только я, пожалуй, буду использовать самую бесполезную клавишу — ScrollLock.
                                                  +1
                                                  Статья интересная, занимался реализацией подобного с распознаванием образов, т.к. было много хитростей. Интересно было бы увидеть подобную статью на opencv — интересна сама реализация. Сейчас по просьбе реализовал того же бота с распознаванием только под Ubuntu на Bash с использованием xte. Если кому интересно могу написать статью.
                                                    0
                                                    19500 со второго раза с не очень удобной мышкой… выгоды никакой нет ради 5% тратить два вечера, ну если не считать фан конечно.
                                                      +2
                                                      Надеюсь, после этого поста к вам не пришли люди в черном плаще с лазером и не «вылечили», как тех кротов в игре =))
                                                        0
                                                        Не подскажете, а что это за язык и как вы находили координаты?
                                                          0
                                                          Судя по коду использовался c# а координаты четко привязывались к точкам на экране выяснив позицию. В идеале на самом деле можно отталкиваться от статичного элемента в флешевой игре, и тут можно не думать о размерах экрана, главное чтобы флешка была видна полностью.
                                                            0
                                                            просто я видел боты написанные на pilot и autoit, вот и возник вопрос
                                                          +1
                                                          Хех, попробовал сыграть и с первого раза набрал 24016 очков, думаю ещё пару игр и вполне реально дойти до планки 25 000, т.к. довольно много промахивался и не успевал. Сразу скажу, я не «задрот», в игры последний раз играл лет 6 назад, когда студентом был)

                                                          А статья интересная, прочитал с удовольствием, спасибо!
                                                            0
                                                            Яб поленился все это писать и набросал бы на AutoHotKey'е (простой скриптовый язык, направленный на эмуляцию действий пользователя)

                                                            Весь скрипт бы выглядел так

                                                            F12::
                                                            Loop.
                                                            {
                                                            MouseClick,Left, 100, 100;
                                                            MouseClick,Left, 100, 150;
                                                            MouseClick,Left, 100, 200;

                                                            Sleep, 50
                                                            }

                                                            Т.е. банально бы кликал по всем возможным местам появления кротов.
                                                              0
                                                              Я же сперва так и начал делать, не получилось. Кроты могут появлятся почти в любом месте экрана, а «прокликивать» весь экран не получится по времени, т.к. кроты вылезают меньше чем на секунду. Плюс всё портит глюк с кротами из кратеров, на которых если кликнуть слишком быстро, то они застревают просто.
                                                              0
                                                              Так вы коррекцию сделали? Если да, то расскажите ощущения :)
                                                                +1
                                                                Сделал, ощущения приятные — всё видно хорошо, но надо было подождать пару дней, пока зрение не стабилизировалось. Все бутылки с раствором для линз отправились в помойку, а вот ощущение «фантомных линз» осталось — с утра просыпаюсь с ощущением, будто спал в линзах, надеюсь, со временем пройдёт.
                                                                  0
                                                                  Класс. А то на эти линзы уже потратил как на 2 операции.
                                                                +2
                                                                А если например играть через какую-ть виртуалку и замедлить скорость?
                                                                  0
                                                                  Ещё одна превосходная идея, спасибо!
                                                                    0
                                                                    Раньше для этого входил в «Right Click => Settings». На старых версиях флеша все игры начинали от этого тормозить =)
                                                                  –1
                                                                  Сыграв несколько раз, я всё же набрал требуемые 17000 очков
                                                                  Сунул сертификат в шоп и поправил 17000 на 25000, 17% на 25% Нет?
                                                                    +1
                                                                    Да, всё так. Про это уже писали выше. И я тоже об этом думал в самом начале. Но дело в том, что пока вы не увидите как выглядит сертификат на 25% вы не сможете его нарисовать. Вполне вероятна ситуация, когда в разных сертификатах используются разные цвета фона, например, или появляются дополнительные элементы оформления. В данном случае этого не было, но мне не хотелось проверять это в день операции.
                                                                      +1
                                                                      Вполне вероятна ситуация, когда в разных сертификатах используются разные цвета фона, например, или появляются дополнительные элементы оформления.


                                                                      Интересный способ защиты, кстати. Но проверка на сервере всё равно лучше, наверное.
                                                                    +2
                                                                    М-Видео устраивал как-то раз акцию с флеш игрой и очень кошерными призами. Во первых они засветили все email игроков, создавая после каждой игры страницу с отдельным id, на котором был email и результат. Во вторых результат можно было подделать просто поправив POST запрос. Я, как честный человек сообщил о проблемах разработчику, email стали закрывать звёздочками, а POST запрос так и не починили. В итоге им пришлось делать лотерею среди победителей, а почти все призы унесла одна и та же девушка.
                                                                      0
                                                                      … девушка, которая в отличие от разработчика знала что такое метод POST :) Да, при наличии хоть сколько-нибудь ценного приза все эти игры превратятся в кормушку для ботоводов, где реальным игрокам делать нечего впринципе.
                                                                        0
                                                                        Может это была девушка разработчика? Подозрительный, кстати тип, поведал о нижнем пороге по отсечению жуликов, который физически нереально набить вручную. В качестве призов были айфоны, айпады, ноутбуки.
                                                                      +1
                                                                      Меня однажды подсадили на игру на iPad. Игра по типу фарминга, где вся суть в сотне нажатий. Решил написать бота, взломал iPad, начал изучать запросы к серваку. А потом подумал: «А что если найти баг в игре, который позволит мне сказочно обогатится?». И такой баг нашел. Баг оказался логическим.
                                                                      В игре было две валюты: орешки и монеты. Орешки давались за реальные деньги, но на старте давали парочку. На орешки можно было купить предметы, которые не продавались за монеты, а также орешки можно было выменять на монеты. Что я делал? Я копил монеты, и покупал на них дорогущий домик, который продавался также при наличии орешков и кучи монет. Купленный домик я тут же продавал на местном рынке. Конечно, монет я получал чуть меньше стоимости домика. Но баг был в том, что орешки не исчезали, а наоборот их становилось чуть больше. Далее я чуть-чуть подкапливал деньги, снова покупал домик, и опять продавал. Где-то за два дня я достиг последнего уровня игры, построил на карте огромный дворец и свалил из игры написав флажками на поле для гольфа слово Bye-Bye.
                                                                      Потом вернулся в игру спустя полгода. Баг с орешками разработчики пофиксили и подняли предельный уровень с 25 до 100.
                                                                        0
                                                                        Такими темпами в онлайн-играх споро придётся вводить капчу, чтобы ударить моба.
                                                                          0
                                                                          Мы с братом так в кофейном аппарате в детстве баг нашли: повышение количества сахара было бесплатным, а уменьшение убирало один сантим от цены… Так мы упивались бесплатным кофе с молоком, пока аппарат не увезли…

                                                                        Only users with full accounts can post comments. Log in, please.