feat: add sweep and task reward automation
This commit is contained in:
1 parent
8479cf4981
commit
44d9a617d1
21 files changed
+516
-8
No files matched your search
@@ -51,6 +51,9 @@ class Matcher:
|
||||
return np.asarray(crop.convert('RGB').resize((562, 1002), Image.Resampling.LANCZOS))
|
||||
|
||||
def score(self, frame, spec):
|
||||
return self.locate(frame, spec)[0]
|
||||
|
||||
def locate(self, frame, spec):
|
||||
name = spec['template']
|
||||
if name not in self.cache:
|
||||
p = self.assets / name
|
||||
@@ -70,8 +73,10 @@ class Matcher:
|
||||
# for the active home page. Also require similar absolute colors.
|
||||
difference = float(np.abs(candidate.astype(float) - template.astype(float)).mean())
|
||||
if difference > spec.get('max_color_difference', 28):
|
||||
return 0.0
|
||||
return float(score) if math.isfinite(score) else 0.0
|
||||
return 0.0, None
|
||||
if not math.isfinite(score):
|
||||
return 0.0, None
|
||||
return float(score), (x1 + x, y1 + y, w, h)
|
||||
|
||||
def matches(self, frame, specs):
|
||||
return all(self.score(frame, s) >= s.get('threshold', 0.86) for s in specs)
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
"""首页小金猪快速扫荡流程。"""
|
||||
import time
|
||||
|
||||
from .engine import WorkflowError
|
||||
|
||||
|
||||
class SweepRunner:
|
||||
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, hold=0.2):
|
||||
_, screenshot, region = observation
|
||||
if self.region_provider(screenshot) != region:
|
||||
raise WorkflowError('识别后游戏窗口移动,已停止,请重新运行。')
|
||||
self.desktop.click(self.flow['clicks'][action], region, screenshot.size, hold=hold)
|
||||
self.log('已执行:' + action)
|
||||
|
||||
def run(self, times=1, dry_run=False):
|
||||
if not 1 <= times <= 20:
|
||||
raise WorkflowError('扫荡次数必须在 1 到 20 之间。')
|
||||
home = self.wait('home')
|
||||
if dry_run:
|
||||
self.log('首页识别通过。计划:小金猪 → 快速扫荡 %s 次 → 每次关闭奖励 → 返回首页。未点击。' % times)
|
||||
return 'preview'
|
||||
|
||||
self.click('open_pig', home)
|
||||
for current in range(1, times + 1):
|
||||
idle = self.wait('sweep_available')
|
||||
self.click('sweep', idle, hold=0.8)
|
||||
reward = self.wait('reward')
|
||||
self.log('第 %s/%s 次扫荡已确认奖励弹框。' % (current, times))
|
||||
self.click('dismiss_reward', reward)
|
||||
if current < times:
|
||||
self.log('等待 %s 秒后进行下一次扫荡。' % self.flow['interval_seconds'])
|
||||
time.sleep(self.flow['interval_seconds'])
|
||||
|
||||
self.click('close', self.wait('sweep_available'))
|
||||
self.wait('home')
|
||||
self.log('快速扫荡完成,已确认返回首页。')
|
||||
return 'completed'
|
||||
@@ -0,0 +1,102 @@
|
||||
"""首页任务奖励领取流程。"""
|
||||
import time
|
||||
|
||||
from .engine import WorkflowError
|
||||
|
||||
|
||||
class TaskRunner:
|
||||
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 observe(self):
|
||||
screenshot = self.desktop.screenshot()
|
||||
region = self.region_provider(screenshot)
|
||||
return screenshot, region, self.matcher.frame(screenshot, region)
|
||||
|
||||
def wait(self, *names):
|
||||
deadline = time.monotonic() + self.timeout
|
||||
previous, stable = None, 0
|
||||
while time.monotonic() < deadline:
|
||||
screenshot, region, frame = self.observe()
|
||||
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, frame
|
||||
time.sleep(0.25)
|
||||
raise WorkflowError('未识别到预期页面 ' + '/'.join(names) + ',已停止。')
|
||||
|
||||
def click(self, point, screenshot, region, hold=0.2):
|
||||
if self.region_provider(screenshot) != region:
|
||||
raise WorkflowError('识别后游戏窗口移动,已停止,请重新运行。')
|
||||
self.desktop.click(point, region, screenshot.size, hold=hold)
|
||||
|
||||
def click_action(self, action, observation):
|
||||
_, screenshot, region, _ = observation
|
||||
self.click(self.flow['clicks'][action], screenshot, region)
|
||||
self.log('已执行:' + action)
|
||||
|
||||
def claim_location(self, frame):
|
||||
spec = self.flow['claim_button']
|
||||
score, location = self.matcher.locate(frame, spec)
|
||||
if score < spec.get('threshold', 0.86):
|
||||
return None
|
||||
x, y, width, height = location
|
||||
return ((x + width / 2) / 562, (y + height / 2) / 1002)
|
||||
|
||||
def panel_dot(self, frame, panel):
|
||||
spec = self.flow['tab_dots'][panel]
|
||||
return self.matcher.score(frame, spec) >= spec.get('threshold', 0.86)
|
||||
|
||||
def claim_panel(self, panel):
|
||||
count = 0
|
||||
while count < self.flow['max_claims_per_panel']:
|
||||
name, screenshot, region, frame = self.wait(panel)
|
||||
location = self.claim_location(frame)
|
||||
if location is None:
|
||||
return count
|
||||
self.click(location, screenshot, region, hold=0.8)
|
||||
count += 1
|
||||
self.log('已执行:%s 第 %s 个领取按钮。' % (panel, count))
|
||||
reward = self.wait('reward')
|
||||
self.click_action('dismiss_reward', reward)
|
||||
raise WorkflowError('单个面板领取次数超过安全上限,已停止。')
|
||||
|
||||
def close_and_home(self):
|
||||
panel = self.wait('daily', 'weekly')
|
||||
self.click_action('close', panel)
|
||||
self.wait('home')
|
||||
self.log('已确认返回首页。')
|
||||
|
||||
def run(self, dry_run=False):
|
||||
home = self.wait('home')
|
||||
if dry_run:
|
||||
self.log('首页识别通过。计划:检测任务红点 → 日常/周常领取 → 关闭任务 → 返回首页。未点击。')
|
||||
return 'preview'
|
||||
|
||||
_, screenshot, region, frame = home
|
||||
if not self.matcher.matches(frame, self.flow['task_dot']):
|
||||
self.log('任务按钮没有红点,本次不进入任务面板。')
|
||||
return 'no_task_dot'
|
||||
self.click_action('open_tasks', home)
|
||||
panel = self.wait('daily', 'weekly')
|
||||
current = panel[0]
|
||||
claimed = self.claim_panel(current)
|
||||
self.log('%s 面板领取 %s 个奖励。' % (current, claimed))
|
||||
|
||||
other = 'weekly' if current == 'daily' else 'daily'
|
||||
_, screenshot, region, frame = self.wait(current)
|
||||
if self.panel_dot(frame, other):
|
||||
self.click_action('open_' + other, (current, screenshot, region, frame))
|
||||
claimed += self.claim_panel(other)
|
||||
self.log('%s 面板领取完成。' % other)
|
||||
else:
|
||||
self.log('%s 标签没有红点,不切换。' % other)
|
||||
self.close_and_home()
|
||||
return 'claimed' if claimed else 'no_claim_button'
|
||||
Reference in new issue
Block a user