Androidgame Bot 4


Selfmade Gamebot with C#

NOTE:

Do not use this bot to cheat in the game. This is for educational purposes only!

Hello to this little series where I will describe how I made a little Bot for an Android game I liked to play. The game is called “Panzersturm” in Germany and is playable with iOS and Android.
I don’t think its really important to know about the game, just about the fact that there is a world map accessable for all player and thats the point of interest. Player can go in mines with their troops to collect ressources, after a time of collecting the mine will appear in a different colour on the world map. It’s easy to spot, but it takes too much time to search for them. Why not help myself and write a bot which will help me with that?
Well here we go!

Because I dont have the stuff anymore I will just write you what I did and what my steps where. Because it was a game for android I first installed Bluestacks on my Computer. Now with an emulator I was ready to roll. I installed the game and saved pictures of all the interesting things I needed for my Bot. I saved the empty mines, some full mines and went into Visual Studio. Now where do I start? Because it is a bot based on whats on the screen, how about getting the Process of the emulator and then the gamescreen?

   Process blueStacks = Process.GetProcessesByName("HD-Frontend")[0];
IntPtr blueStacksWindow = blueStacks.MainWindowHandle;

this was easier then expected. well now lets continue with defining what we need and what the bot should recorgnize. For this let me quickly declare a class for Mines.

class Mine
{
public Color Color { get; private set; }
public Rectangle ScreenRectangle { get; private set; }
public Point ScreenCoordinates { get; private set; }
public Point MapCoordinates  { get; private set; }

public Mine(Color color, Rectangle screenRectangle, Point screenCoordinates, Point mapCoordinates)
{
Color = color;
ScreenRectangle = screenRectangle;
ScreenCoordinates = screenCoordinates;
MapCoordinates = mapCoordinates;
}
}

Alright the class has all the information we need. For this bot we need to know the colour of the mine ( this is just interesting to see how long he is farming ressources, the longer the more.) where the mine is on the captured Screen, where it is in the games and then a Rectangle so we can draw a rectangle around it in our bot (just because we can).

Now that we know what informations we want for our mines we can continue with actually declaring our filter.

Another class so we keep our project clean and easy to manage.

  class MineColorFilter
{
public AForge.Imaging.Filters.ColorFiltering Filter { get; private set; }
public Color Color { get; private set; }

public MineColorFilter(AForge.Imaging.Filters.ColorFiltering filter, Color color)
{
Filter = filter;
Color = color;
}

public Mine[] ScanMines(Bitmap tmp, int mapX, int mapY)
{
List<Mine> mines = new List<Mine>();
Bitmap output = Filter.Apply(tmp);
AForge.Imaging.BlobCounter bc = new AForge.Imaging.BlobCounter(output);
foreach (AForge.Imaging.Blob blob in bc.GetObjects(output, true))
if (blob.Area >= 500 && blob.Area <= 800)
{
Graphics g = Graphics.FromImage(output);
g.DrawRectangle(Pens.Orange, blob.Rectangle);
mines.Add(new Mine(Color, blob.Rectangle, new Point(blob.Rectangle.X + blob.Rectangle.Width / 2, blob.Rectangle.Y + blob.Rectangle.Height / 2), new Point(mapX, mapY)));
}

//output.Save(string.Format("output_{0}.bmp", Color.Name));
return mines.ToArray();
}
}

So let me explain what I did right here. First we created a universal ColorFilter with the AForge.dll we downloaded and implemented into the project. (get it here AForge for .NET)

after that we said our MineColorFilter has to get two variables passed over and that we will assign them to the private variables declared above. As you can see we pass over the filter Variable to our universal Filter. after that we create an Array out of our Mine we declared in the previous class.
Here we give the array a Bitmap which will be the screenshot our bot takes and the X and Y coordinates it took the screenshot at.

Next we Apply the Filter to our picture and then check for blobs on the picture. Blobs are basically recognized Colors next to eachother by AForge (like a circle or a rectangle). With tests we could determine that our mines allways will have a blob count between 500 and 800.
If we find a blob like this we draw a rectanlge around it and go on with adding it to our mines list.

Phewww we’re halfway through guys, hold on.

now that we made these classes what will be our next step? Sure lets go and create a Form to display our stuff! theres not much to do here so I will just show a quick Screenshot of how it looks.

