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

eMouse

Заходим на сайт и её с . Установка — ничего сложного. После установки её запускаем — появляется окно.

Слева кнопка «Rec»(запись) -после нажатия будут записаны ваши действия с мышью и на клавиатуре. «Play» проиграет (повторит записанные действия). А сколько раз можно установить в поле «х» (на картинке установлено 1 раз), при этом ставим галочку в поле «loop Playback»

Внизу слева стоят галочки Record: Keys и Mouse, т.е. если эти галочки стоят, то будут записываться действия мыши и клавиатуры (а если не стоят, то соответственно).

Можно посмотреть (и изменить) настройки. Жмем на ссылку «Edit» и в контекстном меню выбираем «Settings»

Вот и настройки.

Установлены «Горячие клавиши». Для начала записи действий — F5, а для паузы- F11, для воспроизведения- F12.

Записанные действия (скрипт) можно сохранить,т.е. можно написать кучу всяких последовательностей и вызывать их по мере необходимости.

Открываем «File» и выбираем «Save Script as…» (Сохранить скрипт как…) и сохраняем его где нам нужно.

AutoClicker (самый примитивный)

Если нужно делать огромное количество кликом правой или левой кнопкой мыши то эта прога для вас. Отлично использовать Вконтакте в играх PROFIT! Супер Кликер или Кликер.

Программа платная, но её «бесплатный » вариант можно скачать по . Её и запускаем. Интерфейс программы довольно простой.

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

Запускаем программу, т.е. жмем на клавиатуре ALT+1. Это не значит, что мы нажимаем на клавиатуре клавишу ALT, потом клавишу «+», а потом и клавишу с цифрой «1!. Нет, мы просто одновременно нажимаем клавиши «ALT» и «1».

Заходим Вконтакте, ищем игру где нужно делать кучу кликов, подводим мышь и жмем ALT+1. И пока не нажмем ALT+2 клики будут продолжаться.

Ghost Mouse можно скачать на . Установка -ничего сложного. Запускаем.

Для записи действий нажимаем красную кнопку. Или нажимаем F9, Для воспроизведения действий нажимаем «Ctrl+Q «.

Move Mouse служит для имитации присутствия пользователя за компьютером за счёт движения курсора мыши и нажатия её кнопок. Кроме того ею можно воспользоваться для запуска каких-то программ, выполнения команд или скриптов PowerShell. Активироваться утилита может вручную или автоматически, при отсутствии активности пользователя и по расписанию, тоже самое касается и прекращения заданных операций.

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

Кроме автоостановки и автозапуска, о которых мы уже упоминали, в настройках поведения Move Mouse доступна опция автоматического изменения громкости звука, скрытия окна программы с рабочего стола, кнопки и иконки с панели задач, миниатюры из диалогового окна ALt+Tab и перезаписи заголовка. Последние опции помогут скрыть следы ей присутствия и использования на компьютере, если кому-то это потребуется.

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

Распространяется программа бесплатно, язык интерфейса в текущей версии доступен только один — английский. Воспользоваться Move Mouse можно на компьютерах и планшетах с Windows 10. Судя по всему, после публикации программы в Магазине Windows, от распространения через другие источники разработчик отказался.

Установить из Microsoft Store

Каждый, играя, хоть раз думал: «вот бы написать программу, которая играла бы за меня!». Но обычно эта мысль, так мыслью и остается… Постоянно что-то мешает: незнание с чего начать, страх перед неподъемностью задачи, шепоток из-за левого плеча «и зачем это? кому это надо, чтобы программа играла с программой?» и т.д.

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

Сейчас начнем с простого. С установления взаимосвязи между игрой и программой-игроком (ботом). В качестве подопытного кролика берется широкоизвестная игра Zuma.

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

Основная цель этой статьи: получить программу, которая самостоятельно раз за разом заходит в игровой процесс, там что-то делает, а при game over-е начинает всё заново. Далее этот каркас будет развиваться в направлении, чтобы бот всё дальше и всё дольше продержался в игре до game over-а.

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

Отступление

