# How to Make a Bot for Any Android Game in MAS

> How to make a bot for any Android game in Macro Automation Studio: install one from the Marketplace, ask MAS Agent, or script it in Python on BlueStacks.

Source: https://automationmacro.com/docs/make-a-bot-for-any-game (Guides, updated 2026-09-04)

This tutorial shows how to make a bot for any Android game in Macro Automation Studio (MAS), from an installed game to a bot that runs on a schedule, on one account or on several. MAS bots work from the screen: they find buttons by image, read counters with OCR, and tap with humanized timing. It is for first-time bot builders on Windows or Mac.

<div class="doc-video" data-video="SDrdMIV49a8" data-title="How to make a bot for any game"></div>

## Before you start

- MAS is installed and you are signed in with the free trial or a plan. See [Install MAS](/docs/install).
- The game runs on a device MAS can drive: an emulator on this computer, a cloud device, or your own phone. See [Devices](/docs/devices).
- The device sits in a device group and its card shows Stopped, not Error. The [Getting started](/docs/getting-started) page walks through adding one.
- For the Python path you need nothing else. MAS ships its own Python environment with the `mas` package.

## How to make a bot for an Android game: three ways

### Install one from the Marketplace

The fastest route when someone has already built a bot for your game.

1. Open **Marketplace** and search for the game.
2. Open the listing and click **Download**. The bot appears under **Macros**.
3. In **Device Groups**, open your group, choose the bot in the device's **Macro** selector and click **Start**.

The full walkthrough with screenshots is [How to Run a Macro from the Marketplace](/docs/run-macro-from-marketplace). Ready-made bots exist for [Whiteout Survival](/whiteout-survival-bot), [Kingshot](/kingshot-bot) and [Last Asylum: Plague](/last-asylum-plague-bot).

### Ask MAS Agent

The no-code route for a game with no listing.

1. Open **Agent** and pick the device.
2. Describe the routine in one sentence, including when it should stop, and click **Author**.
3. Answer when the agent asks. It stops and asks instead of guessing.
4. When the macro passes 3 validation runs, click **Add to My Macros**.

Authoring spends AI credits; running the finished macro costs none. The result is a normal Python project you can open and edit. See [MAS Agent](/docs/agent).

### Build it in Python

Full control over every decision the bot makes. The rest of this page is this path.

## Automating Android games with Python

### 1. List what the bot must see

Play the routine once by hand and write down every screen it touches. That means the button you press, the popups that interrupt, the counter that shows your energy, and the message that means you are done. Each item becomes a template image or an OCR region. A bot that knows its stop state never runs blind.

### 2. Create the project

1. Open **Macros** and click **Create New Project**.
2. Choose **Code-Based**, set **Target Device** to mobile, name the project and click **Create Project**.
3. Open the project. The Code Editor shows `src/app.py`, the file MAS runs.

### 3. Capture templates in Asset Lab

1. Start the game on the device and go to the screen with the button.
2. In the Code Editor, open the **Assets** panel and click **Open Asset Helper**. Asset Lab opens on the live screen.
3. Crop a tight rectangle around the button and save it. It lands in your Image Library with a numeric ID.
4. Repeat for the popup close button, the confirm button and the "out of energy" message.
5. Back in the **Assets** panel, use **Copy ID** on each image and paste the IDs into `mas.images` at the top of your script.

Keep crops small and distinctive. A whole screen only matches that exact screen; a button matches wherever the button appears. Templates can be jpg, png, gif or webp, up to 10 MB each. The [Asset Lab](/docs/asset-lab) page covers the tool in detail.

### 4. Capture an OCR region

1. In Asset Lab, draw a box around the counter you want to read.
2. Run the live OCR test and tighten the box until the digits come back clean.
3. Copy the coordinates into a `Region(x1, y1, x2, y2)` in your script.

The [OCR guide](/docs/guides/ocr-text-reading) explains the segmentation modes and colour conversion if the text reads badly.

### 5. Write the loop

The loop below is the shape every game bot shares. Each line maps to an SDK call:

- `find_object_retry` looks for the button up to `total_tries=3` times, `time_sleep=2.0` seconds apart, and returns `None` when nothing matches.
- `click(x, y, delay_ms=1000)` taps the centre of the match and waits a second for the game to react.
- `read_text(region, psm=7)` reads the counter as a single line.
- `time.sleep(random.uniform(0.8, 2.0))` adds a human pause between rounds.
- `mas.save` and `mas.retrieve` keep the round counter, so a stopped run resumes where it left off.
- Stop conditions end the loop. The example uses five: a maximum number of rounds, a time budget, the "out of energy" template, a low counter, and too many misses in a row.

### 6. Run it and read the logs

Pick the device in the Code Editor and click **Run** (<kbd>F5</kbd>). Every `mas.log` line appears in the run console. **Stop** is <kbd>Shift</kbd>+<kbd>F5</kbd>. Fix one thing at a time: when a template is not found, recrop it before touching the logic.

### 7. Schedule it