PanzersturmBot

 

 

 

 

 

 

Not that interesting, isn’t it? Well lets continue then!

Now we come to the final part. Combining our classes and making the bot actually do something.

static class Program
{

static frm_display display;
static List<MineColorFilter> mineColorFilters = new List<MineColorFilter>();

static Point gameButtonX = new Point(344, 52);
static Point gameButtonY = new Point(446, 52);
static Point gameButtonSearch = new Point(535, 52);

[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

mineColorFilters.Add(new MineColorFilter(new ColorFiltering(new AForge.IntRange(17, 40), new AForge.IntRange(110, 210), new AForge.IntRange(0, 5)), Color.Green));
mineColorFilters.Add(new MineColorFilter(new ColorFiltering(new AForge.IntRange(20, 55), new AForge.IntRange(70, 138), new AForge.IntRange(130, 255)), Color.Blue));
mineColorFilters.Add(new MineColorFilter(new ColorFiltering(new AForge.IntRange(95, 190), new AForge.IntRange(36, 85), new AForge.IntRange(135, 255)), Color.Purple));

CountdownEvent cde = new CountdownEvent(1);
display = new frm_display();
new Thread(() => { display.ShowDialog(); cde.Signal(); }).Start();
Thread.Sleep(2000);
Process blueStacks = Process.GetProcessesByName("HD-Frontend")[0];
IntPtr blueStacksWindow = blueStacks.MainWindowHandle;

List<Mine> totalMines = new List<Mine>();

//for (int y = 3; y <= 597; y+=6)
for (int y = 200; y <= 400; y += 6)
{
setMapY(y, blueStacksWindow, blueStacks);

//for (int x = 2; x <= 599; x+=3)
for (int x = 400; x <= 600; x += 3)
{
if (cde.IsSet) Environment.Exit(0);

setMapX(x, blueStacksWindow, blueStacks);

BotTools.ClickOnWindowPoint(blueStacksWindow, gameButtonSearch);

Thread.Sleep(500);

//get screen
Bitmap tmp = (Bitmap)BotTools.CaptureWindow(blueStacksWindow, BotTools.CaptureType.PrintWindow);

List<Mine> mines = new List<Mine>();
foreach (MineColorFilter mcf in mineColorFilters)
mines.AddRange(mcf.ScanMines(tmp, x, y));

display.v_list.Items.Clear();
Graphics g = Graphics.FromImage(tmp);
foreach (Mine m in mines)
{
g.DrawRectangle(new Pen(Brushes.Orange, 3.5f), m.ScreenRectangle);
g.FillPie(Brushes.Orange, m.ScreenCoordinates.X - 5, m.ScreenCoordinates.Y - 5, 10, 10, 0, 360);
g.DrawString(m.Color.ToString(), SystemFonts.IconTitleFont, Brushes.Pink, m.ScreenCoordinates);

display.v_list.Items.Add(string.Format("{0} Mine @{1},{2} @{3},{4}", m.Color.Name, m.ScreenCoordinates.X, m.ScreenCoordinates.Y, m.MapCoordinates.X, m.MapCoordinates.Y));

totalMines.Add(m);
}

display.v_totalList.Items.Clear();
foreach (Mine m in totalMines)
display.v_totalList.Items.Add(string.Format("{0} Mine @{1},{2} @{3},{4}", m.Color.Name, m.ScreenCoordinates.X, m.ScreenCoordinates.Y, m.MapCoordinates.X, m.MapCoordinates.Y));

g.Flush();
g.Save();

//debug map out
//tmp.Save(string.Format(".\\map\\y{1:000}_x{0:000}.jpg", x, y), System.Drawing.Imaging.ImageFormat.Jpeg);

display.pic.Image = tmp;
//Thread.Sleep(250);
}
}

////live scan TEST
//while (!cde.IsSet)
//{
//    Bitmap tmp = (Bitmap)BotTools.CaptureWindow(blueStacksWindow, BotTools.CaptureType.PrintWindow);

//    List<Mine> mines = new List<Mine>();
//    foreach (MineColorFilter mcf in mineColorFilters)
//        mines.AddRange(mcf.ScanMines(tmp, 0, 0));

//    display.v_list.Items.Clear();
//    Graphics g = Graphics.FromImage(tmp);
//    foreach (Mine m in mines)
//    {
//        g.DrawRectangle(new Pen(Brushes.Orange, 3.5f), m.ScreenRectangle);
//        g.FillPie(Brushes.Orange, m.ScreenCoordinates.X - 5, m.ScreenCoordinates.Y - 5, 10, 10, 0, 360);
//        g.DrawString(m.Color.ToString(), SystemFonts.IconTitleFont, Brushes.Pink, m.ScreenCoordinates);

//        display.v_list.Items.Add(string.Format("{0} Mine @{1},{2}", m.Color.Name, m.ScreenCoordinates.X, m.ScreenCoordinates.Y));
//    }

//    g.FillPie(Brushes.Orange, gameButtonX.X - 5, gameButtonX.Y - 5, 10, 10, 0, 360);
//    g.FillPie(Brushes.Orange, gameButtonY.X - 5, gameButtonY.Y - 5, 10, 10, 0, 360);
//    g.FillPie(Brushes.Orange, gameButtonSearch.X - 5, gameButtonSearch.Y - 5, 10, 10, 0, 360);

//    tmp.Save(".\\output.bmp");
//    display.pic.Image = tmp;

//    Thread.Sleep(250);
//}

cde.Wait(-1);
}

static void setMapX(int x, IntPtr window, Process process)
{
BotTools.ClickOnWindowPoint(window, gameButtonX);
Thread.Sleep(10);
BotTools.PSSendKeys("{BACKSPACE}{BACKSPACE}{BACKSPACE}" + x.ToString() + "{ENTER}", process);
Thread.Sleep(25);
}

static void setMapY(int y, IntPtr window, Process process)
{
BotTools.ClickOnWindowPoint(window, gameButtonY);
Thread.Sleep(10);
BotTools.PSSendKeys("{BACKSPACE}{BACKSPACE}{BACKSPACE}" + y.ToString() + "{ENTER}", process);
Thread.Sleep(25);
}
}

