# LDPlayer マクロのループ、停止条件、スケジュール実行

> LDPlayer や BlueStacks のマクロを停止条件付きでループさせ、実行間で進捗を保存し、終了コードを返し、MAS のスケジューラーで毎日実行する方法です。

Source: https://automationmacro.com/ja/docs/guides/loops-and-scheduling (ガイド, updated 2026-09-05)

無人で動くマクロには 3 つのものが必要です: いつ止まるかを知っているループ、どこまで進んだかを覚える方法、そしてあなたなしで起動するスケジュール。このガイドでは、Macro Automation Studio (MAS) の LDPlayer や BlueStacks のマクロループについて、この 3 つすべてを扱います。ループのパターン、停止条件、実行間のストレージ、Webhook が報告できる終了コード、そしてスケジューラーです。マクロを「実行をクリックする」から「毎朝勝手に動く」へ移したいすべての方向けです。

## 始める前に

- MAS がインストール済みでサインインしていること。トライアル向けに [無料でダウンロード](/download) できます。
- コードベースのプロジェクトが Code Editor からエミュレータで動くこと。[はじめに](/docs/getting-started) を参照してください。
- エミュレータが、カードで確認できるポートでデバイスとして追加されていること。[デバイス](/docs/devices) を参照してください。
- テンプレートと領域が取り込み済みであること。[画像認識ガイド](/docs/guides/image-recognition-macros) を参照してください。

## LDPlayer と BlueStacks のマクロループのパターン

3 つの形でほぼすべてのマクロをカバーできます。

**停止条件付きの while。** 画面上の何か、またはカウンターが止まれと言うまでループが動きます。

```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。** 何回やるかが分かっている場合です。

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

**テンプレートが現れるまで。** 読み込み画面や長いタイマーに使う、期限付きの待機です。

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

上のすべてのループには出口があります。`find_object` は見失うと `None` を返し、例外は送出しないので、テンプレートがないだけでは `while True` は終わりません。すべてのループに上限を与えてください。

## マクロループの停止条件

これらのうち少なくとも 2 つを組み合わせます。1 つはゲームに対する備え、もう 1 つは自分のバグに対する備えです。

- **カウンター。** `while collected < 50`。安価で予測できます。
- **時間の予算。** `time.monotonic()` は秒を数え、時計が変わっても跳びません。外側のループに使い、スケジュール実行が次の実行が始まる前に必ず終わるようにします。
- **画面の状態。** 「エネルギー切れ」、「インベントリがいっぱい」、日次上限のメッセージなどのテンプレート。他のテンプレートと同じように Asset Lab で取り込み、各周回の先頭で確認します。
- **連続した見失い。** 何回連続で何も見つからなかったかを数え、5 回で諦めます。想定していなかった画面は、まさにこのように見えます。

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

## スリープと人間らしいタイミング

`click` はタップ後に `delay_ms=1000` すでに待ち、`swipe` は既定で `duration_ms=1000` かけます。周回の間には、ばらつく間を加えます。

```python
import random

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

一定の間隔は機械的に見え、ゲームとの競合も起こします。タップの 900 ms 後に出るポップアップは、遅い日には固定の 1000 ms の遅延をすり抜けます。ランダムな間は両方をなだらかにします。アプリ自身の反応時間には `click` の `delay_ms` を残し、人間のリズムには `pause()` を使ってください。

## ストレージで進捗を保持する

途中で止まったスケジュール実行は、次回に続きから始めるべきです。ストレージ関数は、このコンピューター、デバイスのポート、選んだタスク名をキーとして、タスクごとに小さな JSON ドキュメントを保持します。

```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)` は項目を書き込むか置き換え、`retrieve(task_name)` は辞書か空の辞書を返し、`clear(task_name)` は削除します。`data` は JSON にシリアライズできる必要があります。ポートがキーの一部なので、同じマクロを動かす 2 つのエミュレータインスタンスがカウンターを共有することはありません。合計が欲しいときは `retrieve_all(task_name)` がすべてのインスタンスの項目を返します。[ストレージ](/docs/sdk/storage) を参照してください。

> [!TIP]
> カウンターと一緒に日付を保存してください。日次マクロは、再開された実行と新しい日を区別し、自分でリセットできます。

## Webhook が状態を報告できるよう終了コードで終える

MAS はスクリプトの終了時にプロセスの終了コードを読みます。ゼロは実行の完了を意味し、それ以外は失敗としてマークします。Webhook の購読者はそれに応じて `macro.completed` か `macro.failed` を、ペイロード内の `exit_code` とともに受け取ります。

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

