feat(task): 培育中选择技能卡时优先选择游戏推荐的卡
This commit is contained in:
parent
a1f34e5f5f
commit
2727f08faa
Binary file not shown.
After Width: | Height: | Size: 674 KiB |
|
@ -0,0 +1 @@
|
|||
{"definitions":{"b0283997-7931-476d-a92f-d7569f6ea34c":{"name":"InPurodyuusu.TextRecommend","displayName":"おすすめ","type":"template","annotationId":"b0283997-7931-476d-a92f-d7569f6ea34c","useHintRect":false,"description":"技能卡选择对话框中的推荐卡片"}},"annotations":[{"id":"b0283997-7931-476d-a92f-d7569f6ea34c","type":"rect","data":{"x1":179,"y1":959,"x2":262,"y2":982}}]}
|
|
@ -1,4 +1,6 @@
|
|||
from .toolbar import toolbar_home, toolbar_menu
|
||||
from .commu_event_buttons import CommuEventButtonUI, web2cv, DEFAULT_COLORS
|
||||
from .common import WhiteFilter
|
||||
from .scrollable import Scrollable, ScrollableIterator
|
||||
from .scrollable import Scrollable, ScrollableIterator
|
||||
from . import dialog
|
||||
from . import badge
|
|
@ -0,0 +1,96 @@
|
|||
"""
|
||||
badge 模块,用于关联带附加徽章的 UI。
|
||||
例如:培育中的课程按钮+SP 图标、工作中分配偶像时偶像图标+好调图标
|
||||
"""
|
||||
from typing import Literal, NamedTuple
|
||||
|
||||
from kotonebot.util import Rect
|
||||
|
||||
BadgeCorner = Literal['lt', 'lm', 'lb', 'rt', 'rm', 'rb', 'mt', 'm', 'mb']
|
||||
"""
|
||||
Badge 位置。
|
||||
|
||||
可选 ``['l', 'm', 'r']``(左、中、右) 与 ``['t', 'm', 'b']``(上、中、下) 的组合。
|
||||
"""
|
||||
|
||||
class BadgeResult(NamedTuple):
|
||||
object: Rect
|
||||
badge: Rect | None
|
||||
|
||||
def match(
|
||||
objects: list[Rect],
|
||||
badges: list[Rect],
|
||||
corner: BadgeCorner,
|
||||
threshold_distance: float = float('inf')
|
||||
) -> list[BadgeResult]:
|
||||
"""
|
||||
将对象与徽章匹配,根据指定的角落位置。
|
||||
|
||||
:param objects: 对象矩形列表
|
||||
:param badges: 徽章矩形列表
|
||||
:param corner: 徽章相对于对象的位置,如 'lt'(左上)、'rb'(右下)等
|
||||
:param threshold_distance: 匹配的最大距离阈值,超过此距离的匹配将被忽略
|
||||
:return: 匹配结果列表
|
||||
"""
|
||||
# 将 rect 转换为中心点
|
||||
def center(rect: Rect) -> tuple[int, int]:
|
||||
return rect[0] + rect[2] // 2, rect[1] + rect[3] // 2
|
||||
|
||||
# 判断 badge 是否在 object 的指定角落位置
|
||||
def is_in_corner(obj_rect: Rect, badge_center: tuple[int, int]) -> bool:
|
||||
obj_center = center(obj_rect)
|
||||
x_obj, y_obj = obj_center
|
||||
x_badge, y_badge = badge_center
|
||||
|
||||
# 获取对象的边界
|
||||
obj_left = obj_rect[0]
|
||||
obj_right = obj_rect[0] + obj_rect[2]
|
||||
obj_top = obj_rect[1]
|
||||
obj_bottom = obj_rect[1] + obj_rect[3]
|
||||
|
||||
# 检查水平位置
|
||||
if corner.startswith('l') and x_badge >= x_obj:
|
||||
return False
|
||||
if corner.startswith('r') and x_badge <= x_obj:
|
||||
return False
|
||||
if corner.startswith('m') and (x_badge < obj_left or x_badge > obj_right):
|
||||
# 水平中间位置需要在对象的水平范围内
|
||||
return False
|
||||
|
||||
# 检查垂直位置
|
||||
if corner.endswith('t') and y_badge >= y_obj:
|
||||
return False
|
||||
if corner.endswith('b') and y_badge <= y_obj:
|
||||
return False
|
||||
if corner.endswith('m') and (y_badge < obj_top or y_badge > obj_bottom):
|
||||
# 垂直中间位置需要在对象的垂直范围内
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
results = []
|
||||
available_badges = badges.copy()
|
||||
|
||||
for obj_rect in objects:
|
||||
obj_center = center(obj_rect)
|
||||
target_badge = None
|
||||
min_dist = float('inf')
|
||||
target_index = -1
|
||||
|
||||
# 查找最近的符合条件的徽章
|
||||
for i, badge_rect in enumerate(available_badges):
|
||||
badge_center = center(badge_rect)
|
||||
if is_in_corner(obj_rect, badge_center):
|
||||
dist = ((badge_center[0] - obj_center[0]) ** 2 + (badge_center[1] - obj_center[1]) ** 2) ** 0.5
|
||||
if dist < min_dist and dist <= threshold_distance:
|
||||
min_dist = dist
|
||||
target_badge = badge_rect
|
||||
target_index = i
|
||||
|
||||
# 如果找到匹配的徽章,从可用徽章列表中移除
|
||||
if target_badge is not None:
|
||||
available_badges.pop(target_index)
|
||||
|
||||
results.append(BadgeResult(obj_rect, target_badge))
|
||||
|
||||
return results
|
|
@ -14,7 +14,7 @@ from .p_drink import acquire_p_drink
|
|||
from kotonebot.util import measure_time
|
||||
from kotonebot.tasks.common import conf
|
||||
from kotonebot.tasks.actions.loading import loading
|
||||
from kotonebot.tasks.game_ui import CommuEventButtonUI, dialog
|
||||
from kotonebot.tasks.game_ui import CommuEventButtonUI, dialog, badge
|
||||
from kotonebot.tasks.actions.commu import handle_unread_commu
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
@ -29,6 +29,7 @@ def acquire_skill_card():
|
|||
it = Interval()
|
||||
cards = None
|
||||
card_clicked = False
|
||||
target_card = None
|
||||
|
||||
while True:
|
||||
device.screenshot()
|
||||
|
@ -50,10 +51,26 @@ def acquire_skill_card():
|
|||
return
|
||||
cards = sorted(cards, key=lambda x: (x.position[0], x.position[1]))
|
||||
logger.info(f"Found {len(cards)} skill cards")
|
||||
# 判断是否有推荐卡
|
||||
rec_badges = image.find_all(R.InPurodyuusu.TextRecommend)
|
||||
rec_badges = [card.rect for card in rec_badges]
|
||||
if rec_badges:
|
||||
cards = [card.rect for card in cards]
|
||||
matches = badge.match(cards, rec_badges, 'mb')
|
||||
logger.debug("Recommend card badge matches: %s", matches)
|
||||
# 选第一个推荐卡
|
||||
target_match = next(filter(lambda m: m.badge is not None, matches), None)
|
||||
if target_match:
|
||||
target_card = target_match.object
|
||||
else:
|
||||
target_card = cards[0]
|
||||
else:
|
||||
logger.debug("No recommend badge found. Pick first card.")
|
||||
target_card = cards[0].rect
|
||||
continue
|
||||
if not card_clicked:
|
||||
logger.debug("Click first skill card")
|
||||
device.click(cards[0].rect)
|
||||
if not card_clicked and target_card is not None:
|
||||
logger.debug("Click target skill card")
|
||||
device.click(target_card)
|
||||
card_clicked = True
|
||||
sleep(0.2)
|
||||
continue
|
||||
|
|
|
@ -569,7 +569,7 @@ def week_final_exam():
|
|||
exam('final')
|
||||
produce_end()
|
||||
|
||||
@action('执行 Regular 培育')
|
||||
@action('执行 Regular 培育', screenshot_mode='manual-inherit')
|
||||
def hajime_regular(week: int = -1, start_from: int = 1):
|
||||
"""
|
||||
「初」 Regular 模式
|
||||
|
@ -602,7 +602,7 @@ def hajime_regular(week: int = -1, start_from: int = 1):
|
|||
logger.info("Week %d started.", i + start_from)
|
||||
w()
|
||||
|
||||
@action('执行 PRO 培育')
|
||||
@action('执行 PRO 培育', screenshot_mode='manual-inherit')
|
||||
def hajime_pro(week: int = -1, start_from: int = 1):
|
||||
"""
|
||||
「初」 PRO 模式
|
||||
|
@ -636,7 +636,7 @@ def hajime_pro(week: int = -1, start_from: int = 1):
|
|||
logger.info("Week %d started.", i + start_from)
|
||||
w()
|
||||
|
||||
@action("执行 MASTER 培育")
|
||||
@action("执行 MASTER 培育", screenshot_mode='manual-inherit')
|
||||
def hajime_master(week: int = -1, start_from: int = 1):
|
||||
"""
|
||||
「初」 MASTER 模式
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
from unittest import TestCase
|
||||
from kotonebot.tasks.game_ui.badge import match, BadgeResult
|
||||
from kotonebot.util import Rect
|
||||
|
||||
def rect_from_center(x: int, y: int) -> Rect:
|
||||
w, h = 20, 20
|
||||
return x - w // 2, y - h // 2, w, h
|
||||
|
||||
class TestBadge(TestCase):
|
||||
def test_match(self):
|
||||
# 测试数据
|
||||
# https://www.desmos.com/calculator/dsynum9p4i
|
||||
objects = [
|
||||
rect_from_center(125, 125),
|
||||
rect_from_center(230, 230),
|
||||
rect_from_center(320, 120),
|
||||
]
|
||||
badges = [
|
||||
# 左上角徽章
|
||||
rect_from_center(90, 160), # 对应 objects[0] 的左上
|
||||
# 右下角徽章
|
||||
rect_from_center(260, 200), # 对应 objects[1] 的右下
|
||||
# 右上角徽章
|
||||
rect_from_center(340, 130), # 对应 objects[2] 的右上
|
||||
# 不匹配任何对象的徽章
|
||||
rect_from_center(410, 410),
|
||||
]
|
||||
|
||||
# 测试左上角匹配
|
||||
results = match(objects, badges, 'lt', 50)
|
||||
self.assertEqual(len(results), 3)
|
||||
self.assertEqual(results[0].object, objects[0])
|
||||
self.assertEqual(results[0].badge, badges[0])
|
||||
self.assertEqual(results[1].object, objects[1])
|
||||
self.assertIsNone(results[1].badge)
|
||||
self.assertEqual(results[2].object, objects[2])
|
||||
self.assertIsNone(results[2].badge)
|
||||
|
||||
# 测试右下角匹配
|
||||
results = match(objects, badges, 'rb', 50)
|
||||
self.assertEqual(len(results), 3)
|
||||
self.assertEqual(results[0].object, objects[0])
|
||||
self.assertIsNone(results[0].badge)
|
||||
self.assertEqual(results[1].object, objects[1])
|
||||
self.assertEqual(results[1].badge, badges[1])
|
||||
self.assertEqual(results[2].object, objects[2])
|
||||
self.assertIsNone(results[2].badge)
|
||||
|
||||
# 测试右上角匹配
|
||||
results = match(objects, badges, 'rt', 50)
|
||||
self.assertEqual(len(results), 3)
|
||||
self.assertEqual(results[0].object, objects[0])
|
||||
self.assertIsNone(results[0].badge)
|
||||
self.assertEqual(results[1].object, objects[1])
|
||||
self.assertIsNone(results[1].badge)
|
||||
self.assertEqual(results[2].object, objects[2])
|
||||
self.assertEqual(results[2].badge, badges[2])
|
||||
|
||||
# 测试没有匹配的情况
|
||||
results = match(objects, [], 'lt', 50)
|
||||
self.assertEqual(len(results), 3)
|
||||
for result in results:
|
||||
self.assertIsNone(result.badge)
|
||||
|
||||
# 测试空对象列表
|
||||
results = match([], badges, 'lt', 50)
|
||||
self.assertEqual(len(results), 0)
|
||||
|
||||
# 测试当多个徽章符合条件时,选择最近的一个
|
||||
def test_match_with_multiple_badges(self):
|
||||
# 测试数据
|
||||
# https://www.desmos.com/calculator/pytdqaju4w
|
||||
objects = [rect_from_center(125, 125)]
|
||||
badges = [
|
||||
rect_from_center(90, 90),
|
||||
rect_from_center(80, 80),
|
||||
rect_from_center(70, 70),
|
||||
]
|
||||
|
||||
results = match(objects, badges, 'lb')
|
||||
self.assertEqual(len(results), 1)
|
||||
self.assertEqual(results[0].object, objects[0])
|
||||
self.assertEqual(results[0].badge, badges[0])
|
Loading…
Reference in New Issue