Now this maybe looks like a lot now but let me explain it and you will see it isn’t that complicated.

Most of variables are there because we tested them out. The gameButtons are at this points on my resolution, it may be different at your screen. But because I didn’t plan on publishing it I didn’t care about the compatibility. Let’s continue.

We defined our three filters needed and head on with starting the form to keep the bot alive and in a different thread to work on. Without different threads this programm would be a mess,  believe me, I tried it out.

You should recognize a lot of the stuff I do by now. I make a list of mines again and then I just head on with making my bot simulate the emulator I actually am a human who moves his mouse and pushes buttons. The reason I went in 6 and 3 steps is that else I would capture parts of the same map everytime and maybe get some mines double, surely we don’t want that.

The rest is very easy to understand. We set the game map, take a screenshot and then send it through our filter to see if there is a mine of interest. if there is a mine we draw a rectangle around it and save the coordinates in the lists while displaying our screenshot in the form. The reason I made two listviews in my form is to display all found mines in the current screenshot and then display all found mines in the scan. Maked sense to me back then. The part which is commented to be disabled was for livetesting the bot. With this activated the bot would capture every screen while you could move the screen around manually. That was the first time I played around with it.

You maybe wonder what this BootTools class I used is, I can’t show it because it is a Class a friend of mine wrote and he doesn’t want it to get public. But let me tell you its a collection of .NET functions and DLLImports to make life easier when playing with some bots.

I hope you enjoyed this little series. This is what the result looked like when the bot found a mine.

output

 

 

 

 

 

 

 

 


Leave a comment

Your email address will not be published. Required fields are marked *

4 thoughts on “Androidgame Bot

  • Solo

    Hi Nico,

    I also wrote the same program in basic with AutoIt. It takes like 6-7 hours to scan the whole map for me. That is kinda frustrating, I will try to get BlueStacks running 6 times in my virtual machines to make it more efficient 🙂

    How fast is your bot?

    Many thanks in advance.

      • DJPANDA

        Hey, is there a possible way that you can give me a working version whichone I can use? I play Panzersturm via Bluestacks.
        Thanks

        • Nico Madry Post author

          Hello DJPANDA.
          I will not share a ready to use version of the bot.
          This bot is for educational purposes only and I don’t advice in using it.

          You can however take the source code and compile it to a working version. Some adjustments might be needed, this was coded pretty bad and a long time ago.