Skip to content

Commit

Permalink
Merge branch 'release/2.0.0' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
Konano committed Feb 24, 2022
2 parents 6dc556c + 45bdbd2 commit b3e3940
Show file tree
Hide file tree
Showing 14 changed files with 196 additions and 101 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ arknights-mower operation 1-7 99 -R5
arknights-mower operation GT-1 99 -r5 -R5
# 重复刷 GT-1 关卡 99 次,使用理智药以及源石自动回复理智,最多消耗 5 瓶理智药和 5 颗源石
arknights-mower recruit 因陀罗 火神
# 公招自动化,优先选择保底星数高的组合,若有多种标签组合保底星数一致则优先选择包含优先级高的干员的组合,公招干员的优先级从高到低分别是因陀罗和火神
# 公招自动化,优先选择保底星数高的组合,若有多种标签组合保底星数一致则优先选择包含优先级高的干员的组合,公招干员的优先级从高到低分别是因陀罗和火神,默认为高稀有度优先
arknights-mower shop 招聘许可 赤金 龙门币
# 在商场使用信用点消费,购买物品的优先级从高到低分别是招聘许可、赤金和龙门币,其余物品不购买
arknights-mower base -c -d33
Expand Down
2 changes: 1 addition & 1 deletion arknights_mower/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
__cli__ = not (__pyinstall__ and not sys.argv[1:])

__system__ = platform.system().lower()
__version__ = '2.0.0a9'
__version__ = '2.0.0'
4 changes: 3 additions & 1 deletion arknights_mower/data/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,5 +222,7 @@
'令',
'老鲤',
'夜半',
'寒芒克洛丝'
'寒芒克洛丝',
'澄闪',
'夏栎'
]
7 changes: 5 additions & 2 deletions arknights_mower/data/recruit.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# recruit database

# TODO: check/update from gamedata

recruit_database = [
('Lancet-2', 1, ['医疗干员', '远程位', '治疗', '支援机械']),
('Castle-3', 1, ['近卫干员', '近战位', '支援', '支援机械']),
Expand Down Expand Up @@ -35,7 +37,6 @@
('砾', 4, ['特种干员', '近战位', '快速复活', '防护']),
('暗索', 4, ['特种干员', '近战位', '位移']),
('末药', 4, ['医疗干员', '远程位', '治疗']),
('嘉维尔', 4, ['医疗干员', '远程位', '治疗']),
('调香师', 4, ['医疗干员', '远程位', '治疗']),
('角峰', 4, ['重装干员', '近战位', '防护']),
('蛇屠箱', 4, ['重装干员', '近战位', '防护']),
Expand Down Expand Up @@ -104,7 +105,9 @@
('安比尔', 4, ['狙击干员', '远程位', '输出', '减速']),
('阿', 6, ['特种干员', '远程位', '输出', '支援']),
('吽', 5, ['重装干员', '近战位', '防护', '治疗']),
('正义骑士号', 1, ['狙击干员', '远程位', '支援']),
('正义骑士号', 1, ['狙击干员', '远程位', '支援', '支援机械']),
('刻俄柏', 6, ['术师干员', '远程位', '输出', '控场']),
('惊蛰', 5, ['术师干员', '远程位', '输出']),
]

