# LDPlayer macro loop: condições de parada e agendamento

> Coloque uma macro em loop no LDPlayer ou BlueStacks com condições de parada, guarde o progresso entre execuções, saia com um código de status e agende no MAS.

Source: https://automationmacro.com/pt-BR/docs/guides/loops-and-scheduling (Guias, updated 2026-09-05)

Uma macro que roda sem supervisão precisa de três coisas: um loop que sabe quando parar, um jeito de lembrar onde chegou e um agendamento que a inicia sem você. Este guia cobre as três para uma macro LDPlayer ou BlueStacks no Macro Automation Studio (MAS). Você recebe os padrões de loop, as condições de parada, o armazenamento entre execuções, os códigos de saída que os webhooks podem informar e o Agendador. Ele é para quem está levando uma macro de "eu clico em Executar" para "ela roda toda manhã".

## Antes de começar

- O MAS está instalado e você fez login. Ele é um [download gratuito](/download) para o teste.
- Um projeto Baseado em código roda em um emulador a partir do Code Editor. Veja [Primeiros passos](/docs/getting-started).
- O emulador está adicionado como dispositivo com uma porta que você encontra no card dele. Veja [Dispositivos](/docs/devices).
- Seus templates e regiões estão capturados. Veja o [guia de reconhecimento de imagem](/docs/guides/image-recognition-macros).

## Padrões de macro loop para LDPlayer e BlueStacks

Três formas cobrem quase toda macro.

**While, com uma condição de parada.** O loop roda até algo na tela ou nos seus contadores mandar parar.

```python
import mas

images = mas.images({"collect": 401, "done": 402})

collected = 0
while collected < 50:
    if mas.find_object(images.done):
        break
    button = mas.find_object_retry(images.collect, total_tries=3, time_sleep=2.0)
    if button is None:
        continue
    mas.click(button.x, button.y, delay_ms=1000)
    collected += 1
```

**For, com uma contagem fixa.** Você sabe quantas vezes fazer a coisa.

```python
for round_no in range(1, 11):
    mas.log(f"Round {round_no} of 10")
    button = mas.find_object_retry(images.collect)
    if button is None:
        mas.log("Nothing to collect, stopping early", level="warning")
        break
    mas.click(button.x, button.y)
```

**Até um template aparecer.** Uma espera com prazo, usada para telas de carregamento e timers longos.

```python
import time

def wait_for(image, timeout_s=120, every_s=3.0):
    deadline = time.monotonic() + timeout_s
    while time.monotonic() < deadline:
        match = mas.find_object(image)
        if match:
            return match
        time.sleep(every_s)
    return None
```

Todo loop acima tem uma saída. `find_object` retorna `None` quando falha e nunca lança exceção, então um template ausente sozinho não consegue encerrar um `while True`. Dê a todo loop um limite.

## Condições de parada do macro loop

Combine pelo menos duas destas. Uma protege contra o jogo, a outra contra o seu próprio bug.

- **Um contador.** `while collected < 50`. Barato e previsível.
- **Um limite de tempo.** `time.monotonic()` conta segundos e nunca pula quando o relógio muda. Use-o no loop externo para uma execução agendada sempre terminar antes de a próxima começar.
- **Um estado de tela.** Um template como "sem energia", "inventário cheio" ou uma mensagem de limite diário. Capture-o no Asset Lab como qualquer outro template e confira-o no topo de cada iteração.
- **Falhas consecutivas.** Conte quantas iterações seguidas não encontraram nada e desista depois de cinco. Uma tela que você não previu se parece exatamente com isso.

```python
started = time.monotonic()
misses = 0
while time.monotonic() - started < 20 * 60:
    if mas.find_object(images.out_of_energy):
        break
    button = mas.find_object_retry(images.collect)
    if button is None:
        misses += 1
        if misses >= 5:
            break
        continue
    misses = 0
    mas.click(button.x, button.y)
```

## Pausas e tempo humanizado

`click` já espera `delay_ms=1000` depois do toque e `swipe` leva `duration_ms=1000` por padrão. Entre as iterações, adicione uma pausa que varia.

```python
import random

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

Intervalos fixos parecem mecânicos e também disputam com o jogo. Um pop-up que aparece 900 ms depois de um toque escapa de um atraso fixo de 1000 ms em um dia lento. Uma pausa aleatória suaviza os dois. Mantenha `delay_ms` no `click` para o tempo de reação do app e use `pause()` para o ritmo humano.

## Guarde o progresso com o armazenamento

Uma execução agendada que para no meio deve retomar de onde parou na próxima vez. As funções de armazenamento guardam um pequeno documento JSON por tarefa, indexado por este computador, pela porta do dispositivo e por um nome de tarefa que você escolhe.

```python
TASK = "daily_collect"

state = mas.retrieve(TASK)          # {} on the first run
collected = state.get("collected", 0)
last_day = state.get("day")

mas.save(TASK, {"collected": collected, "day": today})   # after each pass of the loop

if collected >= 50:
    mas.clear(TASK)                 # start fresh next time
```

`save(task_name, data)` grava ou substitui a entrada; `retrieve(task_name)` retorna o dicionário ou um vazio; `clear(task_name)` o apaga. `data` precisa ser serializável em JSON. Como a porta faz parte da chave, duas instâncias de emulador rodando a mesma macro nunca compartilham um contador. `retrieve_all(task_name)` retorna a entrada de toda instância quando você quer um total. Veja [Armazenamento](/docs/sdk/storage).

> [!TIP]
> Guarde a data junto com o contador. Uma macro diária consegue então distinguir uma execução retomada de um dia novo e se reiniciar.

## Saia com um código para os webhooks informarem o status

O MAS lê o código de saída do processo quando o seu script termina. Zero significa que a execução foi concluída; qualquer outro valor a marca como falha. Os inscritos em webhooks recebem `macro.completed` ou `macro.failed` conforme o caso, com o `exit_code` no payload.

```python
import sys

if energy is None:
    mas.log("Could not read energy", level="error")
    sys.exit(2)      # macro.failed, exit_code 2

mas.log("All done")
sys.exit(0)          # macro.completed
```

Uma exceção não tratada também sai com valor diferente de zero, então uma falha é informada como falha sem nenhum código extra. Para eventos que só o seu script conhece, `mas.webhook("level.reached", {"level": 40})` envia um evento `custom.level.reached` aos endpoints inscritos em eventos personalizados. Configure os endpoints na página **Webhooks**; veja [Webhooks](/docs/webhooks).

## Um exemplo completo

A macro coleta até cinquenta recompensas por dia, lembra a contagem, para em "sem energia" ou depois de vinte minutos e informa pelo código de saída. Troque os IDs pelos seus.

```python
import datetime
import random
import sys
import time

import mas

images = mas.images({
    "collect": 401,
    "close_popup": 402,
    "out_of_energy": 403,
})

TASK = "daily_collect"
DAILY_LIMIT = 50
TIME_BUDGET_S = 20 * 60


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


def main():
    today = datetime.date.today().isoformat()
    state = mas.retrieve(TASK)
    collected = state.get("collected", 0) if state.get("day") == today else 0
    mas.log(f"{today}: starting at {collected} of {DAILY_LIMIT}")

    started = time.monotonic()
    misses = 0
    while collected < DAILY_LIMIT:
        if time.monotonic() - started > TIME_BUDGET_S:
            mas.log("Time budget reached", level="warning")
            break
        popup = mas.find_object(images.close_popup)
        if popup:
            mas.click(popup.x, popup.y, delay_ms=800)
            continue
        if mas.find_object(images.out_of_energy):
            mas.log("Out of energy")
            break
        button = mas.find_object_retry(images.collect, total_tries=3, time_sleep=2.0)
        if button is None:
            misses += 1
            if misses >= 5:
                mas.log("Five misses in a row, giving up", level="error")
                mas.save(TASK, {"collected": collected, "day": today})
                sys.exit(2)
            continue
        misses = 0
        mas.click(button.x, button.y, delay_ms=1000)
        collected += 1
        mas.save(TASK, {"collected": collected, "day": today})
        pause()

    mas.log(f"Finished at {collected} of {DAILY_LIMIT}")
    sys.exit(0)


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

Rode uma vez pelo Code Editor com **Executar** (<kbd>F5</kbd>) e confirme que ela termina sozinha antes de agendá-la.

## Agende a macro no Agendador do MAS no BlueStacks ou LDPlayer

O Agendador vive dentro do app de desktop e inicia macros nos seus emuladores nos horários que você define.

1. Abra o **Agendador** e clique em **Criar novo agendamento**.
2. Digite um **Nome** (3 a 100 caracteres) e escolha a **Macro**.
3. Em **Porta do emulador**, clique em **Escanear** e escolha a porta da instância, ou digite-a.
4. Defina **Data** e **Hora**. A hora é a hora local do seu computador.
5. Defina a **Recorrência**: **Nenhuma** para uma execução única, **Diária**, **Semanal** com os **Dias da semana** marcados, ou **Mensal**.
6. Deixe **Contagem de repetições** em 1, a menos que queira que a macro rode várias vezes seguidas a cada disparo.
7. Mantenha **Ativo** ligado e clique em **Criar agendamento**.

O que saber antes de confiar nele:

- O app precisa ficar aberto e com login feito. O agendador não é um serviço do sistema e não há sintaxe de cron.
- Os jobs são verificados a cada 30 segundos, então uma execução pode começar até meio minuto depois do horário.
- Um agendamento tem uma porta como alvo. Se a porta estiver ocupada com outra execução na hora, o agendador pula esse ciclo e tenta de novo no próximo.
- Dois agendamentos não podem compartilhar um horário na mesma porta; o app recusa com "Time slot is occupied on this port".
- Até 20 jobs agendados podem rodar ao mesmo tempo.
- Com **Contagem de repetições** acima de 1, uma execução com falha (saída diferente de zero) interrompe as repetições restantes e marca o job como Falhou.
- As execuções agendadas usam os argumentos salvos no card do dispositivo, ou o perfil de configurações que o dispositivo segue. Veja [Perfis de configurações](/docs/settings-profiles).
- **Ver histórico** em um job mostra toda execução com status e logs.

A página [Agendador](/docs/scheduler) cobre editar, pular e cancelar jobs.

## Rode em um grupo

Para rodar a mesma macro em várias instâncias à mão, coloque-as em um grupo de dispositivos, atribua a macro a cada dispositivo e clique em **Iniciar todos**. Os dispositivos iniciam um após o outro, com meio segundo de intervalo, e cada um tem a própria execução, os próprios logs e o próprio código de saída. Para agendar o grupo, crie um job agendado por porta com a mesma macro e o mesmo horário. O [guia de multi-instância](/docs/guides/multi-instance) cobre argumentos, proxies e armazenamento por dispositivo.

## O que pode dar errado

### O loop nunca termina

Um `while True` sem limite, ou uma condição de parada que depende de um template que nunca aparece. Adicione um limite de tempo com `time.monotonic()` e um contador de falhas consecutivas; esses dois encerram qualquer loop.

### O agendamento não disparou

O app estava fechado ou suspenso na hora, a porta estava ocupada, ou o job não está **Ativo**. Confira **Ver histórico** para uma entrada pulada ou com falha, e mantenha o computador acordado na janela agendada.

### Time slot is occupied on this port

Outro job na mesma porta já é dono desse horário. Mova um job alguns minutos, ou coloque a segunda macro dentro da primeira com **Contagem de repetições**.

### A execução mostra Falhou mas a macro terminou

O script terminou com um código de saída diferente de zero ou uma exceção não tratada depois da última ação. Leia as últimas linhas do log; um `sys.exit(1)` em um caminho de limpeza ou um `KeyError` no estado guardado são as causas habituais.

Nenhuma ferramenta de automação é 100% livre de riscos, por isso automatize com responsabilidade e a seu próprio critério.