При разработке кода для данного цикла статей используется подход: как можно быстрее получить результат за минимум усилий. Такой подход позволяет поддерживать мотивацию на высоком уровне, и не дает опустить руки при виде неподъемности задачи. Из-за этого:
- во-первых, многие малозначимые (с точки зрения текущего результата) моменты будут быстро пробегаться, оставляя в коде «костыли и подпорки». И только на следующих итерациях эти моменты будут отдельно разбираться, и «костыли» будут заменяться на полноценный код.
- во-вторых, стиль кода больше «хакерский», чем классический C#-ный. В коде будет много лямд, анонимных данных, трюков, авторского произвола и полное отсутствие комментариев.

Эмуляция мыши

Windows поддерживает 2 штатных способа эмуляции мыши с помощью 4 различных функций WinApi.

Первый способ : посылка программе своих window-сообщений (WM_MOUSEMOVE , WM_LBUTTONDOWN и т.д.) с помощью функций SendMessage или PostMessage .

Для DirectX-игр (как в нашем случае) такой способ не подходит, потому что такие программы для опроса мыши используют DirectInput, который опрашивает мышь напрямую, игнорируя windows-сообщения.

Второй способ : прямая эмуляция поведения мыши с помощью функций mouse_event или SendInput . Этот способ подходит для любых программ, в том числе и для полноэкранных DirectX-игр. Функция mouse_event попроще, но она считается устаревшей, SendInput - современнее, но более громоздкая. Остановимся на mouse_event.

WinApi-функции из C# вызываются с помощью технологии PInvoke . PInvoke-описание для большинства распространных WinApi-функций можно взять на сайте PInvoke.net . Функция mouse_event не является исключением .
public static extern void mouse_event(uint dwFlags, int dx, int dy, uint dwData, UIntPtr dwExtraInfo);

Координаты мыши
Функция mouse_event имеет специфическую особенность: координаты мыши задаются в mickey, а не в пикселях. Перерасчет mickey в пиксели (и обратно) зависит от разрешения основного используемого монитора. (0,0) соответствует левому верхнему углу монитора, а (65535, 65535) нижнему правому, что дает формулы для пересчета mickey в пиксели и обратно: mickey_point = pixel_point * (65536, 65536) / screen_size и pixel_point = mickey_point * screen_size / (65536, 65536) .
Основные операции
Суммируя всё вышеперечисленное, получаем следующие операции для управления мышью.
Передвижение курсора мыши в точку (x,y):
mouse_event(MouseEventFlags.MOVE | MouseEventFlags.ABSOLUTE, x * 65536 / screen_width, y * 65536 / screen_height);
Клик левой кнопкой мыши:
mouse_event((MouseEventFlags.LEFTDOWN), 0, 0); System.Threading.Thread.Sleep(100); mouse_event((MouseEventFlags.LEFTUP), 0, 0);
Клик правой кнопкой мыши:
mouse_event((MouseEventFlags.RIGHTDOWN), 0, 0); System.Threading.Thread.Sleep(100); mouse_event((MouseEventFlags.RIGHTUP), 0, 0);
Проблема: эксклюзивность ввода
При эмуляции мыши через функцию mouse_event присутствует серьезное неудобство: mouse_event имитирует мышь для всей ОС сразу, а не для отдельного приложения. Из этого следует, что пока бот запущен и играется, то невозможна другая работа за компом: отладка бота, активный просмотр состояния бота, чтение интернета и т.д. Но есть выход: виртуальная машина!

Перенос игры на виртуальную машину

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

Бота, в отличии от самой игры, удобнее запускать на основной машине. Это позволяет перезапускать бота напрямую из Visual Studio, там же его отлаживать, есть куда выводить внутреннее состояние бота и т.д.

Развертывание виртуальной машины (в данном случае использовалась Oracle VirtualBox), установка гостевой ОС и перенос игры делается штатным образом за исключением одного момента: для бота необходима возможность установки связи по сети между хостовой ОС и гостевой ОС. Это делается множеством способов. Один из способов, прокинуть с помощью VirtualBox конкретный порт из гостевой ОС в хостовую. Другой способ, настроить режим Bridged Adapter, тогда виртуалка для всей сети будет выглядеть как обычный компьютер, и гостевая ОС будет получать свой ip-адрес через dhcp от роутера. По этому адресу и будет происходит доступ из хостовой ОС в гостевую. (автором, в данном случае, использовался вариант с bridged adapter)