1. Open **Scheduler** and click **Create New Schedule**.
2. Fill in **Name**, pick the **Macro** and the **Emulator Port**, set **Date** and **Time**.
3. Set **Recurrence** to **Daily** and click **Create Schedule**.

The app must stay open; the scheduler runs inside it. See [Scheduler](/docs/scheduler) and [Loops and scheduling](/docs/guides/loops-and-scheduling).

### 8. Run it on a device group

Add every account's emulator instance as a device in one group, assign the bot to each device and click **Start All**. Per-device values such as the account name come from a settings profile. See [Multi-instance farming](/docs/guides/multi-instance).

## A complete example bot

Replace the image IDs and the region with the ones you captured. The script farms a resource until energy runs low, a time budget passes, or thirty rounds are done, and it survives a restart.

```python
import random
import sys
import time

import mas
from mas import Region

images = mas.images({
    "attack_button": 101,
    "confirm_button": 102,
    "close_popup": 103,
    "out_of_energy": 104,
})

ENERGY_REGION = Region(x1=380, y1=20, x2=520, y2=60)
TASK = "farm_bot"
MAX_ROUNDS = 30
TIME_BUDGET_S = 20 * 60
MIN_ENERGY = 10


def pause(low=0.8, high=2.0):
    time.sleep(random.uniform(low, high))


def read_energy():
    result = mas.read_text(region=ENERGY_REGION, psm=7)
    digits = "".join(ch for ch in result.text if ch.isdigit())
    return int(digits) if digits else None


def clear_popups():
    popup = mas.find_any_object([images.close_popup, images.confirm_button])
    if popup:
        mas.click(popup.x, popup.y, delay_ms=800)
        return True
    return False


def main():
    rounds = mas.retrieve(TASK).get("rounds", 0)
    misses = 0
    started = time.monotonic()
    mas.log(f"Starting at round {rounds}")

    while rounds < MAX_ROUNDS:
        if time.monotonic() - started > TIME_BUDGET_S:
            mas.log("Time budget reached", level="warning")
            break
        if clear_popups():
            continue
        if mas.find_object(images.out_of_energy):
            mas.log("Out of energy, stopping")
            break
        energy = read_energy()
        if energy is not None and energy < MIN_ENERGY:
            mas.log(f"Energy {energy} is below {MIN_ENERGY}, stopping")
            break
        button = mas.find_object_retry(images.attack_button, total_tries=3, time_sleep=2.0)
        if button is None:
            misses += 1
            mas.log(f"Attack button not found ({misses})", level="warning")
            if misses >= 5:
                mas.log("Giving up after 5 misses", level="error")
                sys.exit(2)
            continue
        misses = 0
        mas.click(button.x, button.y, delay_ms=1000)
        pause()
        rounds += 1
        mas.save(TASK, {"rounds": rounds})
        mas.log(f"Round {rounds} of {MAX_ROUNDS}")

    if rounds >= MAX_ROUNDS:
        mas.clear(TASK)
    mas.log(f"Finished with {rounds} rounds")


if __name__ == "__main__":
    main()
```

`sys.exit(2)` marks the run as failed, so a webhook subscribed to `macro.failed` hears about it. A clean exit reports `macro.completed`. See [Webhooks](/docs/webhooks).

## Tips that keep a bot stable

- **Resolution.** Templates and regions belong to the resolution and DPI you captured them on. Keep every device that runs the bot on the same setting; the emulator guides use 540x960 in portrait at 240 DPI as the baseline.
- **Template hygiene.** Crop only the button, never the background around it. Recrop after a game update changes the art. When a button has two looks, capture both and search with `find_any_object`.
- **Stop conditions.** Every loop needs at least two: a counter or time budget, and a screen state that means "done". A bot without them runs until you notice.
- **Slow down.** A pause of one to two seconds between rounds costs little and looks less mechanical.
- **Log the decision, not the tap.** `mas.log("Energy 8, stopping")` tells you why a run ended; a log of a hundred clicks does not.

## What can go wrong

### The button is never found

The crop is too large, the threshold is too strict for the art, or the device runs at a different resolution than the one you captured on. Recrop tighter, then try `threshold=0.7` on the call. The [image recognition guide](/docs/guides/image-recognition-macros) has the full checklist.

### The counter reads the wrong number

The region includes a neighbouring icon, or the text is light on a dark background. Tighten the box in Asset Lab and pass `color_conversion=ColorConversion.BLACK_WHITE`. The [OCR guide](/docs/guides/ocr-text-reading) shows how to check the confidence.

### The bot stops at once on the second day

The stored counter is already at the limit. The example clears storage when it reaches `MAX_ROUNDS`; if you changed that, call `mas.clear("farm_bot")` once from a scratch script.

### Clicks land beside the button

The emulator's resolution or DPI changed after capture, or the window is not in portrait. Reset the emulator display to the capture setting and recapture anything that still misses.

No automation tool is 100% risk-free, so automate responsibly and at your own discretion.
