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

Идея была следующая:
Шаг 1. получаем картинку с экрана
Шаг 2. перенос цветов шариков в матрицу
Шаг 3. высчитывание позиции и клик мышкой для выполнения хода
Для написания программы я немного колебался между Qt и Delphi, но так как хотелось всё сделать побыстрей решил сделать на Delphi.
Теперь можно реализовывать Шаг 1.
Для получения картинки задаём приблизительное положение окна с игрой и копируем этот участок в TImage, ранее расположенный на форме.
Если поместить данный код в таймер, двигая окно с игрой можно расположить его так, что копироваться будет именно изображение игры.
Шаг2. Необходимо узнать цвета шариков. Как видно их всего 3 варианта, прям RGB.
Высота между рядами а также диаметры шариков постоянные, поэтому мы можем составить сетку положения шариков.
Чтобы узнать цвет шарика сравним его пиксель с пороговым значением каждого из цветов RGB
И чтобы убедиться что всё в порядке нарисуем на шарике маленький прямоугольник с полученным цветом
Получается такая симпатичная картина.

На этом работа с сеткой заканчивается и начинается работа с полученной матрицей
Шаг 3.
Проверим все шарики на возможность пальнуть в него, для этого нужно проверить, чтобы на пути к нему мы не столкнулись с другим шариком.
Проверка происходит через вложенные циклы (по шарикам и для каждого проверяем остальные)
Само условие проверки получается при системы из трёх неравенств.
{ A=1, B=(x0-y0)/(y1-y0), C=-x0-By0 }
|Ax3+By3+C| / sqrt(A^2+b^2) <= radius
Для наглядности проведём линии к шарикам, к которым можно пулять.
Нужно из разрешенных выбрать шарик с одинаковым цветом.
И сделать клик мышкой на экране через WinApi
Осталось всё выполнить в таймере и идти пить чай.
Видео работы программы
Можно улучшить алгоритм, например находить группы с большим числом шариков или искать линии отрезающие части поля.
Имена функций и переменные может названы не красиво, но это можно исправить.
Если нужны исходники, то они тут.
Захотелось автоматизировать этот процесс. Знаний для игры не требуется, да игр таких много.
Описываю процесс написания бота к данной игре.

Идея была следующая:
Шаг 1. получаем картинку с экрана
Шаг 2. перенос цветов шариков в матрицу
Шаг 3. высчитывание позиции и клик мышкой для выполнения хода
Для написания программы я немного колебался между Qt и Delphi, но так как хотелось всё сделать побыстрей решил сделать на Delphi.
Теперь можно реализовывать Шаг 1.
Для получения картинки задаём приблизительное положение окна с игрой и копируем этот участок в TImage, ранее расположенный на форме.
procedure MakeFrame();
var
bmp:TBitmap;
rect:TRect;
begin
rect.Left := 50;
rect.Top:= 150;
rect.Right:=550; // Screen.Width
rect.Bottom:=550; // Screen.Height
bmp := TBitmap.Create;
bmp.Width := Screen.Width;
bmp.Height := Screen.Height;
BitBlt(bmp.Canvas.Handle, 0, 0, rect.Right, rect.Bottom,
GetDC(0), rect.Left, rect.Top, SRCCOPY);
Form1.Image1.Width := rect.Right;
Form1.Image1.Height := rect.Bottom;
Form1.Image1.Picture.Assign(bmp);
bmp.Free;
end;
Если поместить данный код в таймер, двигая окно с игрой можно расположить его так, что копироваться будет именно изображение игры.
Шаг2. Необходимо узнать цвета шариков. Как видно их всего 3 варианта, прям RGB.
Высота между рядами а также диаметры шариков постоянные, поэтому мы можем составить сетку положения шариков.
for I:=0 to rows do
begin
offset:=10; // сдвиг
if (I mod 2) <> 0 then offset:=offset+(36 div 2);
for J:=0 to cols do
begin
mat[i,j].Y := 10+Round(I*36); // позиция шарика
mat[i,j].X := offset+Round(J*36);
// пулятель (было не охото создавать ещё переменную и повторять код)
if (I=rows) and (J=cols) then begin
mat[i,j].Y := 519;
mat[i,j].X := 262;
end;
// обработка полученного пикселя
end;
end;
Чтобы узнать цвет шарика сравним его пиксель с пороговым значением каждого из цветов RGB
rgb:=Form1.Image1.Canvas.Pixels[ mat[i,j].X, mat[i,j].Y-4 ];
mat[i,j].color := 0;
mat[i,j].summa := 0;
if GetRValue(rgb) > 230 then mat[i,j].color:=1;
if GetGValue(rgb) > 230 then mat[i,j].color:=2;
if GetBValue(rgb) > 230 then mat[i,j].color:=3;
И чтобы убедиться что всё в порядке нарисуем на шарике маленький прямоугольник с полученным цветом
if mat[i,j].color = 0 then Form1.Image1.Canvas.Brush.Color := clWhite;
if mat[i,j].color = 1 then Form1.Image1.Canvas.Brush.Color := clRed;
if mat[i,j].color = 2 then Form1.Image1.Canvas.Brush.Color := clGreen;
if mat[i,j].color = 3 then Form1.Image1.Canvas.Brush.Color := clBlue;
Form1.Image1.Canvas.Rectangle(
mat[i,j].X-5, mat[i,j].Y-5, mat[i,j].X+5, mat[i,j].Y+5
);
Получается такая симпатичная картина.