Прокси
Для управления мышью на гостевой ОС напишем прокси, представляющий из себя простенький консольный tcp-сервер. Его полный код небольшой и представлен под катом. Для упрощения кода и уменьшения зависимостей прокси написан на голом socket-е без использования remoting-а, wcf и т.д.

Код прокси-сервера

using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Text; namespace InputProxy { class Program { static void Main(string args) { var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Bind(new System.Net.IPEndPoint(System.Net.IPAddress.Any, 7001)); socket.Listen(10); for (; ;) { var client = socket.Accept(); Console.WriteLine("connected.."); var thread = new System.Threading.Thread(() => { try { var clientReader = new System.IO.BinaryReader(new NetworkStream(client)); for (; ;) { if (client.Poll(1, SelectMode.SelectRead) && client.Available == 0) { Console.WriteLine("disconnected.."); break; } if (client.Available > 0) { var msgSize = clientReader.ReadInt32(); var message = clientReader.ReadBytes(msgSize); var messageReader = new System.IO.BinaryReader(new System.IO.MemoryStream(message)); var msgKind = messageReader.ReadInt32(); Console.WriteLine("message: kind:{0}, len:{1}", msgKind, message.Length); switch (msgKind) { case 0: { var flags = messageReader.ReadUInt32(); var x = messageReader.ReadInt32(); var y = messageReader.ReadInt32(); var data = messageReader.ReadUInt32(); mouse_event(flags, x, y, data, UIntPtr.Zero); } break; } } else System.Threading.Thread.Sleep(10); } } catch (Exception exc) { Console.WriteLine(exc); } }) { IsBackground = true }; thread.Start(); } } public static extern void mouse_event(uint dwFlags, int dx, int dy, uint dwData, UIntPtr dwExtraInfo); } }


Для работы прокси достаточно его скопировать на виртуальную машину и запустить. Прокси ждет сообщения на порту 7001 и выводит лог своей работы на консоль. Для завершения работы прокси достаточно закрыть консольное окно.
Клиент
Подключение к прокси еще проще, чем код самого прокси.
var client = new System.Net.Sockets.TcpClient(vm_host, 7001); var clientStream = client.GetStream(); var clientWriter = new System.IO.BinaryWriter(clientStream); Action mouse_event = (flags, x, y) => { var messageStream = new System.IO.MemoryStream(); var messageWriter = new System.IO.BinaryWriter(messageStream); messageWriter.Write(0); messageWriter.Write((uint)flags); messageWriter.Write(x); messageWriter.Write(y); messageWriter.Write(0); var message = messageStream.ToArray(); clientWriter.Write(message.Length); clientWriter.Write(message); clientStream.Flush(); };

Перехват изображения

Изображение проще всего захватывать напрямую с экрана. В.net-е для этого есть готовая функция Graphics.CopyFromScreen . На этом способе и остановимся подробнее.
Во-первых, на выходе хочется получить Bitmap, а не Graphics - это решается с помощью вспомогательной функции:
public static Bitmap GetScreenImage(Rectangle rect) { var bmp = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb); using (Graphics graphics = Graphics.FromImage(bmp)) { graphics.CopyFromScreen(rect.Left, rect.Top, 0, 0, rect.Size, CopyPixelOperation.SourceCopy); } return bmp; }
Во-вторых, необходимо знать какую часть экрана надо захватывать. Можно, конечно, захватывать всегда одну и ту же часть экрана, а игру руками располагать в этой части экрана, но это не спортивно не удобно. Тем более автоматизация этого процесса делается минимальными усилиями. В этом нам опять поможет WinApi и PInvoke, а конкретнее две функции: FindWindow и GetWindowRect . FindWindow позволяет по заголовку окна получить handle окна, а GetWindowRect по handle-у возвращает позицию и размер окна на экране.
Pinvoke-описание обеих функций есть на сайте pinvoke.net: FindWindow и GetWindowRect .
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect); public struct RECT { public int Left; public int Top; public int Right; public int Bottom; }
И код захвата изображения окна виртуальной машины получается следующим:
var vm_left = 8; var vm_right = 8; var vm_top = 50; var vm_bottom = 30; var vm_title = "Windows81 - Oracle VM VirtualBox"; var handle = FindWindow(null, vm_title); if (handle == IntPtr.Zero) throw new Exception("Окно не найдено"); RECT rect; GetWindowRect(handle, out rect); var gameScreenRect = new System.Drawing.Rectangle(rect.Left + vm_left, rect.Top + vm_top, rect.Right - rect.Left - vm_right - vm_left, rect.Bottom - rect.Top - vm_bottom - vm_top); var gameBmp = GetScreenImage(gameScreenRect);
Слабое место
Существенным недостатком данного подхода является то, что захватываемое окно, во-первых: обязано целиком располагаться на экране, а во-вторых: обязано располагаться поверх всех остальных окон. Это неудобство нивелируется с помощью двух (и более) мониторов:), тогда окно виртуальной машины располагается на вспомогательном мониторе, ни кому не мешая, оставаясь поверх остальных окон. Также данная проблема полностью решается с помощью ранее расмотренного способа: переноса функции (захват экрана) внутрь виртуальной машины. Для этого достаточно добавить соответствующую функцию в InputProxy.

