diff --git a/README.md b/README.md index 94c6c12..023befd 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,14 @@ 流程为:首页 → 领地 → 合作模式 → 模式选择左侧“合作模式” → 左上“可领取” → 确认奖励弹框与“已领完” → 返回首页。只操作左侧合作模式,不进入“保卫宠物”;已领完时不点击领取,直接返回首页。使用 `--dry-run` 可只验证首页而不点击。 +### 已验证的竞技积分领取脚本 + +```bash +.venv/bin/python claim_arena_points.py +``` + +流程为:首页 → 领地 → 巅峰决斗 → 模式选择左侧“竞技场” → 点击左下积分累积区域 → 确认“恭喜获得”奖励弹框 → 返回首页。积分会持续累积,脚本每次运行领取当前可领取的积分。使用 `--dry-run` 可只验证首页而不点击。 + 需要 Python 3.9 或更新版本,以及带 Tk 支持的 Python。macOS 若当前 Python 缺少 Tk,可使用 python.org 官方安装包。 macOS: diff --git a/assets/arena_dismiss.png b/assets/arena_dismiss.png new file mode 100644 index 0000000..bd38b5f Binary files /dev/null and b/assets/arena_dismiss.png differ diff --git a/assets/arena_page_title.png b/assets/arena_page_title.png new file mode 100644 index 0000000..f79514e Binary files /dev/null and b/assets/arena_page_title.png differ diff --git a/assets/arena_reward.png b/assets/arena_reward.png new file mode 100644 index 0000000..f34f2fc Binary files /dev/null and b/assets/arena_reward.png differ diff --git a/assets/arena_select_title.png b/assets/arena_select_title.png new file mode 100644 index 0000000..5bfd222 Binary files /dev/null and b/assets/arena_select_title.png differ diff --git a/automation/arena.py b/automation/arena.py new file mode 100644 index 0000000..75f37b1 --- /dev/null +++ b/automation/arena.py @@ -0,0 +1,62 @@ +"""领地巅峰决斗竞技场积分领取流程。""" +import time + +from .engine import WorkflowError + + +class ArenaRunner: + def __init__(self, desktop, matcher, flow, region_provider, log=print, timeout=12): + self.desktop = desktop + self.matcher = matcher + self.flow = flow + self.region_provider = region_provider + self.log = log + self.timeout = timeout + + def wait(self, *names): + deadline = time.monotonic() + self.timeout + previous, stable = None, 0 + while time.monotonic() < deadline: + screenshot = self.desktop.screenshot() + region = self.region_provider(screenshot) + frame = self.matcher.frame(screenshot, region) + found = next((name for name in names + if self.matcher.matches(frame, self.flow['states'][name])), None) + stable = stable + 1 if found and found == previous else 1 if found else 0 + previous = found + if stable >= 2: + return found, screenshot, region + time.sleep(0.25) + raise WorkflowError('未识别到预期页面 ' + '/'.join(names) + ',已停止。') + + def click(self, action, observation): + _, screenshot, region = observation + if self.region_provider(screenshot) != region: + raise WorkflowError('识别后游戏窗口移动,已停止,请重新运行。') + self.desktop.click(self.flow['clicks'][action], region, screenshot.size, hold=0.8) + self.log('已执行:' + action) + + def return_home(self): + self.click('arena_back', self.wait('arena_page')) + self.click('territory_back', self.wait('territory')) + self.wait('home') + self.log('已确认返回首页。') + + def run(self, dry_run=False): + home = self.wait('home') + if dry_run: + self.log('首页识别通过。计划:领地 → 巅峰决斗 → 竞技场 → 领取竞技积分 → 返回首页。未点击。') + return 'preview' + + self.click('territory', home) + self.click('peak_duel', self.wait('territory')) + self.click('arena', self.wait('selection')) + arena = self.wait('arena_page') + self.click('claim_points', arena) + reward = self.wait('reward') + self.log('已确认竞技积分奖励弹框。') + self.click('dismiss_reward', reward) + self.wait('arena_page') + self.log('已确认竞技积分领取完成。') + self.return_home() + return 'claimed' diff --git a/claim_arena_points.py b/claim_arena_points.py new file mode 100644 index 0000000..fc7ee8d --- /dev/null +++ b/claim_arena_points.py @@ -0,0 +1,39 @@ +"""Run from the project root: python claim_arena_points.py [--dry-run].""" +import argparse +import sys +import time +from pathlib import Path + +from automation.arena import ArenaRunner +from automation.engine import Matcher, load_flow +from automation.offers import MacGameDesktop + +ROOT = Path(__file__).resolve().parent + + +def main(): + parser = argparse.ArgumentParser(description='巅峰决斗竞技场:领取竞技积分并返回首页') + parser.add_argument('--dry-run', action='store_true', help='只验证首页并显示计划,不点击') + args = parser.parse_args() + if sys.platform != 'darwin': + print('当前脚本已验证 macOS;Windows 版将在校准入口完成后接入。', file=sys.stderr) + return 2 + try: + desktop = MacGameDesktop() + desktop.activate() + print('3 秒后开始;请保持游戏在前台。鼠标移到主屏幕左上角可紧急停止。', flush=True) + time.sleep(3) + runner = ArenaRunner(desktop, Matcher(ROOT / 'assets'), + load_flow(ROOT / 'workflows/arena_points.json'), desktop.region_for) + runner.run(args.dry_run) + return 0 + except KeyboardInterrupt: + print('已停止。', file=sys.stderr) + return 130 + except Exception as exc: + print('流程停止:' + str(exc), file=sys.stderr) + return 1 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/docs/261004_1607-首页功能与日常任务梳理.md b/docs/261004_1607-首页功能与日常任务梳理.md index 4be90e6..21c923e 100644 --- a/docs/261004_1607-首页功能与日常任务梳理.md +++ b/docs/261004_1607-首页功能与日常任务梳理.md @@ -1,7 +1,7 @@ --- created_at: 2026-10-04 16:07:56 CST type: 永远的蔚蓝星球首页、商城、领地与公会入口、日常任务与自动化适配记录 -revision: 第14次 +revision: 第15次 revisions: - time: 2026-10-04 16:07:56 CST version: 第1次 @@ -59,7 +59,11 @@ revisions: version: 第14次 author: gpt-5 content: 补充合作模式合作券领取的实机验证、已领完跳过分支、脚本和汇总表条目。 -summary: 记录首页、商城、领地和公会的日常任务入口、待完善流程及已实现脚本汇总。入口红点表示内部有奖励可领取;特惠赏金战令、小金猪领取与扫荡、任务奖励、狂鼠之家收矿与雇佣、食堂领体力、合作模式合作券均已实机验证并写成脚本。 + - time: 2026-10-04 20:56:07 CST + version: 第15次 + author: gpt-5 + content: 补充巅峰决斗竞技场积分领取的实机验证、脚本和汇总表条目。 +summary: 记录首页、商城、领地和公会的日常任务入口、待完善流程及已实现脚本汇总。入口红点表示内部有奖励可领取;特惠赏金战令、小金猪领取与扫荡、任务奖励、狂鼠之家收矿与雇佣、食堂领体力、合作模式合作券、竞技场积分均已实机验证并写成脚本。 --- # 首页、商城、领地与公会功能及日常任务梳理 @@ -230,7 +234,7 @@ summary: 记录首页、商城、领地和公会的日常任务入口、待完 | 图鉴 | 下方偏右,书本建筑 | 用户确认有固定流程,具体步骤待补充 | | 食堂 | 中部,带食物的建筑 | 一键领取体力已实机验证并有脚本;其他内部功能待补充 | | 排行榜 | 右侧中部,石制台座区域 | 用户确认有固定流程,具体步骤待补充 | -| 巅峰决斗 | 左上部,旗帜建筑 | 用户确认有固定流程,具体步骤待补充 | +| 巅峰决斗 | 左上部,旗帜建筑 | 竞技场积分领取已实机验证并有脚本;巅峰决斗模式及其他内部功能待补充 | | 合作模式 | 上方中部,紫色传送门区域 | 合作券领取已实机验证并有脚本;保卫宠物及其他内部功能待补充 | | 城墙 | 右上部,城门建筑 | 用户确认有固定流程,具体步骤待补充 | @@ -280,6 +284,18 @@ summary: 记录首页、商城、领地和公会的日常任务入口、待完 脚本只选择模式选择页左侧的“合作模式”,不会进入右侧“保卫宠物”。左上角显示“可领取”时才点击并等待奖励弹框;若已显示“已领完”,则跳过领取并依次退出到首页。可使用 `--dry-run` 只验证首页和执行计划,不点击游戏内容。macOS 已实机验证;Windows 仍需校准和验证。 +### 巅峰决斗:竞技场积分领取(已实机验证) + +2026-10-04 已在当前账号完成:**首页 → 领地 → 巅峰决斗 → 模式选择中的左侧“竞技场” → 左下角带时分秒进度条的积分累积区域 → 确认奖励弹框 → 返回领地 → 返回首页**。首次点击获得 3886 竞技积分;脚本随后完成一次实机回归,确认可自动关闭奖励弹框并返回首页。 + +对应脚本为项目根目录的 `claim_arena_points.py`,配置在 `workflows/arena_points.json`。运行方式: + +```bash +.venv/bin/python claim_arena_points.py +``` + +竞技积分会持续累积,该区域可领取时显示“恭喜获得”弹框。脚本确认竞技场页面后点击左下积分累积区域,必须识别奖励弹框才能继续关闭和返回;无法识别时安全停止。脚本只选择左侧“竞技场”,不会进入右侧“巅峰决斗”。可使用 `--dry-run` 只验证首页和执行计划,不点击游戏内容。macOS 已实机验证;Windows 仍需校准和验证。 + ## 公会页面 ### 入口与栏目 @@ -323,7 +339,8 @@ summary: 记录首页、商城、领地和公会的日常任务入口、待完 | 领地:狂鼠之家 | 已验证脚本:一键收矿、等待 10 秒、按白银/黄金/彩虹顺序一键雇佣、返回首页 | 当前仅覆盖该日常流程;其他内部功能、重置规律和 Windows 适配待验证 | | 领地:食堂 | 已验证脚本:发现一键领取时领体力、确认奖励和库存清空、返回首页 | 当前仅覆盖体力领取;其他内部功能、重置规律和 Windows 适配待验证 | | 领地:合作模式 | 已验证脚本:只选择左侧合作模式,发现左上可领取时领取合作券、确认已领完、返回首页 | 保卫宠物及其他内部功能、重置规律和 Windows 适配待验证 | -| 领地:玩法大厅、图鉴、排行榜、巅峰决斗、城墙 | 待完善固定流程后确定 | 用户确认各栏目均有固定自动化流程,具体步骤、执行条件、消耗和完成判定尚未补充 | +| 领地:巅峰决斗 | 已验证脚本:只选择左侧竞技场,领取左下积分累积区域的竞技积分、确认奖励后返回首页 | 右侧巅峰决斗及其他内部功能、重置规律和 Windows 适配待验证 | +| 领地:玩法大厅、图鉴、排行榜、城墙 | 待完善固定流程后确定 | 用户确认各栏目均有固定自动化流程,具体步骤、执行条件、消耗和完成判定尚未补充 | | 公会:公会商城、远征大厅、公会任务、公会祝福、每日捐献、成员大厅、公会 Boss、右下角红色角色入口 | 待完善固定流程后确定 | 用户确认八个入口均有固定自动化流程;右下角入口名称及各流程的具体步骤、消耗和完成判定待补充 | 初步方向是让脚本执行已验证的重复步骤,让 AI 辅助识别变化页面或异常。是否每次调用 AI、调用到哪一步,待明确任务完成标志与变化范围后再决定。 @@ -356,3 +373,4 @@ summary: 记录首页、商城、领地和公会的日常任务入口、待完 | 5 | 收矿与雇佣(`claim_mining.py`) | 领地 | 首页 → 底部领地 → 狂鼠之家 | 发现“一键收矿”后收矿、关闭奖励弹框、等待 10 秒;按白银罗盘、黄金罗盘、彩虹探测仪顺序一键雇佣,无可用道具时关闭弹框,最后返回首页。 | 2026-10-04 18:17 CST | | 6 | 领体力(`claim_stamina.py`) | 领地 | 首页 → 底部领地 → 食堂 | 发现“一键领取”时领取体力,确认“恭喜获得”弹框和烤肉库存 0/60 后返回首页;无可领体力时跳过。 | 2026-10-04 18:32 CST | | 7 | 领取合作券(`claim_cooperation_ticket.py`) | 领地 | 首页 → 底部领地 → 合作模式 → 左侧合作模式 | 左上显示“可领取”时领取合作券,确认奖励和“已领完”后返回首页;已领完时跳过;不会进入保卫宠物。 | 2026-10-04 20:25 CST | +| 8 | 获取竞技积分(`claim_arena_points.py`) | 领地 | 首页 → 底部领地 → 巅峰决斗 → 左侧竞技场 → 左下积分累积区域 | 领取当前累积的竞技积分,确认“恭喜获得”弹框后返回首页;不会进入右侧巅峰决斗。 | 2026-10-04 20:56 CST | diff --git a/tests/test_arena.py b/tests/test_arena.py new file mode 100644 index 0000000..ffe0b2e --- /dev/null +++ b/tests/test_arena.py @@ -0,0 +1,53 @@ +import unittest +from pathlib import Path + +from PIL import Image + +from automation.arena import ArenaRunner +from automation.engine import Matcher, Region, load_flow + +ROOT = Path(__file__).resolve().parent.parent + + +class ArenaDesktop: + def __init__(self, images): + self.images = images + self.index = 0 + self.clicks = [] + + def screenshot(self): + return self.images[min(self.index, len(self.images) - 1)] + + def click(self, point, region, size, hold=0.2): + self.clicks.append((point, hold)) + self.index += 1 + + +class ArenaTests(unittest.TestCase): + def setUp(self): + self.flow = load_flow(ROOT / 'workflows/arena_points.json') + self.matcher = Matcher(ROOT / 'assets') + self.region = Region(0, 0, 562, 1002) + + def image(self, state): + image = Image.new('RGB', (562, 1002), (62, 52, 35)) + for spec in self.flow['states'][state]: + template = Image.open(ROOT / 'assets' / spec['template']).convert('RGB') + x, y, _, _ = spec['search'] + image.paste(template, (x + 5, y + 5)) + return image + + def runner(self, states): + desktop = ArenaDesktop([self.image(state) for state in states]) + return desktop, ArenaRunner(desktop, self.matcher, self.flow, + lambda _: self.region, log=lambda _: None, timeout=0.8) + + def test_claims_points_and_returns_home(self): + desktop, runner = self.runner(['home', 'territory', 'selection', 'arena_page', 'reward', 'arena_page', 'territory', 'home']) + self.assertEqual(runner.run(), 'claimed') + expected = ('territory', 'peak_duel', 'arena', 'claim_points', + 'dismiss_reward', 'arena_back', 'territory_back') + self.assertEqual([point for point, _ in desktop.clicks], [self.flow['clicks'][name] for name in expected]) + +if __name__ == '__main__': + unittest.main() diff --git a/workflows/arena_points.json b/workflows/arena_points.json new file mode 100644 index 0000000..901382e --- /dev/null +++ b/workflows/arena_points.json @@ -0,0 +1,22 @@ +{ + "name": "巅峰决斗竞技场积分领取", + "states": { + "home": [{"template": "home.png", "search": [150, 730, 410, 865]}], + "territory": [{"template": "territory_rat_house.png", "search": [15, 440, 220, 570]}], + "selection": [{"template": "arena_select_title.png", "search": [145, 85, 415, 205]}], + "arena_page": [{"template": "arena_page_title.png", "search": [175, 0, 390, 100]}], + "reward": [ + {"template": "arena_reward.png", "search": [120, 180, 440, 390], "threshold": 0.8}, + {"template": "arena_dismiss.png", "search": [145, 600, 420, 730]} + ] + }, + "clicks": { + "territory": [0.68, 0.94], + "peak_duel": [0.27, 0.35], + "arena": [0.27, 0.47], + "claim_points": [0.18, 0.82], + "dismiss_reward": [0.50, 0.67], + "arena_back": [0.12, 0.94], + "territory_back": [0.68, 0.94] + } +}