recruit_tag = ['资深干员', '高级资深干员']
Expand Down
2 changes: 0 additions & 2 deletions arknights_mower/ocr/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ def fix(s):
"""
s = re.sub(r'[。?!,、;:“”‘’()《》〈〉【】『』「」﹃﹄〔〕…~﹏¥-_]', '', s)
s = re.sub(r'[\'\"\,\.\(\)]', '', s)
# TODO 去除符号
# TODO 如果包含汉字,则前面一定是汉字
if s in ocr_error.keys():
logger.debug(f'fix with ocr_error: {s} -> {ocr_error[s]}')
s = ocr_error[s]
Expand Down
10 changes: 7 additions & 3 deletions arknights_mower/solvers/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,15 +406,15 @@ def choose_zone_supple(self, zone: list, scope: tp.Scope) -> None:
def choose_zone_resource(self, zone: list) -> None:
""" 识别资源收集区域 """
ocr = ocrhandle.predict(self.recog.img)
unable = list(filter(lambda x: x[1] in ['不可进入', '关卡尚未开放'], ocr))
unable = list(filter(lambda x: x[1] in ['不可进入', '本日16:00开启'], ocr))
ocr = list(filter(lambda x: x[1] in weekly_zones, ocr))
weekly = sorted([x[1] for x in ocr])
while zone[0] not in weekly:
_weekly = weekly
self.swipe((self.recog.w // 4, self.recog.h // 4),
(self.recog.w // 16, 0))
ocr = ocrhandle.predict(self.recog.img)
unable = list(filter(lambda x: x[1] in ['不可进入', '关卡尚未开放'], ocr))
unable = list(filter(lambda x: x[1] in ['不可进入', '本日16:00开启'], ocr))
ocr = list(filter(lambda x: x[1] in weekly_zones, ocr))
weekly = sorted([x[1] for x in ocr])
if _weekly == weekly:
Expand All @@ -424,7 +424,7 @@ def choose_zone_resource(self, zone: list) -> None:
self.swipe((self.recog.w // 4, self.recog.h // 4),
(-self.recog.w // 16, 0))
ocr = ocrhandle.predict(self.recog.img)
unable = list(filter(lambda x: x[1] in ['不可进入', '关卡尚未开放'], ocr))
unable = list(filter(lambda x: x[1] in ['不可进入', '本日16:00开启'], ocr))
ocr = list(filter(lambda x: x[1] in weekly_zones, ocr))
weekly = sorted([x[1] for x in ocr])
if _weekly == weekly:
Expand All @@ -437,4 +437,8 @@ def choose_zone_resource(self, zone: list) -> None:
if x[2][0][0] < item[2][0][0] < x[2][1][0]:
raise LevelUnopenError
self.tap(x[2])
ocr = ocrhandle.predict(self.recog.img)
unable = list(filter(lambda x: x[1] == '关卡尚未开放', ocr))
if len(unable):
raise LevelUnopenError
break
178 changes: 127 additions & 51 deletions arknights_mower/solvers/recruit.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,20 @@ class RecruitPoss(object):
""" 记录公招标签组合的可能性数据 """

def __init__(self, choose: int, max: int = 0, min: int = 7) -> None:
self.choose = choose # 标签选择(按位)
self.choose = choose # 标签选择(按位),第 6 个标志位表示是否选满招募时限,0 为选满,1 为选 03:50
self.max = max # 等级上限
self.min = min # 等级下限
self.poss = 0 # 可能性
self.ls = [] # 可能的干员列表

def __lt__(self, another: RecruitPoss) -> bool:
return (self.max, self.min) < (another.max, another.min)
return (self.poss) < (another.poss)

def __str__(self) -> str:
return "%s,%s,%s,%s" % (self.choose, self.max, self.min, self.ls)
return "%s,%s,%s,%s,%s" % (self.choose, self.max, self.min, self.poss, self.ls)

def __repr__(self) -> str:
return "%s,%s,%s,%s,%s" % (self.choose, self.max, self.min, self.poss, self.ls)


class RecruitSolver(BaseSolver):
Expand Down Expand Up @@ -105,15 +109,12 @@ def recruit_tags(self) -> bool:

# choose tags
choose, best = self.tags_choose(tags, self.priority)
if best.max < 4 and best.min < 7:
if best.choose < (1 << 5) and best.min <= 3:
# refresh
if self.tap_element('recruit_refresh', detected=True):
self.tap_element('double_confirm', 0.8,
interval=3, judge=False)
continue
# when the best result is Lv3, no tag is selected
if best.max <= 3:
choose = []
break
logger.info(f'选择:{choose}')

Expand All @@ -123,10 +124,13 @@ def recruit_tags(self) -> bool:
if (color[2] < 100) != (x[1] not in choose):
self.device.tap((left+x[2][0][0]-5, up+x[2][0][1]-5))

# if best.min < 7, conduct 9 hours of recruitment
# best.min == 7 当且仅当目标为公招小车
if best.min < 7:
if best.choose < (1 << 5):
# 09:00
self.tap_element('one_hour', 0.2, 0.8, 0)
else:
# 03:50
[self.tap_element('one_hour', 0.2, 0.2, 0) for _ in range(2)]
[self.tap_element('one_hour', 0.5, 0.2, 0) for _ in range(5)]

# start recruit
self.tap((avail_level[1][0], budget[0][1]), interval=5)
Expand Down Expand Up @@ -158,61 +162,133 @@ def tags_choose(self, tags: list[str], priority: list[str]) -> tuple[list[str],
""" 公招标签选择核心逻辑 """
if priority is None:
priority = []
if len(priority) and isinstance(priority[0], str):
priority = [[x] for x in priority]
possibility: dict[int, RecruitPoss] = {}
agent_level_dict = {}

# 挨个干员判断可能性
for x in recruit_database:
# 先考虑高级资深干员和小车
agent_name, agent_level, agent_tags = x
agent_level_dict[agent_name] = agent_level

# 高级资深干员需要有特定的 tag
if agent_level == 6 and '高级资深干员' not in tags:
continue
if agent_level < 3 and (agent_level != 1 or agent_name not in priority):
continue

# 统计能够将该干员选出的标签,使用 bitset
valid = 0
if agent_level == 6 and '高级资深干员' in tags:
valid |= (1 << tags.index('高级资深干员'))
if agent_level == 5 and '资深干员' in tags:
valid |= (1 << tags.index('资深干员'))
for tag in agent_tags:
if tag in tags:
valid |= (1 << tags.index(tag))
# 统计 9 小时公招的可能性
valid_9 = None
if 3 <= agent_level <= 6:
valid_9 = 0
if agent_level == 6 and '高级资深干员' in tags:
valid_9 |= (1 << tags.index('高级资深干员'))
if agent_level == 5 and '资深干员' in tags:
valid_9 |= (1 << tags.index('资深干员'))
for tag in agent_tags:
if tag in tags:
valid_9 |= (1 << tags.index(tag))

# 统计 3 小时公招的可能性
valid_3 = None
if 1 <= agent_level <= 4:
valid_3 = 0
for tag in agent_tags:
if tag in tags:
valid_3 |= (1 << tags.index(tag))

# 枚举所有可能的标签组合子集
for o in range(1, 1 << 5):
if o & valid == o:
for o in range(1 << 5):
if valid_9 is not None and o & valid_9 == o:
if o not in possibility.keys():
possibility[o] = RecruitPoss(o)
# 优先度量化计算
weight = agent_level
if agent_name in priority:
weight += 0.9 * \
(1 - priority.index(agent_name) / len(priority))
# 更新可能性
possibility[o].max = max(possibility[o].max, weight)
if agent_level != 1:
# 如果是公招小车则不更新等级下限
possibility[o].min = min(possibility[o].min, weight)
possibility[o].ls.append(agent_name)
possibility[o].max = max(possibility[o].max, agent_level)
possibility[o].min = min(possibility[o].min, agent_level)
_o = o + (1 << 5)
if valid_3 is not None and o & valid_3 == o:
if _o not in possibility.keys():
possibility[_o] = RecruitPoss(_o)
possibility[_o].ls.append(agent_name)
possibility[_o].max = max(possibility[_o].max, agent_level)
possibility[_o].min = min(possibility[_o].min, agent_level)

# 检查是否存在无法从公开招募中获得的干员
for considering in priority:
for x in considering:
if agent_level_dict.get(x) is None:
logger.error(f'该干员并不能在公开招募中获得:{x}')
raise RuntimeError

best = RecruitPoss(0)

# 按照优先级判断,必定选中同一星级干员
# 附加限制:min_level = agent_level
if best.poss == 0:
logger.debug('choose: priority, min_level = agent_level')
for considering in priority:
for o in possibility.keys():
possibility[o].poss = 0
for x in considering:
if x in possibility[o].ls:
agent_level = agent_level_dict[x]
if agent_level != 1 and agent_level == possibility[o].min:
possibility[o].poss += 1 / len(possibility[o].ls)
elif agent_level == 1 and agent_level == possibility[o].min == possibility[o].max:
# 必定选中一星干员的特殊逻辑
possibility[o].poss += 1 / len(possibility[o].ls)
if best < possibility[o]:
best = possibility[o]
if best.poss > 0:
break

# 按照优先级判断,必定选中星级 >= 4 的干员
# 附加限制:min_level >= 4
if best.poss == 0:
logger.debug('choose: priority, min_level >= 4')
for considering in priority:
for o in possibility.keys():
possibility[o].poss = 0
if possibility[o].min >= 4:
for x in considering:
if x in possibility[o].ls:
possibility[o].poss += 1 / len(possibility[o].ls)
if best < possibility[o]:
best = possibility[o]
if best.poss > 0:
break

# 按照等级下限判断,必定选中星级 >= 4 的干员
# 附加限制:min_level >= 4
if best.poss == 0:
logger.debug('choose: min_level >= 4')
for o in possibility.keys():
possibility[o].poss = 0
if possibility[o].min >= 4:
possibility[o].poss = possibility[o].min
if best < possibility[o]:
best = possibility[o]

# 按照优先级判断,检查其概率
if best.poss == 0:
logger.debug('choose: priority')
for considering in priority:
for o in possibility.keys():
possibility[o].poss = 0
for x in considering:
if x in possibility[o].ls:
possibility[o].poss += 1 / len(possibility[o].ls)
if best < possibility[o]:
best = possibility[o]
if best.poss > 0:
break

# 小车逻辑选择
level1 = None
for o in possibility.keys():
if possibility[o].min == 7:
level1 = o # 该种标签组合可锁定小车

# 选择最优的公招标签组合
best = RecruitPoss(0, 0, 0)
for o in possibility.keys():
# 虽然有机会高星,但如果出现了低星,则高星可能性很低
while possibility[o].max - 1 >= possibility[o].min:
possibility[o].max -= 1
if best < possibility[o]:
best = possibility[o]
# 如果不能锁定五星,且能锁定小车,则选择小车
if best.max < 5 and level1 is not None:
best = possibility[level1]
# 按照等级下限判断,默认高稀有度优先
if best.poss == 0:
logger.debug('choose: min_level')
for o in possibility.keys():
possibility[o].poss = possibility[o].min
if best < possibility[o]:
best = possibility[o]

logger.debug(f'poss: {possibility}')
logger.debug(f'best: {best}')
Expand Down
21 changes: 12 additions & 9 deletions arknights_mower/solvers/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
from ..utils.yaml import yaml
from .operation import OpeSolver

task_priority = {'base': 0, 'recruit': 1, 'mail': 2, 'credit': 3, 'shop': 4, 'mission': 5, 'operation': 6}
task_priority = {'base': 0, 'recruit': 1, 'mail': 2,
'credit': 3, 'shop': 4, 'mission': 5, 'operation': 6}


class ScheduleLogError(ValueError):
Expand Down Expand Up @@ -89,7 +90,8 @@ def load(self, last_run: str = '', idx: int = 0, pending: bool = False, total: i
if last_run == '':
self.last_run = None
else:
self.last_run = datetime.datetime.strptime(last_run, '%Y-%m-%d %H:%M:%S')
self.last_run = datetime.datetime.strptime(
last_run, '%Y-%m-%d %H:%M:%S')
self.idx = idx
self.pending = pending
self.total = total
Expand Down Expand Up @@ -173,23 +175,25 @@ def __init__(self, device: Device = None, recog: Recognizer = None) -> None:
self.pending_list = PriorityQueue()
self.device = device
self.last_run = None
self.schedule_log_path = Path(config.LOGFILE_PATH).joinpath('schedule.log')
self.schedule_log_path = Path(
config.LOGFILE_PATH).joinpath('schedule.log')

@classmethod
def to_yaml(cls, representer, data):
return representer.represent_mapping('Schedule', {'last_run': data.last_run.strftime('%Y-%m-%d %H:%M:%S'),
'tasks': data.tasks})
'tasks': data.tasks})

def dump_to_disk(self):
with self.schedule_log_path.open('w', encoding='utf8') as f:
yaml.dump(self, f)
logger.info('计划已存档')

def load_from_disk(self, cmd_list = [], matcher: Callable = None) -> bool:
def load_from_disk(self, cmd_list=[], matcher: Callable = None) -> bool:
try:
with self.schedule_log_path.open('r', encoding='utf8') as f:
data = yaml.load(f)
self.last_run = datetime.datetime.strptime(data['last_run'], '%Y-%m-%d %H:%M:%S')
self.last_run = datetime.datetime.strptime(
data['last_run'], '%Y-%m-%d %H:%M:%S')
for task in data['tasks']:
cmd = matcher(task['cmd'], cmd_list)
if cmd is None:
Expand Down Expand Up @@ -246,9 +250,8 @@ def transition(self) -> None:
self.pending_list.push(task)

task = self.pending_list.pop()
if task is not None:
if task.run() is False:
self.pending_list.push(task)
if task is not None and task.run() is False:
self.pending_list.push(task)

self.dump_to_disk()
time.sleep(60)
2 changes: 1 addition & 1 deletion arknights_mower/strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def mission(self) -> None:

def recruit(self, priority: list[str] = None) -> None:
"""
:param priority: list[str], 优先考虑的公招干员,默认为火神和因陀罗
:param priority: list[str], 优先考虑的公招干员,默认为高稀有度优先
"""
RecruitSolver(self.device, self.recog).run(priority)

Expand Down
Loading

0 comments on commit b3e3940

Please sign in to comment.