改写战斗中替换group的逻辑
This commit is contained in:
commit
7f89eb0db8
3890 changed files with 82290 additions and 0 deletions
0
mower/solvers/infra/__init__.py
Normal file
0
mower/solvers/infra/__init__.py
Normal file
234
mower/solvers/infra/base_choose.py
Normal file
234
mower/solvers/infra/base_choose.py
Normal file
|
@ -0,0 +1,234 @@
|
|||
from datetime import datetime
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from mower.models import secret_front
|
||||
from mower.solvers.infra.base_mixin import BaseMixin
|
||||
from mower.solvers.infra.enter_room import EnterRoomSolver
|
||||
from mower.solvers.infra.filter import RIIC_Filter
|
||||
from mower.utils import config
|
||||
from mower.utils.character_recognize import (
|
||||
match_portrait,
|
||||
operator_room_select,
|
||||
)
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.image import cropimg
|
||||
from mower.utils.log import logger
|
||||
from mower.utils.scene import Scene
|
||||
from mower.utils.vector import sa, va
|
||||
|
||||
|
||||
class RIIC_ChooseSolver(SceneGraphSolver, BaseMixin):
|
||||
def run(self, room: str, agent_list: list, wait_time: float = 0) -> bool:
|
||||
self.room = room
|
||||
self.agent_list = agent_list
|
||||
self.wait_time = wait_time
|
||||
self.wait_choose = {}
|
||||
self.choosed = []
|
||||
self.cleard = False
|
||||
self.filter = RIIC_Filter()
|
||||
self.swipe_time = 0
|
||||
self.first_filter = True
|
||||
self.in_adjust = False
|
||||
self.tmp_data = {}
|
||||
self.tmp_checked = {}
|
||||
self.start_time = datetime.now()
|
||||
self.success = False
|
||||
self.check_room()
|
||||
try:
|
||||
return super().run()
|
||||
except ValueError as e:
|
||||
logger.error(e.__traceback__)
|
||||
return False
|
||||
|
||||
def riic_agent_choose(self):
|
||||
if (
|
||||
len(list(set(self.choosed) & set(self.agent_list))) == len(self.agent_list)
|
||||
and len(self.agent_list) > 1
|
||||
):
|
||||
if self.check_agent_order():
|
||||
return True
|
||||
else:
|
||||
return
|
||||
|
||||
if self.first_filter:
|
||||
if self.first_filter:
|
||||
self.filter.run(技能=False)
|
||||
self.first_filter = False
|
||||
choosed_pos = self.get_agent_pos()
|
||||
if choosed_pos:
|
||||
if len(self.agent_list) > 1:
|
||||
self.tap_element("choose_agent/clear", interval=0.1)
|
||||
else:
|
||||
tap_pos = choosed_pos.popitem()[1][0]
|
||||
self.tap(self.tap(va(tap_pos, (100, 100))), interval=0.1)
|
||||
self.first_filter = False
|
||||
return
|
||||
|
||||
if len(self.wait_choose) > 0:
|
||||
logger.debug(f"待选名单{self.wait_choose}")
|
||||
choosed_pos = self.get_agent_pos()
|
||||
for name, pos in list(self.wait_choose.items()):
|
||||
if name in list(choosed_pos):
|
||||
if len(self.agent_list) == 1:
|
||||
return True
|
||||
self.choosed.append(name)
|
||||
del self.wait_choose[name]
|
||||
else:
|
||||
logger.info(f"tap:{name},{pos}")
|
||||
self.tap(va(pos[0], (100, 100)), interval=0.2)
|
||||
logger.debug(f"待选名单{self.wait_choose}")
|
||||
return
|
||||
agents = dict(operator_room_select(config.recog.img))
|
||||
if set(agents.keys()).issubset(self.tmp_data.keys()):
|
||||
self.swipe_time = self.swipe_time + 1
|
||||
if self.swipe_time > 3:
|
||||
raise ValueError("选择干员失败 滑动次数过多")
|
||||
else:
|
||||
self.swipe_time = 0
|
||||
|
||||
self.tmp_data = agents
|
||||
logger.info(agents)
|
||||
intersection = list(set(agents.keys()) & set(self.agent_list))
|
||||
|
||||
if intersection:
|
||||
for i in intersection:
|
||||
if i not in self.choosed:
|
||||
self.wait_choose[i] = agents[i]
|
||||
if self.wait_choose:
|
||||
return
|
||||
else:
|
||||
# 加一个判断最右
|
||||
self.swipe_noinertia((1000, 540), (-1900, 0), interval=0.1)
|
||||
|
||||
def check_agent_order(self):
|
||||
choosed_agents = self.get_agent_pos(no_number=False)
|
||||
logger.info(f"{choosed_agents}{self.agent_list}")
|
||||
if len(choosed_agents) < len(self.agent_list):
|
||||
if self.in_adjust is False:
|
||||
self.filter.run(未进驻=None)
|
||||
return False
|
||||
|
||||
agents = dict(operator_room_select(config.recog.img))
|
||||
for i in self.agent_list:
|
||||
self.tap(va(agents[i][0], (100, 0)), interval=0)
|
||||
# 盲点结束等一下
|
||||
self.sleep()
|
||||
return False
|
||||
|
||||
elif len(choosed_agents) == len(self.agent_list):
|
||||
for i in self.agent_list:
|
||||
if choosed_agents[i][0] != (self.agent_list.index(i)) + 1:
|
||||
self.tap_element("choose_agent/clear", interval=0.5)
|
||||
self.in_adjust = True
|
||||
return False
|
||||
return True
|
||||
|
||||
def check_with_number(self, img, height=60):
|
||||
_, im_w, _ = img.shape
|
||||
img = cropimg(img, ((0, 0), (im_w, 100)))
|
||||
kernel = np.ones((9, 9), np.uint8)
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
|
||||
img = cv2.inRange(img, (0, 0, 200), (225, 15, 255))
|
||||
|
||||
img = cv2.erode(img, kernel)
|
||||
img = cv2.dilate(img, kernel)
|
||||
contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
rect = [cv2.boundingRect(c) for c in contours]
|
||||
rect.sort(key=lambda c: c[0])
|
||||
value = 0
|
||||
for x, y, w, h in rect:
|
||||
digit = cropimg(img, ((x, y), (x + w, y + h)))
|
||||
digit = cv2.copyMakeBorder(
|
||||
digit, 10, 10, 10, 10, cv2.BORDER_CONSTANT, None, (0,)
|
||||
)
|
||||
|
||||
if digit.shape[0] < 65 or digit.shape[1] < 40:
|
||||
continue
|
||||
score = []
|
||||
kernel = np.ones((2, 2), np.uint8)
|
||||
for i in range(10):
|
||||
im = secret_front[i]
|
||||
default_height = 25
|
||||
if height and height != default_height:
|
||||
scale = default_height / height
|
||||
digit_1 = cv2.resize(digit, None, None, scale, scale)
|
||||
|
||||
im = cv2.dilate(im, kernel=kernel)
|
||||
result = cv2.matchTemplate(digit_1, im, cv2.TM_SQDIFF_NORMED)
|
||||
min_val, _, _, _ = cv2.minMaxLoc(result)
|
||||
score.append(min_val)
|
||||
value = value * 10 + score.index(min(score))
|
||||
return value
|
||||
|
||||
def get_agent_pos(self, no_number=True):
|
||||
res = {}
|
||||
img = cv2.cvtColor(config.recog.img, cv2.COLOR_RGB2HSV)
|
||||
img = cv2.inRange(img, (90, 200, 120), (200, 255, 255))
|
||||
contours, _ = cv2.findContours(img, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
|
||||
segments = []
|
||||
for contour in contours:
|
||||
if cv2.contourArea(contour) < 40000 or cv2.contourArea(contour) > 100000:
|
||||
continue
|
||||
x, y, w, h = cv2.boundingRect(contour)
|
||||
segments.append(((x, y + 100), (x + w, y + h - 200)))
|
||||
logger.debug(f"segments:{segments}")
|
||||
|
||||
opear = match_portrait(config.recog.gray, segment=segments)
|
||||
if no_number:
|
||||
return list(dict(opear).keys())
|
||||
if len(self.agent_list) > 1:
|
||||
for i in opear:
|
||||
res[i[0]] = [
|
||||
self.check_with_number(
|
||||
cropimg(config.recog.img, sa([i[1][0], i[1][1]], (0, -100)))
|
||||
),
|
||||
i[1],
|
||||
]
|
||||
else:
|
||||
for i in opear:
|
||||
res[i[0]] = i[1]
|
||||
|
||||
return res
|
||||
|
||||
def check_wait_time(self) -> float:
|
||||
return max(
|
||||
self.wait_time - (datetime.now() - self.start_time).total_seconds(), 0
|
||||
)
|
||||
|
||||
def check_room(self):
|
||||
if (
|
||||
(scene := self.scene()) == Scene.INFRA_DETAILS
|
||||
) and not EnterRoomSolver().detect_room() == self.room:
|
||||
EnterRoomSolver().run(self.room)
|
||||
elif (
|
||||
scene in [Scene.ORDER_LIST, Scene.FACTORY_ROOMS]
|
||||
and not self.detect_room_inside() == self.room
|
||||
):
|
||||
EnterRoomSolver().run(self.room)
|
||||
|
||||
def transition(self) -> bool:
|
||||
if (scene := self.scene()) == Scene.RIIC_OPERATOR_SELECT:
|
||||
if self.riic_agent_choose():
|
||||
self.sleep(self.check_wait_time())
|
||||
self.tap_element("confirm_blue")
|
||||
self.success = True
|
||||
elif self.find("room_detail"):
|
||||
if self.success:
|
||||
return True
|
||||
else:
|
||||
self.tap((1600, 200))
|
||||
elif scene in [Scene.ORDER_LIST, Scene.FACTORY_ROOMS]:
|
||||
if self.success:
|
||||
return True
|
||||
else:
|
||||
self.tap((500, 970))
|
||||
elif scene == Scene.INFRA_ARRANGE_ORDER:
|
||||
self.scene_graph_step(Scene.RIIC_OPERATOR_SELECT)
|
||||
elif scene == Scene.INFRA_ARRANGE_CONFIRM:
|
||||
self.scene_graph_step(Scene.RIIC_OPERATOR_SELECT)
|
||||
elif scene in self.waiting_scene:
|
||||
self.waiting_solver()
|
||||
else:
|
||||
EnterRoomSolver().run(self.room)
|
309
mower/solvers/infra/base_mixin.py
Normal file
309
mower/solvers/infra/base_mixin.py
Normal file
|
@ -0,0 +1,309 @@
|
|||
import lzma
|
||||
import pickle
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from mower import __rootdir__
|
||||
from mower.utils import config, rapidocr
|
||||
from mower.utils.character_recognize import operator_room_select
|
||||
from mower.utils.csleep import MowerExit
|
||||
from mower.utils.image import cropimg, loadres, thres2
|
||||
from mower.utils.log import logger
|
||||
|
||||
with lzma.open(f"{__rootdir__}/models/operator_room.model", "rb") as f:
|
||||
OP_ROOM = pickle.loads(f.read())
|
||||
|
||||
kernel = np.ones((12, 12), np.uint8)
|
||||
|
||||
|
||||
class BaseMixin:
|
||||
def detect_arrange_order(self):
|
||||
name_list = ["工作状态", "技能", "心情", "信赖值"]
|
||||
x_list = (1196, 1320, 1445, 1572)
|
||||
y = 70
|
||||
mask = cv2.inRange(config.recog.hsv, (99, 200, 0), (100, 255, 255))
|
||||
for idx, x in enumerate(x_list):
|
||||
if np.count_nonzero(mask[y : y + 3, x : x + 5]):
|
||||
return (name_list[idx], True)
|
||||
if np.count_nonzero(mask[y + 10 : y + 13, x : x + 5]):
|
||||
return (name_list[idx], False)
|
||||
|
||||
def switch_arrange_order(self, name, ascending=False):
|
||||
name_x = {"工作状态": 1197, "技能": 1322, "心情": 1447, "信赖值": 1575}
|
||||
if isinstance(name, int):
|
||||
name = list(name_x.keys())[name - 1]
|
||||
if isinstance(ascending, str):
|
||||
ascending = ascending == "true"
|
||||
name_y = 60
|
||||
self.tap((name_x[name], name_y), interval=0.5)
|
||||
while True:
|
||||
n, s = self.detect_arrange_order()
|
||||
if n == name and s == ascending:
|
||||
break
|
||||
self.tap((name_x[name], name_y), interval=0.5)
|
||||
|
||||
def scan_agent(self, agent: list[str], error_count=0, max_agent_count=-1):
|
||||
try:
|
||||
# 识别干员
|
||||
while self.find("connecting"):
|
||||
logger.info("等待网络连接")
|
||||
self.sleep()
|
||||
# 返回的顺序是从左往右从上往下
|
||||
ret = operator_room_select(config.recog.img)
|
||||
# 提取识别出来的干员的名字
|
||||
select_name = []
|
||||
for name, scope in ret:
|
||||
if name in agent:
|
||||
select_name.append(name)
|
||||
# self.get_agent_detail((y[1][0]))
|
||||
self.tap(scope, interval=0)
|
||||
agent.remove(name)
|
||||
# 如果是按照个数选择 Free
|
||||
if max_agent_count != -1:
|
||||
if len(select_name) >= max_agent_count:
|
||||
return select_name, ret
|
||||
return select_name, ret
|
||||
except MowerExit:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
error_count += 1
|
||||
if error_count < 3:
|
||||
self.sleep(3)
|
||||
return self.scan_agent(agent, error_count, max_agent_count)
|
||||
else:
|
||||
raise e
|
||||
|
||||
def verify_agent(self, agent: list[str], error_count=0, max_agent_count=-1):
|
||||
try:
|
||||
# 识别干员
|
||||
while self.find("connecting"):
|
||||
logger.info("等待网络连接")
|
||||
self.sleep()
|
||||
ret = operator_room_select(config.recog.img) # 返回的顺序是从左往右从上往下
|
||||
# 提取识别出来的干员的名字
|
||||
index = 0
|
||||
for name, scope in ret:
|
||||
if index >= len(agent):
|
||||
return True
|
||||
if name != agent[index]:
|
||||
return False
|
||||
index += 1
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
error_count += 1
|
||||
self.switch_arrange_order("技能")
|
||||
if error_count < 3:
|
||||
self.sleep(3)
|
||||
return self.verify_agent(agent, error_count, max_agent_count)
|
||||
else:
|
||||
raise e
|
||||
|
||||
def swipe_left(self, right_swipe):
|
||||
if right_swipe > 3:
|
||||
self.detail_filter(控制中枢=True)
|
||||
self.detail_filter(控制中枢=False)
|
||||
else:
|
||||
swipe_time = 2 if right_swipe == 3 else right_swipe
|
||||
for i in range(swipe_time):
|
||||
self.swipe_noinertia((650, 540), (1900, 0))
|
||||
return 0
|
||||
|
||||
def detail_filter(self, **kwargs):
|
||||
if kwargs:
|
||||
text = ",".join(
|
||||
f"{'打开' if value else '关闭'}{label}筛选"
|
||||
for label, value in kwargs.items()
|
||||
)
|
||||
text += ",关闭其余筛选"
|
||||
logger.info(text)
|
||||
else:
|
||||
logger.info("关闭所有筛选")
|
||||
|
||||
labels = [
|
||||
"未进驻",
|
||||
"产出设施",
|
||||
"功能设施",
|
||||
"自定义设施",
|
||||
"控制中枢",
|
||||
"生产类后勤",
|
||||
"功能类后勤",
|
||||
"恢复类后勤",
|
||||
]
|
||||
label_x = (560, 815, 1070, 1330)
|
||||
label_y = (540, 645)
|
||||
|
||||
label_pos = []
|
||||
for y in label_y:
|
||||
for x in label_x:
|
||||
label_pos.append((x, y))
|
||||
|
||||
label_pos_map = dict(zip(labels, label_pos))
|
||||
target_state = dict(zip(labels, [False] * len(labels)))
|
||||
target_state.update(kwargs)
|
||||
|
||||
filter_pos = (config.recog.w * 0.95, config.recog.h * 0.05)
|
||||
self.tap(filter_pos)
|
||||
|
||||
err_cnt = 0
|
||||
while not self.find("arrange_order_options_scene"):
|
||||
self.ctap(filter_pos)
|
||||
err_cnt += 1
|
||||
if err_cnt > 3:
|
||||
raise Exception("未进入筛选页面")
|
||||
|
||||
for label, pos in label_pos_map.items():
|
||||
current_state = self.get_color(pos)[2] > 100
|
||||
if target_state[label] != current_state:
|
||||
self.tap(pos, interval=0.1)
|
||||
|
||||
config.recog.update()
|
||||
confirm_pos = (config.recog.w * 0.8, config.recog.h * 0.8)
|
||||
self.tap(confirm_pos)
|
||||
|
||||
err_cnt = 0
|
||||
while self.find("arrange_order_options_scene"):
|
||||
self.ctap(confirm_pos)
|
||||
err_cnt += 1
|
||||
if err_cnt > 3:
|
||||
raise Exception("筛选确认失败")
|
||||
|
||||
def double_read_time(self, cord, upperLimit=None, use_digit_reader=False):
|
||||
config.recog.update()
|
||||
time_in_seconds = self.read_time(cord, upperLimit, use_digit_reader)
|
||||
if time_in_seconds is None:
|
||||
return datetime.now()
|
||||
execute_time = datetime.now() + timedelta(seconds=(time_in_seconds))
|
||||
return execute_time
|
||||
|
||||
def read_accurate_mood(self, img):
|
||||
try:
|
||||
img = thres2(img, 200)
|
||||
return cv2.countNonZero(img) * 24 / 310
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
return 24
|
||||
|
||||
def detect_product_complete(self) -> str:
|
||||
"""
|
||||
用于识别基建内产物收取信息
|
||||
"""
|
||||
for product in [
|
||||
"赤金",
|
||||
"经验",
|
||||
"龙门币",
|
||||
"源石碎片",
|
||||
"合成玉",
|
||||
"信用",
|
||||
"先锋双芯片",
|
||||
"狙击双芯片",
|
||||
"医疗双芯片",
|
||||
"术师双芯片",
|
||||
"近卫双芯片",
|
||||
"重装双芯片",
|
||||
"辅助双芯片",
|
||||
"特种双芯片",
|
||||
]:
|
||||
if self.find(f"infra_complete/{product}", scope=((1440, 130), (1920, 193))):
|
||||
return product
|
||||
|
||||
def read_operator_in_room(self, img):
|
||||
img = thres2(img, 200)
|
||||
img = cv2.copyMakeBorder(img, 10, 10, 10, 10, cv2.BORDER_CONSTANT, None, (0,))
|
||||
dilation = cv2.dilate(img, kernel, iterations=1)
|
||||
contours, _ = cv2.findContours(dilation, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
rect = map(lambda c: cv2.boundingRect(c), contours)
|
||||
x, y, w, h = sorted(rect, key=lambda c: c[0])[0]
|
||||
img = img[y : y + h, x : x + w]
|
||||
tpl = np.zeros((46, 265), dtype=np.uint8)
|
||||
tpl[: img.shape[0], : img.shape[1]] = img
|
||||
tpl = cv2.copyMakeBorder(tpl, 2, 2, 2, 2, cv2.BORDER_CONSTANT, None, (0,))
|
||||
max_score = 0
|
||||
best_operator = None
|
||||
for operator, template in OP_ROOM.items():
|
||||
result = cv2.matchTemplate(tpl, template, cv2.TM_CCORR_NORMED)
|
||||
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
|
||||
if max_val > max_score:
|
||||
max_score = max_val
|
||||
best_operator = operator
|
||||
return best_operator
|
||||
|
||||
def read_screen(self, img, type="mood", limit=24, cord=None):
|
||||
if cord is not None:
|
||||
img = cropimg(img, cord)
|
||||
if type == "name":
|
||||
img = cropimg(img, ((169, 22), (513, 80)))
|
||||
return self.read_operator_in_room(img)
|
||||
try:
|
||||
ret = rapidocr.engine(img, use_det=False, use_cls=False, use_rec=True)[0]
|
||||
logger.debug(ret)
|
||||
if not ret or not ret[0][0]:
|
||||
raise Exception("识别失败")
|
||||
ret = ret[0][0]
|
||||
if "mood" in type:
|
||||
if (f"/{limit}") in ret:
|
||||
ret = ret.replace(f"/{limit}", "")
|
||||
if len(ret) > 0:
|
||||
if "." in ret:
|
||||
ret = ret.replace(".", "")
|
||||
return int(ret)
|
||||
else:
|
||||
return -1
|
||||
elif "time" in type:
|
||||
if "." in ret:
|
||||
ret = ret.replace(".", ":")
|
||||
return ret.strip()
|
||||
else:
|
||||
return ret
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
return limit + 1
|
||||
|
||||
def read_time(self, cord, upperlimit, error_count=0, use_digit_reader=False):
|
||||
# 刷新图片
|
||||
config.recog.update()
|
||||
try:
|
||||
if use_digit_reader:
|
||||
time_str = self.digit_reader.get_time(config.recog.gray)
|
||||
else:
|
||||
time_str = self.read_screen(config.recog.img, type="time", cord=cord)
|
||||
h, m, s = str(time_str).split(":")
|
||||
if int(m) > 60 or int(s) > 60:
|
||||
raise Exception("读取错误")
|
||||
res = int(h) * 3600 + int(m) * 60 + int(s)
|
||||
if upperlimit is not None and res > upperlimit:
|
||||
raise Exception("超过读取上限")
|
||||
else:
|
||||
return res
|
||||
except Exception:
|
||||
if error_count > 3:
|
||||
logger.exception(f"读取失败{error_count}次超过上限")
|
||||
return None
|
||||
else:
|
||||
logger.exception("读取失败")
|
||||
return self.read_time(
|
||||
cord, upperlimit, error_count + 1, use_digit_reader
|
||||
)
|
||||
|
||||
def detect_room_number(self, img) -> int:
|
||||
score = []
|
||||
for i in range(1, 4):
|
||||
digit = loadres(f"room/{i}")
|
||||
result = cv2.matchTemplate(img, digit, cv2.TM_CCOEFF_NORMED)
|
||||
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
|
||||
score.append(max_val)
|
||||
return score.index(max(score)) + 1
|
||||
|
||||
def detect_room_inside(self) -> str:
|
||||
"""
|
||||
贸易站订单列表或制造站设施列表的房间号
|
||||
"""
|
||||
img = config.recog.img
|
||||
digit_1 = cropimg(img, ((783, 45), (803, 75)))
|
||||
digit_2 = cropimg(img, ((825, 45), (845, 75)))
|
||||
digit_1 = self.detect_room_number(digit_1)
|
||||
digit_2 = self.detect_room_number(digit_2)
|
||||
return f"room_{digit_1}_{digit_2}"
|
22
mower/solvers/infra/clue/__init__.py
Normal file
22
mower/solvers/infra/clue/__init__.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from mower.solvers.shop import CreditShop
|
||||
from mower.utils.log import logger
|
||||
|
||||
from .daily import DailySolver
|
||||
from .get_clue_count import GetClueCountSolver
|
||||
from .give_away import GiveAwaySolver
|
||||
from .party_time import PartyTimeSolver
|
||||
from .place import PlaceSolver
|
||||
from .receive import ReceiveSolver
|
||||
|
||||
|
||||
class ClueSolver:
|
||||
def run(self) -> None:
|
||||
logger.info("基建:线索")
|
||||
DailySolver().run()
|
||||
ReceiveSolver().run()
|
||||
PlaceSolver().run()
|
||||
clue_count = GetClueCountSolver().run()
|
||||
GiveAwaySolver().run(clue_count)
|
||||
party_time = PartyTimeSolver().run()
|
||||
CreditShop().run()
|
||||
return party_time
|
37
mower/solvers/infra/clue/daily.py
Normal file
37
mower/solvers/infra/clue/daily.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
from mower.solvers.infra.enter_room import EnterRoomSolver
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.log import logger
|
||||
from mower.utils.recognize import Scene
|
||||
|
||||
from .utils import clue_cls
|
||||
|
||||
|
||||
class DailySolver(SceneGraphSolver):
|
||||
def run(self) -> None:
|
||||
self.success = False
|
||||
super().run()
|
||||
|
||||
def transition(self) -> bool:
|
||||
if (scene := self.scene()) == Scene.INFRA_DETAILS:
|
||||
self.tap((725, 850))
|
||||
elif scene == Scene.INFRA_CONFIDENTIAL:
|
||||
# 检查是否领过线索
|
||||
daily_scope = ((1815, 200), (1895, 250))
|
||||
if self.find("clue/badge_new", scope=daily_scope) and not self.success:
|
||||
self.tap((1800, 270))
|
||||
else:
|
||||
return True
|
||||
elif scene == Scene.CLUE_DAILY:
|
||||
if not self.find(
|
||||
"clue/icon_notification", scope=((1400, 0), (1920, 400))
|
||||
) and (clue := clue_cls("daily")):
|
||||
logger.info(f"领取今日线索({clue}号)")
|
||||
self.tap_element("clue/button_get")
|
||||
else:
|
||||
# 今日线索已领取,点X退出
|
||||
self.tap((1484, 152))
|
||||
self.success = True
|
||||
elif scene in self.waiting_scene:
|
||||
self.waiting_solver()
|
||||
else:
|
||||
EnterRoomSolver().run("meeting", detail=False)
|
27
mower/solvers/infra/clue/get_clue_count.py
Normal file
27
mower/solvers/infra/clue/get_clue_count.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
from mower.solvers.infra.base_mixin import BaseMixin
|
||||
from mower.solvers.infra.enter_room import EnterRoomSolver
|
||||
from mower.utils import config
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.log import logger
|
||||
from mower.utils.recognize import Scene
|
||||
|
||||
|
||||
class GetClueCountSolver(SceneGraphSolver, BaseMixin):
|
||||
def run(self) -> int:
|
||||
self.res = -1
|
||||
super().run()
|
||||
if self.res >= 0:
|
||||
return self.res
|
||||
raise ValueError("未找到线索数量")
|
||||
|
||||
def transition(self) -> bool:
|
||||
if self.find("arrange_check_in"):
|
||||
self.res = self.read_screen(
|
||||
config.recog.img, limit=10, cord=((645, 977), (755, 1018))
|
||||
)
|
||||
logger.info(f"当前拥有线索数量为{self.res}")
|
||||
return True
|
||||
elif self.scene() == Scene.INFRA_CONFIDENTIAL:
|
||||
self.back()
|
||||
else:
|
||||
EnterRoomSolver().run("meeting", detail=False)
|
72
mower/solvers/infra/clue/give_away.py
Normal file
72
mower/solvers/infra/clue/give_away.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
from mower.solvers.infra.enter_room import EnterRoomSolver
|
||||
from mower.utils import config, rapidocr
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.image import cropimg
|
||||
from mower.utils.log import logger
|
||||
from mower.utils.recognize import Scene
|
||||
from mower.utils.vector import va
|
||||
|
||||
from .utils import clue_cls, clue_scope
|
||||
|
||||
|
||||
class GiveAwaySolver(SceneGraphSolver):
|
||||
def run(self, clue_count) -> None:
|
||||
self.clue_count = clue_count
|
||||
self.friend_clue = []
|
||||
if not (config.conf.leifeng_mode or self.clue_count > 9):
|
||||
return
|
||||
super().run()
|
||||
|
||||
def transition(self) -> bool:
|
||||
if (scene := self.scene()) == Scene.INFRA_DETAILS:
|
||||
self.tap((725, 850))
|
||||
elif scene == Scene.INFRA_CONFIDENTIAL:
|
||||
self.ctap((1799, 578))
|
||||
elif scene == Scene.CLUE_GIVE_AWAY:
|
||||
if (c := clue_cls("give_away")) and (
|
||||
config.conf.leifeng_mode or self.clue_count > 9
|
||||
):
|
||||
if not self.friend_clue:
|
||||
if self.find(
|
||||
"clue/icon_notification", scope=((1400, 0), (1920, 400))
|
||||
):
|
||||
self.sleep()
|
||||
return
|
||||
for i in range(4):
|
||||
label_scope = ((1450, 228 + i * 222), (1580, 278 + i * 222))
|
||||
if not self.find("clue/label_give_away", scope=label_scope):
|
||||
break
|
||||
name_top_left = (870, 127 + 222 * i)
|
||||
name_scope = (name_top_left, va(name_top_left, (383, 62)))
|
||||
name = rapidocr.engine(
|
||||
cropimg(config.recog.gray, name_scope),
|
||||
use_det=True,
|
||||
use_cls=False,
|
||||
use_rec=True,
|
||||
)[0][0][1]
|
||||
if name:
|
||||
name = name.strip()
|
||||
data = {"name": name}
|
||||
for j in range(1, 8):
|
||||
pos = (1230 + j * 64, 142 + i * 222)
|
||||
data[j] = self.get_color(pos)[0] < 137
|
||||
self.friend_clue.append(data)
|
||||
logger.debug(self.friend_clue)
|
||||
friend = None
|
||||
for idx, fc in enumerate(self.friend_clue):
|
||||
if not fc[c]:
|
||||
friend = idx
|
||||
fc[c] = True
|
||||
break
|
||||
friend = friend or 0
|
||||
logger.info(f"给{self.friend_clue[friend]['name']}送一张线索{c}")
|
||||
self.tap(clue_scope["give_away"])
|
||||
self.clue_count -= 1
|
||||
self.tap((1790, 200 + friend * 222))
|
||||
else:
|
||||
self.tap((1868, 54))
|
||||
return True
|
||||
elif scene in self.waiting_scene:
|
||||
self.waiting_solver()
|
||||
else:
|
||||
EnterRoomSolver().run("meeting", detail=False)
|
26
mower/solvers/infra/clue/party_time.py
Normal file
26
mower/solvers/infra/clue/party_time.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
from mower.solvers.infra.base_mixin import BaseMixin
|
||||
from mower.solvers.infra.enter_room import EnterRoomSolver
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.log import logger
|
||||
from mower.utils.recognize import Scene
|
||||
|
||||
|
||||
class PartyTimeSolver(SceneGraphSolver, BaseMixin):
|
||||
def run(self):
|
||||
super().run()
|
||||
return self.party_time
|
||||
|
||||
def transition(self) -> bool:
|
||||
if (scene := self.scene()) == Scene.INFRA_DETAILS:
|
||||
if self.find("clue/title_party", scope=((1600, 190), (1880, 260))):
|
||||
self.party_time = self.double_read_time(((1768, 438), (1902, 480)))
|
||||
logger.info(f"线索交流结束时间:{self.party_time}")
|
||||
return True
|
||||
else:
|
||||
self.party_time = None
|
||||
logger.info("线索交流未开启")
|
||||
return True
|
||||
elif scene == Scene.INFRA_CONFIDENTIAL:
|
||||
self.back()
|
||||
else:
|
||||
EnterRoomSolver().run("meeting", detail=False)
|
163
mower/solvers/infra/clue/place.py
Normal file
163
mower/solvers/infra/clue/place.py
Normal file
|
@ -0,0 +1,163 @@
|
|||
import cv2
|
||||
|
||||
from mower.solvers.infra.enter_room import EnterRoomSolver
|
||||
from mower.utils import config, rapidocr
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.image import cropimg, loadres, thres2
|
||||
from mower.utils.log import logger
|
||||
from mower.utils.recognize import Scene
|
||||
from mower.utils.vector import va
|
||||
|
||||
from .utils import (
|
||||
clue_cls,
|
||||
clue_scope,
|
||||
exit_pos,
|
||||
is_orange,
|
||||
main_dots,
|
||||
main_scope,
|
||||
main_time,
|
||||
tl2p,
|
||||
tm_thres,
|
||||
)
|
||||
|
||||
clue_pos = ((1305, 208), (1305, 503), (1305, 797))
|
||||
filter_receive = (1900, 45)
|
||||
filter_self = (1610, 70)
|
||||
|
||||
|
||||
class PlaceSolver(SceneGraphSolver):
|
||||
def run(self) -> None:
|
||||
self.clue_status = {}
|
||||
super().run()
|
||||
|
||||
def detect_unlock(self):
|
||||
unlock_pos = self.find("clue/button_unlock")
|
||||
if unlock_pos is None:
|
||||
return None
|
||||
color = self.get_color(self.get_pos(unlock_pos))
|
||||
if all(color > [252] * 3):
|
||||
return unlock_pos
|
||||
return None
|
||||
|
||||
def place_index(self):
|
||||
for cl, st in self.clue_status.items():
|
||||
if st in ["available", "self", "available_self_only"]:
|
||||
return cl, st
|
||||
return None, None
|
||||
|
||||
def transition(self) -> bool:
|
||||
if (scene := self.scene()) == Scene.INFRA_DETAILS:
|
||||
self.tap((725, 850))
|
||||
elif scene == Scene.INFRA_CONFIDENTIAL:
|
||||
if unlock_pos := self.detect_unlock():
|
||||
self.tap(unlock_pos)
|
||||
return
|
||||
for i in range(1, 8):
|
||||
if is_orange(self.get_color(main_dots[i])):
|
||||
self.clue_status[i] = "available"
|
||||
elif clue_cls(i):
|
||||
hsv = config.recog.hsv
|
||||
if 160 < hsv[main_time[i][1]][main_time[i][0]][0] < 180:
|
||||
self.clue_status[i] = "friend"
|
||||
else:
|
||||
self.clue_status[i] = "self"
|
||||
else:
|
||||
self.clue_status[i] = None
|
||||
cl, st = self.place_index()
|
||||
if st in ["available", "self", "available_self_only"]:
|
||||
self.tap(main_scope[cl])
|
||||
return
|
||||
else:
|
||||
return True
|
||||
elif scene == Scene.CLUE_PLACE:
|
||||
cl, st = self.place_index()
|
||||
|
||||
if cl is None:
|
||||
if unlock_pos := self.detect_unlock():
|
||||
self.tap(unlock_pos)
|
||||
return
|
||||
else:
|
||||
self.tap(exit_pos)
|
||||
return True
|
||||
if self.get_color((1328 + 77 * cl, 114))[0] < 150:
|
||||
# 右上角 1-7
|
||||
self.tap(clue_scope[cl])
|
||||
return
|
||||
receive = st in ["available", "self"]
|
||||
filter_pos = filter_receive if receive else filter_self
|
||||
if not all(self.get_color(filter_pos) > [252] * 3):
|
||||
self.tap(filter_pos)
|
||||
return
|
||||
self.clue_list = []
|
||||
for cp in clue_pos:
|
||||
clue_img = cropimg(config.recog.img, tl2p(cp))
|
||||
res = loadres(f"clue/{cl}")
|
||||
result = cv2.matchTemplate(clue_img, res, cv2.TM_CCOEFF_NORMED)
|
||||
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
|
||||
if max_val > tm_thres:
|
||||
name_scope = (va(cp, (274, 99)), va(cp, (580, 134)))
|
||||
name_img = cropimg(config.recog.gray, name_scope)
|
||||
name_img = cv2.copyMakeBorder(
|
||||
name_img, 48, 48, 48, 48, cv2.BORDER_REPLICATE
|
||||
)
|
||||
name = rapidocr.engine(
|
||||
name_img,
|
||||
use_det=True,
|
||||
use_cls=False,
|
||||
use_rec=True,
|
||||
)[0][0][1]
|
||||
if name:
|
||||
name = name.strip()
|
||||
time_scope = (va(cp, (45, 222)), va(cp, (168, 255)))
|
||||
time_hsv = cropimg(config.recog.hsv, time_scope)
|
||||
if 165 < time_hsv[0][0][0] < 175:
|
||||
time_img = thres2(cropimg(config.recog.gray, time_scope), 180)
|
||||
time_img = cv2.copyMakeBorder(
|
||||
time_img, 48, 48, 48, 48, cv2.BORDER_REPLICATE
|
||||
)
|
||||
time = rapidocr.engine(
|
||||
time_img,
|
||||
use_det=True,
|
||||
use_cls=False,
|
||||
use_rec=True,
|
||||
)[0][0][1]
|
||||
if time:
|
||||
time = time.strip()
|
||||
else:
|
||||
time = None
|
||||
self.clue_list.append(
|
||||
{"name": name, "time": time, "scope": tl2p(cp)}
|
||||
)
|
||||
else:
|
||||
break
|
||||
if self.clue_list:
|
||||
list_name = "接收库" if receive else "自有库"
|
||||
logger.info(f"{cl}号线索{list_name}:{self.clue_list}")
|
||||
selected = None
|
||||
for c in self.clue_list:
|
||||
if c["time"]:
|
||||
selected = c
|
||||
break
|
||||
selected = selected or self.clue_list[0]
|
||||
self.tap(selected["scope"])
|
||||
if self.clue_status[cl] == "available":
|
||||
self.clue_status[cl] = "friend"
|
||||
elif self.clue_status[cl] == "available_self_only":
|
||||
self.clue_status[cl] = "self_only"
|
||||
elif self.clue_status[cl] == "self":
|
||||
self.clue_status[cl] = "friend"
|
||||
else:
|
||||
self.clue_status[cl] = None
|
||||
else:
|
||||
if self.clue_status[cl] == "available":
|
||||
self.clue_status[cl] = "available_self_only"
|
||||
elif self.clue_status[cl] == "available_self_only":
|
||||
self.clue_status[cl] = None
|
||||
elif self.clue_status[cl] == "self":
|
||||
self.clue_status[cl] = "self_only"
|
||||
else:
|
||||
self.clue_status[cl] = None
|
||||
elif scene in self.waiting_scene:
|
||||
self.waiting_solver()
|
||||
else:
|
||||
EnterRoomSolver().run("meeting", detail=False)
|
50
mower/solvers/infra/clue/receive.py
Normal file
50
mower/solvers/infra/clue/receive.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
import cv2
|
||||
|
||||
from mower.solvers.infra.enter_room import EnterRoomSolver
|
||||
from mower.utils import config, rapidocr
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.image import cropimg
|
||||
from mower.utils.log import logger
|
||||
from mower.utils.recognize import Scene
|
||||
|
||||
from .utils import clue_cls, exit_pos
|
||||
|
||||
|
||||
class ReceiveSolver(SceneGraphSolver):
|
||||
def run(self) -> None:
|
||||
super().run()
|
||||
|
||||
def transition(self) -> bool:
|
||||
if (scene := self.scene()) == Scene.INFRA_DETAILS:
|
||||
self.tap((725, 850))
|
||||
elif scene == Scene.INFRA_CONFIDENTIAL:
|
||||
receive_scope = ((1815, 360), (1895, 410))
|
||||
if self.find("clue/badge_new", scope=receive_scope):
|
||||
self.ctap((1800, 430))
|
||||
else:
|
||||
return True
|
||||
elif scene == Scene.CLUE_RECEIVE:
|
||||
if self.find("infra_complete\信用", scope=((1440, 130), (1920, 193))):
|
||||
self.sleep()
|
||||
return
|
||||
if clue := clue_cls("receive"):
|
||||
name_scope = ((1580, 220), (1880, 255))
|
||||
name_img = cropimg(config.recog.gray, name_scope)
|
||||
name_img = cv2.copyMakeBorder(
|
||||
name_img, 48, 48, 48, 48, cv2.BORDER_REPLICATE
|
||||
)
|
||||
name = rapidocr.engine(
|
||||
name_img,
|
||||
use_det=True,
|
||||
use_cls=False,
|
||||
use_rec=True,
|
||||
)[0][0][1]
|
||||
name = name.strip() if name else "好友"
|
||||
logger.info(f"接收{name}的{clue}号线索")
|
||||
self.tap(name_scope)
|
||||
else:
|
||||
self.tap(exit_pos)
|
||||
elif scene in self.waiting_scene:
|
||||
self.waiting_solver()
|
||||
else:
|
||||
EnterRoomSolver().run("meeting", detail=False)
|
60
mower/solvers/infra/clue/utils.py
Normal file
60
mower/solvers/infra/clue/utils.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
import cv2
|
||||
|
||||
from mower.utils import config
|
||||
from mower.utils.image import cropimg, loadres
|
||||
from mower.utils.vector import va
|
||||
|
||||
clue_size = (162, 216)
|
||||
clue_top_left = {
|
||||
"daily": (1118, 334),
|
||||
"receive": (1305, 122),
|
||||
"give_away": (30, 208),
|
||||
# 摆放线索界面,线索框的左上角
|
||||
1: (72, 228),
|
||||
2: (374, 334),
|
||||
3: (679, 198),
|
||||
4: (1003, 265),
|
||||
5: (495, 660),
|
||||
6: (805, 573),
|
||||
7: (154, 608),
|
||||
}
|
||||
dot_offset = (168, -8)
|
||||
main_offset = (425, 0)
|
||||
main_time_offset = (443, 257)
|
||||
|
||||
|
||||
def tl2p(top_left):
|
||||
return top_left, va(top_left, clue_size)
|
||||
|
||||
|
||||
def is_orange(dot):
|
||||
orange_dot = (255, 104, 1)
|
||||
return all([abs(dot[i] - orange_dot[i]) < 3 for i in range(3)])
|
||||
|
||||
|
||||
clue_scope = {}
|
||||
for index, top_left in clue_top_left.items():
|
||||
clue_scope[index] = tl2p(top_left)
|
||||
clue_dots = {}
|
||||
main_dots = {}
|
||||
main_time = {}
|
||||
main_scope = {}
|
||||
for i in range(1, 8):
|
||||
clue_dots[i] = va(clue_top_left[i], dot_offset)
|
||||
main_dots[i] = va(clue_dots[i], main_offset)
|
||||
main_time[i] = va(clue_top_left[i], main_time_offset)
|
||||
main_scope[i] = tl2p(va(clue_top_left[i], main_offset))
|
||||
tm_thres = 0.6
|
||||
exit_pos = (1239, 144)
|
||||
|
||||
|
||||
def clue_cls(scope):
|
||||
scope_dict = clue_scope if isinstance(scope, str) else main_scope
|
||||
img = cropimg(config.recog.img, scope_dict[scope])
|
||||
for i in range(1, 8):
|
||||
res = loadres(f"clue/{i}")
|
||||
result = cv2.matchTemplate(img, res, cv2.TM_CCOEFF_NORMED)
|
||||
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
|
||||
if max_val > tm_thres:
|
||||
return i
|
||||
return None
|
132
mower/solvers/infra/drone.py
Normal file
132
mower/solvers/infra/drone.py
Normal file
|
@ -0,0 +1,132 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
import cv2
|
||||
|
||||
from mower.models import riic_base_digits
|
||||
from mower.solvers.infra.base_mixin import BaseMixin
|
||||
from mower.solvers.infra.enter_room import EnterRoomSolver
|
||||
from mower.utils import config
|
||||
from mower.utils import typealias as tp
|
||||
from mower.utils.digit_reader import DigitReader
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.image import cropimg, thres2
|
||||
from mower.utils.log import logger
|
||||
from mower.utils.recognize import Scene
|
||||
|
||||
|
||||
class DroneSolver(SceneGraphSolver, BaseMixin):
|
||||
def run(
|
||||
self,
|
||||
room: str,
|
||||
count: int = None,
|
||||
all_in: bool = False,
|
||||
cur_count: int = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Args:
|
||||
room: 房间名
|
||||
count:消耗无人机数量
|
||||
all_in:贸易站-加速完成一笔订单,制造站-消耗全部无人机消耗数量
|
||||
cur_count:当前无人机数量
|
||||
"""
|
||||
logger.info("Start:无人机加速")
|
||||
self.room = room
|
||||
while cur_count is None and not all_in:
|
||||
try:
|
||||
cur_count = DigitReader().get_drone(config.recog.gray)
|
||||
except Exception:
|
||||
EnterRoomSolver().run(self.room, detail=False)
|
||||
|
||||
if count is None and not all_in:
|
||||
count = cur_count - config.conf.drone_count_limit
|
||||
if count <= 0:
|
||||
return True
|
||||
|
||||
self.count = count
|
||||
self.all_in = all_in
|
||||
self.success = False
|
||||
if cur_count < self.count and not self.all_in:
|
||||
logger.info("无人机数量不足")
|
||||
return False
|
||||
if (
|
||||
self.scene() in [Scene.ORDER_LIST, Scene.FACTORY_ROOMS]
|
||||
and not self.detect_room_inside() == self.room
|
||||
):
|
||||
EnterRoomSolver().run(self.room, detail=False)
|
||||
self.wait_start()
|
||||
super().run()
|
||||
return True
|
||||
|
||||
def number(self, scope: tp.Scope, height: int, thres: int) -> int:
|
||||
"数字识别"
|
||||
img = cropimg(config.recog.gray, scope)
|
||||
default_height = 25
|
||||
if height != default_height:
|
||||
scale = 25 / height
|
||||
img = cv2.resize(img, None, None, scale, scale)
|
||||
img = thres2(img, thres)
|
||||
contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
rect = [cv2.boundingRect(c) for c in contours]
|
||||
rect.sort(key=lambda c: c[0])
|
||||
|
||||
value = 0
|
||||
|
||||
for x, y, w, h in rect:
|
||||
digit = cropimg(img, ((x, y), (x + w, y + h)))
|
||||
digit = cv2.copyMakeBorder(
|
||||
digit, 10, 10, 10, 10, cv2.BORDER_CONSTANT, None, (0,)
|
||||
)
|
||||
score = []
|
||||
for i in range(10):
|
||||
im = riic_base_digits[i]
|
||||
result = cv2.matchTemplate(digit, im, cv2.TM_SQDIFF_NORMED)
|
||||
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
|
||||
score.append(min_val)
|
||||
value = value * 10 + score.index(min(score))
|
||||
|
||||
return value
|
||||
|
||||
def timeout(self) -> bool:
|
||||
return datetime.now() > self.start_time + timedelta(seconds=20)
|
||||
|
||||
def wait_start(self):
|
||||
self.start_time = datetime.now()
|
||||
|
||||
def transition(self) -> bool:
|
||||
if (scene := self.scene()) == Scene.INFRA_DETAILS:
|
||||
self.ctap((200, 1000), 1, config.screenshot_avg / 1000)
|
||||
elif scene in [Scene.ORDER_LIST, Scene.FACTORY_ROOMS]:
|
||||
self.wait_start()
|
||||
if self.success:
|
||||
return True
|
||||
elif pos := self.find("factory_accelerate"):
|
||||
self.tap(pos)
|
||||
elif pos := self.find("bill_accelerate"):
|
||||
self.tap(pos)
|
||||
|
||||
elif scene == Scene.DRONE_ACCELERATE:
|
||||
if self.all_in:
|
||||
self.tap((1450, 500))
|
||||
self.tap((1450, 900))
|
||||
self.success = True
|
||||
elif self.timeout():
|
||||
logger.info("加速时间超过订单剩余时间")
|
||||
self.tap((1450, 900))
|
||||
self.success = True
|
||||
elif (
|
||||
tap_count := self.count - self.number(((240, 650), (350, 720)), 40, 150)
|
||||
) == 0:
|
||||
self.tap((1450, 900))
|
||||
self.success = True
|
||||
elif tap_count > 0:
|
||||
for _ in range(tap_count):
|
||||
self.tap((1300, 500), interval=0.07)
|
||||
self.sleep(0.2)
|
||||
elif tap_count < 0:
|
||||
for _ in range(-tap_count):
|
||||
self.tap((900, 500), interval=0.07)
|
||||
self.sleep(0.2)
|
||||
elif scene in self.waiting_scene:
|
||||
self.waiting_solver()
|
||||
else:
|
||||
EnterRoomSolver().run(self.room, detail=False)
|
219
mower/solvers/infra/enter_room.py
Normal file
219
mower/solvers/infra/enter_room.py
Normal file
|
@ -0,0 +1,219 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
import cv2
|
||||
|
||||
from mower.utils import config
|
||||
from mower.utils import typealias as tp
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.image import cropimg, loadres
|
||||
from mower.utils.log import logger
|
||||
from mower.utils.recognize import Scene
|
||||
from mower.utils.translate_room import translate_room
|
||||
from mower.utils.vector import sm, va
|
||||
|
||||
facility = {
|
||||
"central": (
|
||||
(-0.6551724137931034, -0.26436781609195403),
|
||||
(2.3524904214559386, 1.1340996168582376),
|
||||
),
|
||||
"dormitory_1": (
|
||||
(-0.6615384615384616, 0.9692307692307693),
|
||||
(1.7538461538461538, 0.5115384615384616),
|
||||
),
|
||||
"dormitory_2": (
|
||||
(-0.046153846153846156, 1.580769230769231),
|
||||
(1.7461538461538462, 0.5115384615384616),
|
||||
),
|
||||
"dormitory_3": (
|
||||
(-0.6615384615384616, 2.1884615384615387),
|
||||
(1.7538461538461538, 0.5192307692307693),
|
||||
),
|
||||
"dormitory_4": (
|
||||
(-0.046153846153846156, 2.7961538461538464),
|
||||
(1.7461538461538462, 0.5230769230769231),
|
||||
),
|
||||
"factory": (
|
||||
(2.689655172413793, 0.9540229885057471),
|
||||
(1.1379310344827585, 0.5172413793103448),
|
||||
),
|
||||
"meeting": (
|
||||
(2.0804597701149423, 0.3486590038314176),
|
||||
(1.747126436781609, 0.5172413793103448),
|
||||
),
|
||||
"contact": (
|
||||
(2.689655172413793, 1.5632183908045976),
|
||||
(1.1379310344827585, 0.5172413793103448),
|
||||
),
|
||||
"room_1_1": (
|
||||
(-4.626923076923077, 0.9692307692307693),
|
||||
(1.126923076923077, 0.5115384615384616),
|
||||
),
|
||||
"room_1_2": (
|
||||
(-3.403846153846154, 0.9692307692307693),
|
||||
(1.123076923076923, 0.5115384615384616),
|
||||
),
|
||||
"room_1_3": (
|
||||
(-2.184615384615385, 0.9692307692307693),
|
||||
(1.1307692307692307, 0.5115384615384616),
|
||||
),
|
||||
"room_2_1": (
|
||||
(-5.2384615384615385, 1.580769230769231),
|
||||
(1.1346153846153846, 0.5115384615384616),
|
||||
),
|
||||
"room_2_2": (
|
||||
(-4.015384615384615, 1.580769230769231),
|
||||
(1.1307692307692307, 0.5115384615384616),
|
||||
),
|
||||
"room_2_3": (
|
||||
(-2.7923076923076926, 1.580769230769231),
|
||||
(1.126923076923077, 0.5115384615384616),
|
||||
),
|
||||
"room_3_1": (
|
||||
(-4.6230769230769235, 2.1884615384615387),
|
||||
(1.123076923076923, 0.5192307692307693),
|
||||
),
|
||||
"room_3_2": (
|
||||
(-3.4000000000000004, 2.1884615384615387),
|
||||
(1.123076923076923, 0.5192307692307693),
|
||||
),
|
||||
"room_3_3": (
|
||||
(-2.180769230769231, 2.1884615384615387),
|
||||
(1.126923076923077, 0.5192307692307693),
|
||||
),
|
||||
"train": (
|
||||
(2.689655172413793, 2.18007662835249),
|
||||
(1.1379310344827585, 0.5172413793103448),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class EnterRoomSolver(SceneGraphSolver):
|
||||
def run(self, room: str, detail: bool = True):
|
||||
"""
|
||||
Args:
|
||||
room: 房间名
|
||||
detail: 打开进驻信息
|
||||
"""
|
||||
logger.info(f"进入房间:{translate_room(room)}")
|
||||
self.room = room
|
||||
self.detail = detail
|
||||
self.wait_start()
|
||||
super().run()
|
||||
|
||||
def timeout(self) -> bool:
|
||||
return datetime.now() > self.start_time + timedelta(seconds=3)
|
||||
|
||||
def wait_start(self):
|
||||
self.start_time = datetime.now()
|
||||
|
||||
def detect_room_number(self, img) -> int:
|
||||
score = []
|
||||
for i in range(1, 5):
|
||||
digit = loadres(f"room/{i}")
|
||||
result = cv2.matchTemplate(img, digit, cv2.TM_CCOEFF_NORMED)
|
||||
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
|
||||
score.append(max_val)
|
||||
return score.index(max(score)) + 1
|
||||
|
||||
def detect_room(self) -> str:
|
||||
color_map = {
|
||||
"制造站": 25,
|
||||
"贸易站": 99,
|
||||
"发电站": 36,
|
||||
"训练室": 178,
|
||||
"加工站": 32,
|
||||
}
|
||||
img = cropimg(config.recog.img, ((568, 18), (957, 95)))
|
||||
hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
|
||||
colored_room = None
|
||||
for room, color in color_map.items():
|
||||
mask = cv2.inRange(hsv, (color - 1, 0, 0), (color + 2, 255, 255))
|
||||
if cv2.countNonZero(mask) > 1000:
|
||||
colored_room = room
|
||||
break
|
||||
if colored_room in ["制造站", "贸易站", "发电站"]:
|
||||
digit_1 = cropimg(img, ((211, 24), (232, 54)))
|
||||
digit_2 = cropimg(img, ((253, 24), (274, 54)))
|
||||
digit_1 = self.detect_room_number(digit_1)
|
||||
digit_2 = self.detect_room_number(digit_2)
|
||||
logger.debug(f"{colored_room}B{digit_1}0{digit_2}")
|
||||
return f"room_{digit_1}_{digit_2}"
|
||||
elif colored_room == "训练室":
|
||||
logger.debug("训练室B305")
|
||||
return "train"
|
||||
elif colored_room == "加工站":
|
||||
logger.debug("加工站B105")
|
||||
return "factory"
|
||||
white_room = ["central", "dormitory", "meeting", "contact"]
|
||||
score = []
|
||||
for room in white_room:
|
||||
tpl = loadres(f"room/{room}")
|
||||
result = cv2.matchTemplate(img, tpl, cv2.TM_CCOEFF_NORMED)
|
||||
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
|
||||
score.append(max_val)
|
||||
room = white_room[score.index(max(score))]
|
||||
if room == "central":
|
||||
logger.debug("控制中枢")
|
||||
elif room == "dormitory":
|
||||
digit = cropimg(img, ((174, 24), (195, 54)))
|
||||
digit = self.detect_room_number(digit)
|
||||
if digit == 4:
|
||||
logger.debug("宿舍B401")
|
||||
else:
|
||||
logger.debug(f"宿舍B{digit}04")
|
||||
return f"dormitory_{digit}"
|
||||
elif room == "meeting":
|
||||
logger.debug("会客室1F02")
|
||||
else:
|
||||
logger.debug("办公室B205")
|
||||
return room
|
||||
|
||||
@staticmethod
|
||||
def segment(central: tp.Scope) -> dict[str, tp.Rectangle]:
|
||||
top_left = central[0]
|
||||
width = central[1][0] - central[0][0]
|
||||
|
||||
result = {}
|
||||
for name, (position, size) in facility.items():
|
||||
facility_top_left = va(top_left, sm(width, position))
|
||||
scope = facility_top_left, va(facility_top_left, sm(width, size))
|
||||
result[name] = (
|
||||
(max(scope[0][0], 0), scope[0][1]),
|
||||
(min(scope[1][0], 1920), scope[1][1]),
|
||||
)
|
||||
|
||||
logger.debug(result)
|
||||
return result
|
||||
|
||||
def transition(self) -> bool:
|
||||
if (scene := self.scene()) == Scene.INFRA_MAIN:
|
||||
score, scope = config.recog.match2d("control_central")
|
||||
if score >= 0.9:
|
||||
pos = self.segment(scope)[self.room]
|
||||
self.ctap(pos, 1, config.screenshot_avg / 1000)
|
||||
self.wait_start()
|
||||
else:
|
||||
config.recog.update()
|
||||
elif scene in [Scene.CTRLCENTER_ASSISTANT, Scene.INFRA_DETAILS]:
|
||||
if self.detect_room() == self.room:
|
||||
if not self.detail:
|
||||
self.tap((960, 540), interval=0)
|
||||
return True
|
||||
if self.find("room_detail"):
|
||||
return True
|
||||
if pos := self.find("arrange_check_in"):
|
||||
if self.find("infra_overview_top_right"):
|
||||
self.ctap(pos, 1, config.screenshot_avg / 1000)
|
||||
else:
|
||||
config.recog.update()
|
||||
elif self.timeout():
|
||||
self.scene_graph_step(Scene.INFRA_MAIN)
|
||||
else:
|
||||
config.recog.update()
|
||||
else:
|
||||
if self.timeout():
|
||||
self.scene_graph_step(Scene.INFRA_MAIN)
|
||||
else:
|
||||
config.recog.update()
|
||||
else:
|
||||
self.scene_graph_step(Scene.INFRA_MAIN)
|
129
mower/solvers/infra/filter.py
Normal file
129
mower/solvers/infra/filter.py
Normal file
|
@ -0,0 +1,129 @@
|
|||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from mower.utils import config
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.log import logger
|
||||
from mower.utils.scene import Scene
|
||||
|
||||
|
||||
class RIIC_Filter(SceneGraphSolver):
|
||||
def __init__(self) -> None:
|
||||
self.labels = [
|
||||
"未进驻",
|
||||
"产出设施",
|
||||
"功能设施",
|
||||
"自定义设施",
|
||||
"控制中枢",
|
||||
"生产类后勤",
|
||||
"功能类后勤",
|
||||
"恢复类后勤",
|
||||
]
|
||||
|
||||
self.orders = [
|
||||
"工作状态",
|
||||
"技能",
|
||||
"心情",
|
||||
"信赖值",
|
||||
]
|
||||
label_x = (560, 815, 1070, 1330)
|
||||
label_y = (540, 645)
|
||||
|
||||
label_pos = []
|
||||
for y in label_y:
|
||||
for x in label_x:
|
||||
label_pos.append((x, y))
|
||||
self.label_pos_map = dict(zip(self.labels, label_pos))
|
||||
|
||||
def run(self, **kwargs):
|
||||
# 筛选标签 + True/False (开关标签) /None(切换状态)
|
||||
# 排序 + True/False 箭头朝(上/下)
|
||||
self.target_state = None
|
||||
self.order_label = None
|
||||
self.labels_chooed_done = False
|
||||
if kwargs:
|
||||
text = ""
|
||||
for self.label, value in kwargs.items():
|
||||
if self.label in self.orders and self.order_label is None:
|
||||
self.order_label = self.label
|
||||
elif self.order_label is not None:
|
||||
raise ValueError("排序参数仅有一个")
|
||||
text.join(f"{'打开' if value else '关闭'}{self.label}筛选")
|
||||
|
||||
text += ",关闭其余筛选"
|
||||
logger.info(text)
|
||||
else:
|
||||
logger.info("关闭所有筛选")
|
||||
|
||||
if self.target_state is None:
|
||||
self.target_state = dict(zip(self.labels, [False] * len(self.labels)))
|
||||
self.target_state.update(kwargs)
|
||||
|
||||
logger.info(f"target_state :{self.target_state}")
|
||||
|
||||
try:
|
||||
super().run()
|
||||
except ValueError as e:
|
||||
logger.error(e.__traceback__)
|
||||
|
||||
def transition(self) -> bool:
|
||||
if (scene := self.scene()) == Scene.RIIC_OPERATOR_SELECT:
|
||||
if self.labels_chooed_done:
|
||||
if self.order_label in self.orders:
|
||||
n, s = self.detect_arrange_order()
|
||||
logger.info(f"detect_arrange_order {n}{s}")
|
||||
logger.info(
|
||||
f"order_label {self.order_label}{self.target_state[self.order_label]}"
|
||||
)
|
||||
if (
|
||||
n != self.order_label
|
||||
or s != self.target_state[self.order_label]
|
||||
):
|
||||
self.switch_arrange_order(
|
||||
self.order_label, self.target_state[self.order_label]
|
||||
)
|
||||
return
|
||||
return True
|
||||
else:
|
||||
self.tap_element("arrange_order_options")
|
||||
elif scene == Scene.INFRA_ARRANGE_ORDER:
|
||||
logger.debug(self.target_state)
|
||||
not_taped = True
|
||||
for label, pos in self.label_pos_map.items():
|
||||
current_state = self.get_color(pos)[2] > 100
|
||||
|
||||
if self.target_state[label] is None:
|
||||
self.target_state[label] = not current_state
|
||||
if self.target_state[label] != current_state:
|
||||
not_taped = False
|
||||
self.tap(pos, interval=0.1)
|
||||
if not_taped:
|
||||
confirm_pos = (config.recog.w * 0.8, config.recog.h * 0.8)
|
||||
self.tap(confirm_pos)
|
||||
self.labels_chooed_done = True
|
||||
else:
|
||||
return
|
||||
elif scene in self.waiting_scene:
|
||||
self.waiting_solver()
|
||||
else:
|
||||
return False
|
||||
|
||||
def detect_arrange_order(self):
|
||||
x_list = (1196, 1320, 1445, 1572)
|
||||
y = 70
|
||||
hsv = cv2.cvtColor(config.recog.img, cv2.COLOR_RGB2HSV)
|
||||
mask = cv2.inRange(hsv, (99, 200, 0), (100, 255, 255))
|
||||
for idx, x in enumerate(x_list):
|
||||
if np.count_nonzero(mask[y : y + 3, x : x + 5]):
|
||||
return (self.orders[idx], True)
|
||||
if np.count_nonzero(mask[y + 10 : y + 13, x : x + 5]):
|
||||
return (self.orders[idx], False)
|
||||
|
||||
def switch_arrange_order(self, name, ascending=False):
|
||||
name_x = {"工作状态": 1197, "技能": 1322, "心情": 1447, "信赖值": 1575}
|
||||
if isinstance(name, int):
|
||||
name = list(name_x.keys())[name - 1]
|
||||
if isinstance(ascending, str):
|
||||
ascending = ascending == "true"
|
||||
name_y = 60
|
||||
self.tap((name_x[name], name_y), interval=0.5)
|
137
mower/solvers/infra/get_agent_from_room.py
Normal file
137
mower/solvers/infra/get_agent_from_room.py
Normal file
|
@ -0,0 +1,137 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
import cv2
|
||||
|
||||
from mower.models import riic_base_digits
|
||||
from mower.solvers.infra.base_mixin import BaseMixin
|
||||
from mower.solvers.infra.enter_room import EnterRoomSolver
|
||||
from mower.utils import config
|
||||
from mower.utils import typealias as tp
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.image import cropimg, thres2
|
||||
from mower.utils.log import logger
|
||||
|
||||
name_x = (1288, 1869)
|
||||
name_y = [(135, 326), (344, 535), (553, 744), (532, 723), (741, 932)]
|
||||
name_p = [tuple(zip(name_x, y)) for y in name_y]
|
||||
mood_x = (1470, 1780)
|
||||
mood_y = [(219, 220), (428, 429), (637, 638), (615, 616), (823, 824)]
|
||||
mood_p = [tuple(zip(mood_x, y)) for y in mood_y]
|
||||
time_x = (1650, 1780)
|
||||
time_y = [(270, 305), (480, 515), (690, 725), (668, 703), (877, 912)]
|
||||
time_p = [tuple(zip(time_x, y)) for y in time_y]
|
||||
|
||||
|
||||
class GetAgentFromRoomSolver(SceneGraphSolver, BaseMixin):
|
||||
def run(self, room: str, read_agent_time=False):
|
||||
"""
|
||||
Args:
|
||||
room: 房间名
|
||||
read_agent_time: 读取工作或休息时间
|
||||
"""
|
||||
self.room = room
|
||||
self.read_agent_time = read_agent_time
|
||||
self.index = 0
|
||||
self.result = []
|
||||
EnterRoomSolver().run(self.room)
|
||||
self.length = self.number(((1867, 1020), (1885, 1045)), 19, 200)
|
||||
self.need_swipe = True if self.length > 3 else False
|
||||
self.wait_start()
|
||||
super().run()
|
||||
return self.result
|
||||
|
||||
def timeout(self) -> bool:
|
||||
return datetime.now() > self.start_time + timedelta(seconds=3)
|
||||
|
||||
def wait_start(self):
|
||||
self.start_time = datetime.now()
|
||||
|
||||
def number(self, scope: tp.Scope, height: int, thres: int) -> int:
|
||||
"数字识别"
|
||||
img = cropimg(config.recog.gray, scope)
|
||||
default_height = 25
|
||||
if height != default_height:
|
||||
scale = 25 / height
|
||||
img = cv2.resize(img, None, None, scale, scale)
|
||||
img = thres2(img, thres)
|
||||
img = cv2.bitwise_not(img)
|
||||
contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
rect = [cv2.boundingRect(c) for c in contours]
|
||||
rect.sort(key=lambda c: c[0])
|
||||
|
||||
value = 0
|
||||
|
||||
for x, y, w, h in rect:
|
||||
digit = cropimg(img, ((x, y), (x + w, y + h)))
|
||||
digit = cv2.copyMakeBorder(
|
||||
digit, 10, 10, 10, 10, cv2.BORDER_CONSTANT, None, (0,)
|
||||
)
|
||||
score = []
|
||||
for i in range(1, 6):
|
||||
im = riic_base_digits[i]
|
||||
result = cv2.matchTemplate(digit, im, cv2.TM_SQDIFF_NORMED)
|
||||
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
|
||||
score.append(min_val)
|
||||
value = score.index(min(score)) + 1
|
||||
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
def is_power_station() -> bool:
|
||||
img = cropimg(config.recog.img, ((568, 18), (957, 95)))
|
||||
hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
|
||||
mask = cv2.inRange(hsv, (35, 0, 0), (38, 255, 255))
|
||||
if cv2.countNonZero(mask) > 1000:
|
||||
return True
|
||||
return False
|
||||
|
||||
def read_agent_data(self, index):
|
||||
data = {}
|
||||
if self.find("infra_no_operator", scope=name_p[index]):
|
||||
data["agent"] = ""
|
||||
data["mood"] = -1
|
||||
else:
|
||||
data["agent"] = self.read_screen(
|
||||
cropimg(config.recog.gray, name_p[index]), type="name"
|
||||
)
|
||||
data["mood"] = self.read_accurate_mood(
|
||||
cropimg(config.recog.gray, mood_p[index])
|
||||
)
|
||||
if self.read_agent_time:
|
||||
if (
|
||||
data["mood"] == 24
|
||||
or self.room in ["central", "meeting", "factory"]
|
||||
or self.is_power_station()
|
||||
):
|
||||
data["time"] = datetime.now()
|
||||
else:
|
||||
data["time"] = self.double_read_time(time_p[index])
|
||||
self.result.append(data)
|
||||
|
||||
def transition(self) -> bool:
|
||||
if self.detect_product_complete():
|
||||
logger.info("检测到产物收取提示")
|
||||
self.sleep(0.5)
|
||||
elif self.find("room_detail"):
|
||||
self.wait_start()
|
||||
if self.index >= 3 and self.need_swipe:
|
||||
if self.get_color((1800, 930))[0] > 51:
|
||||
self.swipe(
|
||||
(config.recog.w * 0.8, config.recog.h * 0.5),
|
||||
(0, -config.recog.h * 0.45),
|
||||
duration=500,
|
||||
interval=1,
|
||||
)
|
||||
elif self.timeout():
|
||||
EnterRoomSolver().run(self.room)
|
||||
else:
|
||||
self.need_swipe = False
|
||||
elif self.index == self.length:
|
||||
return True
|
||||
elif self.index < self.length:
|
||||
self.read_agent_data(self.index)
|
||||
self.index += 1
|
||||
else:
|
||||
EnterRoomSolver().run(self.room)
|
||||
else:
|
||||
EnterRoomSolver().run(self.room)
|
91
mower/solvers/infra/get_order_remaining_time.py
Normal file
91
mower/solvers/infra/get_order_remaining_time.py
Normal file
|
@ -0,0 +1,91 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
import cv2
|
||||
|
||||
from mower.models import riic_base_digits
|
||||
from mower.solvers.infra.base_mixin import BaseMixin
|
||||
from mower.solvers.infra.enter_room import EnterRoomSolver
|
||||
from mower.utils import config
|
||||
from mower.utils import typealias as tp
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.image import cropimg, thres2
|
||||
from mower.utils.log import logger
|
||||
from mower.utils.recognize import Scene
|
||||
|
||||
|
||||
class GetOrderRemainingTimeSolver(SceneGraphSolver, BaseMixin):
|
||||
def run(self, room: str):
|
||||
logger.info("Start:读取订单剩余时间")
|
||||
self.room = room
|
||||
self.res = None
|
||||
if (
|
||||
self.scene() == Scene.ORDER_LIST
|
||||
and not self.detect_room_inside() == self.room
|
||||
):
|
||||
EnterRoomSolver().run(self.room, detail=False)
|
||||
self.wait_start()
|
||||
super().run()
|
||||
return self.res
|
||||
|
||||
def number(self, scope: tp.Scope, height: int, thres: int) -> str:
|
||||
"数字识别"
|
||||
img = cropimg(config.recog.gray, scope)
|
||||
default_height = 25
|
||||
if height != default_height:
|
||||
scale = 25 / height
|
||||
img = cv2.resize(img, None, None, scale, scale)
|
||||
img = thres2(img, thres)
|
||||
contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
rect = [cv2.boundingRect(c) for c in contours]
|
||||
rect.sort(key=lambda c: c[0])
|
||||
|
||||
value = ""
|
||||
for x, y, w, h in rect:
|
||||
if h < 10 and w < 10:
|
||||
value += ":"
|
||||
continue
|
||||
digit = cropimg(img, ((x, y), (x + w, y + h)))
|
||||
digit = cv2.copyMakeBorder(
|
||||
digit, 10, 10, 10, 10, cv2.BORDER_CONSTANT, None, (0,)
|
||||
)
|
||||
score = []
|
||||
for i in range(10):
|
||||
im = riic_base_digits[i]
|
||||
result = cv2.matchTemplate(digit, im, cv2.TM_SQDIFF_NORMED)
|
||||
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
|
||||
score.append(min_val)
|
||||
value += str(score.index(min(score)))
|
||||
|
||||
return value
|
||||
|
||||
def read_remain_time(self, pos) -> int:
|
||||
h, m, s = self.number(pos, 19, 115).split("::")
|
||||
return int(h) * 3600 + int(m) * 60 + int(s)
|
||||
|
||||
def timeout(self) -> bool:
|
||||
return datetime.now() > self.start_time + timedelta(seconds=10)
|
||||
|
||||
def wait_start(self):
|
||||
self.start_time = datetime.now()
|
||||
|
||||
def transition(self) -> bool:
|
||||
if (scene := self.scene()) == Scene.INFRA_DETAILS:
|
||||
self.ctap((200, 1000), 1, config.screenshot_avg / 1000)
|
||||
self.wait_start()
|
||||
elif scene == Scene.ORDER_LIST:
|
||||
if self.timeout():
|
||||
logger.error("读取订单失败")
|
||||
return True
|
||||
elif pos := self.find("bill_accelerate"):
|
||||
pos[0][0] += 69
|
||||
pos[0][1] -= 214
|
||||
pos[1][0] -= 52
|
||||
pos[1][1] -= 220
|
||||
if res := self.read_remain_time(pos):
|
||||
self.res = res
|
||||
return True
|
||||
|
||||
elif scene in self.waiting_scene:
|
||||
self.waiting_solver()
|
||||
else:
|
||||
EnterRoomSolver().run(self.room, detail=False)
|
234
mower/solvers/infra/record.py
Normal file
234
mower/solvers/infra/record.py
Normal file
|
@ -0,0 +1,234 @@
|
|||
# 用于记录Mower操作行为
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
|
||||
from mower.utils.log import logger
|
||||
from mower.utils.path import get_path
|
||||
|
||||
|
||||
# 记录干员进出站以及心情数据,将记录信息存入agent_action表里
|
||||
def save_action_to_sqlite_decorator(func):
|
||||
def wrapper(self, name, mood, current_room, current_index, update_time=False):
|
||||
agent = self.operators[name] # 干员
|
||||
|
||||
agent_current_room = agent.current_room # 干员所在房间
|
||||
agent_is_high = agent.is_high() # 是否高优先级
|
||||
|
||||
# 调用原函数
|
||||
result = func(self, name, mood, current_room, current_index, update_time)
|
||||
if not update_time:
|
||||
return
|
||||
# 保存到数据库
|
||||
current_time = datetime.now()
|
||||
database_path = get_path("@app/tmp/data.db")
|
||||
|
||||
try:
|
||||
# Create 'tmp' directory if it doesn't exist
|
||||
get_path("@app/tmp").mkdir(exist_ok=True)
|
||||
|
||||
connection = sqlite3.connect(database_path)
|
||||
cursor = connection.cursor()
|
||||
|
||||
# Create a table if it doesn't exist
|
||||
cursor.execute(
|
||||
"CREATE TABLE IF NOT EXISTS agent_action ("
|
||||
"name TEXT,"
|
||||
"agent_current_room TEXT,"
|
||||
"current_room TEXT,"
|
||||
"is_high INTEGER,"
|
||||
"agent_group TEXT,"
|
||||
"mood REAL,"
|
||||
"current_time TEXT"
|
||||
")"
|
||||
)
|
||||
|
||||
# Insert data
|
||||
cursor.execute(
|
||||
"INSERT INTO agent_action VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
(
|
||||
name,
|
||||
agent_current_room,
|
||||
current_room,
|
||||
int(agent_is_high),
|
||||
agent.group,
|
||||
mood,
|
||||
str(current_time),
|
||||
),
|
||||
)
|
||||
|
||||
connection.commit()
|
||||
connection.close()
|
||||
|
||||
# Log the action
|
||||
logger.debug(
|
||||
f"Saved action to SQLite: Name: {name}, Agent's Room: {agent_current_room}, Agent's group: {agent.group}, "
|
||||
f"Current Room: {current_room}, Is High: {agent_is_high}, Current Time: {current_time}"
|
||||
)
|
||||
|
||||
except sqlite3.Error as e:
|
||||
logger.error(f"SQLite error: {e}")
|
||||
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def get_work_rest_ratios():
|
||||
# TODO 整理数据计算工休比
|
||||
database_path = get_path("@app/tmp/data.db")
|
||||
|
||||
try:
|
||||
# 连接到数据库
|
||||
conn = sqlite3.connect(database_path)
|
||||
# conn = sqlite3.connect('../../tmp/data.db')
|
||||
cursor = conn.cursor()
|
||||
# 查询数据
|
||||
cursor.execute("""
|
||||
SELECT a.*
|
||||
FROM agent_action a
|
||||
JOIN (
|
||||
SELECT DISTINCT b.name
|
||||
FROM agent_action b
|
||||
WHERE DATE(b.current_time) >= DATE('now', '-7 day', 'localtime')
|
||||
AND b.is_high = 1 AND b.current_room NOT LIKE 'dormitory%'
|
||||
UNION
|
||||
SELECT '菲亚梅塔' AS name
|
||||
) AS subquery ON a.name = subquery.name
|
||||
WHERE DATE(a.current_time) >= DATE('now', '-1 month', 'localtime')
|
||||
ORDER BY a.current_time;
|
||||
""")
|
||||
data = cursor.fetchall()
|
||||
# 关闭数据库连接
|
||||
conn.close()
|
||||
except sqlite3.Error:
|
||||
data = []
|
||||
processed_data = {}
|
||||
grouped_data = {}
|
||||
for row in data:
|
||||
name = row[0]
|
||||
current_room = row[2]
|
||||
current_time = row[6] # Assuming index 6 is the current_time column
|
||||
agent = grouped_data.get(
|
||||
name,
|
||||
{
|
||||
"agent_data": [
|
||||
{"current_time": current_time, "current_room": current_room}
|
||||
],
|
||||
"difference": [],
|
||||
},
|
||||
)
|
||||
difference = {
|
||||
"time_diff": calculate_time_difference(
|
||||
agent["agent_data"][-1]["current_time"], current_time
|
||||
),
|
||||
"current_room": agent["agent_data"][-1]["current_room"],
|
||||
}
|
||||
agent["agent_data"].append(
|
||||
{"current_time": current_time, "current_room": current_room}
|
||||
)
|
||||
agent["difference"].append(difference)
|
||||
grouped_data[name] = agent
|
||||
for name in grouped_data:
|
||||
work_time = 0
|
||||
rest_time = 0
|
||||
for difference in grouped_data[name]["difference"]:
|
||||
if difference["current_room"].startswith("dormitory"):
|
||||
rest_time += difference["time_diff"]
|
||||
else:
|
||||
work_time += difference["time_diff"]
|
||||
processed_data[name] = {
|
||||
"labels": ["休息时间", "工作时间"],
|
||||
"datasets": [{"data": [rest_time, work_time]}],
|
||||
}
|
||||
|
||||
return processed_data
|
||||
|
||||
|
||||
# 整理心情曲线
|
||||
def get_mood_ratios():
|
||||
database_path = get_path("@app/tmp/data.db")
|
||||
|
||||
try:
|
||||
# 连接到数据库
|
||||
conn = sqlite3.connect(database_path)
|
||||
cursor = conn.cursor()
|
||||
# 查询数据(筛掉宿管和替班组的数据)
|
||||
cursor.execute("""
|
||||
SELECT a.*
|
||||
FROM agent_action a
|
||||
JOIN (
|
||||
SELECT DISTINCT b.name
|
||||
FROM agent_action b
|
||||
WHERE DATE(b.current_time) >= DATE('now', '-7 day', 'localtime')
|
||||
AND b.is_high = 1 AND b.current_room NOT LIKE 'dormitory%'
|
||||
UNION
|
||||
SELECT '菲亚梅塔' AS name
|
||||
) AS subquery ON a.name = subquery.name
|
||||
WHERE DATE(a.current_time) >= DATE('now', '-7 day', 'localtime')
|
||||
ORDER BY a.agent_group DESC, a.current_time;
|
||||
|
||||
""")
|
||||
data = cursor.fetchall()
|
||||
# 关闭数据库连接
|
||||
conn.close()
|
||||
except sqlite3.Error:
|
||||
data = []
|
||||
|
||||
work_rest_data_ratios = get_work_rest_ratios()
|
||||
grouped_data = {}
|
||||
grouped_work_rest_data = {}
|
||||
for row in data:
|
||||
group_name = row[4] # Assuming 'agent_group' is at index 4
|
||||
if not group_name:
|
||||
group_name = row[0]
|
||||
mood_data = grouped_data.get(group_name, {"labels": [], "datasets": []})
|
||||
work_rest_data = grouped_work_rest_data.get(
|
||||
group_name, work_rest_data_ratios[row[0]]
|
||||
)
|
||||
grouped_work_rest_data[group_name] = work_rest_data
|
||||
|
||||
timestamp_datetime = datetime.strptime(
|
||||
row[6], "%Y-%m-%d %H:%M:%S.%f"
|
||||
) # Assuming 'current_time' is at index 6
|
||||
# 创建 Luxon 格式的字符串
|
||||
current_time = f"{timestamp_datetime.year:04d}-{timestamp_datetime.month:02d}-{timestamp_datetime.day:02d}T{timestamp_datetime.hour:02d}:{timestamp_datetime.minute:02d}:{timestamp_datetime.second:02d}.{timestamp_datetime.microsecond:06d}+08:00"
|
||||
|
||||
mood_label = row[0] # Assuming 'name' is at index 0
|
||||
mood_value = row[5] # Assuming 'mood' is at index 5
|
||||
|
||||
if mood_label in [dataset["label"] for dataset in mood_data["datasets"]]:
|
||||
# if mood_label == mood_data['datasets'][0]['label']:
|
||||
mood_data["labels"].append(current_time)
|
||||
# If mood label already exists, find the corresponding dataset
|
||||
for dataset in mood_data["datasets"]:
|
||||
if dataset["label"] == mood_label:
|
||||
dataset["data"].append({"x": current_time, "y": mood_value})
|
||||
break
|
||||
else:
|
||||
# If mood label doesn't exist, create a new dataset
|
||||
mood_data["labels"].append(current_time)
|
||||
mood_data["datasets"].append(
|
||||
{"label": mood_label, "data": [{"x": current_time, "y": mood_value}]}
|
||||
)
|
||||
|
||||
grouped_data[group_name] = mood_data
|
||||
print(grouped_work_rest_data)
|
||||
# 将数据格式整理为数组
|
||||
formatted_data = []
|
||||
for group_name, mood_data in grouped_data.items():
|
||||
formatted_data.append(
|
||||
{
|
||||
"groupName": group_name,
|
||||
"moodData": mood_data,
|
||||
"workRestData": grouped_work_rest_data[group_name],
|
||||
}
|
||||
)
|
||||
return formatted_data
|
||||
|
||||
|
||||
def calculate_time_difference(start_time, end_time):
|
||||
time_format = "%Y-%m-%d %H:%M:%S.%f"
|
||||
start_datetime = datetime.strptime(start_time, time_format)
|
||||
end_datetime = datetime.strptime(end_time, time_format)
|
||||
time_difference = end_datetime - start_datetime
|
||||
return time_difference.total_seconds()
|
105
mower/solvers/infra/reload.py
Normal file
105
mower/solvers/infra/reload.py
Normal file
|
@ -0,0 +1,105 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
import cv2
|
||||
|
||||
from mower.models import riic_base_digits
|
||||
from mower.solvers.infra.base_mixin import BaseMixin
|
||||
from mower.solvers.infra.enter_room import EnterRoomSolver
|
||||
from mower.utils import config
|
||||
from mower.utils import typealias as tp
|
||||
from mower.utils.email import send_message
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.image import cropimg, thres2
|
||||
from mower.utils.log import logger
|
||||
from mower.utils.recognize import Scene
|
||||
|
||||
|
||||
class ReloadSolver(SceneGraphSolver, BaseMixin):
|
||||
def run(self, room) -> None:
|
||||
logger.info(f"开始补货:{room}")
|
||||
self.room = room
|
||||
self.success = False
|
||||
if (
|
||||
self.scene() == Scene.FACTORY_ROOMS
|
||||
and not self.detect_room_inside() == self.room
|
||||
):
|
||||
EnterRoomSolver().run(self.room, detail=False)
|
||||
self.wait_start()
|
||||
super().run()
|
||||
|
||||
def timeout(self) -> bool:
|
||||
return datetime.now() > self.start_time + timedelta(seconds=10)
|
||||
|
||||
def wait_start(self):
|
||||
self.start_time = datetime.now()
|
||||
|
||||
def number(self, scope: tp.Scope, height: int, thres: int) -> int:
|
||||
"数字识别"
|
||||
img = cropimg(config.recog.gray, scope)
|
||||
default_height = 25
|
||||
if height != default_height:
|
||||
scale = 25 / height
|
||||
img = cv2.resize(img, None, None, scale, scale)
|
||||
img = thres2(img, thres)
|
||||
contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
rect = [cv2.boundingRect(c) for c in contours]
|
||||
rect.sort(key=lambda c: c[0])
|
||||
|
||||
value = 0
|
||||
|
||||
for x, y, w, h in rect:
|
||||
digit = cropimg(img, ((x, y), (x + w, y + h)))
|
||||
digit = cv2.copyMakeBorder(
|
||||
digit, 10, 10, 10, 10, cv2.BORDER_CONSTANT, None, (0,)
|
||||
)
|
||||
score = []
|
||||
for i in range(10):
|
||||
im = riic_base_digits[i]
|
||||
result = cv2.matchTemplate(digit, im, cv2.TM_SQDIFF_NORMED)
|
||||
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
|
||||
score.append(min_val)
|
||||
value = value * 10 + score.index(min(score))
|
||||
|
||||
return value
|
||||
|
||||
def transition(self) -> bool:
|
||||
if (scene := self.scene()) == Scene.INFRA_DETAILS:
|
||||
self.wait_start()
|
||||
self.ctap((200, 1000), 1, config.screenshot_avg / 1000)
|
||||
elif scene == Scene.FACTORY_ROOMS:
|
||||
reload_num = 99 - self.number(((1306, 462), (1391, 521)), 50, 200)
|
||||
if self.find("reload_check"):
|
||||
self.ctap((1400, 850), 1, config.screenshot_avg / 1000)
|
||||
self.success = True
|
||||
elif self.timeout():
|
||||
logger.warning(f"材料已耗尽:{self.room} 时间为:{datetime.now()}")
|
||||
send_message(f"材料已耗尽:{self.room}", level="WARNING")
|
||||
return True
|
||||
elif not self.success:
|
||||
if reload_num == 0:
|
||||
logger.info(f"货已满:{self.room} 时间为:{datetime.now()}")
|
||||
send_message(f"货已满:{self.room}")
|
||||
return True
|
||||
else:
|
||||
self.last_num = reload_num
|
||||
self.tap((1450, 300))
|
||||
else:
|
||||
if reload_num == 0:
|
||||
logger.info(
|
||||
f"补货成功:{self.room} 本次补货数量:{self.last_num} 时间:{datetime.now()}"
|
||||
)
|
||||
send_message(f"补货成功:{self.room} 本次补货数量:{self.last_num}")
|
||||
return True
|
||||
else:
|
||||
logger.warning(
|
||||
f"补货成功:{self.room} 剩余数量:{99-reload_num} 剩余材料不足! 时间:{datetime.now()}"
|
||||
)
|
||||
send_message(
|
||||
f"补货成功:{self.room} 剩余数量:{99-reload_num} 剩余材料不足!",
|
||||
level="WARNING",
|
||||
)
|
||||
return True
|
||||
elif scene in self.waiting_scene:
|
||||
self.waiting_solver()
|
||||
else:
|
||||
EnterRoomSolver().run(self.room, detail=False)
|
331
mower/solvers/infra/report.py
Normal file
331
mower/solvers/infra/report.py
Normal file
|
@ -0,0 +1,331 @@
|
|||
import datetime
|
||||
|
||||
import cv2
|
||||
from sqlalchemy import Column, Date, Integer, create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from mower.models import noto_sans
|
||||
from mower.utils import config
|
||||
from mower.utils.digit_reader import DigitReader
|
||||
from mower.utils.email import report_template, send_message
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.image import cropimg, thres2
|
||||
from mower.utils.log import logger
|
||||
from mower.utils.path import get_path
|
||||
from mower.utils.recognize import Scene, tp
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
# Database model for reports
|
||||
class Report(Base):
|
||||
__tablename__ = "reports"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
date = Column(Date, nullable=False, unique=True)
|
||||
作战录像 = Column(Integer)
|
||||
赤金 = Column(Integer)
|
||||
龙门币订单 = Column(Integer)
|
||||
龙门币订单数 = Column(Integer)
|
||||
合成玉 = Column(Integer)
|
||||
合成玉订单数量 = Column(Integer)
|
||||
|
||||
|
||||
class ReportSolver(SceneGraphSolver):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.db_path = get_path("@app/tmp/report.db")
|
||||
self.low_range_gray = (100, 100, 100)
|
||||
self.high_range_gray = (255, 255, 255)
|
||||
self.date = (datetime.datetime.now() - datetime.timedelta(hours=4)).date()
|
||||
self.digitReader = DigitReader()
|
||||
self.report_res = {
|
||||
"作战录像": None,
|
||||
"赤金": None,
|
||||
"龙门币订单": None,
|
||||
"龙门币订单数": None,
|
||||
"合成玉": None,
|
||||
"合成玉订单数量": None,
|
||||
}
|
||||
self.reload_time = 0
|
||||
|
||||
# Setup SQLAlchemy with dynamic DATABASE_URL
|
||||
self.DATABASE_URL = f"sqlite:///{self.db_path}" # Dynamic DB URL
|
||||
self.engine = create_engine(self.DATABASE_URL)
|
||||
Base.metadata.create_all(self.engine)
|
||||
self.Session = sessionmaker(bind=self.engine)
|
||||
|
||||
def run(self):
|
||||
if self.has_record():
|
||||
logger.info("今天的基报看过了")
|
||||
return True
|
||||
logger.info("康康大基报捏~")
|
||||
try:
|
||||
super().run()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
return False
|
||||
|
||||
def transition(self) -> bool:
|
||||
if self.scene() == Scene.RIIC_REPORT:
|
||||
return self.read_report()
|
||||
else:
|
||||
self.scene_graph_step(Scene.RIIC_REPORT)
|
||||
|
||||
def read_report(self):
|
||||
if self.find("riic/manufacture"):
|
||||
try:
|
||||
self.crop_report()
|
||||
logger.info(self.report_res)
|
||||
self.record_report()
|
||||
except Exception as e:
|
||||
logger.exception("基报读取失败:{}".format(e))
|
||||
return True
|
||||
else:
|
||||
if self.reload_time > 3:
|
||||
logger.info("未加载出基报")
|
||||
return True
|
||||
self.reload_time += 1
|
||||
self.sleep(1)
|
||||
|
||||
def record_report(self):
|
||||
logger.info(f"存入{self.date}的数据{self.report_res}")
|
||||
try:
|
||||
with self.Session() as session:
|
||||
report = Report(
|
||||
date=self.date,
|
||||
作战录像=self.report_res["作战录像"],
|
||||
赤金=self.report_res["赤金"],
|
||||
龙门币订单=self.report_res["龙门币订单"],
|
||||
龙门币订单数=self.report_res["龙门币订单数"],
|
||||
合成玉=self.report_res["合成玉"],
|
||||
合成玉订单数量=self.report_res["合成玉订单数量"],
|
||||
)
|
||||
session.merge(report)
|
||||
session.commit()
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"存入数据失败:{e}")
|
||||
self.tap((1253, 81), interval=2)
|
||||
try:
|
||||
send_message(
|
||||
report_template.render(
|
||||
report_data=self.report_res, title_text="基建报告"
|
||||
),
|
||||
"基建报告",
|
||||
"INFO",
|
||||
attach_image=config.recog.img,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception(f"基报邮件发送失败:{e}")
|
||||
self.tap((40, 80), interval=2)
|
||||
|
||||
def has_record(self):
|
||||
try:
|
||||
with self.Session() as session: # 使用上下文管理器
|
||||
record_exists = session.query(Report).filter_by(date=self.date).first()
|
||||
return record_exists is not None
|
||||
except Exception as e:
|
||||
logger.exception(f"查询数据库失败:{e}")
|
||||
return False
|
||||
|
||||
def crop_report(self):
|
||||
exp_area = [[1625, 200], [1800, 230]]
|
||||
iron_pos = self.find("riic/iron")
|
||||
iron_area = [
|
||||
[iron_pos[1][0], iron_pos[0][1]],
|
||||
[1800, iron_pos[1][1]],
|
||||
]
|
||||
trade_pt = self.find("riic/trade")
|
||||
assist_pt = self.find("riic/assistants")
|
||||
area = {
|
||||
"iron_order": [[1620, trade_pt[1][1] + 10], [1740, assist_pt[0][1] - 50]],
|
||||
"iron_order_number": [
|
||||
[1820, trade_pt[1][1] + 10],
|
||||
[1870, assist_pt[0][1] - 65],
|
||||
],
|
||||
"orundum": [[1620, trade_pt[1][1] + 45], [1870, assist_pt[0][1]]],
|
||||
"orundum_number": [
|
||||
[1820, trade_pt[1][1] + 55],
|
||||
[1860, assist_pt[0][1] - 20],
|
||||
],
|
||||
}
|
||||
|
||||
img = cv2.cvtColor(config.recog.img, cv2.COLOR_RGB2HSV)
|
||||
img = cv2.inRange(img, (98, 0, 150), (102, 255, 255))
|
||||
self.report_res["作战录像"] = self.get_number(img, exp_area, height=19)
|
||||
self.report_res["赤金"] = self.get_number(img, iron_area, height=19)
|
||||
self.report_res["龙门币订单"] = self.get_number(
|
||||
img, area["iron_order"], height=19
|
||||
)
|
||||
self.report_res["合成玉"] = self.get_number(img, area["orundum"], height=19)
|
||||
logger.info("蓝字读取完成")
|
||||
|
||||
img = cv2.cvtColor(config.recog.img, cv2.COLOR_RGB2HSV)
|
||||
img = cv2.inRange(img, (0, 0, 50), (100, 100, 170))
|
||||
self.report_res["龙门币订单数"] = self.get_number(
|
||||
img, area["iron_order_number"], height=19, thres=200
|
||||
)
|
||||
self.report_res["合成玉订单数量"] = self.get_number(
|
||||
img, area["orundum_number"], height=19, thres=200
|
||||
)
|
||||
logger.info("订单数读取完成")
|
||||
|
||||
def get_number(
|
||||
self, img, scope: tp.Scope, height: int | None = 18, thres: int | None = 100
|
||||
):
|
||||
img = cropimg(img, scope)
|
||||
|
||||
default_height = 29
|
||||
if height and height != default_height:
|
||||
scale = default_height / height
|
||||
img = cv2.resize(img, None, None, scale, scale)
|
||||
img = thres2(img, thres)
|
||||
contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
rect = [cv2.boundingRect(c) for c in contours]
|
||||
rect.sort(key=lambda c: c[0])
|
||||
value = 0
|
||||
for x, y, w, h in rect:
|
||||
digit = cropimg(img, ((x, y), (x + w, y + h)))
|
||||
digit = cv2.copyMakeBorder(
|
||||
digit, 10, 10, 10, 10, cv2.BORDER_CONSTANT, None, (0,)
|
||||
)
|
||||
|
||||
score = []
|
||||
for i in range(10):
|
||||
im = noto_sans[i]
|
||||
result = cv2.matchTemplate(digit, im, cv2.TM_SQDIFF_NORMED)
|
||||
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
|
||||
score.append(min_val)
|
||||
value = value * 10 + score.index(min(score))
|
||||
|
||||
return value
|
||||
|
||||
def get_report_data(self):
|
||||
# 连接数据库
|
||||
try:
|
||||
with self.Session() as session:
|
||||
format_data = []
|
||||
|
||||
# 查询所有报告数据
|
||||
records = session.query(Report).all()
|
||||
|
||||
# 将记录转化为所需格式
|
||||
for record in records:
|
||||
format_data.append(
|
||||
{
|
||||
"日期": record.date.strftime(
|
||||
"%Y-%m-%d"
|
||||
), # 日期格式化为字符串
|
||||
"作战录像": record.作战录像,
|
||||
"赤金": record.赤金,
|
||||
"制造总数": int(record.赤金 + record.作战录像),
|
||||
"龙门币订单": record.龙门币订单,
|
||||
"反向作战录像": -record.作战录像,
|
||||
"龙门币订单数": record.龙门币订单数,
|
||||
"每单获取龙门币": int(
|
||||
record.龙门币订单 / record.龙门币订单数
|
||||
)
|
||||
if record.龙门币订单数
|
||||
else 0,
|
||||
}
|
||||
)
|
||||
|
||||
# 如果格式化后的数据少于15条,则添加缺失的日期
|
||||
earliest_date = (
|
||||
min(record.date for record in records)
|
||||
if records
|
||||
else datetime.date.today()
|
||||
)
|
||||
if len(format_data) < 15:
|
||||
for i in range(1, 16 - len(format_data)):
|
||||
format_data.insert(
|
||||
0,
|
||||
{
|
||||
"日期": (
|
||||
earliest_date - datetime.timedelta(days=i + 1)
|
||||
).strftime("%Y-%m-%d"),
|
||||
"作战录像": "-",
|
||||
"赤金": "-",
|
||||
"龙门币订单": "-",
|
||||
"龙门币订单数": "-",
|
||||
"每单获取龙门币": "-",
|
||||
},
|
||||
)
|
||||
|
||||
logger.debug(format_data)
|
||||
return format_data
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"读取数据库失败: {e}")
|
||||
|
||||
def get_orundum_data(self):
|
||||
try:
|
||||
format_data = []
|
||||
with self.Session() as session:
|
||||
# 查询所有报告数据
|
||||
records = session.query(Report).all()
|
||||
earliest_date = datetime.datetime.now()
|
||||
|
||||
# 初始化制造合成玉的开始日期
|
||||
begin_make_orundum = (earliest_date + datetime.timedelta(days=1)).date()
|
||||
|
||||
if len(records) >= 15:
|
||||
for i in range(len(records) - 1, -1, -1):
|
||||
record = records[i]
|
||||
if 0 < i < len(records) - 15:
|
||||
continue
|
||||
if record.合成玉 > 0:
|
||||
begin_make_orundum = record.date
|
||||
|
||||
else:
|
||||
for record in records:
|
||||
if record.合成玉 > 0:
|
||||
begin_make_orundum = record.date
|
||||
|
||||
if begin_make_orundum > earliest_date.date():
|
||||
return format_data
|
||||
|
||||
total_orundum = 0
|
||||
for record in records:
|
||||
total_orundum += record.合成玉
|
||||
format_data.append(
|
||||
{
|
||||
"日期": record.date.strftime("%Y-%m-%d"),
|
||||
"合成玉": record.合成玉,
|
||||
"合成玉订单数量": record.合成玉订单数量,
|
||||
"抽数": round((record.合成玉 / 600), 1),
|
||||
"累计制造合成玉": total_orundum,
|
||||
}
|
||||
)
|
||||
|
||||
if len(format_data) < 15:
|
||||
earliest_date = records[0].date
|
||||
for i in range(1, 16 - len(format_data)):
|
||||
format_data.insert(
|
||||
0,
|
||||
{
|
||||
"日期": (
|
||||
earliest_date - datetime.timedelta(days=i + 1)
|
||||
).strftime("%Y-%m-%d"),
|
||||
"合成玉": "-",
|
||||
"合成玉订单数量": "-",
|
||||
"抽数": "-",
|
||||
"累计制造合成玉": 0,
|
||||
},
|
||||
)
|
||||
|
||||
logger.debug(format_data)
|
||||
return format_data
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"获取合成玉数据失败:{e}")
|
||||
return []
|
||||
|
||||
def close_engine(self):
|
||||
self.engine.dispose()
|
||||
|
||||
def __del__(self):
|
||||
self.close_engine()
|
54
mower/solvers/infra/run_order/__init__.py
Normal file
54
mower/solvers/infra/run_order/__init__.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
from mower.solvers.infra.base_choose import RIIC_ChooseSolver
|
||||
from mower.solvers.infra.drone import DroneSolver
|
||||
from mower.solvers.infra.get_agent_from_room import GetAgentFromRoomSolver
|
||||
from mower.solvers.infra.get_order_remaining_time import (
|
||||
GetOrderRemainingTimeSolver,
|
||||
)
|
||||
from mower.utils import config
|
||||
from mower.utils.digit_reader import DigitReader
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.log import logger
|
||||
|
||||
from .read_original_order_remaining_time import ReadOriginalOrderRemainTimeSolver
|
||||
from .wait_for_order import WaitForOrderSolver
|
||||
|
||||
limit_time = 1800
|
||||
|
||||
|
||||
class RunOrderSolver(SceneGraphSolver):
|
||||
def run(
|
||||
self,
|
||||
room: str,
|
||||
agent_list: list,
|
||||
drone: bool = False,
|
||||
) -> None:
|
||||
logger.info("Start:跑单插拔")
|
||||
# 读取原来的干员列表
|
||||
original_agent_list = [
|
||||
data["agent"] for data in GetAgentFromRoomSolver().run(room)
|
||||
]
|
||||
# 使用无人机加速
|
||||
if drone:
|
||||
# 读取当前无人机数量避免重复开关
|
||||
cur_drone_count = DigitReader().get_drone(config.recog.gray)
|
||||
drone_count = ReadOriginalOrderRemainTimeSolver().run(room) // 180 - 1
|
||||
DroneSolver().run(room, drone_count, cur_count=cur_drone_count)
|
||||
|
||||
wait_time = max(
|
||||
GetOrderRemainingTimeSolver().run(room) - config.conf.run_order_buffer_time,
|
||||
0,
|
||||
)
|
||||
if wait_time > limit_time:
|
||||
logger.error("检测到漏单")
|
||||
return
|
||||
logger.info(f"等待时间:{wait_time}")
|
||||
# 换上跑单干员等待
|
||||
RIIC_ChooseSolver().run(room, agent_list, wait_time)
|
||||
if (wait_time := GetOrderRemainingTimeSolver().run(room)) > limit_time:
|
||||
logger.error("检测到漏单")
|
||||
return
|
||||
logger.info(f"等待时间:{wait_time}")
|
||||
# 等待订单完成
|
||||
WaitForOrderSolver().run(room, wait_time)
|
||||
# 换回原来的干员
|
||||
RIIC_ChooseSolver().run(room, original_agent_list)
|
|
@ -0,0 +1,80 @@
|
|||
import cv2
|
||||
|
||||
from mower.models import riic_base_digits
|
||||
from mower.solvers.infra.base_mixin import BaseMixin
|
||||
from mower.solvers.infra.enter_room import EnterRoomSolver
|
||||
from mower.utils import config
|
||||
from mower.utils import typealias as tp
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.image import cropimg, thres2
|
||||
from mower.utils.recognize import Scene
|
||||
|
||||
|
||||
class ReadOriginalOrderRemainTimeSolver(SceneGraphSolver, BaseMixin):
|
||||
"""
|
||||
返回剩余时间,换算为秒
|
||||
"""
|
||||
|
||||
def run(
|
||||
self,
|
||||
room: str,
|
||||
):
|
||||
self.room = room
|
||||
if (
|
||||
self.scene() == Scene.DRONE_ACCELERATE
|
||||
and not self.detect_room_inside() == self.room
|
||||
):
|
||||
EnterRoomSolver().run(self.room, detail=False)
|
||||
super().run()
|
||||
return self.res
|
||||
|
||||
def number(self, scope: tp.Scope, height: int, thres: int) -> str:
|
||||
"数字识别"
|
||||
img = cropimg(config.recog.gray, scope)
|
||||
default_height = 25
|
||||
if height != default_height:
|
||||
scale = 25 / height
|
||||
img = cv2.resize(img, None, None, scale, scale)
|
||||
img = thres2(img, thres)
|
||||
contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
rect = [cv2.boundingRect(c) for c in contours]
|
||||
rect.sort(key=lambda c: c[0])
|
||||
|
||||
value = ""
|
||||
for x, y, w, h in rect:
|
||||
if h < 7 and w < 7:
|
||||
value += ":"
|
||||
continue
|
||||
digit = cropimg(img, ((x, y), (x + w, y + h)))
|
||||
digit = cv2.copyMakeBorder(
|
||||
digit, 10, 10, 10, 10, cv2.BORDER_CONSTANT, None, (0,)
|
||||
)
|
||||
score = []
|
||||
for i in range(10):
|
||||
im = riic_base_digits[i]
|
||||
result = cv2.matchTemplate(digit, im, cv2.TM_SQDIFF_NORMED)
|
||||
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
|
||||
score.append(min_val)
|
||||
value += str(score.index(min(score)))
|
||||
|
||||
return value
|
||||
|
||||
def read_remain_time(self) -> int:
|
||||
h, m, s = self.number(((758, 670), (960, 705)), 30, 100).split("::")
|
||||
return int(h) * 3600 + int(m) * 60 + int(s)
|
||||
|
||||
def transition(self) -> bool:
|
||||
if (scene := self.scene()) == Scene.INFRA_DETAILS:
|
||||
self.ctap((200, 1000), 1, config.screenshot_avg / 1000)
|
||||
elif scene == Scene.ORDER_LIST:
|
||||
if pos := self.find("bill_accelerate"):
|
||||
self.tap(pos)
|
||||
|
||||
elif scene == Scene.DRONE_ACCELERATE:
|
||||
if res := self.read_remain_time():
|
||||
self.res = res
|
||||
return True
|
||||
elif scene in self.waiting_scene:
|
||||
self.waiting_solver()
|
||||
else:
|
||||
EnterRoomSolver().run(self.room, detail=False)
|
37
mower/solvers/infra/run_order/wait_for_order.py
Normal file
37
mower/solvers/infra/run_order/wait_for_order.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
from mower.solvers.infra.base_mixin import BaseMixin
|
||||
from mower.solvers.infra.enter_room import EnterRoomSolver
|
||||
from mower.utils import config
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.recognize import Scene
|
||||
|
||||
|
||||
class WaitForOrderSolver(SceneGraphSolver, BaseMixin):
|
||||
def run(self, room, wait_time) -> None:
|
||||
self.room = room
|
||||
self.wait_start()
|
||||
self.sleep(wait_time)
|
||||
self.success = False
|
||||
super().run()
|
||||
|
||||
def timeout(self) -> bool:
|
||||
return datetime.now() > self.start_time + timedelta(seconds=180)
|
||||
|
||||
def wait_start(self):
|
||||
self.start_time = datetime.now()
|
||||
|
||||
def transition(self) -> bool:
|
||||
if (scene := self.scene()) == Scene.INFRA_DETAILS:
|
||||
self.ctap((200, 1000), 1, config.screenshot_avg / 1000)
|
||||
elif scene == Scene.ORDER_LIST:
|
||||
if pos := self.find("order_ready"):
|
||||
self.tap(pos)
|
||||
self.success = True
|
||||
elif self.success:
|
||||
return True
|
||||
|
||||
elif scene in self.waiting_scene:
|
||||
self.waiting_solver()
|
||||
else:
|
||||
EnterRoomSolver().run(self.room, detail=False)
|
47
mower/solvers/infra/switch_order.py
Normal file
47
mower/solvers/infra/switch_order.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
from mower.solvers.infra.base_mixin import BaseMixin
|
||||
from mower.solvers.infra.enter_room import EnterRoomSolver
|
||||
from mower.utils import config
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.log import logger
|
||||
from mower.utils.recognize import Scene
|
||||
|
||||
check_order_scope = {"lmb": ((750, 796), (803, 849)), "oru": ((1111, 796), (1164, 849))}
|
||||
|
||||
|
||||
class SwitchOrderSolver(SceneGraphSolver, BaseMixin):
|
||||
def run(self, room: str, tar_order: str):
|
||||
"""
|
||||
Args:
|
||||
room: 房间名
|
||||
tar_order: 目标订单 lmb或oru
|
||||
"""
|
||||
order_type = "龙门币" if tar_order == "lmb" else "合成玉"
|
||||
logger.info(f"Start:切换为{order_type}订单 ")
|
||||
self.tar_order = tar_order
|
||||
self.scope = check_order_scope[tar_order]
|
||||
self.room = room
|
||||
if (
|
||||
self.scene() == Scene.ORDER_LIST
|
||||
and not self.detect_room_inside() == self.room
|
||||
):
|
||||
EnterRoomSolver().run(self.room, detail=False)
|
||||
super().run()
|
||||
|
||||
def transition(self) -> bool:
|
||||
if (scene := self.scene()) == Scene.INFRA_DETAILS:
|
||||
self.ctap((200, 1000), 1, config.screenshot_avg / 1000)
|
||||
elif scene == Scene.ORDER_LIST:
|
||||
if self.find(f"switch_order/{self.tar_order}"):
|
||||
return True
|
||||
else:
|
||||
self.tap((1500, 1000))
|
||||
elif scene == Scene.SWITCH_ORDER:
|
||||
if self.find("switch_order/check", scope=self.scope):
|
||||
self.scene_graph_step(Scene.ORDER_LIST)
|
||||
else:
|
||||
self.tap(self.get_pos(self.scope))
|
||||
|
||||
elif scene in self.waiting_scene:
|
||||
self.waiting_solver()
|
||||
else:
|
||||
EnterRoomSolver().run(self.room, detail=False)
|
116
mower/solvers/infra/switch_product/__init__.py
Normal file
116
mower/solvers/infra/switch_product/__init__.py
Normal file
|
@ -0,0 +1,116 @@
|
|||
from datetime import datetime
|
||||
|
||||
import cv2
|
||||
|
||||
from mower.models import riic_base_digits
|
||||
from mower.solvers.infra.base_mixin import BaseMixin
|
||||
from mower.solvers.infra.drone import DroneSolver
|
||||
from mower.solvers.infra.enter_room import EnterRoomSolver
|
||||
from mower.utils import config
|
||||
from mower.utils import typealias as tp
|
||||
from mower.utils.digit_reader import DigitReader
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.image import cropimg, thres2
|
||||
from mower.utils.log import logger
|
||||
|
||||
from .check_current_product import CheckCurrentProductSolver
|
||||
from .choose_product import ChooseProductSolver
|
||||
from .get_remain_time import GetRemainTimeSolver
|
||||
from .wait_for_product import WaitForProductSolver
|
||||
|
||||
production_time = {
|
||||
"经验": 180 * 60,
|
||||
"赤金": 72 * 60,
|
||||
"源石碎片": 60 * 60,
|
||||
"先锋双芯片": 60 * 60,
|
||||
"狙击双芯片": 60 * 60,
|
||||
"医疗双芯片": 60 * 60,
|
||||
"术师双芯片": 60 * 60,
|
||||
"近卫双芯片": 60 * 60,
|
||||
"重装双芯片": 60 * 60,
|
||||
"辅助双芯片": 60 * 60,
|
||||
"特种双芯片": 60 * 60,
|
||||
}
|
||||
|
||||
|
||||
class SwitchProductSolver(SceneGraphSolver, BaseMixin):
|
||||
def run(self, room, tar_product, only_get_time=False):
|
||||
logger.info(f"Start:切换产物 房间:{room} 目标产物:{tar_product}")
|
||||
EnterRoomSolver().run(room, detail=False)
|
||||
|
||||
# 检查当前产物
|
||||
cur_product = CheckCurrentProductSolver().run(room)
|
||||
logger.info(f"当前产物:{cur_product}")
|
||||
if cur_product == tar_product:
|
||||
logger.info("当前产物已为目标产物")
|
||||
return True
|
||||
|
||||
# 读取当前无人机数量避免重复开关
|
||||
cur_drone_count = DigitReader().get_drone(config.recog.gray)
|
||||
# 读取生产速度
|
||||
speed = self.read_speed()
|
||||
# 获取无人机加速的剩余时间
|
||||
remain_time = GetRemainTimeSolver().run(room)
|
||||
if only_get_time:
|
||||
return remain_time % production_time[cur_product] / speed
|
||||
# 使用无人机加速
|
||||
start_time = datetime.now()
|
||||
drone_count = remain_time % production_time[cur_product] // 180
|
||||
logger.info(f"应使用无人机数量:{drone_count}")
|
||||
if not DroneSolver().run(room, drone_count, cur_count=cur_drone_count):
|
||||
return False
|
||||
|
||||
# 等待产物完成
|
||||
wait_time = max(
|
||||
remain_time % production_time[cur_product] % 180 / speed
|
||||
- (datetime.now() - start_time).total_seconds(),
|
||||
0,
|
||||
)
|
||||
|
||||
logger.info(f"等待时间:{wait_time}")
|
||||
WaitForProductSolver().run(room, wait_time)
|
||||
|
||||
# 选择目标产物
|
||||
if not ChooseProductSolver().run(room, tar_product):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def number(self, scope: tp.Scope, height: int, thres: int) -> int:
|
||||
"数字识别"
|
||||
img = cropimg(config.recog.gray, scope)
|
||||
default_height = 25
|
||||
if height != default_height:
|
||||
scale = 25 / height
|
||||
img = cv2.resize(img, None, None, scale, scale)
|
||||
img = thres2(img, thres)
|
||||
img = cv2.bitwise_not(img)
|
||||
contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
rect = [cv2.boundingRect(c) for c in contours]
|
||||
rect.sort(key=lambda c: c[0])
|
||||
|
||||
value = ""
|
||||
for x, y, w, h in rect:
|
||||
if h < 8 and w < 8:
|
||||
value += "."
|
||||
continue
|
||||
elif h < 20 and w < 20:
|
||||
continue
|
||||
digit = cropimg(img, ((x, y), (x + w, y + h)))
|
||||
digit = cv2.copyMakeBorder(
|
||||
digit, 10, 10, 10, 10, cv2.BORDER_CONSTANT, None, (0,)
|
||||
)
|
||||
score = []
|
||||
for i in range(10):
|
||||
im = riic_base_digits[i]
|
||||
result = cv2.matchTemplate(digit, im, cv2.TM_SQDIFF_NORMED)
|
||||
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
|
||||
score.append(min_val)
|
||||
value += str(score.index(min(score)))
|
||||
|
||||
return value
|
||||
|
||||
def read_speed(self) -> float:
|
||||
speed1 = self.number(((1185, 955), (1255, 977)), 17, 120)
|
||||
speed2 = self.number(((1285, 955), (1355, 977)), 17, 150)
|
||||
return float(speed1) + float(speed2) + 1.0
|
49
mower/solvers/infra/switch_product/check_current_product.py
Normal file
49
mower/solvers/infra/switch_product/check_current_product.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
from mower.solvers.infra.base_mixin import BaseMixin
|
||||
from mower.solvers.infra.enter_room import EnterRoomSolver
|
||||
from mower.utils import config
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.recognize import Scene
|
||||
|
||||
from .utils import product
|
||||
|
||||
|
||||
class CheckCurrentProductSolver(SceneGraphSolver, BaseMixin):
|
||||
def run(self, room) -> None:
|
||||
self.room = room
|
||||
if (
|
||||
self.scene() == Scene.FACTORY_ROOMS
|
||||
and not self.detect_room_inside() == self.room
|
||||
):
|
||||
EnterRoomSolver().run(self.room, detail=False)
|
||||
self.wait_start()
|
||||
super().run()
|
||||
return self.res
|
||||
|
||||
def timeout(self) -> bool:
|
||||
return datetime.now() > self.start_time + timedelta(seconds=5)
|
||||
|
||||
def wait_start(self):
|
||||
self.start_time = datetime.now()
|
||||
|
||||
def check_product(self) -> str:
|
||||
for p in product:
|
||||
if self.find(f"product/{p}"):
|
||||
return p
|
||||
|
||||
def transition(self) -> bool:
|
||||
if (scene := self.scene()) == Scene.INFRA_DETAILS:
|
||||
self.wait_start()
|
||||
self.ctap((200, 1000), 1, config.screenshot_avg / 1000)
|
||||
elif scene == Scene.FACTORY_ROOMS:
|
||||
if res := self.check_product():
|
||||
self.res = res
|
||||
return True
|
||||
elif self.timeout():
|
||||
self.res = None
|
||||
return True
|
||||
elif scene in self.waiting_scene:
|
||||
self.waiting_solver()
|
||||
else:
|
||||
EnterRoomSolver().run(self.room, detail=False)
|
119
mower/solvers/infra/switch_product/choose_product.py
Normal file
119
mower/solvers/infra/switch_product/choose_product.py
Normal file
|
@ -0,0 +1,119 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
import cv2
|
||||
|
||||
from mower.models import riic_base_digits
|
||||
from mower.solvers.infra.enter_room import EnterRoomSolver
|
||||
from mower.utils import config
|
||||
from mower.utils import typealias as tp
|
||||
from mower.utils.email import send_message
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.image import cmatch, cropimg, loadres, thres2
|
||||
from mower.utils.log import logger
|
||||
from mower.utils.recognize import Scene
|
||||
|
||||
production_index = {
|
||||
"经验": [0, 1],
|
||||
"赤金": [1, 0],
|
||||
"源石碎片": [3, 0],
|
||||
"先锋双芯片": [2, 0],
|
||||
"狙击双芯片": [2, 5],
|
||||
"医疗双芯片": [2, 6],
|
||||
"术师双芯片": [2, 2],
|
||||
"近卫双芯片": [2, 4],
|
||||
"重装双芯片": [2, 1],
|
||||
"辅助双芯片": [2, 3],
|
||||
"特种双芯片": [2, 7],
|
||||
}
|
||||
first_pos = [((180, 215 + 140 * i), (190, 225 + 140 * i)) for i in range(4)]
|
||||
|
||||
second_pos = [(500 + 742 * j, 200 + 270 * i) for j in range(2) for i in range(4)]
|
||||
|
||||
|
||||
class ChooseProductSolver(SceneGraphSolver):
|
||||
def run(self, room, tar_product) -> bool:
|
||||
self.room = room
|
||||
self.tar_product = tar_product
|
||||
self.first_pos = first_pos[production_index[self.tar_product][0]]
|
||||
self.second_pos = second_pos[production_index[self.tar_product][1]]
|
||||
self.success = False
|
||||
self.num_flag = False
|
||||
self.wait_start()
|
||||
super().run()
|
||||
return self.success
|
||||
|
||||
def be_choosen(self) -> bool:
|
||||
img = cropimg(config.recog.img, self.first_pos)
|
||||
res = loadres("product_be_choosen")
|
||||
return cmatch(img, res, 60)
|
||||
|
||||
def timeout(self) -> bool:
|
||||
return datetime.now() > self.start_time + timedelta(seconds=10)
|
||||
|
||||
def wait_start(self):
|
||||
self.start_time = datetime.now()
|
||||
|
||||
def number(self, scope: tp.Scope, height: int, thres: int) -> int:
|
||||
"数字识别"
|
||||
img = cropimg(config.recog.gray, scope)
|
||||
default_height = 25
|
||||
if height != default_height:
|
||||
scale = 25 / height
|
||||
img = cv2.resize(img, None, None, scale, scale)
|
||||
img = thres2(img, thres)
|
||||
contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
rect = [cv2.boundingRect(c) for c in contours]
|
||||
rect.sort(key=lambda c: c[0])
|
||||
|
||||
value = 0
|
||||
|
||||
for x, y, w, h in rect:
|
||||
digit = cropimg(img, ((x, y), (x + w, y + h)))
|
||||
digit = cv2.copyMakeBorder(
|
||||
digit, 10, 10, 10, 10, cv2.BORDER_CONSTANT, None, (0,)
|
||||
)
|
||||
score = []
|
||||
for i in range(10):
|
||||
im = riic_base_digits[i]
|
||||
result = cv2.matchTemplate(digit, im, cv2.TM_SQDIFF_NORMED)
|
||||
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
|
||||
score.append(min_val)
|
||||
value = value * 10 + score.index(min(score))
|
||||
|
||||
return value
|
||||
|
||||
def transition(self) -> bool:
|
||||
if (scene := self.scene()) == Scene.INFRA_DETAILS:
|
||||
self.ctap((200, 1000), 1, config.screenshot_avg / 1000)
|
||||
elif scene == Scene.FACTORY_ROOMS:
|
||||
if self.success:
|
||||
return True
|
||||
elif self.find("reload_check"):
|
||||
if self.num_flag:
|
||||
self.ctap((1400, 850), 1, config.screenshot_avg / 1000)
|
||||
elif self.timeout():
|
||||
logger.warning("切换成功,剩余材料不足1")
|
||||
send_message(f"切换成功,剩余材料不足1:{self.room}", level="WARNING")
|
||||
self.num_flag = True
|
||||
else:
|
||||
self.tap((1450, 300))
|
||||
if self.number(((1306, 462), (1391, 521)), 50, 200) != 1:
|
||||
self.num_flag = True
|
||||
else:
|
||||
self.tap((1750, 400))
|
||||
elif scene == Scene.CHOOSE_PRODUCT:
|
||||
if self.find("icon_notification_black"):
|
||||
logger.warning("切换产物材料不足")
|
||||
send_message(f"切换产物材料不足:{self.room}", level="WARNING")
|
||||
return True
|
||||
elif not self.be_choosen():
|
||||
self.tap(self.get_pos(self.first_pos))
|
||||
else:
|
||||
self.tap(self.second_pos)
|
||||
elif scene == Scene.PRODUCT_SWITCHING_CONFIRM:
|
||||
self.tap_element("double_confirm/main", x_rate=1)
|
||||
self.success = True
|
||||
elif scene in self.waiting_scene:
|
||||
self.waiting_solver()
|
||||
else:
|
||||
EnterRoomSolver().run(self.room, detail=False)
|
80
mower/solvers/infra/switch_product/get_remain_time.py
Normal file
80
mower/solvers/infra/switch_product/get_remain_time.py
Normal file
|
@ -0,0 +1,80 @@
|
|||
import cv2
|
||||
|
||||
from mower.models import riic_base_digits
|
||||
from mower.solvers.infra.base_mixin import BaseMixin
|
||||
from mower.solvers.infra.enter_room import EnterRoomSolver
|
||||
from mower.utils import config
|
||||
from mower.utils import typealias as tp
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.image import cropimg, thres2
|
||||
from mower.utils.recognize import Scene
|
||||
|
||||
|
||||
class GetRemainTimeSolver(SceneGraphSolver, BaseMixin):
|
||||
"""
|
||||
返回剩余时间,换算为秒
|
||||
"""
|
||||
|
||||
def run(
|
||||
self,
|
||||
room: str,
|
||||
):
|
||||
self.room = room
|
||||
if (
|
||||
self.scene() == Scene.FACTORY_ROOMS
|
||||
and not self.detect_room_inside() == self.room
|
||||
):
|
||||
EnterRoomSolver().run(self.room, detail=False)
|
||||
super().run()
|
||||
return self.res
|
||||
|
||||
def number(self, scope: tp.Scope, height: int, thres: int) -> str:
|
||||
"数字识别"
|
||||
img = cropimg(config.recog.gray, scope)
|
||||
default_height = 25
|
||||
if height != default_height:
|
||||
scale = 25 / height
|
||||
img = cv2.resize(img, None, None, scale, scale)
|
||||
img = thres2(img, thres)
|
||||
contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
rect = [cv2.boundingRect(c) for c in contours]
|
||||
rect.sort(key=lambda c: c[0])
|
||||
|
||||
value = ""
|
||||
for x, y, w, h in rect:
|
||||
if h < 7 and w < 7:
|
||||
value += ":"
|
||||
continue
|
||||
digit = cropimg(img, ((x, y), (x + w, y + h)))
|
||||
digit = cv2.copyMakeBorder(
|
||||
digit, 10, 10, 10, 10, cv2.BORDER_CONSTANT, None, (0,)
|
||||
)
|
||||
score = []
|
||||
for i in range(10):
|
||||
im = riic_base_digits[i]
|
||||
result = cv2.matchTemplate(digit, im, cv2.TM_SQDIFF_NORMED)
|
||||
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
|
||||
score.append(min_val)
|
||||
value += str(score.index(min(score)))
|
||||
|
||||
return value
|
||||
|
||||
def read_remain_time(self) -> int:
|
||||
h, m, s = self.number(((758, 670), (960, 705)), 30, 100).split("::")
|
||||
return int(h) * 3600 + int(m) * 60 + int(s)
|
||||
|
||||
def transition(self) -> bool:
|
||||
if (scene := self.scene()) == Scene.INFRA_DETAILS:
|
||||
self.ctap((200, 1000), 1, config.screenshot_avg / 1000)
|
||||
elif scene == Scene.FACTORY_ROOMS:
|
||||
if pos := self.find("factory_accelerate"):
|
||||
self.tap(pos)
|
||||
|
||||
elif scene == Scene.DRONE_ACCELERATE:
|
||||
if res := self.read_remain_time():
|
||||
self.res = res
|
||||
return True
|
||||
elif scene in self.waiting_scene:
|
||||
self.waiting_solver()
|
||||
else:
|
||||
EnterRoomSolver().run(self.room, detail=False)
|
13
mower/solvers/infra/switch_product/utils.py
Normal file
13
mower/solvers/infra/switch_product/utils.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
product = [
|
||||
"经验",
|
||||
"赤金",
|
||||
"源石碎片",
|
||||
"先锋双芯片",
|
||||
"狙击双芯片",
|
||||
"医疗双芯片",
|
||||
"术师双芯片",
|
||||
"近卫双芯片",
|
||||
"重装双芯片",
|
||||
"辅助双芯片",
|
||||
"特种双芯片",
|
||||
]
|
36
mower/solvers/infra/switch_product/wait_for_product.py
Normal file
36
mower/solvers/infra/switch_product/wait_for_product.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
from mower.solvers.infra.base_mixin import BaseMixin
|
||||
from mower.solvers.infra.enter_room import EnterRoomSolver
|
||||
from mower.utils import config
|
||||
from mower.utils.graph import SceneGraphSolver
|
||||
from mower.utils.recognize import Scene
|
||||
|
||||
|
||||
class WaitForProductSolver(SceneGraphSolver, BaseMixin):
|
||||
def run(self, room, wait_time) -> None:
|
||||
self.room = room
|
||||
self.wait_start()
|
||||
self.sleep(wait_time)
|
||||
super().run()
|
||||
|
||||
def timeout(self) -> bool:
|
||||
return datetime.now() > self.start_time + timedelta(seconds=180)
|
||||
|
||||
def wait_start(self):
|
||||
self.start_time = datetime.now()
|
||||
|
||||
def transition(self) -> bool:
|
||||
if (scene := self.scene()) == Scene.INFRA_DETAILS:
|
||||
self.ctap((200, 1000), 1, config.screenshot_avg / 1000)
|
||||
elif scene == Scene.FACTORY_ROOMS:
|
||||
if self.detect_product_complete():
|
||||
return True
|
||||
elif self.timeout():
|
||||
return True
|
||||
else:
|
||||
self.tap((1750, 950))
|
||||
elif scene in self.waiting_scene:
|
||||
self.waiting_solver()
|
||||
else:
|
||||
EnterRoomSolver().run(self.room, detail=False)
|
Loading…
Add table
Add a link
Reference in a new issue