処理されない例外もゼロ以外で終了するので、クラッシュは追加のコードなしで失敗として報告されます。スクリプトだけが知っているイベントには、`mas.webhook("level.reached", {"level": 40})` が、カスタムイベントを購読するエンドポイントに `custom.level.reached` イベントを送ります。エンドポイントは **Webhook** ページで設定します。[Webhook](/docs/webhooks) を参照してください。

## 完全な例

このマクロは 1 日に最大 50 個の報酬を集め、数を覚え、「エネルギー切れ」か 20 分後に止まり、終了コードで報告します。ID を自分のものに置き換えてください。

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

スケジュールする前に、Code Editor の **実行** (<kbd>F5</kbd>) で 1 度実行し、自分で終わることを確かめてください。

## BlueStacks や LDPlayer で MAS のスケジューラーにマクロを登録する

スケジューラーはデスクトップアプリの中にあり、設定した時刻にエミュレータでマクロを起動します。

1. **スケジューラー** を開き、**新しいスケジュールを作成** をクリックします。
2. **名前** (3 から 100 文字) を入力し、**マクロ** を選びます。
3. **エミュレータポート** で **スキャン** をクリックしてインスタンスのポートを選ぶか、入力します。
4. **日付** と **時刻** を設定します。時刻はコンピューターのローカル時刻です。
5. **繰り返し** を設定します: 1 回だけなら **なし**、**毎日**、**曜日** にチェックを入れた **毎週**、または **毎月**。
6. 発火のたびにマクロを続けて複数回動かしたいのでなければ、**繰り返し回数** は 1 のままにします。
7. **有効** をオンのままにして、**スケジュールを作成** をクリックします。

頼りにする前に知っておくこと:

- アプリは開いたまま、サインインしたままにする必要があります。スケジューラーはシステムサービスではなく、cron 構文もありません。
- ジョブは 30 秒ごとに確認されるので、実行は時刻から最大 30 秒遅れて始まることがあります。
- スケジュールは 1 つのポートを対象にします。時刻になったときにポートが別の実行で使用中なら、スケジューラーはそのサイクルを飛ばし、次のサイクルでもう一度試します。
- 同じポートで 2 つのスケジュールが同じ時間枠を共有することはできません。アプリは「Time slot is occupied on this port」で拒否します。
- 同時に実行できるスケジュールジョブは最大 20 個です。
- **繰り返し回数** が 1 より大きいとき、失敗した実行 (ゼロ以外の終了) は残りの繰り返しを止め、ジョブを Failed とマークします。
- スケジュール実行はデバイスカードに保存された引数、またはデバイスが従う設定プロファイルを使います。[設定プロファイル](/docs/settings-profiles) を参照してください。
- ジョブの **履歴を表示** に、すべての実行が状態とログ付きで表示されます。

[スケジューラー](/docs/scheduler) ページでは、ジョブの編集、スキップ、キャンセルを説明しています。

## グループで実行する

同じマクロを複数のインスタンスで手動で実行するには、それらを 1 つのデバイスグループに入れ、各デバイスにマクロを割り当てて **すべて開始** をクリックします。デバイスは 0.5 秒間隔で順番に開始し、それぞれが固有の実行、ログ、終了コードを持ちます。グループをスケジュールするには、同じマクロと時刻でポートごとに 1 つのスケジュールジョブを作ります。[多重起動ガイド](/docs/guides/multi-instance) では、デバイスごとの引数、プロキシ、ストレージを扱います。

## うまくいかないとき

### ループが終わらない

上限のない `while True` か、決して現れないテンプレートに依存する停止条件です。`time.monotonic()` による時間の予算と連続見失いカウンターを加えてください。この 2 つがすべてのループを終わらせます。

### スケジュールが発火しなかった

その時刻にアプリが閉じていたかスリープ中だった、ポートが使用中だった、またはジョブが **有効** ではありません。**履歴を表示** でスキップや失敗の項目を確認し、スケジュールの時間帯はコンピューターを起こしたままにしてください。

### Time slot is occupied on this port

同じポートの別のジョブがすでにその時刻を持っています。どちらかのジョブを数分ずらすか、**繰り返し回数** で 2 つ目のマクロを 1 つ目に含めてください。

### マクロは終わったのに実行が Failed と表示される

スクリプトが最後の操作の後にゼロ以外の終了コードか処理されない例外で終わりました。ログの最後の行を読んでください。クリーンアップ経路の `sys.exit(1)` や、保存された状態に対する `KeyError` がよくある原因です。

100%リスクのない自動化ツールは存在しないため、自動化は責任を持って、ご自身の判断で行ってください。
