- Регистрация
- 28.12.2016
- Сообщения
- 89
- Благодарностей
- 45
- Баллы
- 18
Как и многие после TON, NOT я обнаружил различные мини-приложения в телеграм типа BLUM, в которых можно фармить токены с помощью мини игр. Я начал копать как играть в игры, сначала захотел решить это с помощью скриншотов, но это дело очень долгое, в динамике такое решение не подойдет. Думал что можно без скринов искать нужный цвет пикселя на экране, но так и не нашел решения.
Вернулся опять к скриншотам, смотрел в сторону ADB, OpenCV. Но хотелось что-то простое и быстрое, в итоге пришел к тому что скриншот будем сразу переводить в base64 и искать нужный пиксель. Этот метод отвечает скорости и сравнительной простоте решения. Реализую с помощью скрипта на C#.
1. Шаблон и переменные
Логика шаблона простая, всем будет понятно:
- Запускаем эмулятор;
- Заходим в телеграм;
- Заходим в BLUM;
- Запускаем бота;
- Фармим пока есть игры, после закрываем.
Для игр лучше использовать разрешение экрана 720*1280(240dpi), т.к. чем меньше разрешение тем лучше для поиска нужных пикселей. Также обращу внимание, что нужно убрать галочку "Ограничивать частоту захвата кадров" во вкладке "Выполнение" в настройках ZennoDroid, как на скрине ниже.
Далее распишу переменные и для чего они нужны:
xMin, xMax, yMin, yMax - ограничиваю область поиска пикселей для ускорения работы скрипта;
xCheckMin, xCheckMax, yCheckMin, yCheckMax -область поиска цветов пикселей для окончания работы скрипта;
coefficient - коэффициент для поиска смещения пикселей;
blumGameMaxTime - максимальное время игры(если не срабатывает основной триггер для окончания);
blumFindPixels - RGB коды пикселей которые ищем, в случае с BLUM это цветочки и льдинки;
blumStopPixels - RGB коды пикселей для пропуска ненужных тапов, в случае с BLUM это бомбочки;
blumBreakPixels - RGB коды пикселей для остановки игры(основной триггер окончания игры, как только появляется цвет кнопки);
maxSearchOnScreen - количество поисков нужного цвета на одном скриншоте;
radius - радиус поиска blumStopPixels перед тапом, чтобы случайно не нажать на бомбочку.
2. Логика скрипта игры
Сам скрипт из шаблона:
C#:
long unixStart = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
int baseDif = Int32.Parse(project.Variables["blumGameDifferentY"].Value); // Базовое смещение по Y для тапа на каждые 10 мс
int interval = Int32.Parse(project.Variables["blumGameInterval"].Value); // Шаг поиска
int xMin = Int32.Parse(project.Variables["xMin"].Value);
int xMax = Int32.Parse(project.Variables["xMax"].Value);
int yMin = Int32.Parse(project.Variables["yMin"].Value);
int yMax = Int32.Parse(project.Variables["yMax"].Value);
int k = Int32.Parse(project.Variables["coefficient"].Value);
int xCheckMin = Int32.Parse(project.Variables["xCheckMin"].Value);
int xCheckMax = Int32.Parse(project.Variables["xCheckMax"].Value);
int yCheckMin = Int32.Parse(project.Variables["yCheckMin"].Value);
int yCheckMax = Int32.Parse(project.Variables["yCheckMax"].Value);
int maxTime = Int32.Parse(project.Variables["blumGameMaxTime"].Value); // Максимальное время работы в секундах
string stopPixels = project.Variables["blumStopPixels"].Value; // Стоп-цвета
Bitmap Base64ToImage(string base64String)
{
byte[] imageBytes = Convert.FromBase64String(base64String);
using (MemoryStream ms = new MemoryStream(imageBytes))
{
Bitmap image = new Bitmap(ms);
return image;
}
}
List<(int R, int G, int B)> ParseColors(string colorsString)
{
return colorsString
.Split(';')
.Select(color =>
{
var rgb = color.Split(',').Select(int.Parse).ToArray();
return (rgb[0], rgb[1], rgb[2]);
})
.ToList();
}
// Проверка радиуса вокруг координаты
bool IsInRadius(int x, int y, List<(int x, int y)> tappedPixels, int radius)
{
foreach (var (tapX, tapY) in tappedPixels)
{
int dx = tapX - x;
int dy = tapY - y;
if (dx * dx + dy * dy <= radius * radius)
{
return true;
}
}
return false;
}
// Проверка на наличие стоп-цвета в радиусе с шагом 5 пикселей
bool HasStopColor(Bitmap image, int x, int y, List<(int R, int G, int B)> stopColors, int radius)
{
for (int dy = -radius; dy <= radius; dy += 5)
{
for (int dx = -radius; dx <= radius; dx += 5)
{
int checkX = x + dx;
int checkY = y + dy;
if (checkX >= 0 && checkX < image.Width && checkY >= 0 && checkY < image.Height)
{
Color pixelColor = image.GetPixel(checkX, checkY);
if (stopColors.Any(stopColor =>
pixelColor.R == stopColor.R && pixelColor.G == stopColor.G && pixelColor.B == stopColor.B))
{
return true;
}
}
}
}
return false;
}
// Основная функция для поиска пикселя и выполнения двойного тапа
bool FindAndTapPixels(Bitmap image, List<(int R, int G, int B)> colors, List<(int R, int G, int B)> stopColors, int step, int maxTaps, int radius)
{
DateTime startTime = DateTime.UtcNow;
int foundCount = 0;
List<(int x, int y)> tappedPixels = new List<(int x, int y)>();
for (int y = yMin; y <= yMax; y += step)
{
for (int x = xMin; x <= xMax; x += step)
{
if (IsInRadius(x, y, tappedPixels, radius))
{
continue; // Пропускаем область вокруг уже найденного пикселя
}
Color pixelColor = image.GetPixel(x, y);
foreach (var color in colors)
{
if (pixelColor.R == color.R && pixelColor.G == color.G && pixelColor.B == color.B)
{
// Проверка на наличие стоп-цвета перед тапом
if (HasStopColor(image, x, y, stopColors, 25))
{
project.SendInfoToLog($"Стоп-цвет найден рядом с пикселем ({x}, {y}). Тап пропущен.", true);
continue;
}
// Рассчитываем динамическое смещение по Y
TimeSpan searchTime = DateTime.UtcNow - startTime;
double timeFactor = searchTime.TotalMilliseconds / k;
int dynamicDif = (int)(baseDif * timeFactor);
// Первый тач
instance.DroidInstance.Input.Tap(x, y);
// Второй тач с учетом смещения по Y
instance.DroidInstance.Input.Tap(x, y + dynamicDif);
project.SendInfoToLog($"Цвет найден на координатах ({x}, {y}), смещение: {dynamicDif}", true);
// Добавляем координаты в список для исключения области поиска
tappedPixels.Add((x, y));
foundCount++;
if (foundCount >= maxTaps)
{
return true; // Достигли лимита нужных пикселей
}
break; // Переходим к следующей координате
}
}
}
}
project.SendInfoToLog("Цвет не найден в изображении.", true);
return foundCount > 0;
}
// Проверка участка экрана на наличие цветов завершения
bool CheckForBreakCondition(Bitmap image, List<(int R, int G, int B)> colorsBreak)
{
for (int y = yCheckMin; y <= yCheckMax; y++)
{
for (int x = xCheckMin; x <= xCheckMax; x++)
{
Color pixelColor = image.GetPixel(x, y);
bool match = colorsBreak.Any(color =>
pixelColor.R == color.R && pixelColor.G == color.G && pixelColor.B == color.B);
if (!match)
{
project.SendInfoToLog($"Несоответствие цвета на координатах ({x}, {y}): R={pixelColor.R}, G={pixelColor.G}, B={pixelColor.B}", true);
return false;
}
}
}
project.SendInfoToLog("Все пиксели в области соответствуют цветам завершения.", true);
return true;
}
// Основной процесс поиска и клика
void FindPixelAndTapLoop()
{
string colorsString = project.Variables["blumFindPixels"].Value;
string colorsBreakString = project.Variables["blumBreakPixels"].Value;
List<(int R, int G, int B)> colorsToFind = ParseColors(colorsString);
List<(int R, int G, int B)> colorsBreak = ParseColors(colorsBreakString);
List<(int R, int G, int B)> stopColors = ParseColors(stopPixels);
int maxTaps = Int32.Parse(project.Variables["maxSearchOnScreen"].Value);
int searchRadius = Int32.Parse(project.Variables["radius"].Value);
while (true)
{
// Проверка максимального времени выполнения
long unixNow = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (unixNow - unixStart >= maxTime)
{
project.SendInfoToLog("Максимальное время работы скрипта достигнуто. Остановка.", true);
break;
}
string base64Screenshot = instance.DroidInstance.Screen.ScreenshotAsBase64String();
Bitmap screenshot = Base64ToImage(base64Screenshot);
try
{
// Проверка условия завершения
if (CheckForBreakCondition(screenshot, colorsBreak))
{
project.SendInfoToLog("Условие завершения достигнуто. Остановка скрипта.", true);
break;
}
FindAndTapPixels(screenshot, colorsToFind, stopColors, interval, maxTaps, searchRadius);
}
finally
{
screenshot.Dispose();
}
}
}
// Запуск основного процесса
FindPixelAndTapLoop();
1. Берем скрин переводим в base64;
2. На скрине ищем совпадения по цветам пикселей из переменной blumFindPixels;
3. Находим нужный цвет, проверяем нет ли пикселя из blumStopPixels в радиусе radius делаем тап если бомбочки рядом нет;
4. После высчитываем смещение по Y оси в зависимости от времени, т.к. на поиск пикселя было затрачено время и элемент который мы ищем сместился вниз. Перед тапом снова проверяем нет ли рядом бомбочки и если нет тапаем;
5. Продолжаем поиск пикселей, пока не сделаем maxSearchOnScreen поисков на 1 скрине;
6. После делаем новый скрин и так по циклу;
7. Завершаем игру когда в области проверки(xCheckMin, xCheckMax, yCheckMin, yCheckMax) появятся цвета пикселей из blumBreakPixels или по прошествии максимального времени выполнения скрипта.
3. FindPixelsKonkurs
Также прикрепляю шаблон для составления списков пикселей, на вход подаете картинку, на выходе получаете список пикселей. Есть несколько режимов составления списков: все пиксели, самые распространенные и по количеству совпадений. Для BLUM я составляю списки в режиме "самые распространенные".
Обращу внимание, что скармливать весь скрин из BLUM не нужно. Нужно вырезать пару цветочков, льдинок, тыкв, бомбочек или что там будет падать. Далее прогнать через шаблон и получить итоговый файл. Опять же обращу внимание, что для бомбочек нужно составлять отдельный список пикселей для переменной blumStopPixels. Сейчас в основном шаблоне с игрой уже вписаны актуальные цвета.
4. Видео работы скрипта в шаблоне
Crypto Game - BLUM - YouTube
Видео автоматизации для игры BLUM для конкурса Zennolab Masters 3
youtube.com
Во вложении файл заготовки шаблона, файл шаблона составляющий список пикселей.
5. Подытожим
Сейчас у меня работает одна ферма для фарма по этому методу. В принципе, с помощью этого скрипта(с небольшой доработкой) можно автоматизировать многие игры, которые завязаны на чем-то похожем где нужно тапать по динамическим объектам. Надеюсь, будет полезно.
Смотреть видео
Вложения
-
20,6 КБ Просмотры: 11
-
100,3 КБ Просмотры: 11
Последнее редактирование модератором: