# LDPlayer macro loop: vòng lặp, điều kiện dừng và lập lịch

> Lặp macro trên LDPlayer hoặc BlueStacks với điều kiện dừng, giữ tiến độ giữa các lần chạy, thoát bằng mã trạng thái, và lập lịch chạy hằng ngày trong MAS.

Source: https://automationmacro.com/vi/docs/guides/loops-and-scheduling (Hướng dẫn, updated 2026-09-05)

Một macro chạy không cần trông chừng cần ba thứ: một vòng lặp biết khi nào dừng, một cách nhớ nó đã đi tới đâu, và một lịch khởi động nó mà không cần bạn. Hướng dẫn này bao cả ba cho một LDPlayer macro loop hoặc macro BlueStacks trong Macro Automation Studio (MAS). Bạn có các mẫu vòng lặp, điều kiện dừng, lưu trữ giữa các lần chạy, mã thoát mà webhook báo được, và Bộ lập lịch. Trang dành cho bất kỳ ai đưa macro từ "tôi nhấn Chạy" sang "nó tự chạy mỗi sáng".

## Trước khi bắt đầu

- MAS đã cài và đăng nhập. Nó là [bản tải miễn phí](/download) để dùng thử.
- Một dự án Code-Based chạy được trên giả lập từ Code Editor. Xem [Bắt đầu](/docs/getting-started).
- Giả lập đã thêm làm thiết bị với cổng bạn tìm được trên thẻ của nó. Xem [Thiết bị](/docs/devices).
- Ảnh mẫu và vùng của bạn đã chụp. Xem [hướng dẫn nhận diện hình ảnh](/docs/guides/image-recognition-macros).

## Mẫu LDPlayer macro loop và BlueStacks

Ba hình dạng bao gần như mọi macro.

**While, với điều kiện dừng.** Vòng lặp chạy cho đến khi thứ gì đó trên màn hình hoặc trong bộ đếm của bạn bảo dừng.

```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 với số lần cố định.** Bạn biết cần làm việc đó bao nhiêu lần.

```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)
```

**Cho đến khi một ảnh mẫu xuất hiện.** Một lần chờ có hạn chót, dùng cho màn hình tải và đồng hồ dài.

```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
```

Mọi vòng lặp ở trên đều có lối ra. `find_object` trả về `None` khi trượt và không bao giờ ném lỗi, nên một ảnh mẫu thiếu tự nó không kết thúc được `while True`. Hãy cho mọi vòng lặp một giới hạn.

## Điều kiện dừng của macro loop

Kết hợp ít nhất hai trong số này. Một cái đề phòng game, cái kia đề phòng lỗi của chính bạn.

- **Một bộ đếm.** `while collected < 50`. Rẻ và dễ đoán.
- **Ngân sách thời gian.** `time.monotonic()` đếm giây và không bao giờ nhảy khi đồng hồ đổi. Dùng nó cho vòng lặp ngoài để một lần chạy theo lịch luôn kết thúc trước khi lần tiếp theo bắt đầu.
- **Trạng thái màn hình.** Một ảnh mẫu như "hết năng lượng", "kho đầy" hoặc thông báo giới hạn hằng ngày. Chụp trong Asset Lab như mọi ảnh mẫu khác và kiểm tra ở đầu mỗi lượt.
- **Trượt liên tiếp.** Đếm bao nhiêu lượt liên tiếp không tìm thấy gì và bỏ cuộc sau năm lượt. Một màn hình bạn không lường trước trông y hệt như vậy.

```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)
```

## Khoảng nghỉ và nhịp giống người

`click` đã chờ `delay_ms=1000` sau cú chạm và `swipe` kéo dài `duration_ms=1000` theo mặc định. Giữa các lượt, thêm một khoảng nghỉ thay đổi.

```python
import random

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

Khoảng cách cố định trông máy móc và cũng chạy đua với game. Một popup xuất hiện 900 ms sau cú chạm lọt qua độ trễ cố định 1000 ms vào một ngày máy chậm. Khoảng nghỉ ngẫu nhiên làm mượt cả hai. Giữ `delay_ms` trên `click` cho thời gian phản ứng của chính ứng dụng và dùng `pause()` cho nhịp của con người.

## Lưu tiến độ bằng bộ nhớ

Một lần chạy theo lịch dừng giữa chừng nên tiếp tục từ chỗ đã dừng ở lần sau. Các hàm lưu trữ giữ một tài liệu JSON nhỏ cho mỗi tác vụ, khóa theo máy tính này, cổng của thiết bị và tên tác vụ bạn chọn.

```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)` ghi hoặc thay thế mục; `retrieve(task_name)` trả về từ điển hoặc một từ điển rỗng; `clear(task_name)` xóa nó. `data` phải tuần tự hóa được sang JSON. Vì cổng là một phần của khóa, hai instance giả lập chạy cùng macro không bao giờ dùng chung bộ đếm. `retrieve_all(task_name)` trả về mục của mọi instance khi bạn muốn tổng. Xem [Lưu trữ](/docs/sdk/storage).

> [!TIP]
> Lưu ngày cùng với bộ đếm. Macro hằng ngày khi đó phân biệt được lần chạy tiếp tục với ngày mới và tự đặt lại.

## Thoát bằng mã để webhook báo trạng thái