На этом работа с сеткой заканчивается и начинается работа с полученной матрицей
Шаг 3.
Проверим все шарики на возможность пальнуть в него, для этого нужно проверить, чтобы на пути к нему мы не столкнулись с другим шариком.
Проверка происходит через вложенные циклы (по шарикам и для каждого проверяем остальные)
Само условие проверки получается при системы из трёх неравенств.
{ A=1, B=(x0-y0)/(y1-y0), C=-x0-By0 }
|Ax3+By3+C| / sqrt(A^2+b^2) <= radius
/// теперь найдём шар куда пулять
for I:=0 to rows-1 do
for J:=0 to cols do
begin
if (mat[I,J].color = 0) and (I <> 0) then continue;
mat[i,j].allow:=1;
// куда хотим пулять
LineMaxX:=mat[I,J].X;
LineMaxY:=mat[I,J].Y;
// откуда хотим пулять
LineMinX:=mat[rows,cols].X;
LineMinY:=mat[rows,cols].Y;
mat[I,J].dist := sqrt(
sqr(mat[I,J].X-mat[rows,cols].X)+
sqr(mat[I,J].Y-mat[rows,cols].Y));
for II:=I+1 to rows-1 do
for JJ:=0 to cols do
begin
if mat[II,JJ].color = 0 then continue; // если шарика нет
LineMiddleX:=mat[II,JJ].X;
LineMiddleY:=mat[II,JJ].Y;
// не множко не красивый код но переписывалось
// с решенных уравнений на бумажке
ka:=1;
kb:=(LineMinX-LineMaxX)/(LineMaxY-LineMinY);
kc:=-LineMinX-kb*LineMinY;
kz:=
abs(ka*LineMiddleX + kb*LineMiddleY+kc)/
sqrt(sqr(ka)+sqr(kb));
if kz < 39 then mat[i,j].allow:=0;
//Form1.Memo1.Lines.Add(FloatToStr(kz));
end;
Для наглядности проведём линии к шарикам, к которым можно пулять.
if mat[i,j].allow = 1 then begin
Form1.Image1.Canvas.Pen.Width:= 2;
Form1.Image1.Canvas.Pen.Color:= clWhite;
Form1.Image1.Canvas.MoveTo(Round(LineMinX),Round(LineMinY));
Form1.Image1.Canvas.LineTo(Round(LineMaxX),Round(LineMaxY));
end;
Нужно из разрешенных выбрать шарик с одинаковым цветом.
for I:=0 to rows-1 do
for J:=0 to cols do
begin
if mat[i,j].allow = 1 then
begin
Form1.Image1.Canvas.Pen.Width:= 2;
Form1.Image1.Canvas.Pen.Color:= clWhite;
Form1.Image1.Canvas.MoveTo(Round(LineMinX),Round(LineMinY));
Form1.Image1.Canvas.LineTo(Round(LineMaxX),Round(LineMaxY)); // линия
if mat[i,j].color=mat[rows,cols].color // если цвета совпали
then begin
MouseX:=mat[i,j].X+50;
MouseY:=mat[i,j].Y+250; // шарик + смещение по экрану
break;
end;
end;
end;
И сделать клик мышкой на экране через WinApi
if Form1.CheckBox2.Checked then
begin
GetCursorPos(MouseL);
SetCursorPos(MouseX,MouseY);
mouse_event(MOUSEEVENTF_LEFTDOWN,MouseX,MouseY,0,0);// - нажать левой кнопки
mouse_event(MOUSEEVENTF_LEFTUP,MouseX,MouseY,0,0);// - отпустить левую кнопку
SetCursorPos(MouseL.X,MouseL.Y);
end;
Осталось всё выполнить в таймере и идти пить чай.
Видео работы программы
Можно улучшить алгоритм, например находить группы с большим числом шариков или искать линии отрезающие части поля.
Имена функций и переменные может названы не красиво, но это можно исправить.
Если нужны исходники, то они тут.