Написание бота для игры в Шарики 2.0

в 19:10, , рубрики: бот игры, игра, Песочница, Программирование, метки: , ,

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

Написание бота для игры в Шарики 2.0

Идея была следующая:
Шаг 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
      );

Получается такая симпатичная картина.
Написание бота для игры в Шарики 2.0

На этом работа с сеткой заканчивается и начинается работа с полученной матрицей

Шаг 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;

Осталось всё выполнить в таймере и идти пить чай.

Видео работы программы

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

Имена функций и переменные может названы не красиво, но это можно исправить.
Если нужны исходники, то они тут.

Автор: darkoff

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js