MAS đọc mã thoát của tiến trình khi mã của bạn kết thúc. Không nghĩa là lần chạy hoàn thành; bất kỳ giá trị nào khác đánh dấu là thất bại. Bên đăng ký webhook nhận `macro.completed` hoặc `macro.failed` tương ứng, kèm `exit_code` trong 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
```

Ngoại lệ không được xử lý cũng thoát với mã khác 0, nên một lần sập được báo là thất bại mà không cần thêm mã. Với các sự kiện chỉ mã của bạn biết, `mas.webhook("level.reached", {"level": 40})` gửi sự kiện `custom.level.reached` tới các điểm cuối đăng ký sự kiện tùy chỉnh. Thiết lập điểm cuối trên trang **Webhook**; xem [Webhook](/docs/webhooks).

## Một ví dụ hoàn chỉnh

Macro nhận tối đa năm mươi phần thưởng mỗi ngày, nhớ số đã nhận, dừng khi "hết năng lượng" hoặc sau hai mươi phút, và báo cáo qua mã thoát. Thay ID bằng của bạn.

```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()
```

Chạy một lần từ Code Editor bằng **Chạy** (<kbd>F5</kbd>) và xác nhận nó tự kết thúc trước khi bạn lập lịch.

## Lập lịch macro trong Bộ lập lịch MAS trên BlueStacks hoặc LDPlayer

Bộ lập lịch nằm bên trong ứng dụng máy tính và khởi động macro trên giả lập của bạn vào thời điểm bạn đặt.

1. Mở **Lập lịch** và nhấn **Tạo lịch mới**.
2. Nhập **Tên** (3 đến 100 ký tự) và chọn **Macro**.
3. Dưới **Cổng giả lập**, nhấn **Quét** và chọn cổng của instance, hoặc gõ vào.
4. Đặt **Ngày** và **Giờ**. Giờ là giờ địa phương của máy tính bạn.
5. Đặt **Chu kỳ**: **Không** cho một lần chạy, **Hằng ngày**, **Hằng tuần** với **Ngày trong tuần** đã tích, hoặc **Hằng tháng**.
6. Để **Số lần lặp** ở 1 trừ khi bạn muốn macro chạy nhiều lần liên tiếp ở mỗi lần kích hoạt.
7. Giữ **Hoạt động** bật và nhấn **Tạo lịch**.

Những điều cần biết trước khi bạn dựa vào nó:

- Ứng dụng phải mở và đã đăng nhập. Bộ lập lịch không phải dịch vụ hệ thống và không có cú pháp cron.
- Công việc được kiểm tra mỗi 30 giây, nên một lần chạy có thể bắt đầu muộn tới nửa phút so với giờ đặt.
- Một lịch nhắm vào một cổng. Nếu cổng đang bận với lần chạy khác khi tới giờ, bộ lập lịch bỏ qua chu kỳ đó và thử lại ở chu kỳ tiếp theo.
- Hai lịch không thể dùng chung một khung giờ trên cùng cổng; ứng dụng từ chối với "Time slot is occupied on this port".
- Tối đa 20 công việc theo lịch chạy được cùng lúc.
- Với **Số lần lặp** trên 1, một lần chạy thất bại (mã thoát khác 0) dừng các lần lặp còn lại và đánh dấu công việc là Thất bại.
- Các lần chạy theo lịch dùng tham số đã lưu trên thẻ thiết bị, hoặc hồ sơ cài đặt mà thiết bị đi theo. Xem [Hồ sơ cài đặt](/docs/settings-profiles).
- **Xem lịch sử** trên một công việc hiển thị mọi lần chạy kèm trạng thái và nhật ký.

Trang [Bộ lập lịch](/docs/scheduler) nói về sửa, bỏ qua và hủy công việc.

## Chạy trên nhóm

Để chạy cùng macro trên nhiều instance bằng tay, đặt chúng vào một nhóm thiết bị, gán macro cho từng thiết bị và nhấn **Bắt đầu tất cả**. Thiết bị bắt đầu lần lượt, cách nhau nửa giây, và mỗi cái có lần chạy, nhật ký và mã thoát riêng. Để lập lịch cho nhóm, tạo một công việc theo lịch cho mỗi cổng với cùng macro và giờ. [Hướng dẫn đa nhiệm](/docs/guides/multi-instance) nói về tham số, proxy và lưu trữ cho từng thiết bị.

## Điều gì có thể sai

### Vòng lặp không bao giờ kết thúc

Một `while True` không có giới hạn, hoặc một điều kiện dừng phụ thuộc vào ảnh mẫu không bao giờ xuất hiện. Thêm ngân sách thời gian bằng `time.monotonic()` và bộ đếm trượt liên tiếp; hai thứ đó kết thúc mọi vòng lặp.

### Lịch không kích hoạt

Ứng dụng đã đóng hoặc máy ngủ vào lúc đó, cổng đang bận, hoặc công việc không **Hoạt động**. Kiểm tra **Xem lịch sử** để tìm mục bị bỏ qua hoặc thất bại, và giữ máy tính thức trong khung giờ đã lên lịch.

### Time slot is occupied on this port

Một công việc khác trên cùng cổng đã giữ giờ đó. Dời một công việc vài phút, hoặc đặt macro thứ hai vào trong macro thứ nhất bằng **Số lần lặp**.

### Lần chạy hiển thị Thất bại nhưng macro đã xong

Mã kết thúc với mã thoát khác 0 hoặc một ngoại lệ không được xử lý sau hành động cuối. Đọc các dòng cuối của nhật ký; một `sys.exit(1)` trong đường dọn dẹp hoặc một `KeyError` trên trạng thái đã lưu là nguyên nhân thường gặp.

Không có công cụ tự động hóa nào an toàn 100%, vì vậy hãy tự động hóa một cách có trách nhiệm và theo quyết định của riêng bạn.