Зацикливаем игровой процесс

Наконец-то, приступаем непосредственно к решению поставленной на сегодня задаче: зацикливанию игрового процесса - все необходимые подзадачи решены. Игровой процесс в Zuma крутится вокруг трех окон: main, mission и action. Main-окно содержит основное меню, позволяя выбрать вид игры, mission-окно предлагает выбрать миссию, а в action-окне происходит сам игровой процесс.
Бот определяет текущее окно самым простым способом: по значению цвета в нескольких ключевых точках. Точки выбираются вручную: методом «пристального всматривания».
var screenChecks = new { new { Name = "main", Points = new { new CheckPoint(200, 190, 0xff554a22), new CheckPoint(65, 400, 0xfff44c41) } }, new { Name = "mission", Points = new { new CheckPoint(200, 190, 0xffb5d0c7), new CheckPoint(65, 400, 0xffad7630) } }, new { Name = "action", Points = new { new CheckPoint(950, 10, 0xff72554b), new CheckPoint(10, 10, 0xff462b1d), } }, }; Func check = image => screenChecks.Where(_check => image.Check(_check.Points)).Select(_check => _check.Name).FirstOrDefault();
Основной цикл бота:
var startButtonPoint = new Point(950, 430); var startMissionPoint = new Point(600, 750); for (; ;) { try { var bmp = GetScreenImage(gameScreenRect); var screenName = check(bmp); Console.Write(screenName + new string(" ", 20) + new string("\x8", 40)); switch (screenName) { case "main": mouse_event(MouseEventFlags.MOVE | MouseEventFlags.ABSOLUTE, startButtonPoint.X * 65536 / game_width, startButtonPoint.Y * 65536 / game_height); System.Threading.Thread.Sleep(400); mouse_event(MouseEventFlags.LEFTDOWN, 0, 0); System.Threading.Thread.Sleep(150); mouse_event(MouseEventFlags.LEFTUP, 0, 0); System.Threading.Thread.Sleep(50); System.Threading.Thread.Sleep(400); break; case "mission": mouse_event(MouseEventFlags.MOVE | MouseEventFlags.ABSOLUTE, startMissionPoint.X * 65536 / game_width, startMissionPoint.Y * 65536 / game_height); System.Threading.Thread.Sleep(10); mouse_event(MouseEventFlags.LEFTDOWN, 0, 0); System.Threading.Thread.Sleep(150); mouse_event(MouseEventFlags.LEFTUP, 0, 0); System.Threading.Thread.Sleep(50); break; case "action": mouse_event(MouseEventFlags.LEFTDOWN, 0, 0); System.Threading.Thread.Sleep(150); mouse_event(MouseEventFlags.LEFTUP, 0, 0); System.Threading.Thread.Sleep(50); break; case null: bmp.Save("unknown.bmp"); break; } } catch (Exception exc) { Console.WriteLine(exc); } }
В игровой фазе бот постоянно кликает, выпуская шарики в одну точку. На такой простой (скорее даже тупой) тактике бот в первой миссии набирает 1000-2000 очков, и иногда даже полностью набирает полоску Zuma.

Резюме

Поставленная цель выполнена: каркас бота написан - игровой процесс зациклен.
Следующие цели: подключить OpenCV, распознать положение и цвет шаров.
Ps
Изображение для привлечение внимания. (Оранжевым показаны области, которые следующая версия бота распознала как шары)